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