فهرست منبع

AMBARI-742. Implement running a bootstrap api, keep a fifo queue and logic for getting a request. (mahadev)

git-svn-id: https://svn.apache.org/repos/asf/incubator/ambari/branches/AMBARI-666@1385220 13f79535-47bb-0310-9956-ffa450edef68
Mahadev Konar 12 سال پیش
والد
کامیت
47d1386ffb

+ 3 - 0
AMBARI-666-CHANGES.txt

@@ -12,6 +12,9 @@ AMBARI-666 branch (unreleased changes)
 
   NEW FEATURES
 
+  AMBARI-742. Implement running a bootstrap api, keep a fifo queue and logic
+  for getting a request. (mahadev)
+
   AMBARI-741. Use ember-i18n to externalize string resources in Ambari Web.
   (yusaku)
 

+ 0 - 1
ambari-server/src/main/java/org/apache/ambari/server/agent/rest/AgentResource.java

@@ -34,7 +34,6 @@ import org.apache.ambari.server.agent.Register;
 import org.apache.ambari.server.agent.RegistrationResponse;
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
-
 import com.google.inject.Inject;
 
 /**

+ 25 - 7
ambari-server/src/main/java/org/apache/ambari/server/api/rest/BootStrapResource.java

@@ -22,16 +22,30 @@ import javax.ws.rs.Consumes;
 import javax.ws.rs.GET;
 import javax.ws.rs.POST;
 import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
 import javax.ws.rs.Produces;
+import javax.ws.rs.WebApplicationException;
 import javax.ws.rs.core.MediaType;
-
-import org.apache.ambari.server.bootstrap.BootStrapPostStatus;
+import javax.ws.rs.core.Response;
+import org.apache.ambari.server.bootstrap.BSResponse;
+import org.apache.ambari.server.bootstrap.BootStrapImpl;
 import org.apache.ambari.server.bootstrap.BootStrapStatus;
 import org.apache.ambari.server.bootstrap.SshHostInfo;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import com.google.inject.Inject;
 
 @Path("/bootstrap")
 public class BootStrapResource {
   
+  private static BootStrapImpl bsImpl;
+  private static Log LOG = LogFactory.getLog(BootStrapResource.class);
+  
+  @Inject
+  static void init(BootStrapImpl instance) {
+    bsImpl = instance;
+  }
   /**
    * Run bootstrap on a list of hosts.
    * @response.representation.200.doc 
@@ -44,9 +58,8 @@ public class BootStrapResource {
   @POST
   @Consumes(MediaType.APPLICATION_JSON)
   @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) 
-  public BootStrapPostStatus bootStrap(SshHostInfo sshInfo) {
-    
-    return new BootStrapPostStatus();
+  public BSResponse bootStrap(SshHostInfo sshInfo) {
+    return bsImpl.runBootStrap(sshInfo);
   }
   
   /**
@@ -59,8 +72,13 @@ public class BootStrapResource {
    * @throws Exception
    */
   @GET
+  @Path("/{requestId}")
   @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) 
-  public BootStrapStatus getBootStrapStatus() {
-    return new BootStrapStatus();
+  public BootStrapStatus getBootStrapStatus(@PathParam("requestId") 
+  long requestId) {
+    BootStrapStatus status = bsImpl.getStatus(0);
+    if (status == null) 
+      throw new WebApplicationException(Response.Status.NO_CONTENT);
+    return status;
   }
 }

+ 7 - 7
ambari-server/src/main/java/org/apache/ambari/server/bootstrap/BootStrapPostStatus.java → ambari-server/src/main/java/org/apache/ambari/server/bootstrap/BSResponse.java

@@ -32,16 +32,16 @@ import javax.xml.bind.annotation.XmlType;
 @XmlRootElement
 @XmlAccessorType(XmlAccessType.FIELD)
 @XmlType(name = "", propOrder = {})
-public class BootStrapPostStatus {
+public class BSResponse {
   @XmlType(name="status")
   @XmlEnum
-  public enum BSPostStat {
+  public enum BSRunStat {
     OK,
     ERROR
   }
   
   @XmlElement 
-  private BSPostStat postStatus;
+  private BSRunStat status;
   @XmlElement 
   private String log;
   @XmlElement 
@@ -55,12 +55,12 @@ public class BootStrapPostStatus {
     this.requestId = requestId;
   }
   
-  public BSPostStat getStatus() {
-    return this.postStatus;
+  public BSRunStat getStatus() {
+    return this.status;
   }
   
-  public void setStatus(BSPostStat status) {
-    this.postStatus  = status;
+  public void setStatus(BSRunStat status) {
+    this.status  = status;
   }
   
   public String getLog() {

+ 131 - 0
ambari-server/src/main/java/org/apache/ambari/server/bootstrap/BootStrapImpl.java

@@ -0,0 +1,131 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ambari.server.bootstrap;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.apache.ambari.server.bootstrap.BSResponse.BSRunStat;
+import org.apache.ambari.server.configuration.Configuration;
+import org.mortbay.log.Log;
+
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+@Singleton
+public class BootStrapImpl {
+  private Configuration conf;
+  private File bootStrapDir;
+  private String bootScript;
+  private BSRunner bsRunner;
+ 
+  /* Monotonically increasing requestid for the bootstrap api to query on */
+  AtomicLong requestId = new AtomicLong();
+  private static final int MAX_ENTRIES = 100;
+  private FifoLinkedHashMap<Long, BootStrapStatus> bsStatus;
+
+  /**
+   * Only Store the Last 100 BootStrap Status'es
+   *
+   */
+  @SuppressWarnings("serial")
+  public static class FifoLinkedHashMap<K, V> extends 
+  LinkedHashMap<K, V> {
+    protected boolean removeEldestEntry(Map.Entry<K, 
+        V> eldest) {
+      return size() > MAX_ENTRIES;
+    }
+
+  }
+  
+  /**
+   * Return {@link BootStrapStatus} for a given responseId.
+   * @param requestId the responseId for which the status needs to be returned.
+   * @return status for a specific response id. A response Id of -1 means the 
+   * latest responseId.
+   */
+  public synchronized BootStrapStatus getStatus(long requestId) {
+    if (! bsStatus.containsKey(requestId)) {
+      return null;
+    }
+    return bsStatus.get(requestId);
+  }
+  
+  class BSRunner extends Thread {
+    private  boolean finished = false;
+    private SshHostInfo sshHostInfo;
+    private String bootDir;
+    private String bsString;
+    private String bsScript;
+    int requestId;
+    public BSRunner(SshHostInfo sshHostInfo, String bootDir, String bsScript,
+        int requestId)
+    {
+      this.requestId = requestId;
+      this.sshHostInfo = sshHostInfo;
+      this.bsString = bsString;
+      this.bsScript = bsScript;
+    }
+    String[] command = new String[3];
+    
+    @Override
+    public synchronized void run() {
+      
+    }
+
+    public synchronized boolean isRunning() {
+      return !this.finished;
+    }
+  }
+
+  @Inject
+  public BootStrapImpl(Configuration conf) {
+    this.conf = conf;
+    this.bootStrapDir = conf.getBootStrapDir();
+    this.bootScript = conf.getBootStrapScript();   
+    this.bsStatus = new FifoLinkedHashMap<Long, BootStrapStatus>();
+  }
+
+  public synchronized void init() throws IOException {
+    boolean mkdirs = bootStrapDir.mkdirs();
+    if (!mkdirs) throw new IOException("Unable to make directory for " +
+        "bootstrap " + bootStrapDir);
+
+  }
+
+  public  synchronized BSResponse runBootStrap(SshHostInfo info) {
+    BSResponse response = new BSResponse();
+    /* Run some checks for ssh host */
+    Log.info("BootStrapping hosts " + info.hostListAsString());
+    if (bsRunner != null) {
+      response.setLog("BootStrap in Progress: Cannot Run more than one.");
+      response.setStatus(BSRunStat.ERROR);
+      return response;
+    } 
+    
+    
+    response.setStatus(BSRunStat.OK);
+    response.setLog("Running Bootstrap now.");
+    return response;
+  }
+
+}

+ 13 - 1
ambari-server/src/main/java/org/apache/ambari/server/bootstrap/BootStrapStatus.java

@@ -40,7 +40,8 @@ public class BootStrapStatus {
   @XmlType(name="status")
   @XmlEnum
   public enum BSStat {
-    OK,
+    RUNNING,
+    SUCCESS,
     ERROR
   }
   
@@ -50,6 +51,9 @@ public class BootStrapStatus {
   @XmlElement
   private List<BSHostStatus> hostsStatus;
   
+  @XmlElement
+  private String log;
+  
   public void setStatus(BSStat status) {
     this.status = status;
   }
@@ -65,4 +69,12 @@ public class BootStrapStatus {
   public List<BSHostStatus> getHostsStatus() {
     return this.hostsStatus;
   }
+  
+  public void setLog(String log) {
+    this.log = log;
+  }
+  
+  public String getLog() {
+    return this.log;
+  }
 }

+ 11 - 0
ambari-server/src/main/java/org/apache/ambari/server/bootstrap/SshHostInfo.java

@@ -57,4 +57,15 @@ public class SshHostInfo {
   public List<String> getHosts() {
     return this.hosts;
   }
+  
+  public String hostListAsString() {
+    String ret = "";
+    if (this.hosts == null) {
+      return "";
+    }
+    for (String host : this.hosts) {
+      ret = ret + host + ":";
+    }
+    return ret;
+  }
 }

+ 27 - 21
ambari-server/src/main/java/org/apache/ambari/server/configuration/Configuration.java

@@ -17,11 +17,10 @@
  */
 package org.apache.ambari.server.configuration;
 
+import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.IOException;
-import java.net.URI;
-import java.net.URISyntaxException;
 import java.util.Properties;
 
 import org.apache.commons.logging.Log;
@@ -35,25 +34,24 @@ import org.apache.commons.logging.LogFactory;
 public class Configuration {
   private static final String AMBARI_CONF_VAR = "AMBARI_CONF_DIR";
   private static final String CONFIG_FILE = "ambari.properties";
+  private static final String BOOTSTRAP_DIR = "bootstrap.dir";
+  private static final String BOOTSTRAP_SCRIPT = "bootstrap.script";
 
   private static final Log LOG = LogFactory.getLog(Configuration.class);
 
-  private final URI dataStore;
-
+  private Properties properties;
+  
   Configuration() {
     this(readConfigFile());
   }
 
-  protected Configuration(Properties properties) {
-    // get the data store
-    String dataStoreString = properties.getProperty("data.store",
-                                                    "test://test/");
-    try {
-      dataStore = new URI(dataStoreString);
-    } catch (URISyntaxException e) {
-      throw new IllegalArgumentException("Can't parse data.store: " +
-                                         dataStoreString, e);
-    }
+  /**
+   * For Testing only. This is to be able to create Configuration object 
+   * for testing.
+   * @param properties properties to use for testing using the Conf object.
+   */
+  public Configuration(Properties properties) {
+    this.properties = properties;
   }
 
   /**
@@ -81,12 +79,20 @@ public class Configuration {
     }
     return properties;
   }
-
-  /**
-   * Get the URI for the persistent data store.
-   * @return the data store URI
-   */
-  public URI getDataStore() {
-    return dataStore;
+  
+  public File getBootStrapDir() {
+    String fileName = properties.getProperty(BOOTSTRAP_DIR);
+    if (fileName == null) {
+      return new File("/var/run/ambari/bootstrap");
+    }
+    return new File(fileName);
+  }
+  
+  public String getBootStrapScript() {
+    String bootscript = properties.getProperty(BOOTSTRAP_SCRIPT);
+    if (bootscript == null) {
+      return "/usr/bin/ambari_bootstrap";
+    }
+    return bootscript;
   }
 }

+ 124 - 0
ambari-server/src/test/java/org/apache/ambari/server/bootstrap/BootStrapResourceTest.java

@@ -0,0 +1,124 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ambari.server.bootstrap;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.util.ArrayList;
+
+import javax.ws.rs.core.MediaType;
+
+import junit.framework.Assert;
+
+import org.apache.ambari.server.agent.AgentResourceTest;
+import org.apache.ambari.server.api.rest.BootStrapResource;
+import org.apache.ambari.server.bootstrap.BSResponse.BSRunStat;
+import org.apache.ambari.server.bootstrap.BootStrapStatus.BSStat;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.codehaus.jettison.json.JSONException;
+import org.codehaus.jettison.json.JSONObject;
+import org.junit.Test;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.sun.jersey.api.client.UniformInterfaceException;
+import com.sun.jersey.api.client.WebResource;
+import com.sun.jersey.spi.container.servlet.ServletContainer;
+import com.sun.jersey.test.framework.JerseyTest;
+import com.sun.jersey.test.framework.WebAppDescriptor;
+
+/**
+ *  Testing bootstrap API.
+ */
+public class BootStrapResourceTest extends JerseyTest {
+  
+  static String PACKAGE_NAME = "org.apache.ambari.server.api.rest";
+  private static Log LOG = LogFactory.getLog(AgentResourceTest.class);
+  Injector injector;
+  BootStrapImpl bsImpl;
+  
+  public BootStrapResourceTest() {
+    super(new WebAppDescriptor.Builder(PACKAGE_NAME).servletClass(ServletContainer.class)
+        .build());
+  }
+  
+  public class MockModule extends AbstractModule {
+    @Override
+    protected void configure() {
+      BootStrapImpl bsImpl = mock(BootStrapImpl.class);
+      when(bsImpl.getStatus(0)).thenReturn(generateDummyBSStatus());
+      when(bsImpl.runBootStrap(any(SshHostInfo.class))).thenReturn(generateBSResponse());
+      bind(BootStrapImpl.class).toInstance(bsImpl);
+      requestStaticInjection(BootStrapResource.class);
+    }    
+  }
+  
+  @Override
+  public void setUp() throws Exception {
+    super.setUp();
+    injector = Guice.createInjector(new MockModule());
+  }
+  
+  protected JSONObject createDummySshInfo() throws JSONException {
+    JSONObject json = new JSONObject();
+    json.put("sshkey", "awesome");
+    json.put("hosts", new ArrayList<String>());
+    return json;
+  }
+  
+  protected BSResponse generateBSResponse() {
+    BSResponse response = new BSResponse();
+    response.setLog("Logging");
+    response.setRequestId(1);
+    response.setStatus(BSRunStat.OK);
+    return response;
+  }
+  
+  protected BootStrapStatus generateDummyBSStatus() {
+    BootStrapStatus status = new BootStrapStatus();
+    status.setLog("Logging ");
+    status.setStatus(BSStat.ERROR);
+    status.setHostsStatus(new ArrayList<BSHostStatus>());
+    return status;
+  }
+  
+  @Test
+  public void bootStrapGet() throws UniformInterfaceException, JSONException {
+    WebResource webResource = resource();
+    BootStrapStatus status = webResource.path("/bootstrap/0").type(
+        MediaType.APPLICATION_JSON)
+        .get(BootStrapStatus.class);
+    LOG.info("GET Response from the API " + status.getLog() + " " + 
+        status.getStatus());
+    Assert.assertEquals(status.getStatus(), BSStat.ERROR);
+  }
+  
+  @Test
+  public void bootStrapPost() throws UniformInterfaceException, JSONException {
+    WebResource webResource = resource();
+    JSONObject object = webResource.path("/bootstrap").type(
+        MediaType.APPLICATION_JSON).post(JSONObject.class, createDummySshInfo());
+    
+    Assert.assertEquals("OK", object.get("status"));
+  }
+}

+ 39 - 0
ambari-server/src/test/java/org/apache/ambari/server/bootstrap/BootStrapTest.java

@@ -0,0 +1,39 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ambari.server.bootstrap;
+
+import java.util.Properties;
+
+import junit.framework.TestCase;
+
+import org.apache.ambari.server.configuration.Configuration;
+import org.junit.Test;
+/**
+ * Test BootStrap Implementation.
+ */
+public class BootStrapTest extends TestCase {
+  
+  @Test
+  public void testRun() throws Exception {
+    Properties properties = new Properties();
+    Configuration conf = new Configuration(properties);
+    BootStrapImpl impl = new BootStrapImpl(conf);
+  }
+  
+}