Support openstack controller node status check feature

Change-Id: I285b977ae32dd6d140aca7f25b00962db77b1054
diff --git a/apps/openstacknode/app/src/main/java/org/onosproject/openstacknode/impl/DefaultOpenstackNode.java b/apps/openstacknode/app/src/main/java/org/onosproject/openstacknode/impl/DefaultOpenstackNode.java
index 73c8e1b..e6592b0 100644
--- a/apps/openstacknode/app/src/main/java/org/onosproject/openstacknode/impl/DefaultOpenstackNode.java
+++ b/apps/openstacknode/app/src/main/java/org/onosproject/openstacknode/impl/DefaultOpenstackNode.java
@@ -398,9 +398,6 @@
                 }
             } else {
                 checkArgument(endPoint != null, NOT_NULL_MSG, "endpoint URL");
-
-                // we force controller node to have COMPLETE state for now
-                state = NodeState.COMPLETE;
             }
 
             if (type == NodeType.GATEWAY && uplinkPort == null) {
diff --git a/apps/openstacknode/app/src/main/java/org/onosproject/openstacknode/impl/DefaultOpenstackNodeHandler.java b/apps/openstacknode/app/src/main/java/org/onosproject/openstacknode/impl/DefaultOpenstackNodeHandler.java
index 6064267..ba7e8ec 100644
--- a/apps/openstacknode/app/src/main/java/org/onosproject/openstacknode/impl/DefaultOpenstackNodeHandler.java
+++ b/apps/openstacknode/app/src/main/java/org/onosproject/openstacknode/impl/DefaultOpenstackNodeHandler.java
@@ -60,6 +60,7 @@
 import org.onosproject.openstacknode.api.OpenstackNodeService;
 import org.onosproject.openstacknode.api.OpenstackPhyInterface;
 import org.onosproject.ovsdb.controller.OvsdbController;
+import org.openstack4j.api.OSClient;
 import org.osgi.service.component.ComponentContext;
 import org.slf4j.Logger;
 
@@ -80,8 +81,11 @@
 import static org.onosproject.openstacknode.api.NodeState.COMPLETE;
 import static org.onosproject.openstacknode.api.NodeState.DEVICE_CREATED;
 import static org.onosproject.openstacknode.api.NodeState.INCOMPLETE;
+import static org.onosproject.openstacknode.api.NodeState.INIT;
+import static org.onosproject.openstacknode.api.OpenstackNode.NodeType.CONTROLLER;
 import static org.onosproject.openstacknode.api.OpenstackNode.NodeType.GATEWAY;
 import static org.onosproject.openstacknode.api.OpenstackNodeService.APP_ID;
+import static org.onosproject.openstacknode.util.OpenstackNodeUtil.getConnectedClient;
 import static org.onosproject.openstacknode.util.OpenstackNodeUtil.isOvsdbConnected;
 import static org.slf4j.LoggerFactory.getLogger;
 
@@ -422,11 +426,39 @@
      * @param osNode openstack node
      */
     private void bootstrapNode(OpenstackNode osNode) {
-        if (isCurrentStateDone(osNode)) {
-            setState(osNode, osNode.state().nextState());
+        if (osNode.type() == CONTROLLER) {
+            if (osNode.state() == INIT && checkEndpoint(osNode)) {
+                setState(osNode, COMPLETE);
+            }
         } else {
-            log.trace("Processing {} state for {}", osNode.state(), osNode.hostname());
-            osNode.state().process(this, osNode);
+            if (isCurrentStateDone(osNode)) {
+                setState(osNode, osNode.state().nextState());
+            } else {
+                log.trace("Processing {} state for {}", osNode.state(), osNode.hostname());
+                osNode.state().process(this, osNode);
+            }
+        }
+    }
+
+    /**
+     * Checks the validity of the given endpoint.
+     *
+     * @param osNode gateway node
+     * @return validity result
+     */
+    private boolean checkEndpoint(OpenstackNode osNode) {
+        if (osNode == null) {
+            log.warn("Keystone auth info has not been configured. " +
+                     "Please specify auth info via network-cfg.json.");
+            return false;
+        }
+
+        OSClient client = getConnectedClient(osNode);
+
+        if (client == null) {
+            return false;
+        } else {
+            return client.getSupportedServices().size() != 0;
         }
     }
 
@@ -442,7 +474,8 @@
             NodeId leader = leadershipService.getLeader(appId.name());
             return Objects.equals(localNode, leader) &&
                     event.subject().type() == Device.Type.CONTROLLER &&
-                    osNodeService.node(event.subject().id()) != null;
+                    osNodeService.node(event.subject().id()) != null &&
+                    osNodeService.node(event.subject().id()).type() != CONTROLLER;
         }
 
         @Override
@@ -483,7 +516,8 @@
             NodeId leader = leadershipService.getLeader(appId.name());
             return Objects.equals(localNode, leader) &&
                     event.subject().type() == Device.Type.SWITCH &&
-                    osNodeService.node(event.subject().id()) != null;
+                    osNodeService.node(event.subject().id()) != null &&
+                    osNodeService.node(event.subject().id()).type() != CONTROLLER;
         }
 
         @Override
diff --git a/apps/openstacknode/app/src/main/java/org/onosproject/openstacknode/impl/DistributedOpenstackNodeStore.java b/apps/openstacknode/app/src/main/java/org/onosproject/openstacknode/impl/DistributedOpenstackNodeStore.java
index be49135..4b40775 100644
--- a/apps/openstacknode/app/src/main/java/org/onosproject/openstacknode/impl/DistributedOpenstackNodeStore.java
+++ b/apps/openstacknode/app/src/main/java/org/onosproject/openstacknode/impl/DistributedOpenstackNodeStore.java
@@ -166,23 +166,6 @@
         @Override
         public void event(MapEvent<String, OpenstackNode> event) {
 
-            OpenstackNode node;
-
-            if (event.type() == INSERT || event.type() == UPDATE) {
-                node = event.newValue().value();
-            } else {
-                node = event.oldValue().value();
-            }
-
-            // we do not notify the controller node related event
-            // controller node event should be handled in different way
-            if (node.type() == CONTROLLER) {
-                // TODO: need to find a way to check the controller node availability
-                log.info("node {} is detected", node.hostname());
-
-                return;
-            }
-
             switch (event.type()) {
                 case INSERT:
                     log.debug("OpenStack node created {}", event.newValue());
@@ -198,6 +181,13 @@
                                 OPENSTACK_NODE_UPDATED,
                                 event.newValue().value()
                         ));
+
+                        // if the event is about controller node, we will not
+                        // process COMPLETE and INCOMPLETE state
+                        if (isControllerNode(event)) {
+                            return;
+                        }
+
                         if (event.newValue().value().state() == COMPLETE) {
                             notifyDelegate(new OpenstackNodeEvent(
                                     OPENSTACK_NODE_COMPLETE,
@@ -223,5 +213,25 @@
                     break;
             }
         }
+
+        /**
+         * Checks the openstack node whether a controller node or not with
+         * the given MapEvent.
+         *
+         * @param event map event
+         * @return controller node indicator flag
+         */
+        private boolean isControllerNode(MapEvent<String, OpenstackNode> event) {
+
+            OpenstackNode node;
+
+            if (event.type() == INSERT || event.type() == UPDATE) {
+                node = event.newValue().value();
+            } else {
+                node = event.oldValue().value();
+            }
+
+            return node.type() == CONTROLLER;
+        }
     }
 }
diff --git a/apps/openstacknode/app/src/main/java/org/onosproject/openstacknode/util/OpenstackNodeUtil.java b/apps/openstacknode/app/src/main/java/org/onosproject/openstacknode/util/OpenstackNodeUtil.java
index 79526e6..8e1de52 100644
--- a/apps/openstacknode/app/src/main/java/org/onosproject/openstacknode/util/OpenstackNodeUtil.java
+++ b/apps/openstacknode/app/src/main/java/org/onosproject/openstacknode/util/OpenstackNodeUtil.java
@@ -16,21 +16,44 @@
 package org.onosproject.openstacknode.util;
 
 import org.onosproject.net.device.DeviceService;
+import org.onosproject.openstacknode.api.OpenstackAuth;
+import org.onosproject.openstacknode.api.OpenstackAuth.Perspective;
 import org.onosproject.openstacknode.api.OpenstackNode;
 import org.onosproject.ovsdb.controller.OvsdbClientService;
 import org.onosproject.ovsdb.controller.OvsdbController;
 import org.onosproject.ovsdb.controller.OvsdbNodeId;
+import org.openstack4j.api.OSClient;
+import org.openstack4j.api.client.IOSClientBuilder;
+import org.openstack4j.api.exceptions.AuthenticationException;
+import org.openstack4j.api.types.Facing;
+import org.openstack4j.core.transport.Config;
+import org.openstack4j.model.common.Identifier;
+import org.openstack4j.openstack.OSFactory;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.X509TrustManager;
+import java.security.cert.X509Certificate;
+
 /**
  * An utility that used in openstack node app.
  */
 public final class OpenstackNodeUtil {
     protected static final Logger log = LoggerFactory.getLogger(OpenstackNodeUtil.class);
 
+    // keystone endpoint related variables
+    private static final String DOMAIN_DEFAULT = "default";
+    private static final String KEYSTONE_V2 = "v2.0";
+    private static final String KEYSTONE_V3 = "v3";
+    private static final String IDENTITY_PATH = "identity/";
+    private static final String SSL_TYPE = "SSL";
+
     /**
-     * Prevents object instantiation from external.
+     * Prevents object installation from external.
      */
     private OpenstackNodeUtil() {
     }
@@ -55,4 +78,147 @@
                 client != null &&
                 client.isConnected();
     }
+
+    /**
+     * Obtains a connected openstack client.
+     *
+     * @param osNode openstack node
+     * @return a connected openstack client
+     */
+    public static OSClient getConnectedClient(OpenstackNode osNode) {
+        OpenstackAuth auth = osNode.authentication();
+        String endpoint = buildEndpoint(osNode);
+        Perspective perspective = auth.perspective();
+
+        Config config = getSslConfig();
+
+        try {
+            if (endpoint.contains(KEYSTONE_V2)) {
+                IOSClientBuilder.V2 builder = OSFactory.builderV2()
+                        .endpoint(endpoint)
+                        .tenantName(auth.project())
+                        .credentials(auth.username(), auth.password())
+                        .withConfig(config);
+
+                if (perspective != null) {
+                    builder.perspective(getFacing(perspective));
+                }
+
+                return builder.authenticate();
+            } else if (endpoint.contains(KEYSTONE_V3)) {
+
+                Identifier project = Identifier.byName(auth.project());
+                Identifier domain = Identifier.byName(DOMAIN_DEFAULT);
+
+                IOSClientBuilder.V3 builder = OSFactory.builderV3()
+                        .endpoint(endpoint)
+                        .credentials(auth.username(), auth.password(), domain)
+                        .scopeToProject(project, domain)
+                        .withConfig(config);
+
+                if (perspective != null) {
+                    builder.perspective(getFacing(perspective));
+                }
+
+                return builder.authenticate();
+            } else {
+                log.warn("Unrecognized keystone version type");
+                return null;
+            }
+        } catch (AuthenticationException e) {
+            log.error("Authentication failed due to {}", e.toString());
+            return null;
+        }
+    }
+
+    /**
+     * Builds up and a complete endpoint URL from gateway node.
+     *
+     * @param node gateway node
+     * @return a complete endpoint URL
+     */
+    private static String buildEndpoint(OpenstackNode node) {
+
+        OpenstackAuth auth = node.authentication();
+
+        StringBuilder endpointSb = new StringBuilder();
+        endpointSb.append(auth.protocol().name().toLowerCase());
+        endpointSb.append("://");
+        endpointSb.append(node.endPoint());
+        endpointSb.append(":");
+        endpointSb.append(auth.port());
+        endpointSb.append("/");
+
+        // in case the version is v3, we need to append identity path into endpoint
+        if (auth.version().equals(KEYSTONE_V3)) {
+            endpointSb.append(IDENTITY_PATH);
+        }
+
+        endpointSb.append(auth.version());
+        return endpointSb.toString();
+    }
+
+    /**
+     * Obtains the SSL config without verifying the certification.
+     *
+     * @return SSL config
+     */
+    private static Config getSslConfig() {
+        // we bypass the SSL certification verification for now
+        // TODO: verify server side SSL using a given certification
+        Config config = Config.newConfig().withSSLVerificationDisabled();
+
+        TrustManager[] trustAllCerts = new TrustManager[]{
+                new X509TrustManager() {
+                    public X509Certificate[] getAcceptedIssuers() {
+                        return null;
+                    }
+
+                    public void checkClientTrusted(X509Certificate[] certs,
+                                                   String authType) {
+                    }
+
+                    public void checkServerTrusted(X509Certificate[] certs,
+                                                   String authType) {
+                    }
+                }
+        };
+
+        HostnameVerifier allHostsValid = (hostname, session) -> true;
+
+        try {
+            SSLContext sc = SSLContext.getInstance(SSL_TYPE);
+            sc.init(null, trustAllCerts,
+                    new java.security.SecureRandom());
+            HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
+            HttpsURLConnection.setDefaultHostnameVerifier(allHostsValid);
+
+            config.withSSLContext(sc);
+        } catch (Exception e) {
+            log.error("Failed to access OpenStack service due to {}", e.toString());
+            return null;
+        }
+
+        return config;
+    }
+
+    /**
+     * Obtains the facing object with given openstack perspective.
+     *
+     * @param perspective keystone perspective
+     * @return facing object
+     */
+    private static Facing getFacing(Perspective perspective) {
+
+        switch (perspective) {
+            case PUBLIC:
+                return Facing.PUBLIC;
+            case ADMIN:
+                return Facing.ADMIN;
+            case INTERNAL:
+                return Facing.INTERNAL;
+            default:
+                return null;
+        }
+    }
 }