You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cloudstack.apache.org by bh...@apache.org on 2017/08/30 16:06:53 UTC

[cloudstack] 02/04: CLOUDSTACK-9782: Nested-oobm CloudStack plugin

This is an automated email from the ASF dual-hosted git repository.

bhaisaab pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/cloudstack.git

commit c0b33db5ce7dc95f4d6e04910600ff14cea24b76
Author: Rohit Yadav <ro...@shapeblue.com>
AuthorDate: Sat Aug 19 00:04:02 2017 +0200

    CLOUDSTACK-9782: Nested-oobm CloudStack plugin
    
    Nested out-of-band management plugin to work with hosts that are VMs in
    a CloudStack env.
    
    Signed-off-by: Rohit Yadav <ro...@shapeblue.com>
---
 .travis.yml                                        |   1 +
 .../api/response/OutOfBandManagementResponse.java  |   2 +-
 .../outofbandmanagement/OutOfBandManagement.java   |   4 +-
 client/pom.xml                                     |   5 +
 .../outofbandmanagement/OutOfBandManagementVO.java |   6 +-
 .../nested-cloudstack/pom.xml                      |  46 ++++
 .../cloudstack/nested-cloudstack/module.properties |  18 ++
 .../spring-nested-cloudstack-context.xml           |  29 +++
 .../NestedCloudStackOutOfBandManagementDriver.java | 147 ++++++++++++
 ...tedCloudStackOutOfBandManagementDriverTest.java |  75 ++++++
 plugins/pom.xml                                    |   1 +
 .../OutOfBandManagementServiceImpl.java            |   6 +-
 .../OutOfBandManagementServiceTest.java            |   2 +-
 setup/db/db/schema-41000to41100.sql                |   3 +
 .../smoke/test_outofbandmanagement_nestedplugin.py | 256 +++++++++++++++++++++
 ui/scripts/system.js                               |   4 +
 16 files changed, 594 insertions(+), 11 deletions(-)

diff --git a/.travis.yml b/.travis.yml
index ad4154a..f9ef0fc 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -64,6 +64,7 @@ env:
              smoke/test_non_contigiousvlan"
 
     - TESTS="smoke/test_outofbandmanagement
+             smoke/test_outofbandmanagement_nestedplugin
              smoke/test_over_provisioning
              smoke/test_password_server
              smoke/test_portable_publicip
diff --git a/api/src/org/apache/cloudstack/api/response/OutOfBandManagementResponse.java b/api/src/org/apache/cloudstack/api/response/OutOfBandManagementResponse.java
index 19594d2..c0282c8 100644
--- a/api/src/org/apache/cloudstack/api/response/OutOfBandManagementResponse.java
+++ b/api/src/org/apache/cloudstack/api/response/OutOfBandManagementResponse.java
@@ -91,7 +91,7 @@ public class OutOfBandManagementResponse extends BaseResponse {
         this.setDriver(outOfBandManagementConfig.getDriver());
         this.setIpAddress(outOfBandManagementConfig.getAddress());
         if (outOfBandManagementConfig.getPort() != null) {
-            this.setPort(String.valueOf(outOfBandManagementConfig.getPort()));
+            this.setPort(outOfBandManagementConfig.getPort());
         }
         this.setUsername(outOfBandManagementConfig.getUsername());
         if (!Strings.isNullOrEmpty(outOfBandManagementConfig.getPassword())) {
diff --git a/api/src/org/apache/cloudstack/outofbandmanagement/OutOfBandManagement.java b/api/src/org/apache/cloudstack/outofbandmanagement/OutOfBandManagement.java
index 1a22328..972d626 100644
--- a/api/src/org/apache/cloudstack/outofbandmanagement/OutOfBandManagement.java
+++ b/api/src/org/apache/cloudstack/outofbandmanagement/OutOfBandManagement.java
@@ -39,7 +39,7 @@ public interface OutOfBandManagement extends StateObject<OutOfBandManagement.Pow
 
     String getAddress();
 
-    Integer getPort();
+    String getPort();
 
     String getUsername();
 
@@ -53,7 +53,7 @@ public interface OutOfBandManagement extends StateObject<OutOfBandManagement.Pow
 
     void setAddress(String address);
 
-    void setPort(Integer port);
+    void setPort(String port);
 
     void setUsername(String username);
 
diff --git a/client/pom.xml b/client/pom.xml
index 7af4324..90b9e97 100644
--- a/client/pom.xml
+++ b/client/pom.xml
@@ -266,6 +266,11 @@
     </dependency>
     <dependency>
       <groupId>org.apache.cloudstack</groupId>
+      <artifactId>cloud-plugin-outofbandmanagement-driver-nested-cloudstack</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.cloudstack</groupId>
       <artifactId>cloud-mom-rabbitmq</artifactId>
       <version>${project.version}</version>
     </dependency>
diff --git a/engine/schema/src/org/apache/cloudstack/outofbandmanagement/OutOfBandManagementVO.java b/engine/schema/src/org/apache/cloudstack/outofbandmanagement/OutOfBandManagementVO.java
index b5c357c..2f975ca 100644
--- a/engine/schema/src/org/apache/cloudstack/outofbandmanagement/OutOfBandManagementVO.java
+++ b/engine/schema/src/org/apache/cloudstack/outofbandmanagement/OutOfBandManagementVO.java
@@ -59,7 +59,7 @@ public class OutOfBandManagementVO implements OutOfBandManagement {
     private String address;
 
     @Column(name = "port")
-    private Integer port;
+    private String port;
 
     @Column(name = "username")
     private String username;
@@ -121,7 +121,7 @@ public class OutOfBandManagementVO implements OutOfBandManagement {
     }
 
     @Override
-    public Integer getPort() {
+    public String getPort() {
         return port;
     }
 
@@ -173,7 +173,7 @@ public class OutOfBandManagementVO implements OutOfBandManagement {
     }
 
     @Override
-    public void setPort(Integer port) {
+    public void setPort(String port) {
         this.port = port;
     }
 
diff --git a/plugins/outofbandmanagement-drivers/nested-cloudstack/pom.xml b/plugins/outofbandmanagement-drivers/nested-cloudstack/pom.xml
new file mode 100644
index 0000000..34a631a
--- /dev/null
+++ b/plugins/outofbandmanagement-drivers/nested-cloudstack/pom.xml
@@ -0,0 +1,46 @@
+<!--
+  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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <artifactId>cloud-plugin-outofbandmanagement-driver-nested-cloudstack</artifactId>
+  <name>Apache CloudStack Plugin - Power Management Driver nested-cloudstack</name>
+  <parent>
+    <groupId>org.apache.cloudstack</groupId>
+    <artifactId>cloudstack-plugins</artifactId>
+    <version>4.11.0.0-SNAPSHOT</version>
+    <relativePath>../../pom.xml</relativePath>
+  </parent>
+  <dependencies>
+    <dependency>
+      <groupId>org.apache.cloudstack</groupId>
+      <artifactId>cloud-utils</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.cloudstack</groupId>
+      <artifactId>cloud-api</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+        <groupId>br.com.autonomiccs</groupId>
+        <artifactId>apache-cloudstack-java-client</artifactId>
+        <version>1.0.4</version>
+    </dependency>
+  </dependencies>
+</project>
diff --git a/plugins/outofbandmanagement-drivers/nested-cloudstack/resources/META-INF/cloudstack/nested-cloudstack/module.properties b/plugins/outofbandmanagement-drivers/nested-cloudstack/resources/META-INF/cloudstack/nested-cloudstack/module.properties
new file mode 100644
index 0000000..c992ed1
--- /dev/null
+++ b/plugins/outofbandmanagement-drivers/nested-cloudstack/resources/META-INF/cloudstack/nested-cloudstack/module.properties
@@ -0,0 +1,18 @@
+# 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.
+name=nested-cloudstack
+parent=outofbandmanagement
diff --git a/plugins/outofbandmanagement-drivers/nested-cloudstack/resources/META-INF/cloudstack/nested-cloudstack/spring-nested-cloudstack-context.xml b/plugins/outofbandmanagement-drivers/nested-cloudstack/resources/META-INF/cloudstack/nested-cloudstack/spring-nested-cloudstack-context.xml
new file mode 100644
index 0000000..252a95c
--- /dev/null
+++ b/plugins/outofbandmanagement-drivers/nested-cloudstack/resources/META-INF/cloudstack/nested-cloudstack/spring-nested-cloudstack-context.xml
@@ -0,0 +1,29 @@
+<!--
+  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.
+-->
+<beans xmlns="http://www.springframework.org/schema/beans"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xsi:schemaLocation="http://www.springframework.org/schema/beans
+                      http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"
+>
+
+    <bean id="nestedCloudStackOutOfBandManagementDriver" class="org.apache.cloudstack.outofbandmanagement.driver.nestedcloudstack.NestedCloudStackOutOfBandManagementDriver">
+        <property name="name" value="NESTEDCLOUDSTACK" />
+    </bean>
+
+</beans>
diff --git a/plugins/outofbandmanagement-drivers/nested-cloudstack/src/org/apache/cloudstack/outofbandmanagement/driver/nestedcloudstack/NestedCloudStackOutOfBandManagementDriver.java b/plugins/outofbandmanagement-drivers/nested-cloudstack/src/org/apache/cloudstack/outofbandmanagement/driver/nestedcloudstack/NestedCloudStackOutOfBandManagementDriver.java
new file mode 100644
index 0000000..37d5c57
--- /dev/null
+++ b/plugins/outofbandmanagement-drivers/nested-cloudstack/src/org/apache/cloudstack/outofbandmanagement/driver/nestedcloudstack/NestedCloudStackOutOfBandManagementDriver.java
@@ -0,0 +1,147 @@
+// 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.cloudstack.outofbandmanagement.driver.nestedcloudstack;
+
+import br.com.autonomiccs.apacheCloudStack.client.ApacheCloudStackClient;
+import br.com.autonomiccs.apacheCloudStack.client.ApacheCloudStackRequest;
+import br.com.autonomiccs.apacheCloudStack.client.beans.ApacheCloudStackUser;
+import br.com.autonomiccs.apacheCloudStack.exceptions.ApacheCloudStackClientRequestRuntimeException;
+import com.cloud.utils.component.AdapterBase;
+import com.cloud.utils.exception.CloudRuntimeException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableMap;
+import org.apache.cloudstack.outofbandmanagement.OutOfBandManagement;
+import org.apache.cloudstack.outofbandmanagement.OutOfBandManagementDriver;
+import org.apache.cloudstack.outofbandmanagement.driver.OutOfBandManagementDriverChangePasswordCommand;
+import org.apache.cloudstack.outofbandmanagement.driver.OutOfBandManagementDriverCommand;
+import org.apache.cloudstack.outofbandmanagement.driver.OutOfBandManagementDriverPowerCommand;
+import org.apache.cloudstack.outofbandmanagement.driver.OutOfBandManagementDriverResponse;
+import org.apache.log4j.Logger;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+
+public final class NestedCloudStackOutOfBandManagementDriver extends AdapterBase implements OutOfBandManagementDriver {
+    private static final Logger LOG = Logger.getLogger(NestedCloudStackOutOfBandManagementDriver.class);
+
+    public OutOfBandManagementDriverResponse execute(final OutOfBandManagementDriverCommand cmd) {
+        OutOfBandManagementDriverResponse response = new OutOfBandManagementDriverResponse(null, "Unsupported Command", false);
+
+        if (cmd instanceof OutOfBandManagementDriverPowerCommand) {
+            response = execute((OutOfBandManagementDriverPowerCommand) cmd);
+        } else if (cmd instanceof OutOfBandManagementDriverChangePasswordCommand) {
+            throw new CloudRuntimeException("Change password operation is not supported by the nested-cloudstack out-of-band management driver");
+        }
+
+        return response;
+    }
+
+    protected void ensureOptionExists(final ImmutableMap<OutOfBandManagement.Option, String> options, final OutOfBandManagement.Option option) {
+        if (options != null && option != null && options.containsKey(option) && !Strings.isNullOrEmpty(options.get(option))) {
+            return;
+        }
+        throw new CloudRuntimeException("Invalid out-of-band management configuration detected for the nested-cloudstack driver");
+    }
+
+    protected OutOfBandManagement.PowerState getNestedVMPowerState(final String jsonResponse) {
+        if (Strings.isNullOrEmpty(jsonResponse)) {
+            return OutOfBandManagement.PowerState.Unknown;
+        }
+
+        final ObjectMapper mapper = new ObjectMapper();
+        try {
+            Map<String, Object> listResponse = mapper.readValue(jsonResponse, Map.class);
+            if (listResponse != null && listResponse.containsKey("listvirtualmachinesresponse")
+                    && ((Map<String, Object>) listResponse.get("listvirtualmachinesresponse")).containsKey("virtualmachine")) {
+                Map<String, String> vmResponse = ((Map<String, List<Map<String, String>>>) listResponse.get("listvirtualmachinesresponse")).get("virtualmachine").get(0);
+                if (vmResponse != null && vmResponse.containsKey("state")) {
+                    if("Running".equals(vmResponse.get("state"))) {
+                        return OutOfBandManagement.PowerState.On;
+                    } else if("Stopped".equals(vmResponse.get("state"))) {
+                        return OutOfBandManagement.PowerState.Off;
+                    }
+                }
+            }
+        } catch (IOException e) {
+            LOG.warn("Exception caught while de-serializing and reading state of the nested-cloudstack VM from the response: " + jsonResponse + ", with exception:", e);
+        }
+        return OutOfBandManagement.PowerState.Unknown;
+    }
+
+    private OutOfBandManagementDriverResponse execute(final OutOfBandManagementDriverPowerCommand cmd) {
+        if (cmd == null || cmd.getPowerOperation() == null) {
+            throw new CloudRuntimeException("Invalid out-of-band management power command provided to the nested-cloudstack driver");
+        }
+
+        final ImmutableMap<OutOfBandManagement.Option, String> options = cmd.getOptions();
+        ensureOptionExists(options, OutOfBandManagement.Option.ADDRESS);
+        ensureOptionExists(options, OutOfBandManagement.Option.PORT);
+        ensureOptionExists(options, OutOfBandManagement.Option.USERNAME);
+        ensureOptionExists(options, OutOfBandManagement.Option.PASSWORD);
+
+        final String url = options.get(OutOfBandManagement.Option.ADDRESS);
+        final String vmUuid = options.get(OutOfBandManagement.Option.PORT);
+        final String apiKey = options.get(OutOfBandManagement.Option.USERNAME);
+        final String secretKey = options.get(OutOfBandManagement.Option.PASSWORD);
+
+        final ApacheCloudStackUser apacheCloudStackUser = new ApacheCloudStackUser(secretKey, apiKey);
+        final ApacheCloudStackClient client = new ApacheCloudStackClient(url, apacheCloudStackUser);
+        client.setValidateServerHttpsCertificate(false);
+        client.setShouldRequestsExpire(false);
+        client.setConnectionTimeout((int) cmd.getTimeout().getStandardSeconds());
+
+        String apiName = "listVirtualMachines";
+        switch (cmd.getPowerOperation()) {
+            case ON:
+                apiName = "startVirtualMachine";
+                break;
+            case OFF:
+            case SOFT:
+                apiName = "stopVirtualMachine";
+                break;
+            case CYCLE:
+            case RESET:
+                apiName = "rebootVirtualMachine";
+                break;
+        }
+
+        final ApacheCloudStackRequest apacheCloudStackRequest = new ApacheCloudStackRequest(apiName);
+        apacheCloudStackRequest.addParameter("response", "json");
+        apacheCloudStackRequest.addParameter("forced", "true");
+        apacheCloudStackRequest.addParameter("id", vmUuid);
+
+        final String apiResponse;
+        try {
+            apiResponse = client.executeRequest(apacheCloudStackRequest);
+        } catch (final ApacheCloudStackClientRequestRuntimeException e) {
+            LOG.error("Nested CloudStack oobm plugin failed due to API error: ", e);
+            final OutOfBandManagementDriverResponse failedResponse = new OutOfBandManagementDriverResponse(e.getResponse(), "HTTP error code: " + e.getStatusCode(), false);
+            if (e.getStatusCode() == 401) {
+                failedResponse.setAuthFailure(true);
+            }
+            return failedResponse;
+        }
+
+        final OutOfBandManagementDriverResponse response = new OutOfBandManagementDriverResponse(apiResponse, null, true);
+        if (OutOfBandManagement.PowerOperation.STATUS.equals(cmd.getPowerOperation())) {
+            response.setPowerState(getNestedVMPowerState(apiResponse));
+        }
+        return response;
+    }
+}
diff --git a/plugins/outofbandmanagement-drivers/nested-cloudstack/test/org/apache/cloudstack/outofbandmanagement/driver/nestedcloudstack/NestedCloudStackOutOfBandManagementDriverTest.java b/plugins/outofbandmanagement-drivers/nested-cloudstack/test/org/apache/cloudstack/outofbandmanagement/driver/nestedcloudstack/NestedCloudStackOutOfBandManagementDriverTest.java
new file mode 100644
index 0000000..5629773
--- /dev/null
+++ b/plugins/outofbandmanagement-drivers/nested-cloudstack/test/org/apache/cloudstack/outofbandmanagement/driver/nestedcloudstack/NestedCloudStackOutOfBandManagementDriverTest.java
@@ -0,0 +1,75 @@
+//
+// 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.cloudstack.outofbandmanagement.driver.nestedcloudstack;
+
+import com.cloud.utils.exception.CloudRuntimeException;
+import com.google.common.collect.ImmutableMap;
+import org.apache.cloudstack.outofbandmanagement.OutOfBandManagement;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.runners.MockitoJUnitRunner;
+
+import java.io.IOException;
+
+@RunWith(MockitoJUnitRunner.class)
+public class NestedCloudStackOutOfBandManagementDriverTest {
+    private NestedCloudStackOutOfBandManagementDriver driver = new NestedCloudStackOutOfBandManagementDriver();
+
+    @Test
+    public void testEnsureOptionExists() throws IOException {
+        final ImmutableMap.Builder<OutOfBandManagement.Option, String> builder = ImmutableMap.builder();
+        builder.put(OutOfBandManagement.Option.ADDRESS, "http://some.cloud/client/api");
+        final ImmutableMap<OutOfBandManagement.Option, String> options = builder.build();
+        driver.ensureOptionExists(options, OutOfBandManagement.Option.ADDRESS);
+
+        boolean caughtException = false;
+        try {
+            driver.ensureOptionExists(options, OutOfBandManagement.Option.PORT);
+        } catch (CloudRuntimeException e) {
+            caughtException = true;
+        }
+        Assert.assertTrue(caughtException);
+    }
+
+    @Test
+    public void testIsVMRunningTrue() throws IOException {
+        String json = "{\"listvirtualmachinesresponse\":{\"count\":1,\"virtualmachine\":[{\"id\":\"38fa7380-9543-486a-b083-190ecf726ba4\",\"name\":\"test-vm\",\"displayname\":\"test-vm\",\"account\":\"admin\",\"userid\":\"78ed9ce8-f3ee-11e4-91ab-00012e4fde1c\",\"username\":\"admin\",\"domainid\":\"53601d4b-f3ee-11e4-91ab-00012e4fde1c\",\"domain\":\"ROOT\",\"created\":\"2017-04-04T19:50:56+0200\",\"state\":\"Running\"}]}}";
+        Assert.assertEquals(driver.getNestedVMPowerState(json), OutOfBandManagement.PowerState.On);
+    }
+
+    @Test
+    public void testIsVMRunningFalse() throws IOException {
+        String json = "{\"listvirtualmachinesresponse\":{\"count\":1,\"virtualmachine\":[{\"id\":\"38fa7380-9543-486a-b083-190ecf726ba4\",\"name\":\"test-vm\",\"displayname\":\"test-vm\",\"account\":\"admin\",\"userid\":\"78ed9ce8-f3ee-11e4-91ab-00012e4fde1c\",\"username\":\"admin\",\"domainid\":\"53601d4b-f3ee-11e4-91ab-00012e4fde1c\",\"domain\":\"ROOT\",\"created\":\"2017-04-04T19:50:56+0200\",\"state\":\"Stopped\"}]}}";
+        Assert.assertEquals(driver.getNestedVMPowerState(json), OutOfBandManagement.PowerState.Off);
+    }
+
+    @Test
+    public void testIsVMRunningInvalidJson() throws IOException {
+        String json = "{\"listvirtualmachinesresponse\":{\"count\":1,\"virtualmachine\"83-190ecf726ba4\",\"name";
+        Assert.assertEquals(driver.getNestedVMPowerState(json), OutOfBandManagement.PowerState.Unknown);
+    }
+
+    @Test
+    public void testIsVMRunningEmptyJson() throws IOException {
+        String json = "{}";
+        Assert.assertEquals(driver.getNestedVMPowerState(json), OutOfBandManagement.PowerState.Unknown);
+    }
+}
diff --git a/plugins/pom.xml b/plugins/pom.xml
index bcc7240..2973d43 100755
--- a/plugins/pom.xml
+++ b/plugins/pom.xml
@@ -83,6 +83,7 @@
     <module>network-elements/stratosphere-ssp</module>
     <module>network-elements/opendaylight</module>
     <module>outofbandmanagement-drivers/ipmitool</module>
+    <module>outofbandmanagement-drivers/nested-cloudstack</module>
     <module>storage-allocators/random</module>
     <module>user-authenticators/ldap</module>
     <module>user-authenticators/md5</module>
diff --git a/server/src/org/apache/cloudstack/outofbandmanagement/OutOfBandManagementServiceImpl.java b/server/src/org/apache/cloudstack/outofbandmanagement/OutOfBandManagementServiceImpl.java
index cb6ac10..fe58c64 100644
--- a/server/src/org/apache/cloudstack/outofbandmanagement/OutOfBandManagementServiceImpl.java
+++ b/server/src/org/apache/cloudstack/outofbandmanagement/OutOfBandManagementServiceImpl.java
@@ -138,7 +138,7 @@ public class OutOfBandManagementServiceImpl extends ManagerBase implements OutOf
                     outOfBandManagementConfig.setAddress(value);
                     break;
                 case PORT:
-                    outOfBandManagementConfig.setPort(Integer.parseInt(value));
+                    outOfBandManagementConfig.setPort(value);
                     break;
                 case USERNAME:
                     outOfBandManagementConfig.setUsername(value);
@@ -166,9 +166,7 @@ public class OutOfBandManagementServiceImpl extends ManagerBase implements OutOf
                     value = outOfBandManagementConfig.getAddress();
                     break;
                 case PORT:
-                    if (outOfBandManagementConfig.getPort() != null) {
-                        value = String.valueOf(outOfBandManagementConfig.getPort());
-                    }
+                    value = outOfBandManagementConfig.getPort();
                     break;
                 case USERNAME:
                     value = outOfBandManagementConfig.getUsername();
diff --git a/server/test/org/apache/cloudstack/outofbandmanagement/OutOfBandManagementServiceTest.java b/server/test/org/apache/cloudstack/outofbandmanagement/OutOfBandManagementServiceTest.java
index 69f03ff..7f84819 100644
--- a/server/test/org/apache/cloudstack/outofbandmanagement/OutOfBandManagementServiceTest.java
+++ b/server/test/org/apache/cloudstack/outofbandmanagement/OutOfBandManagementServiceTest.java
@@ -101,7 +101,7 @@ public class OutOfBandManagementServiceTest {
         OutOfBandManagement config = new OutOfBandManagementVO(123L);
         config.setAddress("localhost");
         config.setDriver("ipmitool");
-        config.setPort(1234);
+        config.setPort("1234");
         ImmutableMap<OutOfBandManagement.Option, String> options = oobmService.getOptions(config);
         Assert.assertEquals(options.get(OutOfBandManagement.Option.ADDRESS), "localhost");
         Assert.assertEquals(options.get(OutOfBandManagement.Option.DRIVER), "ipmitool");
diff --git a/setup/db/db/schema-41000to41100.sql b/setup/db/db/schema-41000to41100.sql
index c67e710..1399e28 100644
--- a/setup/db/db/schema-41000to41100.sql
+++ b/setup/db/db/schema-41000to41100.sql
@@ -241,3 +241,6 @@ CREATE VIEW `cloud`.`host_view` AS
             left join
         `cloud`.`ha_config` ON ha_config.resource_id=host.id
             and ha_config.resource_type='Host';
+
+-- Out-of-band management driver for nested-cloudstack
+ALTER TABLE `cloud`.`oobm` MODIFY COLUMN port VARCHAR(255);
diff --git a/test/integration/smoke/test_outofbandmanagement_nestedplugin.py b/test/integration/smoke/test_outofbandmanagement_nestedplugin.py
new file mode 100644
index 0000000..13fb9dd
--- /dev/null
+++ b/test/integration/smoke/test_outofbandmanagement_nestedplugin.py
@@ -0,0 +1,256 @@
+# 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.
+
+import marvin
+from marvin.cloudstackTestCase import *
+from marvin.cloudstackAPI import *
+from marvin.lib.utils import *
+from marvin.lib.base import *
+from marvin.lib.common import *
+from nose.plugins.attrib import attr
+
+from BaseHTTPServer import BaseHTTPRequestHandler,HTTPServer
+
+import socket
+import sys
+import thread
+import time
+
+
+apiRequests = []
+state = "Running"
+
+
+class MockedCloudStackServer(BaseHTTPRequestHandler):
+    """
+        Mocked ACS Mgmt Server
+    """
+    def do_GET(self):
+        global apiRequests, state
+        command = self.path.split('command=')[1].split('&')[0]
+        if command == 'startVirtualMachine':
+            state = "Running"
+        elif command == 'stopVirtualMachine':
+            state = "Stopped"
+        elif command == 'rebootVirtualMachine':
+            state = "Running"
+
+        apiRequests.append(command)
+
+        self.send_response(200)
+        self.send_header('Content-type','application/json')
+        self.end_headers()
+
+        json = "{\"listvirtualmachinesresponse\":{\"count\":1,\"virtualmachine\":[{\"id\":\"some-uuid\",\"name\":\"test-vm\",\"state\":\"%s\"}]}}" % state
+        self.wfile.write(json)
+
+    def log_message(self, format, *args):
+        return
+
+
+class TestOutOfBandManagement(cloudstackTestCase):
+    """ Test cases for out of band management
+    """
+
+    def setUp(self):
+        self.apiclient = self.testClient.getApiClient()
+        self.hypervisor = self.testClient.getHypervisorInfo()
+        self.dbclient = self.testClient.getDbConnection()
+        self.services = self.testClient.getParsedTestDataConfig()
+        self.mgtSvrDetails = self.config.__dict__["mgtSvr"][0].__dict__
+
+        self.zone = get_zone(self.apiclient, self.testClient.getZoneForTests())
+        self.host = None
+        self.server = None
+
+        # use random port for mocked-mgmt server
+        s = socket.socket()
+        s.bind(('', 0))
+        self.serverPort = s.getsockname()[1]
+        s.close()
+
+        self.cleanup = []
+        global state, apiRequests
+        state = "Running"
+        apiRequests = []
+
+
+    def tearDown(self):
+        try:
+            self.dbclient.execute("delete from oobm where driver='nestedcloudstack' and port='some-uuid'")
+            cleanup_resources(self.apiclient, self.cleanup)
+            if self.server:
+                self.server.socket.close()
+            global apiRequests
+            apiRequests = []
+        except Exception as e:
+            raise Exception("Warning: Exception during cleanup : %s" % e)
+
+
+    def getHost(self, hostId=None):
+        if self.host and hostId is None:
+            return self.host
+
+        response = list_hosts(
+                        self.apiclient,
+                        zoneid=self.zone.id,
+                        type='Routing',
+                        id=hostId)
+        if len(response) > 0:
+            self.host = response[0]
+            return self.host
+        raise self.skipTest("No hosts found, skipping out-of-band management test")
+
+
+    def getServerIp(self):
+        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+        s.connect((self.mgtSvrDetails["mgtSvrIp"], self.mgtSvrDetails["port"]))
+        return s.getsockname()[0]
+
+
+    def getServerPort(self):
+        return self.serverPort
+
+
+    def getOobmConfigCmd(self):
+        cmd = configureOutOfBandManagement.configureOutOfBandManagementCmd()
+        cmd.driver = 'nestedcloudstack'
+        cmd.address = 'http://%s:%s/client/api' % (self.getServerIp(), self.getServerPort())
+        cmd.port = 'some-uuid'
+        cmd.username = 'admin'
+        cmd.password = 'password'
+        cmd.hostid = self.getHost().id
+        return cmd
+
+
+    def getOobmEnableCmd(self):
+        cmd = enableOutOfBandManagementForHost.enableOutOfBandManagementForHostCmd()
+        cmd.hostid = self.getHost().id
+        return cmd
+
+
+    def getOobmIssueActionCmd(self):
+        cmd = issueOutOfBandManagementPowerAction.issueOutOfBandManagementPowerActionCmd()
+        cmd.hostid = self.getHost().id
+        cmd.action = 'STATUS'
+        return cmd
+
+
+    def issuePowerActionCmd(self, action):
+        cmd = self.getOobmIssueActionCmd()
+        cmd.action = action
+        return self.apiclient.issueOutOfBandManagementPowerAction(cmd)
+
+
+    def configureAndEnableOobm(self):
+        self.apiclient.configureOutOfBandManagement(self.getOobmConfigCmd())
+        response = self.apiclient.enableOutOfBandManagementForHost(self.getOobmEnableCmd())
+        self.assertEqual(response.enabled, True)
+
+
+    def startMgmtServer(self):
+        def startMgmtServer(tname, server):
+            self.debug("Starting ACS mocked-mgmt server")
+            try:
+                server.serve_forever()
+            except Exception: pass
+        server = HTTPServer(('0.0.0.0', self.getServerPort()), MockedCloudStackServer)
+        thread.start_new_thread(startMgmtServer, ("mocked-mgmt-server", server,))
+        self.server = server
+
+
+    def configureAndStartMgmtServer(self):
+        """
+            Configure mocked-mgmt server and enable out-of-band management for host
+        """
+        self.configureAndEnableOobm()
+        self.startMgmtServer()
+
+
+    def assertIssueCommandState(self, command, expected):
+        """
+            Asserts power action result for a given power command
+        """
+
+        if command != 'STATUS':
+            self.issuePowerActionCmd(command)
+        response = self.issuePowerActionCmd('STATUS')
+        self.assertEqual(response.powerstate, expected)
+
+
+    @attr(tags = ["devcloud", "advanced", "advancedns", "smoke", "basic", "sg"], required_hardware="false")
+    def test_oobm_issue_power_status(self):
+        """
+            Tests out-of-band management issue power action
+        """
+        self.configureAndStartMgmtServer()
+        self.assertIssueCommandState('STATUS', 'On')
+
+
+    @attr(tags = ["devcloud", "advanced", "advancedns", "smoke", "basic", "sg"], required_hardware="false")
+    def test_oobm_issue_power_on(self):
+        """
+            Tests out-of-band management issue power on action
+        """
+        self.configureAndStartMgmtServer()
+        self.assertIssueCommandState('ON', 'On')
+        global apiRequests
+        self.assertTrue('startVirtualMachine' in apiRequests)
+
+
+    @attr(tags = ["devcloud", "advanced", "advancedns", "smoke", "basic", "sg"], required_hardware="false")
+    def test_oobm_issue_power_off(self):
+        """
+            Tests out-of-band management issue power off action
+        """
+        self.configureAndStartMgmtServer()
+        self.assertIssueCommandState('OFF', 'Off')
+        global apiRequests
+        self.assertTrue('stopVirtualMachine' in apiRequests)
+
+
+    @attr(tags = ["devcloud", "advanced", "advancedns", "smoke", "basic", "sg"], required_hardware="false")
+    def test_oobm_issue_power_cycle(self):
+        """
+            Tests out-of-band management issue power cycle action
+        """
+        self.configureAndStartMgmtServer()
+        self.assertIssueCommandState('CYCLE', 'On')
+        global apiRequests
+        self.assertTrue('rebootVirtualMachine' in apiRequests)
+
+
+    @attr(tags = ["devcloud", "advanced", "advancedns", "smoke", "basic", "sg"], required_hardware="false")
+    def test_oobm_issue_power_reset(self):
+        """
+            Tests out-of-band management issue power reset action
+        """
+        self.configureAndStartMgmtServer()
+        self.assertIssueCommandState('RESET', 'On')
+        global apiRequests
+        self.assertTrue('rebootVirtualMachine' in apiRequests)
+
+
+    @attr(tags = ["devcloud", "advanced", "advancedns", "smoke", "basic", "sg"], required_hardware="false")
+    def test_oobm_issue_power_soft(self):
+        """
+            Tests out-of-band management issue power soft action
+        """
+        self.configureAndStartMgmtServer()
+        self.assertIssueCommandState('SOFT', 'Off')
+        global apiRequests
+        self.assertTrue('stopVirtualMachine' in apiRequests)
diff --git a/ui/scripts/system.js b/ui/scripts/system.js
index a0bcb3a..d13e35f 100755
--- a/ui/scripts/system.js
+++ b/ui/scripts/system.js
@@ -16786,6 +16786,10 @@
                                                     id: 'ipmitool',
                                                     description: 'ipmitool - ipmitool based shell driver'
                                                 });
+                                                items.push({
+                                                    id: 'nestedcloudstack',
+                                                    description: 'nested-cloudstack - controls host that is a VM in a parent cloudstack (testing purposes only)'
+                                                });
                                                 args.response.success({
                                                     data: items
                                                 });

-- 
To stop receiving notification emails like this one, please contact
"commits@cloudstack.apache.org" <co...@cloudstack.apache.org>.