Add DB update support for IPAM interaction

For assign and unassign scenario, we need to interact
with the SDNC DB in order to cache the IP Address
information used for a given ServiceInstance / VfModule.

Change-Id: Id349338216d12d2dc9efd76cd672b5cc9fdc6192
Issue-ID: CCSDK-462
Signed-off-by: Alexis de Talhouët <adetalhouet89@gmail.com>
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
index 57d727a..e11fe8b 100644
--- 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
@@ -15,26 +15,37 @@
  */
 package org.onap.ccsdk.sli.adaptors.netbox.api;
 
+import java.sql.SQLException;
 import org.onap.ccsdk.sli.adaptors.netbox.model.IPAddress;
 import org.onap.ccsdk.sli.adaptors.netbox.model.Prefix;
 
+/**
+ * This client is meant to interact both with the IPAM system, and the SDNC DB, in order to provide, at any time,
+ * an up to date status of the assigned resources.
+ */
 public interface NetboxClient {
 
     /**
-     * Assign next available IP in prefix.
+     * Assign next available IP in prefix and store it in the SDNC database, table IPAM_IP_ASSIGNEMENT.
      *
      * @param prefix The prefix from which to get next available IP.
+     * @param serviceInstanceId The service instance ID uniquely identifying the service.
+     * @param vfModuleId The VF module ID uniquely identifying the VF.
      * @return The IPAddress
-     * @throws IpamException If something goes wrong.
+     * @throws IpamException If something goes wrong while communicating with the IPAM system.
+     * @throws SQLException If something goes wrong while communicating with the SDNC DB.
      */
-    IPAddress assign(Prefix prefix) throws IpamException;
+    IPAddress assign(Prefix prefix, String serviceInstanceId, String vfModuleId) throws IpamException, SQLException;
 
     /**
-     * Free the IP.
+     * Release the IP and remove the entry in the SDNC database, table IPAM_IP_ASSIGNEMENT.
      *
      * @param ip The IP to release.
-     * @throws IpamException If something goes wrong.
+     * @param serviceInstanceId The service instance ID uniquely identifying the service.
+     * @param vfModuleId The VF module ID uniquely identifying the VF.
+     * @throws IpamException If something goes wrong while communicating with the IPAM system.
+     * @throws SQLException If something goes wrong while communicating with the SDNC DB.
      */
-    void unassign(IPAddress ip) throws IpamException;
+    void unassign(IPAddress ip, String serviceInstanceId, String vfModuleId) throws IpamException, SQLException;
 }
 
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
index 0520ad5..036ff44 100644
--- 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
@@ -16,12 +16,15 @@
 package org.onap.ccsdk.sli.adaptors.netbox.impl;
 
 import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.Lists;
 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.sql.SQLException;
+import java.util.ArrayList;
 import java.util.concurrent.CompletionException;
 import org.apache.http.HttpResponse;
 import org.onap.ccsdk.sli.adaptors.netbox.api.IpamException;
@@ -29,19 +32,29 @@
 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;
+import org.onap.ccsdk.sli.core.dblib.DbLibService;
+import org.onap.ccsdk.sli.core.sli.SvcLogicJavaPlugin;
 
-public class NetboxClientImpl implements NetboxClient {
+public class NetboxClientImpl implements NetboxClient, SvcLogicJavaPlugin {
 
     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 static final String ASSIGN_IP_SQL_STATEMENT =
+        "INSERT INTO IPAM_IP_ASSIGNEMENT (service_instance_id, vf_module_id, prefix_id, ip_address_id, ip_adress, ip_status) \n"
+            + "VALUES (?, ?, ?, ?, ?, ?)";
+    private static final String UNASSIGN_IP_SQL_STATEMENT =
+        "DELETE FROM IPAM_IP_ASSIGNEMENT WHERE service_instance_id = ? AND vf_module_id = ? AND ip_address_id = ?";
+
     private final NetboxHttpClient client;
     private final Gson gson;
+    private final DbLibService dbLibService;
 
-    public NetboxClientImpl(final NetboxHttpClient client) {
+    public NetboxClientImpl(final NetboxHttpClient client, final DbLibService dbLibService) {
         this.client = client;
+        this.dbLibService = dbLibService;
         final JsonSerializer<Status> vlanStatusDeserializer = (val, type, context) -> val.toJson();
         gson = new GsonBuilder()
             .registerTypeAdapter(Status.class, vlanStatusDeserializer)
@@ -49,31 +62,54 @@
     }
 
     @Override
-    public IPAddress assign(final Prefix prefix) throws IpamException {
+    public IPAddress assign(final Prefix prefix, final String serviceInstanceId, final String vfModuleId)
+        throws IpamException, SQLException {
+
         checkArgument(prefix.getId() != null);
         try {
-            return client.post(String.format(NEXT_AVAILABLE_IP_IN_PREFIX_PATH, prefix.getId()), EMPTY_STRING)
+            IPAddress ipAddress = client
+                .post(String.format(NEXT_AVAILABLE_IP_IN_PREFIX_PATH, prefix.getId()), EMPTY_STRING)
                 .thenApply(this::getIpAddress)
                 .toCompletableFuture()
                 .join();
+
+            ArrayList<String> args = Lists.newArrayList(serviceInstanceId,
+                vfModuleId,
+                String.valueOf(prefix.getId()),
+                String.valueOf(ipAddress.getId()),
+                ipAddress.getAddress(),
+                ipAddress.getStatus().getLabel());
+            dbLibService.writeData(ASSIGN_IP_SQL_STATEMENT, args, null);
+
+            return ipAddress;
         } catch (CompletionException e) {
-            // Unwrap the ComplettionException and wrap in IpamException
+            // Unwrap the CompletionException 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 {
+    public void unassign(final IPAddress ipAddress, final String serviceInstanceId, final String vfModuleId)
+        throws IpamException, SQLException {
+
         checkArgument(ipAddress.getId() != null);
         try {
             client.delete(String.format(IP_ADDRESS_PATH, ipAddress.getId()))
                 .thenAccept(this::checkResult)
                 .toCompletableFuture()
                 .join();
+
+            ArrayList<String> args = Lists.newArrayList(
+                serviceInstanceId,
+                vfModuleId,
+                String.valueOf(ipAddress.getId()));
+            dbLibService.writeData(UNASSIGN_IP_SQL_STATEMENT, args, null);
+
         } catch (CompletionException e) {
-            // Unwrap the ComplettionException and wrap in IpamException
-            throw new IpamException("Fail to unassign IP for IPAddress(id= " + ipAddress.getId() + "). " + e.getMessage(),
+            // Unwrap the CompletionException and wrap in IpamException
+            throw new IpamException(
+                "Fail to unassign IP for IPAddress(id= " + ipAddress.getId() + "). " + e.getMessage(),
                 e.getCause());
         }
     }
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
index cf8a1af..950fd97 100644
--- 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
@@ -17,6 +17,9 @@
 <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">
 
+    <reference id="dbLibService"
+      interface="org.onap.ccsdk.sli.core.dblib.DbLibService"/>
+
     <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">
@@ -25,6 +28,7 @@
 
     <bean id="netboxClient" class="org.onap.ccsdk.sli.adaptors.netbox.impl.NetboxClientImpl">
         <argument ref="httpClient"/>
+        <argument ref="dbLibService"/>
     </bean>
 
     <service ref="netboxClient"
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
index 19b178c..f1eda73 100644
--- 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
@@ -30,6 +30,9 @@
 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.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 
@@ -41,6 +44,9 @@
 import java.io.InputStream;
 import java.net.URL;
 import java.nio.charset.StandardCharsets;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.UUID;
 import org.apache.http.HttpEntity;
 import org.apache.http.HttpResponse;
 import org.apache.http.StatusLine;
@@ -50,11 +56,14 @@
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
 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;
+import org.onap.ccsdk.sli.core.dblib.DbLibService;
 
 @RunWith(MockitoJUnitRunner.class)
 public class NetboxClientImplTest {
@@ -64,7 +73,12 @@
     @Rule
     public WireMockRule wm = new WireMockRule(wireMockConfig().dynamicPort());
 
+    @Mock
+    private DbLibService dbLib;
+
     private String token = "token";
+    private String serviceInstanceId = UUID.randomUUID().toString();
+    private String vfModuleId = UUID.randomUUID().toString();
 
     private NetboxHttpClient httpClient;
     private NetboxClientImpl netboxClient;
@@ -76,7 +90,7 @@
         httpClient = new NetboxHttpClient(baseUrl, token);
         httpClient.init();
 
-        netboxClient = new NetboxClientImpl(httpClient);
+        netboxClient = new NetboxClientImpl(httpClient, dbLib);
 
         wm.addMockServiceRequestListener(
             (request, response) -> {
@@ -93,11 +107,11 @@
     }
 
     @Test
-    public void nextAvailableIpInPrefixTestNoId() {
+    public void nextAvailableIpInPrefixTestNoId() throws SQLException {
         Prefix prefix = mock(Prefix.class);
         doReturn(null).when(prefix).getId();
         try {
-            netboxClient.assign(prefix);
+            netboxClient.assign(prefix, serviceInstanceId, vfModuleId);
         } catch (IpamException e) {
             Assert.assertEquals("Id must be set", e.getMessage());
             return;
@@ -106,7 +120,7 @@
     }
 
     @Test
-    public void nextAvailableIpInPrefixTest() throws IOException, IpamException {
+    public void nextAvailableIpInPrefixTest() throws IOException, IpamException, SQLException {
         Integer id = 3;
         Prefix prefix = mock(Prefix.class);
         doReturn(id).when(prefix).getId();
@@ -117,16 +131,17 @@
         String expectedUrl = "/api/ipam/prefixes/" + id + "/available-ips/";
         givenThat(post(urlEqualTo(expectedUrl)).willReturn(created().withBody(response)));
 
-        netboxClient.assign(prefix);
+        netboxClient.assign(prefix, serviceInstanceId, vfModuleId);
 
         verify(postRequestedFor(urlEqualTo(expectedUrl))
             .withHeader(ACCEPT, equalTo(APPLICATION_JSON))
             .withHeader(CONTENT_TYPE, equalTo(APPLICATION_JSON))
             .withHeader(AUTHORIZATION, equalTo("Token " + token)));
+        Mockito.verify(dbLib).writeData(anyString(), any(ArrayList.class), eq((null)));
     }
 
     @Test
-    public void deleteIpTestError500() {
+    public void deleteIpTestError500() throws SQLException {
         Integer id = 3;
         IPAddress ipAddress = mock(IPAddress.class);
         doReturn(id).when(ipAddress).getId();
@@ -134,7 +149,7 @@
         String expectedUrl = "/api/ipam/ip-addresses/" + id + "/";
         givenThat(delete(urlEqualTo(expectedUrl)).willReturn(serverError()));
         try {
-            netboxClient.unassign(ipAddress);
+            netboxClient.unassign(ipAddress, serviceInstanceId, vfModuleId);
         } catch (IpamException e) {
             Assert.assertEquals(IllegalStateException.class, e.getCause().getClass());
             Assert.assertTrue(e.getMessage().contains(
@@ -145,18 +160,19 @@
     }
 
     @Test
-    public void deleteIpTest() throws IpamException {
+    public void deleteIpTest() throws IpamException, SQLException {
         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);
+        netboxClient.unassign(ipAddress, serviceInstanceId, vfModuleId);
         verify(deleteRequestedFor(urlEqualTo(expectedUrl))
             .withHeader(ACCEPT, equalTo(APPLICATION_JSON))
             .withHeader(CONTENT_TYPE, equalTo(APPLICATION_JSON))
             .withHeader(AUTHORIZATION, equalTo("Token " + token)));
+        Mockito.verify(dbLib).writeData(anyString(), any(ArrayList.class), eq((null)));
     }