CORD-537 Implemented ServiceNetworkService with XOS VTN APIs

Change-Id: If2ece511400c0720dc425f7ad9acd9b11d64d566
diff --git a/src/main/java/org/opencord/cordvtn/api/config/AbstractApiConfig.java b/src/main/java/org/opencord/cordvtn/api/config/AbstractApiConfig.java
new file mode 100644
index 0000000..ffac44f
--- /dev/null
+++ b/src/main/java/org/opencord/cordvtn/api/config/AbstractApiConfig.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2016-present Open Networking Laboratory
+ *
+ * Licensed 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.opencord.cordvtn.api.config;
+
+import com.google.common.base.MoreObjects;
+
+import java.util.Objects;
+
+/**
+ * Representation of external API access configuration.
+ */
+public abstract class AbstractApiConfig {
+
+    protected final String endpoint;
+    protected final String user;
+    protected final String password;
+
+    /**
+     * Default constructor.
+     *
+     * @param endpoint api endpoint
+     * @param user     user name
+     * @param password password of the user
+     */
+    protected AbstractApiConfig(String endpoint, String user, String password) {
+        this.endpoint = endpoint;
+        this.user = user;
+        this.password = password;
+    }
+
+    /**
+     * Returns the endpoint.
+     *
+     * @return endpoint
+     */
+    public String endpoint() {
+        return endpoint;
+    }
+
+    /**
+     * Returns the user.
+     *
+     * @return user
+     */
+    public String user() {
+        return user;
+    }
+
+    /**
+     * Returns the password.
+     *
+     * @return password
+     */
+    public String password() {
+        return password;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(endpoint, user, password);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if ((obj instanceof AbstractApiConfig)) {
+            AbstractApiConfig that = (AbstractApiConfig) obj;
+            if (Objects.equals(endpoint, that.endpoint) &&
+                    Objects.equals(user, that.user) &&
+                    Objects.equals(password, that.password)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public String toString() {
+        return MoreObjects.toStringHelper(getClass())
+                .add("endpoint", endpoint)
+                .add("user", user)
+                .add("password", password)
+                .toString();
+    }
+}
diff --git a/src/main/java/org/opencord/cordvtn/api/config/CordVtnConfig.java b/src/main/java/org/opencord/cordvtn/api/config/CordVtnConfig.java
index 9dfa929..0789d51 100644
--- a/src/main/java/org/opencord/cordvtn/api/config/CordVtnConfig.java
+++ b/src/main/java/org/opencord/cordvtn/api/config/CordVtnConfig.java
@@ -237,5 +237,15 @@
         return publicGateways;
     }
 
-    // TODO add methods to get XOS and OpenStack API access
+    /**
+     * Returns XOS API endpoint and credential configuration.
+     *
+     * @return xos api configuration
+     */
+    public XosConfig xosConfig() {
+        JsonNode jsonNode = object.get(XOS);
+        return new XosConfig(getConfig(jsonNode, ENDPOINT),
+                             getConfig(jsonNode, USER),
+                             getConfig(jsonNode, PASSWORD));
+    }
 }
diff --git a/src/main/java/org/opencord/cordvtn/api/config/XosConfig.java b/src/main/java/org/opencord/cordvtn/api/config/XosConfig.java
new file mode 100644
index 0000000..b2387a6
--- /dev/null
+++ b/src/main/java/org/opencord/cordvtn/api/config/XosConfig.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2016-present Open Networking Laboratory
+ *
+ * Licensed 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.opencord.cordvtn.api.config;
+
+/**
+ * Representation of XOS API access configuration.
+ */
+public final class XosConfig extends AbstractApiConfig {
+
+    /**
+     * Default constructor.
+     *
+     * @param endpoint api endpoint
+     * @param user     user name
+     * @param password password of the user
+     */
+    public XosConfig(String endpoint, String user, String password) {
+        super(endpoint, user, password);
+    }
+}
diff --git a/src/main/java/org/opencord/cordvtn/api/net/NetworkId.java b/src/main/java/org/opencord/cordvtn/api/net/NetworkId.java
index 8bdafee..9129053 100644
--- a/src/main/java/org/opencord/cordvtn/api/net/NetworkId.java
+++ b/src/main/java/org/opencord/cordvtn/api/net/NetworkId.java
@@ -15,8 +15,11 @@
  */
 package org.opencord.cordvtn.api.net;
 
+import com.google.common.base.Strings;
 import org.onlab.util.Identifier;
 
+import static com.google.common.base.Preconditions.checkArgument;
+
 /**
  * Representation of the network identifier.
  */
@@ -38,6 +41,7 @@
      * @return network identifier
      */
     public static NetworkId of(String id) {
+        checkArgument(!Strings.isNullOrEmpty(id));
         return new NetworkId(id);
     }
 }
diff --git a/src/main/java/org/opencord/cordvtn/api/net/PortId.java b/src/main/java/org/opencord/cordvtn/api/net/PortId.java
index 10c9e8d..4a3d36e 100644
--- a/src/main/java/org/opencord/cordvtn/api/net/PortId.java
+++ b/src/main/java/org/opencord/cordvtn/api/net/PortId.java
@@ -15,8 +15,11 @@
  */
 package org.opencord.cordvtn.api.net;
 
+import com.google.common.base.Strings;
 import org.onlab.util.Identifier;
 
+import static com.google.common.base.Preconditions.checkArgument;
+
 /**
  * Representation of the port identifier.
  */
@@ -38,6 +41,7 @@
      * @return port identifier
      */
     public static PortId of(String id) {
+        checkArgument(!Strings.isNullOrEmpty(id));
         return new PortId(id);
     }
 }
diff --git a/src/main/java/org/opencord/cordvtn/codec/ServiceNetworkCodec.java b/src/main/java/org/opencord/cordvtn/codec/ServiceNetworkCodec.java
index b2ab70f..84226b9 100644
--- a/src/main/java/org/opencord/cordvtn/codec/ServiceNetworkCodec.java
+++ b/src/main/java/org/opencord/cordvtn/codec/ServiceNetworkCodec.java
@@ -16,6 +16,7 @@
 package org.opencord.cordvtn.codec;
 
 import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.NullNode;
 import com.fasterxml.jackson.databind.node.ObjectNode;
 import com.google.common.collect.Sets;
 import org.onosproject.codec.CodecContext;
@@ -28,6 +29,7 @@
 
 import java.util.Set;
 
+import static com.google.common.base.Preconditions.checkArgument;
 import static java.lang.Boolean.FALSE;
 import static java.lang.Boolean.TRUE;
 import static org.opencord.cordvtn.api.dependency.Dependency.Type.BIDIRECTIONAL;
@@ -44,6 +46,10 @@
     private static final String PROVIDER_NETWORKS = "providerNetworks";
     private static final String BIDIRECT = "bidirectional";
 
+    private static final String ERR_JSON = "Invalid ServiceNetwork received";
+    private static final String ERR_ID = ": network ID cannot be null";
+    private static final String ERR_TYPE = ": type cannot be null";
+
     @Override
     public ObjectNode encode(ServiceNetwork snet, CodecContext context) {
         ObjectNode result = context.mapper().createObjectNode()
@@ -64,9 +70,13 @@
 
     @Override
     public ServiceNetwork decode(ObjectNode json, CodecContext context) {
-        if (json == null || !json.isObject()) {
-            return null;
-        }
+        checkArgument(json != null && json.isObject(), ERR_JSON);
+        checkArgument(json.get(ID) != null &&
+                              json.get(ID) != NullNode.getInstance(),
+                      ERR_JSON + ERR_ID);
+        checkArgument(json.get(TYPE) != null &&
+                              json.get(TYPE) != NullNode.getInstance(),
+                      ERR_JSON + ERR_TYPE);
 
         NetworkId netId = NetworkId.of(json.get(ID).asText());
         ServiceNetworkType netType = valueOf(json.get(TYPE).asText().toUpperCase());
@@ -79,7 +89,6 @@
                 providers.add(ProviderNetwork.of(providerId, type));
             });
         }
-
         return new ServiceNetwork(netId, netType, providers);
     }
 }
diff --git a/src/main/java/org/opencord/cordvtn/codec/ServicePortCodec.java b/src/main/java/org/opencord/cordvtn/codec/ServicePortCodec.java
index 9630ee8..bb4a82b 100644
--- a/src/main/java/org/opencord/cordvtn/codec/ServicePortCodec.java
+++ b/src/main/java/org/opencord/cordvtn/codec/ServicePortCodec.java
@@ -16,6 +16,7 @@
 package org.opencord.cordvtn.codec;
 
 import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.NullNode;
 import com.fasterxml.jackson.databind.node.ObjectNode;
 import com.google.common.collect.Sets;
 import org.onlab.packet.IpAddress;
@@ -29,6 +30,8 @@
 
 import java.util.Set;
 
+import static com.google.common.base.Preconditions.checkArgument;
+
 /**
  * Service port JSON codec.
  */
@@ -40,6 +43,9 @@
     private static final String IP_ADDRESS = "ip_address";
     private static final String MAC_ADDRESS = "mac_address";
 
+    private static final String ERR_JSON = "Invalid ServicePort received";
+    private static final String ERR_ID = ": port ID cannot be null";
+
     @Override
     public ObjectNode encode(ServicePort sport, CodecContext context) {
         ObjectNode result = context.mapper().createObjectNode()
@@ -61,13 +67,14 @@
 
     @Override
     public ServicePort decode(ObjectNode json, CodecContext context) {
-        if (json == null || !json.isObject()) {
-            return null;
-        }
+        checkArgument(json != null && json.isObject(), ERR_JSON);
+        checkArgument(json.get(ID) != null &&
+                              json.get(ID) != NullNode.getInstance(),
+                      ERR_JSON + ERR_ID);
 
         PortId portId = PortId.of(json.get(ID).asText());
         VlanId vlanId = null;
-        if (json.get(VLAN_ID) != null) {
+        if (json.get(VLAN_ID) != NullNode.getInstance()) {
             try {
                 vlanId = VlanId.vlanId(json.get(VLAN_ID).asText());
             } catch (Exception ignore) {
diff --git a/src/main/java/org/opencord/cordvtn/impl/external/XosServiceNetworking.java b/src/main/java/org/opencord/cordvtn/impl/external/XosServiceNetworking.java
new file mode 100644
index 0000000..1c232cf
--- /dev/null
+++ b/src/main/java/org/opencord/cordvtn/impl/external/XosServiceNetworking.java
@@ -0,0 +1,269 @@
+/*
+ * Copyright 2016-present Open Networking Laboratory
+ *
+ * Licensed 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.opencord.cordvtn.impl.external;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.SerializationFeature;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableSet;
+import org.glassfish.jersey.client.ClientProperties;
+import org.onlab.util.Tools;
+import org.onosproject.rest.AbstractWebResource;
+import org.opencord.cordvtn.api.net.NetworkId;
+import org.opencord.cordvtn.api.net.PortId;
+import org.opencord.cordvtn.api.net.ServiceNetwork;
+import org.opencord.cordvtn.api.net.ServiceNetworkService;
+import org.opencord.cordvtn.api.net.ServicePort;
+import org.slf4j.Logger;
+
+import javax.ws.rs.client.Client;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.client.Invocation;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.Response;
+import java.io.IOException;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.net.MediaType.JSON_UTF_8;
+import static java.net.HttpURLConnection.HTTP_OK;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Implementation of {@link ServiceNetworkService} with XOS VTN service.
+ */
+public final class XosServiceNetworking extends AbstractWebResource
+        implements ServiceNetworkService {
+
+    protected final Logger log = getLogger(getClass());
+
+    private static final String URL_BASE = "/api/service/vtn/";
+    private static final String URL_SERVICE_NETWORKS = "serviceNetworks/";
+    private static final String URL_SERVICE_PORTS = "servicePorts/";
+
+    private static final String SERVICE_PORTS = "servicePorts";
+    private static final String SERVICE_PORT  = "servicePort";
+    private static final String SERVICE_NETWORKS = "serviceNetworks";
+    private static final String SERVICE_NETWORK  = "serviceNetwork";
+    private static final String EMPTY_JSON_STRING = "{}";
+
+    private static final String MSG_RECEIVED = "Received ";
+    private static final String ERR_LOG = "Received %s result with wrong format: %s";
+
+    private static final int DEFAULT_TIMEOUT_MS = 2000;
+
+    private final String endpoint;
+    private final String user;
+    private final String password;
+    private final Client client = ClientBuilder.newClient();
+
+    private XosServiceNetworking(String endpoint, String user, String password) {
+        this.endpoint = endpoint;
+        this.user = user;
+        this.password = password;
+
+        client.property(ClientProperties.CONNECT_TIMEOUT, DEFAULT_TIMEOUT_MS);
+        client.property(ClientProperties.READ_TIMEOUT, DEFAULT_TIMEOUT_MS);
+        mapper().enable(SerializationFeature.INDENT_OUTPUT);
+    }
+
+    @Override
+    public Set<ServiceNetwork> serviceNetworks() {
+        String response = restGet(URL_SERVICE_NETWORKS);
+        final String error = String.format(ERR_LOG, SERVICE_NETWORKS, response);
+        try {
+            JsonNode jsonTree = mapper().readTree(response).get(SERVICE_NETWORKS);
+            if (jsonTree == null) {
+                return ImmutableSet.of();
+            }
+            log.trace(MSG_RECEIVED + SERVICE_NETWORKS);
+            log.trace(mapper().writeValueAsString(jsonTree));
+            return Tools.stream(jsonTree).map(snet -> codec(ServiceNetwork.class)
+                    .decode((ObjectNode) snet, this))
+                    .collect(Collectors.toSet());
+        } catch (IOException e) {
+            throw new IllegalArgumentException(error);
+        }
+    }
+
+    @Override
+    public ServiceNetwork serviceNetwork(NetworkId netId) {
+        String response = restGet(URL_SERVICE_NETWORKS + netId.id());
+        final String error = String.format(ERR_LOG, SERVICE_NETWORK, response);
+        try {
+            JsonNode jsonTree = mapper().readTree(response).get(SERVICE_NETWORK);
+            if (jsonTree == null) {
+                throw new IllegalArgumentException(error);
+            }
+            log.trace(MSG_RECEIVED + SERVICE_NETWORK);
+            log.trace(mapper().writeValueAsString(jsonTree));
+            return codec(ServiceNetwork.class).decode((ObjectNode) jsonTree, this);
+        } catch (IOException e) {
+            throw new IllegalArgumentException(error);
+        }
+    }
+
+    @Override
+    public Set<ServicePort> servicePorts() {
+        String response = restGet(URL_SERVICE_PORTS);
+        final String error = String.format(ERR_LOG, SERVICE_PORTS, response);
+        try {
+            JsonNode jsonTree = mapper().readTree(response).get(SERVICE_PORTS);
+            if (jsonTree == null) {
+                ImmutableSet.of();
+            }
+            log.trace(MSG_RECEIVED + SERVICE_PORTS);
+            log.trace(mapper().writeValueAsString(jsonTree));
+            return Tools.stream(jsonTree).map(sport -> codec(ServicePort.class)
+                    .decode((ObjectNode) sport, this))
+                    .collect(Collectors.toSet());
+        } catch (IOException e) {
+            throw new IllegalArgumentException(error);
+        }
+    }
+
+    @Override
+    public ServicePort servicePort(PortId portId) {
+        String response = restGet(URL_SERVICE_PORTS + portId.id());
+        final String error = String.format(ERR_LOG, SERVICE_PORT, response);
+        try {
+            JsonNode jsonTree = mapper().readTree(response).get(SERVICE_PORT);
+            if (jsonTree == null) {
+                throw new IllegalArgumentException(error);
+            }
+            log.trace(MSG_RECEIVED + SERVICE_PORT);
+            log.trace(mapper().writeValueAsString(jsonTree));
+            return codec(ServicePort.class).decode((ObjectNode) jsonTree, this);
+        } catch (IOException e) {
+            throw new IllegalArgumentException(error);
+        }
+    }
+
+    private String restGet(String path) {
+        WebTarget wt = client.target(endpoint + URL_BASE).path(path);
+        Invocation.Builder builder = wt.request(JSON_UTF_8.toString());
+        try {
+            Response response = builder.get();
+            if (response.getStatus() != HTTP_OK) {
+                log.warn("Failed to get resource {}", endpoint + path);
+                return EMPTY_JSON_STRING;
+            }
+        } catch (javax.ws.rs.ProcessingException e) {
+            return EMPTY_JSON_STRING;
+        }
+        return builder.get(String.class);
+    }
+
+    /**
+     * Returns endpoint url for the XOS service API access.
+     *
+     * @return endpoint url as a string
+     */
+    public String endpoint() {
+        return endpoint;
+    }
+
+    /**
+     * Returns user name for the XOS service API access.
+     *
+     * @return user name as a string
+     */
+    public String user() {
+        return user;
+    }
+
+    /**
+     * Returns password for the XOS service API access.
+     *
+     * @return password as a string
+     */
+    public String password() {
+        return password;
+    }
+
+    /**
+     * Returns new XOS service networking builder instance.
+     *
+     * @return xos service networking builder
+     */
+    public static Builder builder() {
+        return new Builder();
+    }
+
+    /**
+     * Builder of the XOS service networking entities.
+     */
+    public static final class Builder {
+
+        private String endpoint;
+        private String user;
+        private String password;
+
+        private Builder() {
+        }
+
+        /**
+         * Builds immutable XOS service networking instance.
+         *
+         * @return xos service networking instance
+         */
+        public XosServiceNetworking build() {
+            checkArgument(!Strings.isNullOrEmpty(endpoint));
+            checkArgument(!Strings.isNullOrEmpty(user));
+            checkArgument(!Strings.isNullOrEmpty(password));
+
+            // TODO perform authentication when XOS provides it
+            return new XosServiceNetworking(endpoint, user, password);
+        }
+
+        /**
+         * Returns XOS service networking builder with the supplied endpoint.
+         *
+         * @param endpoint endpoint url
+         * @return xos service networking builder
+         */
+        public Builder endpoint(String endpoint) {
+            this.endpoint = endpoint;
+            return this;
+        }
+
+        /**
+         * Returns XOS service networking builder with the supplied user
+         * credential.
+         *
+         * @param user user
+         * @return xos service networking builder
+         */
+        public Builder user(String user) {
+            this.user = user;
+            return this;
+        }
+
+        /**
+         * Returns XOS service networking builder with the supplied password
+         * credential.
+         *
+         * @param password password
+         * @return xos service networking builder
+         */
+        public Builder password(String password) {
+            this.password = password;
+            return this;
+        }
+    }
+}
diff --git a/src/main/java/org/opencord/cordvtn/impl/external/package-info.java b/src/main/java/org/opencord/cordvtn/impl/external/package-info.java
new file mode 100644
index 0000000..a096589
--- /dev/null
+++ b/src/main/java/org/opencord/cordvtn/impl/external/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2015-present Open Networking Laboratory
+ *
+ * Licensed 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.
+ */
+
+/**
+ * Implementation of NetworkService and ServiceNetworkService for external services.
+ */
+package org.opencord.cordvtn.impl.external;
\ No newline at end of file