summaryrefslogtreecommitdiffstats
path: root/netbox-client/provider/src
diff options
context:
space:
mode:
Diffstat (limited to 'netbox-client/provider/src')
-rw-r--r--netbox-client/provider/src/main/java/org/onap/ccsdk/sli/adaptors/netbox/api/IpamException.java27
-rw-r--r--netbox-client/provider/src/main/java/org/onap/ccsdk/sli/adaptors/netbox/api/NetboxClient.java40
-rw-r--r--netbox-client/provider/src/main/java/org/onap/ccsdk/sli/adaptors/netbox/impl/NetboxClientImpl.java105
-rw-r--r--netbox-client/provider/src/main/java/org/onap/ccsdk/sli/adaptors/netbox/impl/NetboxHttpClient.java151
-rw-r--r--netbox-client/provider/src/main/java/org/onap/ccsdk/sli/adaptors/netbox/model/IPAddress.java58
-rw-r--r--netbox-client/provider/src/main/java/org/onap/ccsdk/sli/adaptors/netbox/model/Identifiable.java29
-rw-r--r--netbox-client/provider/src/main/java/org/onap/ccsdk/sli/adaptors/netbox/model/Prefix.java20
-rw-r--r--netbox-client/provider/src/main/java/org/onap/ccsdk/sli/adaptors/netbox/model/Status.java77
-rw-r--r--netbox-client/provider/src/main/java/org/onap/ccsdk/sli/adaptors/netbox/property/NetboxProperties.java116
-rwxr-xr-xnetbox-client/provider/src/main/resources/netbox.properties19
-rw-r--r--netbox-client/provider/src/main/resources/org/opendaylight/blueprint/netbox-client.xml34
-rw-r--r--netbox-client/provider/src/test/java/org/onap/ccsdk/sli/adaptors/netbox/impl/NetboxClientImplTest.java185
-rw-r--r--netbox-client/provider/src/test/java/org/onap/ccsdk/sli/adaptors/netbox/impl/NetboxHttpClientTest.java127
-rw-r--r--netbox-client/provider/src/test/java/org/onap/ccsdk/sli/adaptors/netbox/property/NetboxPropertiesTest.java67
-rw-r--r--netbox-client/provider/src/test/resources/nextAvailableIpResponse.json13
15 files changed, 1068 insertions, 0 deletions
diff --git a/netbox-client/provider/src/main/java/org/onap/ccsdk/sli/adaptors/netbox/api/IpamException.java b/netbox-client/provider/src/main/java/org/onap/ccsdk/sli/adaptors/netbox/api/IpamException.java
new file mode 100644
index 00000000..869a57ce
--- /dev/null
+++ b/netbox-client/provider/src/main/java/org/onap/ccsdk/sli/adaptors/netbox/api/IpamException.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2018 Bell Canada.
+ *
+ * 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.onap.ccsdk.sli.adaptors.netbox.api;
+
+public class IpamException extends Exception {
+
+ public IpamException(final String message) {
+ super(message);
+ }
+
+ public IpamException(final String message, final Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/netbox-client/provider/src/main/java/org/onap/ccsdk/sli/adaptors/netbox/api/NetboxClient.java b/netbox-client/provider/src/main/java/org/onap/ccsdk/sli/adaptors/netbox/api/NetboxClient.java
new file mode 100644
index 00000000..57d727ad
--- /dev/null
+++ b/netbox-client/provider/src/main/java/org/onap/ccsdk/sli/adaptors/netbox/api/NetboxClient.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2018 Bell Canada.
+ *
+ * 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.onap.ccsdk.sli.adaptors.netbox.api;
+
+import org.onap.ccsdk.sli.adaptors.netbox.model.IPAddress;
+import org.onap.ccsdk.sli.adaptors.netbox.model.Prefix;
+
+public interface NetboxClient {
+
+ /**
+ * Assign next available IP in prefix.
+ *
+ * @param prefix The prefix from which to get next available IP.
+ * @return The IPAddress
+ * @throws IpamException If something goes wrong.
+ */
+ IPAddress assign(Prefix prefix) throws IpamException;
+
+ /**
+ * Free the IP.
+ *
+ * @param ip The IP to release.
+ * @throws IpamException If something goes wrong.
+ */
+ void unassign(IPAddress ip) throws IpamException;
+}
+
diff --git a/netbox-client/provider/src/main/java/org/onap/ccsdk/sli/adaptors/netbox/impl/NetboxClientImpl.java b/netbox-client/provider/src/main/java/org/onap/ccsdk/sli/adaptors/netbox/impl/NetboxClientImpl.java
new file mode 100644
index 00000000..0520ad5e
--- /dev/null
+++ b/netbox-client/provider/src/main/java/org/onap/ccsdk/sli/adaptors/netbox/impl/NetboxClientImpl.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2018 Bell Canada.
+ *
+ * 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.onap.ccsdk.sli.adaptors.netbox.impl;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonSerializer;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.util.concurrent.CompletionException;
+import org.apache.http.HttpResponse;
+import org.onap.ccsdk.sli.adaptors.netbox.api.IpamException;
+import org.onap.ccsdk.sli.adaptors.netbox.api.NetboxClient;
+import org.onap.ccsdk.sli.adaptors.netbox.model.IPAddress;
+import org.onap.ccsdk.sli.adaptors.netbox.model.Prefix;
+import org.onap.ccsdk.sli.adaptors.netbox.model.Status;
+
+public class NetboxClientImpl implements NetboxClient {
+
+ private static final String NEXT_AVAILABLE_IP_IN_PREFIX_PATH = "/api/ipam/prefixes/%s/available-ips/";
+ private static final String IP_ADDRESS_PATH = "/api/ipam/ip-addresses/%s/";
+ private static final String EMPTY_STRING = "";
+ private static final String ID_MISSING_MSG = "Id must be set";
+
+ private final NetboxHttpClient client;
+ private final Gson gson;
+
+ public NetboxClientImpl(final NetboxHttpClient client) {
+ this.client = client;
+ final JsonSerializer<Status> vlanStatusDeserializer = (val, type, context) -> val.toJson();
+ gson = new GsonBuilder()
+ .registerTypeAdapter(Status.class, vlanStatusDeserializer)
+ .create();
+ }
+
+ @Override
+ public IPAddress assign(final Prefix prefix) throws IpamException {
+ checkArgument(prefix.getId() != null);
+ try {
+ return client.post(String.format(NEXT_AVAILABLE_IP_IN_PREFIX_PATH, prefix.getId()), EMPTY_STRING)
+ .thenApply(this::getIpAddress)
+ .toCompletableFuture()
+ .join();
+ } catch (CompletionException e) {
+ // Unwrap the ComplettionException and wrap in IpamException
+ throw new IpamException("Fail to assign IP for Prefix(id= " + prefix.getId() + "). " + e.getMessage(),
+ e.getCause());
+ }
+ }
+
+ @Override
+ public void unassign(final IPAddress ipAddress) throws IpamException {
+ checkArgument(ipAddress.getId() != null);
+ try {
+ client.delete(String.format(IP_ADDRESS_PATH, ipAddress.getId()))
+ .thenAccept(this::checkResult)
+ .toCompletableFuture()
+ .join();
+ } catch (CompletionException e) {
+ // Unwrap the ComplettionException and wrap in IpamException
+ throw new IpamException("Fail to unassign IP for IPAddress(id= " + ipAddress.getId() + "). " + e.getMessage(),
+ e.getCause());
+ }
+ }
+
+ @VisibleForTesting
+ IPAddress getIpAddress(final HttpResponse response) {
+ if (response.getStatusLine().getStatusCode() != 201) {
+ throw new IllegalStateException(NetboxHttpClient.getBodyAsString(response));
+ }
+ try (final Reader reader = new InputStreamReader(response.getEntity().getContent())) {
+ return gson.fromJson(reader, IPAddress.class);
+ } catch (final IOException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ private static void checkArgument(final boolean argument) throws IpamException {
+ if (!argument) {
+ throw new IpamException(ID_MISSING_MSG);
+ }
+ }
+
+ private void checkResult(final HttpResponse response) {
+ if (response.getStatusLine().getStatusCode() - 200 >= 100) {
+ throw new IllegalStateException(
+ "Netbox request failed with status: " + NetboxHttpClient.getBodyAsString(response));
+ }
+ }
+}
diff --git a/netbox-client/provider/src/main/java/org/onap/ccsdk/sli/adaptors/netbox/impl/NetboxHttpClient.java b/netbox-client/provider/src/main/java/org/onap/ccsdk/sli/adaptors/netbox/impl/NetboxHttpClient.java
new file mode 100644
index 00000000..a77b4d3e
--- /dev/null
+++ b/netbox-client/provider/src/main/java/org/onap/ccsdk/sli/adaptors/netbox/impl/NetboxHttpClient.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2018 Bell Canada.
+ *
+ * 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.onap.ccsdk.sli.adaptors.netbox.impl;
+
+import static org.apache.http.HttpHeaders.ACCEPT;
+import static org.apache.http.HttpHeaders.AUTHORIZATION;
+import static org.apache.http.HttpHeaders.CONTENT_TYPE;
+
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.security.KeyManagementException;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.util.Scanner;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
+import java.util.function.Function;
+import javax.net.ssl.SSLContext;
+import org.apache.http.HttpEntityEnclosingRequest;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.methods.HttpDelete;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.client.methods.HttpUriRequest;
+import org.apache.http.concurrent.FutureCallback;
+import org.apache.http.conn.ssl.NoopHostnameVerifier;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.nio.client.CloseableHttpAsyncClient;
+import org.apache.http.impl.nio.client.HttpAsyncClientBuilder;
+import org.apache.http.ssl.SSLContexts;
+import org.apache.http.ssl.TrustStrategy;
+import org.onap.ccsdk.sli.adaptors.netbox.api.IpamException;
+import org.onap.ccsdk.sli.adaptors.netbox.property.NetboxProperties;
+
+public class NetboxHttpClient implements AutoCloseable {
+
+ private static final String APPLICATION_JSON = "application/json";
+
+ private final CloseableHttpAsyncClient client;
+ private final String url;
+ private final String token;
+
+ // Used by the blueprint container
+ public NetboxHttpClient(NetboxProperties properties) {
+ this(properties.getHost(), properties.getApiKey());
+ }
+
+ NetboxHttpClient(final String url, final String token) {
+ this.url = url;
+ this.token = token;
+
+ final TrustStrategy acceptingTrustStrategy = (certificate, authType) -> true;
+ final SSLContext sslContext;
+ try {
+ sslContext = SSLContexts.custom()
+ .loadTrustMaterial(null, acceptingTrustStrategy).build();
+ } catch (final NoSuchAlgorithmException | KeyManagementException | KeyStoreException e) {
+ throw new IllegalStateException("Can't create http client", e);
+ }
+ client = HttpAsyncClientBuilder.create()
+ .setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE)
+ .setSSLContext(sslContext)
+ .build();
+
+ }
+
+ // Has to be public for blueprint container to access it
+ public void init() {
+ client.start();
+ }
+
+ @Override
+ public void close() throws IOException {
+ client.close();
+ }
+
+ CompletionStage<HttpResponse> post(final String uri, final String requestBody) {
+ return sendRequest(uri, requestBody, HttpPost::new);
+ }
+
+ CompletionStage<HttpResponse> delete(final String uri) {
+ return sendRequest(uri, HttpDelete::new);
+ }
+
+ static String getBodyAsString(final HttpResponse response) {
+ final String body;
+ if (response.getEntity() != null) {
+ try (final Scanner s = new java.util.Scanner(response.getEntity().getContent()).useDelimiter("\\A")) {
+ body = s.hasNext() ? s.next() : "";
+ } catch (final IOException e) {
+ throw new IllegalStateException(e);
+ }
+ } else {
+ body = "";
+ }
+ return response.toString() + "\n" + body;
+ }
+
+ private <T extends HttpUriRequest> CompletionStage<HttpResponse> sendRequest(final String uri,
+ final Function<String, T> supplier) {
+ final T request = supplier.apply(url + uri);
+ request.addHeader(ACCEPT, APPLICATION_JSON);
+ request.addHeader(CONTENT_TYPE, APPLICATION_JSON);
+ request.addHeader(AUTHORIZATION, "Token " + token);
+ return sendRequest(request);
+ }
+
+ private <T extends HttpEntityEnclosingRequest & HttpUriRequest>
+ CompletionStage<HttpResponse> sendRequest(final String uri, final String body,
+ final Function<String, T> supplier) {
+ final T request = supplier.apply(url + uri);
+ request.addHeader(ACCEPT, APPLICATION_JSON);
+ request.addHeader(CONTENT_TYPE, APPLICATION_JSON);
+ request.addHeader(AUTHORIZATION, "Token " + token);
+ request.setEntity(new StringEntity(body, Charset.forName("UTF-8")));
+ return sendRequest(request);
+ }
+
+ private CompletionStage<HttpResponse> sendRequest(final HttpUriRequest request) {
+ final CompletableFuture<HttpResponse> future = new CompletableFuture<>();
+ client.execute(request, new FutureCallback<HttpResponse>() {
+ @Override
+ public void completed(final HttpResponse httpResponse) {
+ future.complete(httpResponse);
+ }
+
+ @Override
+ public void failed(final Exception e) {
+ future.completeExceptionally(new IpamException("Netbox request failed", e));
+ }
+
+ @Override
+ public void cancelled() {
+ future.cancel(false);
+ }
+ });
+ return future;
+ }
+} \ No newline at end of file
diff --git a/netbox-client/provider/src/main/java/org/onap/ccsdk/sli/adaptors/netbox/model/IPAddress.java b/netbox-client/provider/src/main/java/org/onap/ccsdk/sli/adaptors/netbox/model/IPAddress.java
new file mode 100644
index 00000000..6d62fff9
--- /dev/null
+++ b/netbox-client/provider/src/main/java/org/onap/ccsdk/sli/adaptors/netbox/model/IPAddress.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2018 Bell Canada.
+ *
+ * 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.onap.ccsdk.sli.adaptors.netbox.model;
+
+import java.util.Objects;
+
+public class IPAddress extends Identifiable {
+
+ private Status.Values status;
+ private String address;
+
+ public void setStatus(Status.Values status) {
+ this.status = status;
+ }
+
+ public void setAddress(String address) {
+ this.address = address;
+ }
+
+ public Status.Values getStatus() {
+ return status;
+ }
+
+ public String getAddress() {
+ return address;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ IPAddress ipAddress = (IPAddress) o;
+ return Objects.equals(status, ipAddress.status) &&
+ Objects.equals(address, ipAddress.address);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(status, address);
+ }
+}
diff --git a/netbox-client/provider/src/main/java/org/onap/ccsdk/sli/adaptors/netbox/model/Identifiable.java b/netbox-client/provider/src/main/java/org/onap/ccsdk/sli/adaptors/netbox/model/Identifiable.java
new file mode 100644
index 00000000..501088d7
--- /dev/null
+++ b/netbox-client/provider/src/main/java/org/onap/ccsdk/sli/adaptors/netbox/model/Identifiable.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2018 Bell Canada.
+ *
+ * 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.onap.ccsdk.sli.adaptors.netbox.model;
+
+public abstract class Identifiable {
+
+ private Integer id;
+
+ public Integer getId() {
+ return id;
+ }
+
+ public void setId(final Integer id) {
+ this.id = id;
+ }
+}
diff --git a/netbox-client/provider/src/main/java/org/onap/ccsdk/sli/adaptors/netbox/model/Prefix.java b/netbox-client/provider/src/main/java/org/onap/ccsdk/sli/adaptors/netbox/model/Prefix.java
new file mode 100644
index 00000000..b20be91d
--- /dev/null
+++ b/netbox-client/provider/src/main/java/org/onap/ccsdk/sli/adaptors/netbox/model/Prefix.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2018 Bell Canada.
+ *
+ * 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.onap.ccsdk.sli.adaptors.netbox.model;
+
+public class Prefix extends Identifiable {
+
+}
diff --git a/netbox-client/provider/src/main/java/org/onap/ccsdk/sli/adaptors/netbox/model/Status.java b/netbox-client/provider/src/main/java/org/onap/ccsdk/sli/adaptors/netbox/model/Status.java
new file mode 100644
index 00000000..c56828a8
--- /dev/null
+++ b/netbox-client/provider/src/main/java/org/onap/ccsdk/sli/adaptors/netbox/model/Status.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2018 Bell Canada.
+ *
+ * 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.onap.ccsdk.sli.adaptors.netbox.model;
+
+import com.google.gson.JsonElement;
+import com.google.gson.JsonPrimitive;
+import com.google.gson.annotations.SerializedName;
+
+public class Status {
+
+ private Integer value;
+ private String label;
+
+ public Integer getValue() {
+ return value;
+ }
+
+ public void setValue(final Integer value) {
+ this.value = value;
+ }
+
+ public String getLabel() {
+ return label;
+ }
+
+ public void setLabel(final String label) {
+ this.label = label;
+ }
+
+ public JsonElement toJson() {
+ return new JsonPrimitive(value);
+ }
+
+ public enum Values {
+ @SerializedName("1")
+ ACTIVE(1, "Active"),
+ @SerializedName("2")
+ RESERVED(2, "Reserved");
+
+ private final int value;
+ private final String label;
+
+ Values(final int value, final String label) {
+ this.value = value;
+ this.label = label;
+ }
+
+ public int getValue() {
+ return value;
+ }
+
+ public String getLabel() {
+ return label;
+ }
+
+ public Status getStatus() {
+ final Status status = new Status();
+ status.setValue(value);
+ status.setLabel(label);
+ return status;
+ }
+
+ }
+}
diff --git a/netbox-client/provider/src/main/java/org/onap/ccsdk/sli/adaptors/netbox/property/NetboxProperties.java b/netbox-client/provider/src/main/java/org/onap/ccsdk/sli/adaptors/netbox/property/NetboxProperties.java
new file mode 100644
index 00000000..ee493ec1
--- /dev/null
+++ b/netbox-client/provider/src/main/java/org/onap/ccsdk/sli/adaptors/netbox/property/NetboxProperties.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2018 Bell Canada.
+ *
+ * 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.onap.ccsdk.sli.adaptors.netbox.property;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.Optional;
+import java.util.Properties;
+import java.util.Set;
+
+import org.onap.ccsdk.sli.adaptors.netbox.api.IpamException;
+import org.onap.ccsdk.sli.core.utils.JREFileResolver;
+import org.onap.ccsdk.sli.core.utils.KarafRootFileResolver;
+import org.onap.ccsdk.sli.core.utils.PropertiesFileResolver;
+import org.onap.ccsdk.sli.core.utils.common.BundleContextFileResolver;
+import org.onap.ccsdk.sli.core.utils.common.SdncConfigEnvVarFileResolver;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Responsible for determining the properties file to use.
+ *
+ * <ol>
+ * <li>A directory identified by the system environment variable <code>SDNC_CONFIG_DIR</code></li>
+ * <li>A directory identified by the JRE argument <code>netbox.properties</code></li>
+ * <li>A <code>netbox.properties</code> file located in the karaf root directory</li>
+ * </ol>
+ *
+ * Partial copy and adaptation of org.onap.ccsdk.sli.adaptors.aai.AAIServiceProvider
+ */
+public class NetboxProperties {
+
+ private static final Logger LOG = LoggerFactory.getLogger(NetboxProperties.class);
+
+ private static final String NETBOX_PROPERTY_FILE_NAME = "netbox.properties";
+ private static final String MISSING_PROPERTY_FILE =
+ "Missing configuration properties resource for Netbox: " + NETBOX_PROPERTY_FILE_NAME;
+ private static final String NETBOX_URL_PROP = "org.onap.ccsdk.sli.adaptors.netbox.url";
+ private static final String NETBOX_API_KEY_PROP = "org.onap.ccsdk.sli.adaptors.netbox.apikey";
+
+ private Set<PropertiesFileResolver> fileResolvers = new HashSet<>();
+ private Properties properties;
+
+ public NetboxProperties() {
+ fileResolvers.add(new SdncConfigEnvVarFileResolver("Using property file (1) from environment variable"));
+ fileResolvers.add(new BundleContextFileResolver("Using property file (2) from BundleContext property",
+ NetboxProperties.class));
+ fileResolvers.add(new JREFileResolver("Using property file (3) from JRE argument", NetboxProperties.class));
+ fileResolvers.add(new KarafRootFileResolver("Using property file (4) from karaf root", this));
+
+ loadProps();
+ }
+
+ public String getHost() {
+ checkArgument(properties != null);
+ return properties.getProperty(NETBOX_URL_PROP);
+ }
+
+ public String getApiKey() {
+ checkArgument(properties != null);
+ return properties.getProperty(NETBOX_API_KEY_PROP);
+ }
+
+ private void checkArgument(final boolean argument) {
+ if (!argument) {
+ LOG.info("Propety file {} was missing, trying to reload it", NETBOX_PROPERTY_FILE_NAME);
+ loadProps();
+ if (properties == null) {
+ throw new IllegalArgumentException(MISSING_PROPERTY_FILE);
+ }
+ }
+ }
+
+ private void loadProps() {
+ // determines properties file as according to the priority described in the class header comment
+ final File propertiesFile = determinePropertiesFile();
+ if (propertiesFile != null) {
+ try (FileInputStream fileInputStream = new FileInputStream(propertiesFile)) {
+ properties = new Properties();
+ properties.load(fileInputStream);
+ } catch (final IOException e) {
+ String errorMsg = "Failed to load properties for file: " + propertiesFile.toString();
+ LOG.error(errorMsg, new IpamException(errorMsg));
+ }
+ }
+ }
+
+ private File determinePropertiesFile() {
+ for (final PropertiesFileResolver resolver : fileResolvers) {
+ final Optional<File> fileOptional = resolver.resolveFile(NETBOX_PROPERTY_FILE_NAME);
+ if (fileOptional.isPresent()) {
+ final File file = fileOptional.get();
+ LOG.info("{} {}", resolver.getSuccessfulResolutionMessage(), file.getPath());
+ return file;
+ }
+ }
+
+ LOG.error(MISSING_PROPERTY_FILE, new IpamException(MISSING_PROPERTY_FILE));
+ return null;
+ }
+}
diff --git a/netbox-client/provider/src/main/resources/netbox.properties b/netbox-client/provider/src/main/resources/netbox.properties
new file mode 100755
index 00000000..ee5e67b4
--- /dev/null
+++ b/netbox-client/provider/src/main/resources/netbox.properties
@@ -0,0 +1,19 @@
+#
+# Copyright (C) 2018 AT&T, Bell Canada.
+#
+# 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.
+#
+
+# Configuration file for Netbox client
+org.onap.ccsdk.sli.adaptors.netbox.url=http://localhost:9998
+org.onap.ccsdk.sli.adaptors.netbox.apikey=onceuponatimeiplayedwithnetbox20180814 \ No newline at end of file
diff --git a/netbox-client/provider/src/main/resources/org/opendaylight/blueprint/netbox-client.xml b/netbox-client/provider/src/main/resources/org/opendaylight/blueprint/netbox-client.xml
new file mode 100644
index 00000000..cf8a1af4
--- /dev/null
+++ b/netbox-client/provider/src/main/resources/org/opendaylight/blueprint/netbox-client.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (C) 2018 AT&T, Bell Canada
+
+ 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.
+ -->
+<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0" xmlns:odl="http://opendaylight.org/xmlns/blueprint/v1.0.0"
+ odl:use-default-for-reference-types="true">
+
+ <bean id="netboxProperty" class="org.onap.ccsdk.sli.adaptors.netbox.property.NetboxProperties"/>
+ <bean id="httpClient" class="org.onap.ccsdk.sli.adaptors.netbox.impl.NetboxHttpClient" init-method="init"
+ destroy-method="close">
+ <argument ref="netboxProperty"/>
+ </bean>
+
+ <bean id="netboxClient" class="org.onap.ccsdk.sli.adaptors.netbox.impl.NetboxClientImpl">
+ <argument ref="httpClient"/>
+ </bean>
+
+ <service ref="netboxClient"
+ interface="org.onap.ccsdk.sli.adaptors.netbox.api.NetboxClient"
+ odl:type="default"/>
+
+</blueprint>
diff --git a/netbox-client/provider/src/test/java/org/onap/ccsdk/sli/adaptors/netbox/impl/NetboxClientImplTest.java b/netbox-client/provider/src/test/java/org/onap/ccsdk/sli/adaptors/netbox/impl/NetboxClientImplTest.java
new file mode 100644
index 00000000..19b178c9
--- /dev/null
+++ b/netbox-client/provider/src/test/java/org/onap/ccsdk/sli/adaptors/netbox/impl/NetboxClientImplTest.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2018 Bell Canada.
+ *
+ * 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.onap.ccsdk.sli.adaptors.netbox.impl;
+
+import static com.github.tomakehurst.wiremock.client.WireMock.created;
+import static com.github.tomakehurst.wiremock.client.WireMock.delete;
+import static com.github.tomakehurst.wiremock.client.WireMock.deleteRequestedFor;
+import static com.github.tomakehurst.wiremock.client.WireMock.equalTo;
+import static com.github.tomakehurst.wiremock.client.WireMock.givenThat;
+import static com.github.tomakehurst.wiremock.client.WireMock.ok;
+import static com.github.tomakehurst.wiremock.client.WireMock.post;
+import static com.github.tomakehurst.wiremock.client.WireMock.postRequestedFor;
+import static com.github.tomakehurst.wiremock.client.WireMock.serverError;
+import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;
+import static com.github.tomakehurst.wiremock.client.WireMock.verify;
+import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;
+import static org.apache.http.HttpHeaders.ACCEPT;
+import static org.apache.http.HttpHeaders.AUTHORIZATION;
+import static org.apache.http.HttpHeaders.CONTENT_TYPE;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+
+import com.github.tomakehurst.wiremock.junit.WireMockRule;
+import com.google.common.base.Charsets;
+import com.google.common.io.Resources;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+import org.apache.http.StatusLine;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.onap.ccsdk.sli.adaptors.netbox.api.IpamException;
+import org.onap.ccsdk.sli.adaptors.netbox.model.IPAddress;
+import org.onap.ccsdk.sli.adaptors.netbox.model.Prefix;
+import org.onap.ccsdk.sli.adaptors.netbox.model.Status.Values;
+
+@RunWith(MockitoJUnitRunner.class)
+public class NetboxClientImplTest {
+
+ private static final String APPLICATION_JSON = "application/json";
+
+ @Rule
+ public WireMockRule wm = new WireMockRule(wireMockConfig().dynamicPort());
+
+ private String token = "token";
+
+ private NetboxHttpClient httpClient;
+ private NetboxClientImpl netboxClient;
+
+ @Before
+ public void setup() {
+ String baseUrl = "http://localhost:" + wm.port();
+
+ httpClient = new NetboxHttpClient(baseUrl, token);
+ httpClient.init();
+
+ netboxClient = new NetboxClientImpl(httpClient);
+
+ wm.addMockServiceRequestListener(
+ (request, response) -> {
+ System.out.println("Request URL :" + request.getAbsoluteUrl());
+ System.out.println("Request body :" + request.getBodyAsString());
+ System.out.println("Response status :" + response.getStatus());
+ System.out.println("Response body :" + response.getBodyAsString());
+ });
+ }
+
+ @After
+ public void tearDown() throws IOException {
+ httpClient.close();
+ }
+
+ @Test
+ public void nextAvailableIpInPrefixTestNoId() {
+ Prefix prefix = mock(Prefix.class);
+ doReturn(null).when(prefix).getId();
+ try {
+ netboxClient.assign(prefix);
+ } catch (IpamException e) {
+ Assert.assertEquals("Id must be set", e.getMessage());
+ return;
+ }
+ Assert.fail();
+ }
+
+ @Test
+ public void nextAvailableIpInPrefixTest() throws IOException, IpamException {
+ Integer id = 3;
+ Prefix prefix = mock(Prefix.class);
+ doReturn(id).when(prefix).getId();
+
+ URL url = Resources.getResource("nextAvailableIpResponse.json");
+ String response = Resources.toString(url, Charsets.UTF_8);
+
+ String expectedUrl = "/api/ipam/prefixes/" + id + "/available-ips/";
+ givenThat(post(urlEqualTo(expectedUrl)).willReturn(created().withBody(response)));
+
+ netboxClient.assign(prefix);
+
+ verify(postRequestedFor(urlEqualTo(expectedUrl))
+ .withHeader(ACCEPT, equalTo(APPLICATION_JSON))
+ .withHeader(CONTENT_TYPE, equalTo(APPLICATION_JSON))
+ .withHeader(AUTHORIZATION, equalTo("Token " + token)));
+ }
+
+ @Test
+ public void deleteIpTestError500() {
+ Integer id = 3;
+ IPAddress ipAddress = mock(IPAddress.class);
+ doReturn(id).when(ipAddress).getId();
+
+ String expectedUrl = "/api/ipam/ip-addresses/" + id + "/";
+ givenThat(delete(urlEqualTo(expectedUrl)).willReturn(serverError()));
+ try {
+ netboxClient.unassign(ipAddress);
+ } catch (IpamException e) {
+ Assert.assertEquals(IllegalStateException.class, e.getCause().getClass());
+ Assert.assertTrue(e.getMessage().contains(
+ "Fail to unassign IP for IPAddress(id= 3). java.lang.IllegalStateException: Netbox request failed with status: HTTP/1.1 500 Server Error"));
+ return;
+ }
+ Assert.fail();
+ }
+
+ @Test
+ public void deleteIpTest() throws IpamException {
+ Integer id = 3;
+ IPAddress ipAddress = mock(IPAddress.class);
+ doReturn(id).when(ipAddress).getId();
+
+ String expectedUrl = "/api/ipam/ip-addresses/" + id + "/";
+ givenThat(delete(urlEqualTo(expectedUrl)).willReturn(ok()));
+ netboxClient.unassign(ipAddress);
+ verify(deleteRequestedFor(urlEqualTo(expectedUrl))
+ .withHeader(ACCEPT, equalTo(APPLICATION_JSON))
+ .withHeader(CONTENT_TYPE, equalTo(APPLICATION_JSON))
+ .withHeader(AUTHORIZATION, equalTo("Token " + token)));
+ }
+
+
+ @Test
+ public void getIpAddressTest() throws IOException {
+ StatusLine statusLine = mock(StatusLine.class);
+ doReturn(201).when(statusLine).getStatusCode();
+
+ URL url = Resources.getResource("nextAvailableIpResponse.json");
+ String response = Resources.toString(url, Charsets.UTF_8);
+ InputStream stream = new ByteArrayInputStream(response.getBytes(StandardCharsets.UTF_8));
+
+ HttpEntity entity = mock(HttpEntity.class);
+ doReturn(stream).when(entity).getContent();
+
+ HttpResponse httpResponse = mock(HttpResponse.class);
+ doReturn(statusLine).when(httpResponse).getStatusLine();
+ doReturn(entity).when(httpResponse).getEntity();
+
+ IPAddress ipAddress = netboxClient.getIpAddress(httpResponse);
+
+ Assert.assertEquals("192.168.20.7/32", ipAddress.getAddress());
+ Assert.assertEquals(Integer.valueOf(8), ipAddress.getId());
+ Assert.assertEquals(Values.ACTIVE, ipAddress.getStatus());
+ }
+} \ No newline at end of file
diff --git a/netbox-client/provider/src/test/java/org/onap/ccsdk/sli/adaptors/netbox/impl/NetboxHttpClientTest.java b/netbox-client/provider/src/test/java/org/onap/ccsdk/sli/adaptors/netbox/impl/NetboxHttpClientTest.java
new file mode 100644
index 00000000..ee2861c0
--- /dev/null
+++ b/netbox-client/provider/src/test/java/org/onap/ccsdk/sli/adaptors/netbox/impl/NetboxHttpClientTest.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2018 Bell Canada.
+ *
+ * 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.onap.ccsdk.sli.adaptors.netbox.impl;
+
+import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
+import static com.github.tomakehurst.wiremock.client.WireMock.delete;
+import static com.github.tomakehurst.wiremock.client.WireMock.deleteRequestedFor;
+import static com.github.tomakehurst.wiremock.client.WireMock.equalTo;
+import static com.github.tomakehurst.wiremock.client.WireMock.givenThat;
+import static com.github.tomakehurst.wiremock.client.WireMock.ok;
+import static com.github.tomakehurst.wiremock.client.WireMock.post;
+import static com.github.tomakehurst.wiremock.client.WireMock.postRequestedFor;
+import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;
+import static com.github.tomakehurst.wiremock.client.WireMock.verify;
+import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;
+import static org.apache.http.HttpHeaders.ACCEPT;
+import static org.apache.http.HttpHeaders.CONTENT_TYPE;
+
+import com.github.tomakehurst.wiremock.http.Fault;
+import com.github.tomakehurst.wiremock.junit.WireMockRule;
+import java.io.IOException;
+import java.util.concurrent.CompletionException;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.onap.ccsdk.sli.adaptors.netbox.api.IpamException;
+
+public class NetboxHttpClientTest {
+
+ private static final String APPLICATION_JSON = "application/json";
+
+ @Rule
+ public WireMockRule wm = new WireMockRule(wireMockConfig().dynamicPort());
+
+ private NetboxHttpClient httpClient;
+
+ @Before
+ public void setup() {
+ String baseUrl = "http://localhost:" + wm.port();
+ String token = "token";
+
+ httpClient = new NetboxHttpClient(baseUrl, token);
+ httpClient.init();
+
+ wm.addMockServiceRequestListener(
+ (request, response) -> {
+ System.out.println("Request URL :" + request.getAbsoluteUrl());
+ System.out.println("Request body :" + request.getBodyAsString());
+ System.out.println("Response status :" + response.getStatus());
+ System.out.println("Response body :" + response.getBodyAsString());
+ });
+ }
+
+ @After
+ public void tearDown() throws IOException {
+ httpClient.close();
+ }
+
+ @Test
+ public void postTest() {
+ String expectedUrl = "/testPost";
+ givenThat(post(urlEqualTo(expectedUrl)).willReturn(ok()));
+
+ httpClient.post(expectedUrl, "").toCompletableFuture().join();
+
+ verify(postRequestedFor(urlEqualTo(expectedUrl))
+ .withHeader(ACCEPT, equalTo(APPLICATION_JSON))
+ .withHeader(CONTENT_TYPE, equalTo(APPLICATION_JSON)));
+ }
+
+ @Test
+ public void postTestException() {
+ String expectedUrl = "/testPost";
+ givenThat(post(urlEqualTo(expectedUrl)).willReturn(aResponse().withFault(Fault.MALFORMED_RESPONSE_CHUNK)));
+
+ try {
+ httpClient.post(expectedUrl, "").toCompletableFuture().join();
+ } catch (CompletionException e) {
+ Assert.assertEquals(IpamException.class, e.getCause().getClass());
+ Assert.assertEquals("Netbox request failed", e.getCause().getMessage());
+ return;
+ }
+ Assert.fail();
+ }
+
+ @Test
+ public void deleteTest() {
+ String expectedUrl = "/testDelete";
+ givenThat(delete(urlEqualTo(expectedUrl)).willReturn(ok()));
+
+ httpClient.delete(expectedUrl).toCompletableFuture().join();
+
+ verify(deleteRequestedFor(urlEqualTo(expectedUrl))
+ .withHeader(ACCEPT, equalTo(APPLICATION_JSON))
+ .withHeader(CONTENT_TYPE, equalTo(APPLICATION_JSON)));
+ }
+
+ @Test
+ public void deleteTestException() {
+ String expectedUrl = "/testDelete";
+ givenThat(delete(urlEqualTo(expectedUrl)).willReturn(aResponse().withFault(Fault.MALFORMED_RESPONSE_CHUNK)));
+
+ try {
+ httpClient.delete(expectedUrl).toCompletableFuture().join();
+ } catch (CompletionException e) {
+ Assert.assertEquals(IpamException.class, e.getCause().getClass());
+ Assert.assertEquals("Netbox request failed", e.getCause().getMessage());
+ return;
+ }
+ Assert.fail();
+ }
+}
diff --git a/netbox-client/provider/src/test/java/org/onap/ccsdk/sli/adaptors/netbox/property/NetboxPropertiesTest.java b/netbox-client/provider/src/test/java/org/onap/ccsdk/sli/adaptors/netbox/property/NetboxPropertiesTest.java
new file mode 100644
index 00000000..d9916707
--- /dev/null
+++ b/netbox-client/provider/src/test/java/org/onap/ccsdk/sli/adaptors/netbox/property/NetboxPropertiesTest.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2018 Bell Canada.
+ *
+ * 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.onap.ccsdk.sli.adaptors.netbox.property;
+
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import ch.qos.logback.classic.spi.ILoggingEvent;
+import ch.qos.logback.core.Appender;
+import java.util.List;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.slf4j.LoggerFactory;
+
+@RunWith(MockitoJUnitRunner.class)
+public class NetboxPropertiesTest {
+
+ private NetboxProperties props;
+
+ @Mock
+ private Appender<ILoggingEvent> appender;
+ @Captor
+ private ArgumentCaptor<ILoggingEvent> captor;
+
+ @Before
+ public void setup() {
+ ch.qos.logback.classic.Logger logger = (ch.qos.logback.classic.Logger) LoggerFactory
+ .getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME);
+ logger.addAppender(appender);
+ }
+
+ @Test
+ public void testMissingFile() {
+ props = new NetboxProperties();
+
+ verifyLogEntry(
+ "Missing configuration properties resource for Netbox: netbox.properties");
+ }
+
+
+ private void verifyLogEntry(String message) {
+ verify(appender, times(1)).doAppend(captor.capture());
+ List<ILoggingEvent> allValues = captor.getAllValues();
+ for (ILoggingEvent loggingEvent : allValues) {
+ Assert.assertTrue(loggingEvent.getFormattedMessage().contains(message));
+ }
+ }
+} \ No newline at end of file
diff --git a/netbox-client/provider/src/test/resources/nextAvailableIpResponse.json b/netbox-client/provider/src/test/resources/nextAvailableIpResponse.json
new file mode 100644
index 00000000..dec1245e
--- /dev/null
+++ b/netbox-client/provider/src/test/resources/nextAvailableIpResponse.json
@@ -0,0 +1,13 @@
+{
+ "id": 8,
+ "address": "192.168.20.7/32",
+ "vrf": null,
+ "tenant": 1,
+ "status": 1,
+ "role": null,
+ "interface": null,
+ "description": "",
+ "nat_inside": null,
+ "created": "2018-08-15",
+ "last_updated": "2018-08-15T15:51:26.634903Z"
+} \ No newline at end of file