error when trying to archive\restore 91/72991/12
authoravigaffa <avi.gaffa@amdocs.com>
Sun, 18 Nov 2018 14:01:07 +0000 (16:01 +0200)
committerOren Kleks <orenkle@amdocs.com>
Mon, 26 Nov 2018 19:45:30 +0000 (19:45 +0000)
fix bug: getting server error when trying to archive\restore VLM\VSP

Change-Id: I7abefd2d8ac368d590329071a56f200c203cf966
Issue-ID: SDC-1667
Signed-off-by: avigaffa <avi.gaffa@amdocs.com>
22 files changed:
openecomp-be/api/openecomp-sdc-rest-webapp/item-rest/item-rest-services/pom.xml
openecomp-be/api/openecomp-sdc-rest-webapp/item-rest/item-rest-services/src/main/java/org/openecomp/sdcrests/item/rest/services/CatalogNotifier.java [deleted file]
openecomp-be/api/openecomp-sdc-rest-webapp/item-rest/item-rest-services/src/main/java/org/openecomp/sdcrests/item/rest/services/ItemsImpl.java
openecomp-be/api/openecomp-sdc-rest-webapp/item-rest/item-rest-services/src/main/java/org/openecomp/sdcrests/item/rest/services/catalog/notification/AsyncNotifier.java [new file with mode: 0644]
openecomp-be/api/openecomp-sdc-rest-webapp/item-rest/item-rest-services/src/main/java/org/openecomp/sdcrests/item/rest/services/catalog/notification/EntryNotConfiguredException.java [new file with mode: 0644]
openecomp-be/api/openecomp-sdc-rest-webapp/item-rest/item-rest-services/src/main/java/org/openecomp/sdcrests/item/rest/services/catalog/notification/Notifier.java [new file with mode: 0644]
openecomp-be/api/openecomp-sdc-rest-webapp/item-rest/item-rest-services/src/main/java/org/openecomp/sdcrests/item/rest/services/catalog/notification/NotifierFactory.java [new file with mode: 0644]
openecomp-be/api/openecomp-sdc-rest-webapp/item-rest/item-rest-services/src/main/java/org/openecomp/sdcrests/item/rest/services/catalog/notification/http/HttpConfiguration.java [new file with mode: 0644]
openecomp-be/api/openecomp-sdc-rest-webapp/item-rest/item-rest-services/src/main/java/org/openecomp/sdcrests/item/rest/services/catalog/notification/http/HttpNotificationTask.java [new file with mode: 0644]
openecomp-be/api/openecomp-sdc-rest-webapp/item-rest/item-rest-services/src/main/java/org/openecomp/sdcrests/item/rest/services/catalog/notification/http/HttpTaskProducer.java [new file with mode: 0644]
openecomp-be/api/openecomp-sdc-rest-webapp/item-rest/item-rest-services/src/main/resources/logback-test.xml [new file with mode: 0644]
openecomp-be/api/openecomp-sdc-rest-webapp/item-rest/item-rest-services/src/test/java/org/openecomp/sdcrests/item/rest/services/catalog/notification/AsyncNotifierTest.java [new file with mode: 0644]
openecomp-be/api/openecomp-sdc-rest-webapp/item-rest/item-rest-services/src/test/java/org/openecomp/sdcrests/item/rest/services/catalog/notification/NotifierFactoryTest.java [new file with mode: 0644]
openecomp-be/api/openecomp-sdc-rest-webapp/item-rest/item-rest-services/src/test/java/org/openecomp/sdcrests/item/rest/services/catalog/notification/http/HttpNotificationTaskTest.java [new file with mode: 0644]
openecomp-be/api/openecomp-sdc-rest-webapp/item-rest/item-rest-services/src/test/java/org/openecomp/sdcrests/item/rest/services/catalog/notification/http/HttpTaskProducerTest.java [new file with mode: 0644]
openecomp-be/api/openecomp-sdc-rest-webapp/item-rest/item-rest-services/src/test/resources/catalog-notification-config-correct.yaml [new file with mode: 0644]
openecomp-be/api/openecomp-sdc-rest-webapp/item-rest/item-rest-services/src/test/resources/catalog-notification-config-empty.yaml [new file with mode: 0644]
openecomp-be/api/openecomp-sdc-rest-webapp/item-rest/item-rest-services/src/test/resources/catalog-notification-config-without-notification-section.yaml [new file with mode: 0644]
openecomp-be/dist/sdc-onboard-backend-docker/artifacts/chef-repo/cookbooks/sdc-onboard-backend/recipes/ON_5_setup_configuration.rb
openecomp-be/dist/sdc-onboard-backend-docker/artifacts/chef-repo/cookbooks/sdc-onboard-backend/templates/default/configuration.yaml.erb
openecomp-be/lib/openecomp-core-lib/openecomp-nosqldb-lib/openecomp-nosqldb-core/src/test/java/org/openecomp/core/nosqldb/util/ConfigurationManagerTest.java
sdc-os-chef/environments/Template.json

index d54fc79..a389560 100644 (file)
             <groupId>org.springframework</groupId>
             <artifactId>spring-context</artifactId>
         </dependency>
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-core</artifactId>
+        </dependency>
         <dependency>
             <groupId>junit</groupId>
             <artifactId>junit</artifactId>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-core</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.github.tomakehurst</groupId>
+            <artifactId>wiremock</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <scope>provided</scope>
+        </dependency>
     </dependencies>
 
-
 </project>
\ No newline at end of file
diff --git a/openecomp-be/api/openecomp-sdc-rest-webapp/item-rest/item-rest-services/src/main/java/org/openecomp/sdcrests/item/rest/services/CatalogNotifier.java b/openecomp-be/api/openecomp-sdc-rest-webapp/item-rest/item-rest-services/src/main/java/org/openecomp/sdcrests/item/rest/services/CatalogNotifier.java
deleted file mode 100644 (file)
index deced74..0000000
+++ /dev/null
@@ -1,193 +0,0 @@
-/*
- * Copyright © 2018 European Support Limited
- *
- * 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.openecomp.sdcrests.item.rest.services;
-
-import org.apache.http.HttpEntity;
-import org.apache.http.HttpResponse;
-import org.apache.http.client.methods.HttpPost;
-import org.apache.http.entity.StringEntity;
-import org.apache.http.impl.client.CloseableHttpClient;
-import org.apache.http.impl.client.HttpClients;
-import org.onap.sdc.tosca.services.YamlUtil;
-import org.openecomp.core.utilities.json.JsonUtil;
-import org.openecomp.sdc.common.session.SessionContextProviderFactory;
-import org.openecomp.sdc.logging.api.Logger;
-import org.openecomp.sdc.logging.api.LoggerFactory;
-import org.openecomp.sdc.logging.api.LoggingContext;
-import org.openecomp.sdcrests.item.types.ItemAction;
-
-import javax.ws.rs.core.HttpHeaders;
-import javax.ws.rs.core.MediaType;
-import javax.ws.rs.core.Response;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.UnsupportedEncodingException;
-import java.util.Collection;
-import java.util.LinkedHashMap;
-import java.util.Map;
-import java.util.concurrent.Callable;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.TimeUnit;
-import java.util.function.Function;
-
- class CatalogNotifier {
-
-    private static final Logger LOGGER = LoggerFactory.getLogger(CatalogNotifier.class);
-
-    private static final String USER_ID_HEADER_PARAM = "USER_ID";
-    private static final String CONFIG_FILE = "configuration.yaml";
-    private static final String PROTOCOL_KEY = "beProtocol";
-    private static final String HTTP_PROTOCOL = "http|HTTP";
-    private static final String HTTPS_PROTOCOL = "https|HTTPS";
-    private static final String HOST_KEY = "beFqdn";
-    private static final String HTTP_PORT_KEY = "beHttpPort";
-    private static final String HTTPS_PORT_KEY = "beSslPort";
-    private static final String URL_KEY = "onboardCatalogNotificationUrl";
-    private static final String URL_DEFAULT_FORMAT = "%s://%s:%s/sdc2/rest/v1/catalog/notif/vsp/";
-
-    private static String configurationYamlFile = System.getProperty(CONFIG_FILE);
-    private static String notifyCatalogUrl;
-
-    private static ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
-
-
-    static {
-        Function<InputStream, Map<String, LinkedHashMap<String, Object>>> reader = is -> {
-            YamlUtil yamlUtil = new YamlUtil();
-            return yamlUtil.yamlToMap(is);
-        };
-
-        Map<String, LinkedHashMap<String, Object>> configurationMap;
-
-        try {
-            configurationMap = readFromFile(configurationYamlFile, reader);
-            Object protocol = configurationMap.get(PROTOCOL_KEY);
-            Object host = configurationMap.get(HOST_KEY);
-
-            if (protocol == null || host == null) {
-                throw new ExceptionInInitializerError("Could not read configuration file configuration.yaml.");
-            }
-
-            Object port = null;
-            if (String.valueOf(protocol).matches(HTTP_PROTOCOL)) {
-                port = configurationMap.get(HTTP_PORT_KEY);
-            }
-            if (String.valueOf(protocol).matches(HTTPS_PROTOCOL)) {
-                port = configurationMap.get(HTTPS_PORT_KEY);
-            }
-
-            if (configurationMap.get(URL_KEY) != null) {
-                String urlFormat = String.valueOf(configurationMap.get(URL_KEY));
-                notifyCatalogUrl =
-                        String.format(urlFormat, String.valueOf(protocol), String.valueOf(host), String.valueOf(port));
-
-            } else {
-                notifyCatalogUrl = String.format(URL_DEFAULT_FORMAT, String.valueOf(protocol), String.valueOf(host),
-                        String.valueOf(port));
-            }
-
-        } catch (Exception e) {
-            throw new ExceptionInInitializerError(
-                    "Could not read configuration file configuration.yaml. Error: " + e.getMessage());
-
-        }
-    }
-
-
-    public void execute(Collection<String> itemIds, ItemAction action, int numOfRetries) {
-
-        String userId = SessionContextProviderFactory.getInstance().createInterface().get().getUser().getUserId();
-
-        Callable callable = createCallable(JsonUtil.object2Json(itemIds), action, numOfRetries, userId);
-
-        executor.submit(callable);
-
-    }
-
-    private Callable createCallable(String itemIds, ItemAction action, int numOfRetries, String userId) {
-        Callable callable = () -> handleHttpRequest(getUrl(action), itemIds, action, userId, numOfRetries);
-        LoggingContext.copyToCallable(callable);
-        return callable;
-    }
-
-    private Void handleHttpRequest(String url, String itemIds, ItemAction action, String userId,
-            int numOfRetries) {
-
-        try (CloseableHttpClient httpclient = HttpClients.createDefault()) {
-            HttpPost request = createPostRequest(url, itemIds, userId);
-            HttpResponse response = httpclient.execute(request);
-            LOGGER.debug(String.format("Catalog notification on vspId - %s action  - %s. Response: %s", itemIds,
-                    action.name(), response.getStatusLine()));
-
-            if (numOfRetries > 1 && response.getStatusLine().getStatusCode() == Response.Status.INTERNAL_SERVER_ERROR
-                                                                                        .getStatusCode()) {
-                Callable callable =
-                        createCallable(getFailedIds(itemIds, response.getEntity()), action, --numOfRetries, userId);
-                executor.schedule(callable, 5, TimeUnit.SECONDS);
-            }
-
-        } catch (Exception e) {
-            LOGGER.error(String.format("Catalog notification on vspId - %s action  - %s FAILED. Error: %S", itemIds,
-                    action.name(), e.getMessage()));
-        }
-        return null;
-    }
-
-    private String getFailedIds(String itemIds, HttpEntity responseBody) {
-        try {
-            Map jsonBody = JsonUtil.json2Object(responseBody.getContent(), Map.class);
-            return jsonBody.get("failedIds").toString();
-        } catch (Exception e) {
-            LOGGER.error("Catalog Notification RETRY - no failed IDs in response");
-        }
-        return JsonUtil.object2Json(itemIds);
-    }
-
-    private HttpPost createPostRequest(String postUrl, String itemIds, String userId)
-            throws UnsupportedEncodingException {
-
-        HttpPost request = new HttpPost(postUrl);
-
-        request.addHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON);
-        request.addHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON);
-        request.addHeader(USER_ID_HEADER_PARAM, userId);
-
-        HttpEntity entity = new StringEntity(itemIds);
-        request.setEntity(entity);
-
-        return request;
-    }
-
-    private String getUrl(ItemAction action) {
-        String actionStr = "";
-        if (action == ItemAction.ARCHIVE) {
-            actionStr = "archived";
-        } else if (action == ItemAction.RESTORE) {
-            actionStr = "restored";
-        }
-        LOGGER.debug("Catalog notification URL - " + notifyCatalogUrl + actionStr);
-        return notifyCatalogUrl + actionStr;
-    }
-
-    private static <T> T readFromFile(String file, Function<InputStream, T> reader) throws IOException {
-        try (InputStream is = new FileInputStream(file)) {
-            return reader.apply(is);
-        }
-    }
-}
index b00e459..a93c063 100644 (file)
@@ -40,6 +40,8 @@ import org.openecomp.sdc.versioning.types.ItemStatus;
 import org.openecomp.sdc.versioning.types.NotificationEventTypes;
 import org.openecomp.sdcrests.item.rest.Items;
 import org.openecomp.sdcrests.item.rest.mapping.MapItemToDto;
+import org.openecomp.sdcrests.item.rest.services.catalog.notification.Notifier;
+import org.openecomp.sdcrests.item.rest.services.catalog.notification.NotifierFactory;
 import org.openecomp.sdcrests.item.types.ItemAction;
 import org.openecomp.sdcrests.item.types.ItemActionRequestDto;
 import org.openecomp.sdcrests.item.types.ItemDto;
@@ -113,9 +115,9 @@ public class ItemsImpl implements Items {
 
         actionSideAffectsMap.get(request.getAction()).execute(item, user);
         try {
-            CatalogNotifier catalogNotifier = new CatalogNotifier();
-            catalogNotifier.execute(Collections.singleton(itemId), request.getAction(), 2);
-        } catch (Exception e){
+            Notifier notifier = NotifierFactory.getInstance();
+            notifier.execute(Collections.singleton(itemId), request.getAction());
+        } catch (Exception e) {
             LOGGER.error("Failed to send catalog notification on item " + itemId + " Error: " + e.getMessage());
         }
 
@@ -323,6 +325,6 @@ public class ItemsImpl implements Items {
 
     //Do not delete - is in use, duplicates code to prevent dependency on openecomp-sdc-vendor-software-product-api
     private enum OnboardingMethod {
-        NetworkPackage, Manual;
+        NetworkPackage, Manual
     }
 }
\ No newline at end of file
diff --git a/openecomp-be/api/openecomp-sdc-rest-webapp/item-rest/item-rest-services/src/main/java/org/openecomp/sdcrests/item/rest/services/catalog/notification/AsyncNotifier.java b/openecomp-be/api/openecomp-sdc-rest-webapp/item-rest/item-rest-services/src/main/java/org/openecomp/sdcrests/item/rest/services/catalog/notification/AsyncNotifier.java
new file mode 100644 (file)
index 0000000..8288010
--- /dev/null
@@ -0,0 +1,101 @@
+/*
+ * Copyright © 2016-2018 European Support Limited
+ *
+ * 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.openecomp.sdcrests.item.rest.services.catalog.notification;
+
+import java.util.Collection;
+import java.util.concurrent.Callable;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.function.BiFunction;
+import org.openecomp.sdc.logging.api.Logger;
+import org.openecomp.sdc.logging.api.LoggerFactory;
+import org.openecomp.sdc.logging.api.LoggingContext;
+import org.openecomp.sdcrests.item.types.ItemAction;
+
+/**
+ * Asynchronously runs a notification task.
+ *
+ * @author evitaliy
+ * @since 22 Nov 2018
+ */
+public class AsyncNotifier implements Notifier {
+
+    private static final ScheduledExecutorService EXECUTOR_SERVICE = Executors.newScheduledThreadPool(1);
+
+    private static final int DEFAULT_NUM_OF_RETRIES = 2;
+    private static final long DEFAULT_INTERVAL = 5000;
+
+    private final BiFunction<Collection<String>, ItemAction, Callable<NextAction>> taskProducer;
+
+    AsyncNotifier(BiFunction<Collection<String>, ItemAction, Callable<NextAction>> taskProducer) {
+        this.taskProducer = taskProducer;
+    }
+
+    @Override
+    public void execute(Collection<String> itemIds, ItemAction action) {
+
+        Callable<AsyncNotifier.NextAction> worker = taskProducer.apply(itemIds, action);
+
+        RetryingTask retryingTask =
+                new RetryingTask(worker, DEFAULT_NUM_OF_RETRIES, DEFAULT_INTERVAL, EXECUTOR_SERVICE);
+
+        EXECUTOR_SERVICE.submit(LoggingContext.copyToCallable(retryingTask));
+    }
+
+    public enum NextAction {
+        RETRY, DONE
+    }
+
+    static class RetryingTask implements Callable<Void> {
+
+        private static final Logger LOGGER = LoggerFactory.getLogger(RetryingTask.class);
+
+        private final Callable<AsyncNotifier.NextAction> worker;
+        private final long delay;
+        private final ScheduledExecutorService scheduler;
+        private volatile int retries;
+
+        RetryingTask(Callable<AsyncNotifier.NextAction> worker, int numOfRetries, long delay,
+                ScheduledExecutorService scheduler) {
+
+            this.worker = worker;
+            this.retries = numOfRetries;
+            this.delay = delay;
+            this.scheduler = scheduler;
+        }
+
+        @Override
+        public synchronized Void call() throws Exception {
+
+            NextAction next = worker.call();
+            if (next == NextAction.DONE) {
+                LOGGER.debug("Task successful: {}. Not going to retry", worker);
+                return null;
+            }
+
+            retries--;
+            if (retries == 0) {
+                LOGGER.warn("Exhausted number of retries for task {}, exiting", worker);
+                return null;
+            }
+
+            scheduler.schedule(this, delay, TimeUnit.MILLISECONDS);
+            return null;
+        }
+    }
+}
diff --git a/openecomp-be/api/openecomp-sdc-rest-webapp/item-rest/item-rest-services/src/main/java/org/openecomp/sdcrests/item/rest/services/catalog/notification/EntryNotConfiguredException.java b/openecomp-be/api/openecomp-sdc-rest-webapp/item-rest/item-rest-services/src/main/java/org/openecomp/sdcrests/item/rest/services/catalog/notification/EntryNotConfiguredException.java
new file mode 100644 (file)
index 0000000..070164a
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ * Copyright © 2016-2018 European Support Limited
+ *
+ * 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.openecomp.sdcrests.item.rest.services.catalog.notification;
+
+/**
+ * Thrown when an expected entry was not found in configuration.
+ *
+ * @author evitaliy
+ * @since 21 Nov 2018
+ */
+public class EntryNotConfiguredException extends RuntimeException {
+
+    public EntryNotConfiguredException(String configEntry) {
+        super(configEntry + " must be configured");
+    }
+}
diff --git a/openecomp-be/api/openecomp-sdc-rest-webapp/item-rest/item-rest-services/src/main/java/org/openecomp/sdcrests/item/rest/services/catalog/notification/Notifier.java b/openecomp-be/api/openecomp-sdc-rest-webapp/item-rest/item-rest-services/src/main/java/org/openecomp/sdcrests/item/rest/services/catalog/notification/Notifier.java
new file mode 100644 (file)
index 0000000..9143de4
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * Copyright © 2016-2018 European Support Limited
+ *
+ * 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.openecomp.sdcrests.item.rest.services.catalog.notification;
+
+import java.util.Collection;
+import org.openecomp.sdcrests.item.types.ItemAction;
+
+/**
+ * Notifies the Catalog of an action being performed on items referenced by their IDs.
+ *
+ * @author evitaliy
+ * @since 21 Nov 2018
+ */
+@FunctionalInterface
+public interface Notifier {
+
+    void execute(Collection<String> itemIds, ItemAction action);
+}
diff --git a/openecomp-be/api/openecomp-sdc-rest-webapp/item-rest/item-rest-services/src/main/java/org/openecomp/sdcrests/item/rest/services/catalog/notification/NotifierFactory.java b/openecomp-be/api/openecomp-sdc-rest-webapp/item-rest/item-rest-services/src/main/java/org/openecomp/sdcrests/item/rest/services/catalog/notification/NotifierFactory.java
new file mode 100644 (file)
index 0000000..a7f1e9c
--- /dev/null
@@ -0,0 +1,105 @@
+/*
+ * Copyright © 2018 European Support Limited
+ *
+ * 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.openecomp.sdcrests.item.rest.services.catalog.notification;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Collection;
+import java.util.Map;
+import java.util.Objects;
+import org.onap.sdc.tosca.services.YamlUtil;
+import org.openecomp.sdc.logging.api.Logger;
+import org.openecomp.sdc.logging.api.LoggerFactory;
+import org.openecomp.sdcrests.item.rest.services.catalog.notification.http.HttpConfiguration;
+import org.openecomp.sdcrests.item.rest.services.catalog.notification.http.HttpTaskProducer;
+import org.openecomp.sdcrests.item.types.ItemAction;
+
+/**
+ * Creates an instance of {@link Notifier}, initialized according to current configuration.
+ * The configuration must be passed via the {@link #CONFIG_FILE_PROPERTY} JVM argument.
+ */
+public class NotifierFactory {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(NotifierFactory.class);
+
+    private static final String CONFIG_FILE_PROPERTY = "configuration.yaml";
+    private static final String CONFIG_SECTION = "catalogNotificationsConfig";
+
+    private static final Notifier INSTANCE;
+
+    static {
+        INSTANCE = createInstance();
+    }
+
+    /**
+     * Returns a {@link Notifier} instance according to the provided configuration. If no configuration was not
+     * provided, or the given configuration is incorrect, then an instance with reduced functionality will be returned.
+     *
+     * @return available instance of {@link Notifier}
+     */
+    public static Notifier getInstance() {
+        return INSTANCE;
+    }
+
+    static Notifier createInstance() {
+
+        try {
+
+            String file = Objects.requireNonNull(System.getProperty(CONFIG_FILE_PROPERTY),
+                    "Config file location must be specified via system property " + CONFIG_FILE_PROPERTY);
+
+            Object config = getNotificationConfiguration(file);
+            ObjectMapper mapper = new ObjectMapper();
+            HttpConfiguration httpConfig = mapper.convertValue(config, HttpConfiguration.class);
+
+            return new AsyncNotifier(new HttpTaskProducer(httpConfig));
+
+        } catch (Exception e) {
+            LOGGER.warn("Failed to initialize notifier. Notifications will not be sent", e);
+            return new UnsupportedConfigurationNotifier();
+        }
+    }
+
+    private static Object getNotificationConfiguration(String file) throws IOException {
+
+        Map<?, ?> configuration = Objects.requireNonNull(readConfigurationFile(file), "Configuration cannot be empty");
+        Object notificationConfig = configuration.get(CONFIG_SECTION);
+        if (notificationConfig == null) {
+            throw new EntryNotConfiguredException(CONFIG_SECTION + " section");
+        }
+
+        return notificationConfig;
+    }
+
+    private static Map<?, ?> readConfigurationFile(String file) throws IOException {
+
+        try (InputStream fileInput = new FileInputStream(file)) {
+            YamlUtil yamlUtil = new YamlUtil();
+            return yamlUtil.yamlToMap(fileInput);
+        }
+    }
+
+    static class UnsupportedConfigurationNotifier implements Notifier {
+
+        @Override
+        public void execute(Collection<String> itemIds, ItemAction action) {
+            throw new IllegalStateException("Cannot send notifications. The notifier was not properly initialized");
+        }
+    }
+}
diff --git a/openecomp-be/api/openecomp-sdc-rest-webapp/item-rest/item-rest-services/src/main/java/org/openecomp/sdcrests/item/rest/services/catalog/notification/http/HttpConfiguration.java b/openecomp-be/api/openecomp-sdc-rest-webapp/item-rest/item-rest-services/src/main/java/org/openecomp/sdcrests/item/rest/services/catalog/notification/http/HttpConfiguration.java
new file mode 100644 (file)
index 0000000..4403bd8
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ * Copyright © 2016-2018 European Support Limited
+ *
+ * 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.openecomp.sdcrests.item.rest.services.catalog.notification.http;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import lombok.ToString;
+
+/**
+ * Represents configuration for sending notifications to the Catalog side.
+ *
+ * @author evitaliy
+ * @since 21 Nov 2018
+ */
+@Setter
+@Getter
+@NoArgsConstructor
+@AllArgsConstructor
+@ToString
+public class HttpConfiguration {
+
+    private String catalogBeProtocol;
+    private String catalogBeHttpPort;
+    private String catalogBeSslPort;
+    private String catalogBeFqdn;
+    private String catalogNotificationUrl;
+}
diff --git a/openecomp-be/api/openecomp-sdc-rest-webapp/item-rest/item-rest-services/src/main/java/org/openecomp/sdcrests/item/rest/services/catalog/notification/http/HttpNotificationTask.java b/openecomp-be/api/openecomp-sdc-rest-webapp/item-rest/item-rest-services/src/main/java/org/openecomp/sdcrests/item/rest/services/catalog/notification/http/HttpNotificationTask.java
new file mode 100644 (file)
index 0000000..c88ac4e
--- /dev/null
@@ -0,0 +1,135 @@
+/*
+ * Copyright © 2016-2018 European Support Limited
+ *
+ * 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.openecomp.sdcrests.item.rest.services.catalog.notification.http;
+
+import static org.openecomp.sdcrests.item.rest.services.catalog.notification.AsyncNotifier.NextAction.DONE;
+import static org.openecomp.sdcrests.item.rest.services.catalog.notification.AsyncNotifier.NextAction.RETRY;
+
+import java.io.UnsupportedEncodingException;
+import java.util.Collection;
+import java.util.concurrent.Callable;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import lombok.ToString;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpHeaders;
+import org.apache.http.HttpStatus;
+import org.apache.http.StatusLine;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.entity.ContentType;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+import org.openecomp.core.utilities.json.JsonUtil;
+import org.openecomp.sdc.logging.api.Logger;
+import org.openecomp.sdc.logging.api.LoggerFactory;
+import org.openecomp.sdcrests.item.rest.services.catalog.notification.AsyncNotifier;
+
+/**
+ * HTTP client for notifying the Catalog of an action on items. The items are referenced by their IDs. The client can
+ * run multiple times, in which case only failed IDs will be re-attempted.
+ *
+ * @author evitaliy
+ * @since 21 Nov 2018
+ */
+@ToString
+class HttpNotificationTask implements Callable<AsyncNotifier.NextAction> {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(HttpNotificationTask.class);
+
+    private static final String APPLICATION_JSON = ContentType.APPLICATION_JSON.getMimeType();
+    private static final String USER_ID_HEADER_PARAM = "USER_ID";
+
+    private final String endpoint;
+    private final String userId;
+    private volatile Collection<String> itemIds;
+
+    HttpNotificationTask(String endpoint, String userId, Collection<String> itemIds) {
+        this.endpoint = endpoint;
+        this.userId = userId;
+        this.itemIds = itemIds;
+    }
+
+    @Override
+    public synchronized AsyncNotifier.NextAction call() {
+
+        try (CloseableHttpClient client = HttpClients.createDefault()) {
+
+            HttpPost request = createPostRequest(endpoint, itemIds, userId);
+
+            try (CloseableHttpResponse response = client.execute(request)) {
+
+                StatusLine status = response.getStatusLine();
+
+                LOGGER.debug("Catalog notification on VSP IDs: {}, endpoint: {}, response: {}",
+                        itemIds, endpoint, status);
+
+                itemIds = getFailedIds(itemIds, response.getEntity());
+
+                if ((status.getStatusCode() == HttpStatus.SC_INTERNAL_SERVER_ERROR)
+                            && (itemIds != null) && !itemIds.isEmpty()) {
+
+                    LOGGER.debug("Catalog notification on VSP IDs {} failed. Endpoint: {}. Retry", itemIds, endpoint);
+                    return RETRY;
+                }
+
+                return DONE;
+            }
+
+        } catch (Exception e) {
+            LOGGER.error("Catalog notification on VSP IDs {} failed. Endpoint: {}", itemIds, endpoint, e);
+            return DONE;
+        }
+    }
+
+    private HttpPost createPostRequest(String postUrl, Collection<String> itemIds, String userId)
+            throws UnsupportedEncodingException {
+
+        HttpPost request = new HttpPost(postUrl);
+
+        request.addHeader(HttpHeaders.ACCEPT, APPLICATION_JSON);
+        request.addHeader(HttpHeaders.CONTENT_TYPE, APPLICATION_JSON);
+        request.addHeader(USER_ID_HEADER_PARAM, userId);
+
+        HttpEntity entity = new StringEntity(JsonUtil.object2Json(itemIds));
+        request.setEntity(entity);
+        return request;
+    }
+
+    private Collection<String> getFailedIds(Collection<String> itemIds, HttpEntity responseBody) {
+
+        try {
+            NotificationResponse response = JsonUtil.json2Object(responseBody.getContent(), NotificationResponse.class);
+            return response != null ? response.failedIds : null;
+        } catch (Exception e) {
+            LOGGER.error("Error getting failed IDs from response", e);
+        }
+
+        return itemIds;
+    }
+
+    @Setter
+    @Getter
+    @ToString
+    @NoArgsConstructor
+    private static class NotificationResponse {
+
+        private Collection<String> failedIds;
+    }
+}
diff --git a/openecomp-be/api/openecomp-sdc-rest-webapp/item-rest/item-rest-services/src/main/java/org/openecomp/sdcrests/item/rest/services/catalog/notification/http/HttpTaskProducer.java b/openecomp-be/api/openecomp-sdc-rest-webapp/item-rest/item-rest-services/src/main/java/org/openecomp/sdcrests/item/rest/services/catalog/notification/http/HttpTaskProducer.java
new file mode 100644 (file)
index 0000000..c6abd34
--- /dev/null
@@ -0,0 +1,107 @@
+/*
+ * Copyright © 2016-2018 European Support Limited
+ *
+ * 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.openecomp.sdcrests.item.rest.services.catalog.notification.http;
+
+import java.util.Collection;
+import java.util.concurrent.Callable;
+import java.util.function.BiFunction;
+import org.openecomp.sdc.common.session.SessionContextProviderFactory;
+import org.openecomp.sdc.logging.api.Logger;
+import org.openecomp.sdc.logging.api.LoggerFactory;
+import org.openecomp.sdcrests.item.rest.services.catalog.notification.AsyncNotifier;
+import org.openecomp.sdcrests.item.rest.services.catalog.notification.EntryNotConfiguredException;
+import org.openecomp.sdcrests.item.rest.services.catalog.notification.Notifier;
+import org.openecomp.sdcrests.item.types.ItemAction;
+
+/**
+ * Notifies the Catalog via an HTTP.
+ *
+ * @author evitaliy
+ * @since 21 Nov 2018
+ */
+public class HttpTaskProducer
+        implements BiFunction<Collection<String>, ItemAction, Callable<AsyncNotifier.NextAction>>, Notifier {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(HttpTaskProducer.class);
+
+    private static final String CATALOG_HTTP_PROTOCOL = "HTTP";
+    private static final String CATALOG_HTTPS_PROTOCOL = "HTTPS";
+
+    private final String notifyCatalogUrl;
+
+    /**
+     * Initializes the producer from a provided configuration.
+     *
+     * @param config HTTP-specific configuration, cannot be null
+     */
+    public HttpTaskProducer(HttpConfiguration config) {
+        String protocol = ensureEntryConfigured(config.getCatalogBeProtocol(), "Protocol");
+        String host = ensureEntryConfigured(config.getCatalogBeFqdn(), "Catalog host");
+        String url = ensureEntryConfigured(config.getCatalogNotificationUrl(), "Notification URL");
+        String port = getPortConfiguration(protocol, config);
+        this.notifyCatalogUrl = String.format(url, protocol, host, port);
+    }
+
+    private static String ensureEntryConfigured(String value, String entryName) {
+
+        if (value == null) {
+            throw new EntryNotConfiguredException(entryName);
+        }
+
+        return value;
+    }
+
+    private static String getPortConfiguration(String protocol, HttpConfiguration config) {
+
+        if (CATALOG_HTTP_PROTOCOL.equalsIgnoreCase(protocol)) {
+            return ensureEntryConfigured(config.getCatalogBeHttpPort(), "HTTP port");
+        } else if (CATALOG_HTTPS_PROTOCOL.equalsIgnoreCase(protocol)) {
+            return ensureEntryConfigured(config.getCatalogBeSslPort(), "SSL port");
+        } else {
+            throw new IllegalArgumentException("Unsupported protocol: " + protocol);
+        }
+    }
+
+    @Override
+    public Callable<AsyncNotifier.NextAction> apply(Collection<String> itemIds, ItemAction action) {
+        return createNotificationTask(itemIds, action);
+    }
+
+    private static String getEndpoint(ItemAction action) {
+
+        if (action == ItemAction.ARCHIVE) {
+            return "archived";
+        } else if (action == ItemAction.RESTORE) {
+            return "restored";
+        } else {
+            throw new IllegalArgumentException("Unsupported action: " + action.name());
+        }
+    }
+
+    @Override
+    public void execute(Collection<String> itemIds, ItemAction action) {
+        HttpNotificationTask task = createNotificationTask(itemIds, action);
+        task.call();
+    }
+
+    private HttpNotificationTask createNotificationTask(Collection<String> itemIds, ItemAction action) {
+        String userId = SessionContextProviderFactory.getInstance().createInterface().get().getUser().getUserId();
+        String notificationEndpoint = notifyCatalogUrl + getEndpoint(action);
+        LOGGER.debug("Catalog notification URL: " + notificationEndpoint);
+        return new HttpNotificationTask(notificationEndpoint, userId, itemIds);
+    }
+}
diff --git a/openecomp-be/api/openecomp-sdc-rest-webapp/item-rest/item-rest-services/src/main/resources/logback-test.xml b/openecomp-be/api/openecomp-sdc-rest-webapp/item-rest/item-rest-services/src/main/resources/logback-test.xml
new file mode 100644 (file)
index 0000000..f9fa3d8
--- /dev/null
@@ -0,0 +1,13 @@
+<configuration>
+
+    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
+        <encoder>
+            <pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern>
+        </encoder>
+    </appender>
+
+    <root level="OFF">
+        <appender-ref ref="STDOUT" />
+    </root>
+
+</configuration>
\ No newline at end of file
diff --git a/openecomp-be/api/openecomp-sdc-rest-webapp/item-rest/item-rest-services/src/test/java/org/openecomp/sdcrests/item/rest/services/catalog/notification/AsyncNotifierTest.java b/openecomp-be/api/openecomp-sdc-rest-webapp/item-rest/item-rest-services/src/test/java/org/openecomp/sdcrests/item/rest/services/catalog/notification/AsyncNotifierTest.java
new file mode 100644 (file)
index 0000000..900fc94
--- /dev/null
@@ -0,0 +1,104 @@
+/*
+ * Copyright © 2016-2018 European Support Limited
+ *
+ * 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.openecomp.sdcrests.item.rest.services.catalog.notification;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.openecomp.sdcrests.item.rest.services.catalog.notification.AsyncNotifier.NextAction.DONE;
+import static org.openecomp.sdcrests.item.rest.services.catalog.notification.AsyncNotifier.NextAction.RETRY;
+
+import java.util.concurrent.Callable;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import org.apache.commons.lang.mutable.MutableInt;
+import org.junit.Test;
+import org.mockito.Mockito;
+import org.mockito.stubbing.Answer;
+
+/**
+ * @author evitaliy
+ * @since 22 Nov 2018
+ */
+public class AsyncNotifierTest {
+
+    @Test
+    public void taskRunsOnceWhenSuccessful() throws Exception {
+
+        ScheduledExecutorService executorServiceMock = createMockScheduledExecutor();
+
+        MutableInt counter = new MutableInt(0);
+        Callable<AsyncNotifier.NextAction> countingTask = () -> {
+            counter.increment();
+            return DONE;
+        };
+
+        AsyncNotifier.RetryingTask retryingTask =
+                new AsyncNotifier.RetryingTask(countingTask, 10, 10, executorServiceMock);
+        retryingTask.call();
+        assertEquals(1, counter.intValue());
+    }
+
+    @Test
+    public void taskRunsTwiceWhenFailedFirstTime() throws Exception {
+
+        ScheduledExecutorService executorServiceMock = createMockScheduledExecutor();
+
+        MutableInt counter = new MutableInt(0);
+        Callable<AsyncNotifier.NextAction> countingTask = () -> {
+            counter.increment();
+            return counter.intValue() < 2 ? RETRY : DONE;
+        };
+
+        AsyncNotifier.RetryingTask retryingTask =
+                new AsyncNotifier.RetryingTask(countingTask, 10, 10, executorServiceMock);
+        retryingTask.call();
+        assertEquals(2, counter.intValue());
+    }
+
+    @Test
+    public void exhaustedAttemptsWhenTaskAlwaysFails() throws Exception {
+
+        ScheduledExecutorService executorServiceMock = createMockScheduledExecutor();
+
+        MutableInt counter = new MutableInt(0);
+        Callable<AsyncNotifier.NextAction> countingTask = () -> {
+            counter.increment();
+            return RETRY;
+        };
+
+        final int numOfRetries = 10;
+        AsyncNotifier.RetryingTask retryingTask =
+                new AsyncNotifier.RetryingTask(countingTask, numOfRetries, 10, executorServiceMock);
+        retryingTask.call();
+        assertEquals(numOfRetries, counter.intValue());
+    }
+
+    private ScheduledExecutorService createMockScheduledExecutor() {
+
+        ScheduledExecutorService executorServiceMock = Mockito.mock(ScheduledExecutorService.class);
+        Answer passThrough = invocation -> {
+            ((Callable<?>) invocation.getArgument(0)).call();
+            return null;
+        };
+
+        Mockito.doAnswer(passThrough).when(executorServiceMock).submit(any(Callable.class));
+        Mockito.doAnswer(passThrough).when(executorServiceMock)
+                .schedule(any(Callable.class), anyLong(), any(TimeUnit.class));
+        return executorServiceMock;
+    }
+}
\ No newline at end of file
diff --git a/openecomp-be/api/openecomp-sdc-rest-webapp/item-rest/item-rest-services/src/test/java/org/openecomp/sdcrests/item/rest/services/catalog/notification/NotifierFactoryTest.java b/openecomp-be/api/openecomp-sdc-rest-webapp/item-rest/item-rest-services/src/test/java/org/openecomp/sdcrests/item/rest/services/catalog/notification/NotifierFactoryTest.java
new file mode 100644 (file)
index 0000000..2744682
--- /dev/null
@@ -0,0 +1,101 @@
+/*
+ * Copyright © 2016-2018 European Support Limited
+ *
+ * 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.openecomp.sdcrests.item.rest.services.catalog.notification;
+
+import static org.hamcrest.CoreMatchers.startsWith;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import java.io.FileNotFoundException;
+import java.net.URL;
+import java.util.Collections;
+import java.util.Set;
+import java.util.UUID;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.openecomp.sdcrests.item.types.ItemAction;
+
+/**
+ * @author evitaliy
+ * @since 26 Nov 2018
+ */
+public class NotifierFactoryTest {
+
+    private static final String CONFIG_LOCATION_PROPERTY = "configuration.yaml";
+
+    @Rule
+    public ExpectedException exception = ExpectedException.none();
+
+    @Before
+    public void clearConfigLocation() {
+        System.clearProperty(CONFIG_LOCATION_PROPERTY);
+    }
+
+    @Test
+    public void notifierFactoryReturnsAnInstance() {
+        assertNotNull(NotifierFactory.getInstance());
+    }
+
+    @Test
+    public void unsupportedConfigurationNotifierWhenConfigurationLocationNotGiven() {
+        assertTrue(NotifierFactory.createInstance() instanceof NotifierFactory.UnsupportedConfigurationNotifier);
+    }
+
+    @Test
+    public void asyncNotifierReturnedWhenConfigurationCorrect() throws FileNotFoundException {
+        String configPath = getConfigPath("catalog-notification-config-correct.yaml");
+        System.setProperty(CONFIG_LOCATION_PROPERTY, configPath);
+        assertTrue("Configuration file must be present and correct",
+                NotifierFactory.createInstance() instanceof AsyncNotifier);
+    }
+
+    private String getConfigPath(String classpathFile) throws FileNotFoundException {
+
+        URL resource = Thread.currentThread().getContextClassLoader().getResource(classpathFile);
+        if (resource == null) {
+            throw new FileNotFoundException("Cannot find resource: " + classpathFile);
+        }
+
+        return resource.getPath();
+    }
+
+    @Test
+    public void unsupportedConfigurationNotifierReturnedWhenConfigurationEmpty() throws FileNotFoundException {
+        String configPath = getConfigPath("catalog-notification-config-empty.yaml");
+        System.setProperty(CONFIG_LOCATION_PROPERTY, configPath);
+        assertTrue(NotifierFactory.createInstance() instanceof NotifierFactory.UnsupportedConfigurationNotifier);
+    }
+
+    @Test
+    public void unsupportedConfigurationNotifierReturnedWhenConfigurationDoesNotHaveNotificationSection()
+            throws FileNotFoundException {
+        String configPath = getConfigPath("catalog-notification-config-without-notification-section.yaml");
+        System.setProperty(CONFIG_LOCATION_PROPERTY, configPath);
+        assertTrue(NotifierFactory.createInstance() instanceof NotifierFactory.UnsupportedConfigurationNotifier);
+    }
+
+    @Test
+    public void unsupportedConfigurationNotifierThrowsException() {
+        exception.expect(IllegalStateException.class);
+        exception.expectMessage(startsWith("Cannot send notifications"));
+        Set<String> itemIds = Collections.singleton(UUID.randomUUID().toString());
+        new NotifierFactory.UnsupportedConfigurationNotifier().execute(itemIds, ItemAction.ARCHIVE);
+    }
+
+}
\ No newline at end of file
diff --git a/openecomp-be/api/openecomp-sdc-rest-webapp/item-rest/item-rest-services/src/test/java/org/openecomp/sdcrests/item/rest/services/catalog/notification/http/HttpNotificationTaskTest.java b/openecomp-be/api/openecomp-sdc-rest-webapp/item-rest/item-rest-services/src/test/java/org/openecomp/sdcrests/item/rest/services/catalog/notification/http/HttpNotificationTaskTest.java
new file mode 100644 (file)
index 0000000..1511fc7
--- /dev/null
@@ -0,0 +1,150 @@
+/*
+ * Copyright © 2016-2018 European Support Limited
+ *
+ * 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.openecomp.sdcrests.item.rest.services.catalog.notification.http;
+
+import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
+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.stubFor;
+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.junit.Assert.assertEquals;
+import static org.openecomp.sdcrests.item.rest.services.catalog.notification.AsyncNotifier.NextAction.DONE;
+import static org.openecomp.sdcrests.item.rest.services.catalog.notification.AsyncNotifier.NextAction.RETRY;
+
+import com.github.tomakehurst.wiremock.junit.WireMockRule;
+import com.github.tomakehurst.wiremock.matching.EqualToJsonPattern;
+import com.github.tomakehurst.wiremock.matching.EqualToPattern;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.UUID;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Test;
+
+/**
+ * @author evitaliy
+ * @since 22 Nov 2018
+ */
+public class HttpNotificationTaskTest {
+
+    @ClassRule
+    public static final WireMockRule wireMockRule = new WireMockRule(wireMockConfig().dynamicPort());
+
+    private static final String NOTIFICATION_PATH = "/notification";
+    private static final String USER_ID = "d75360e1-f393-480f-b39e-fbbdf38a22c1";
+    private static final String RETRY_SCENARIO = "RETRY_SCENARIO";
+    private static final String MALFORMED_RESPONSE_SCENARIO = "MALFORMED_RESPONSE_SCENARIO";
+
+    private static String endpoint;
+
+    @BeforeClass
+    public static void initConfiguration() {
+        endpoint = "http://localhost:" + wireMockRule.port() + NOTIFICATION_PATH;
+    }
+
+    @Test
+    public void doneWhenResponseOk() {
+        assertDone(200, arrayToJson(UUID.randomUUID().toString()));
+    }
+
+    private void assertDone(int responseStatus, String body) {
+        final String itemId = UUID.randomUUID().toString();
+        stubFor(post(NOTIFICATION_PATH).willReturn(aResponse().withStatus(responseStatus).withBody(body)));
+        HttpNotificationTask task = new HttpNotificationTask(endpoint, USER_ID, Collections.singleton(itemId));
+        assertEquals(DONE, task.call());
+        verify(postRequestedFor(urlEqualTo(NOTIFICATION_PATH))
+                       .withRequestBody(new EqualToJsonPattern(arrayToJson(itemId), true, false)));
+    }
+
+    private String arrayToJson(String... ids) {
+        return ids.length == 0 ? "[]" : "[ \"" + String.join("\", \"", ids) + "\" ]";
+    }
+
+    @Test
+    public void doneWhenResponse400() {
+        assertDone(400, arrayToJson(UUID.randomUUID().toString()));
+    }
+
+    @Test
+    public void doneWhenResponse522() {
+        assertDone(522, arrayToJson(UUID.randomUUID().toString()));
+    }
+
+    @Test
+    public void doneWhenResponse500ButFailedIdsNotReturned() {
+        assertDone(500, "{}");
+    }
+
+    @Test
+    public void doneWhenResponse500ButFailedIdsEmpty() {
+        assertDone(500, toFailedIdsResponse());
+    }
+
+    private String toFailedIdsResponse(String... ids) {
+        return "{ \"failedIds\": " + arrayToJson(ids) + " }";
+    }
+
+    @Test
+    public void retryWithSameItemIdsWhenResponse500AndFailedToParseResponse() {
+
+        final String[] expectedItemIds = {UUID.randomUUID().toString(), UUID.randomUUID().toString()};
+
+        stubFor(post(NOTIFICATION_PATH).willReturn(aResponse().withStatus(500).withBody("d[g.0g,y/"))
+                        .inScenario(MALFORMED_RESPONSE_SCENARIO));
+        HttpNotificationTask task = new HttpNotificationTask(endpoint, USER_ID, Arrays.asList(expectedItemIds));
+        assertEquals(RETRY, task.call());
+
+        EqualToJsonPattern expectedRequestBody = new EqualToJsonPattern(arrayToJson(expectedItemIds), true, false);
+        verify(postRequestedFor(urlEqualTo(NOTIFICATION_PATH)).withRequestBody(expectedRequestBody));
+
+        stubFor(post(NOTIFICATION_PATH).willReturn(aResponse().withStatus(200).withBody("{}"))
+                        .inScenario(MALFORMED_RESPONSE_SCENARIO));
+        assertEquals(DONE, task.call());
+
+        verify(postRequestedFor(urlEqualTo(NOTIFICATION_PATH)).withRequestBody(expectedRequestBody));
+    }
+
+    @Test
+    public void retryWithFailedItemsWhenResponse500() {
+
+        final String failedId = UUID.randomUUID().toString();
+        final String successId = UUID.randomUUID().toString();
+
+        stubFor(post(NOTIFICATION_PATH).willReturn(aResponse().withStatus(500).withBody(toFailedIdsResponse(failedId)))
+                        .inScenario(RETRY_SCENARIO));
+        HttpNotificationTask task = new HttpNotificationTask(endpoint, USER_ID, Arrays.asList(failedId, successId));
+        assertEquals(RETRY, task.call());
+        verify(postRequestedFor(urlEqualTo(NOTIFICATION_PATH))
+                       .withRequestBody(new EqualToJsonPattern(arrayToJson(failedId, successId), true, false)));
+
+        stubFor(post(NOTIFICATION_PATH).willReturn(aResponse().withStatus(200).withBody("{}"))
+                        .inScenario(RETRY_SCENARIO));
+        assertEquals(DONE, task.call());
+        verify(postRequestedFor(urlEqualTo(NOTIFICATION_PATH))
+                       .withRequestBody(new EqualToJsonPattern(arrayToJson(failedId), true, false)));
+    }
+
+    @Test
+    public void userIdSentToServer() {
+        stubFor(post(NOTIFICATION_PATH).willReturn(aResponse().withStatus(200)));
+        HttpNotificationTask task = new HttpNotificationTask(endpoint, USER_ID, Collections.emptyList());
+        assertEquals(DONE, task.call());
+        verify(postRequestedFor(urlEqualTo(NOTIFICATION_PATH)).withHeader("USER_ID", new EqualToPattern(USER_ID)));
+    }
+}
\ No newline at end of file
diff --git a/openecomp-be/api/openecomp-sdc-rest-webapp/item-rest/item-rest-services/src/test/java/org/openecomp/sdcrests/item/rest/services/catalog/notification/http/HttpTaskProducerTest.java b/openecomp-be/api/openecomp-sdc-rest-webapp/item-rest/item-rest-services/src/test/java/org/openecomp/sdcrests/item/rest/services/catalog/notification/http/HttpTaskProducerTest.java
new file mode 100644 (file)
index 0000000..3c12b37
--- /dev/null
@@ -0,0 +1,121 @@
+/*
+ * Copyright © 2016-2018 European Support Limited
+ *
+ * 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.openecomp.sdcrests.item.rest.services.catalog.notification.http;
+
+import static org.hamcrest.CoreMatchers.containsString;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.openecomp.sdcrests.item.rest.services.catalog.notification.EntryNotConfiguredException;
+
+/**
+ * @author evitaliy
+ * @since 26 Nov 2018
+ */
+public class HttpTaskProducerTest {
+
+    @Rule
+    public ExpectedException exception = ExpectedException.none();
+
+    @Test
+    public void errorWhenProtocolNotDefined() {
+        HttpConfiguration config = mockConfiguration();
+        config.setCatalogBeProtocol(null);
+        exception.expect(EntryNotConfiguredException.class);
+        exception.expectMessage(containsString("Protocol"));
+        new HttpTaskProducer(config);
+    }
+
+    @Test
+    public void errorWhenFqdnNotDefined() {
+        HttpConfiguration config = mockConfiguration();
+        config.setCatalogBeFqdn(null);
+        exception.expect(EntryNotConfiguredException.class);
+        exception.expectMessage(containsString("Catalog host"));
+        new HttpTaskProducer(config);
+    }
+
+    @Test
+    public void errorWhenNotificationUrlNotDefined() {
+        HttpConfiguration config = mockConfiguration();
+        config.setCatalogNotificationUrl(null);
+        exception.expect(EntryNotConfiguredException.class);
+        exception.expectMessage(containsString("Notification URL"));
+        new HttpTaskProducer(config);
+    }
+
+    @Test
+    public void errorWhenUnknownProtocol() {
+        HttpConfiguration config = mockConfiguration();
+        config.setCatalogBeProtocol("invented-protocol");
+        exception.expect(IllegalArgumentException.class);
+        exception.expectMessage(containsString("Unsupported protocol"));
+        new HttpTaskProducer(config);
+    }
+
+    @Test
+    public void errorWhenHttpUsedButHttpPortUndefined() {
+        HttpConfiguration config = mockConfiguration();
+        config.setCatalogBeProtocol("http");
+        config.setCatalogBeHttpPort(null);
+        exception.expect(EntryNotConfiguredException.class);
+        exception.expectMessage(containsString("HTTP port"));
+        new HttpTaskProducer(config);
+    }
+
+    @Test
+    public void errorWhenSslUsedButHttpsPortUndefined() {
+        HttpConfiguration config = mockConfiguration();
+        config.setCatalogBeProtocol("https");
+        config.setCatalogBeSslPort(null);
+        exception.expect(EntryNotConfiguredException.class);
+        exception.expectMessage(containsString("SSL port"));
+        new HttpTaskProducer(config);
+    }
+
+    @Test
+    public void okWhenProtocolHttps() {
+        HttpConfiguration config = mockConfiguration();
+        config.setCatalogBeProtocol("https");
+        new HttpTaskProducer(config);
+    }
+
+    @Test
+    public void okWhenProtocolHttpsMixedCase() {
+        HttpConfiguration config = mockConfiguration();
+        config.setCatalogBeProtocol("hTTpS");
+        new HttpTaskProducer(config);
+    }
+
+    @Test
+    public void okWhenProtocolHttpMixedCase() {
+        HttpConfiguration config = mockConfiguration();
+        config.setCatalogBeProtocol("HTtp");
+        new HttpTaskProducer(config);
+    }
+
+    private HttpConfiguration mockConfiguration() {
+        HttpConfiguration config = new HttpConfiguration();
+        config.setCatalogBeFqdn("fqdn");
+        config.setCatalogBeHttpPort("http-port");
+        config.setCatalogBeProtocol("http");
+        config.setCatalogBeSslPort("ssl-port");
+        config.setCatalogNotificationUrl("url");
+        return config;
+    }
+}
\ No newline at end of file
diff --git a/openecomp-be/api/openecomp-sdc-rest-webapp/item-rest/item-rest-services/src/test/resources/catalog-notification-config-correct.yaml b/openecomp-be/api/openecomp-sdc-rest-webapp/item-rest/item-rest-services/src/test/resources/catalog-notification-config-correct.yaml
new file mode 100644 (file)
index 0000000..6bf2b3e
--- /dev/null
@@ -0,0 +1,6 @@
+catalogNotificationsConfig:
+  catalogBeProtocol: Http
+  catalogBeHttpPort: E
+  catalogBeSslPort: L
+  catalogBeFqdn: L
+  catalogNotificationUrl: O
\ No newline at end of file
diff --git a/openecomp-be/api/openecomp-sdc-rest-webapp/item-rest/item-rest-services/src/test/resources/catalog-notification-config-empty.yaml b/openecomp-be/api/openecomp-sdc-rest-webapp/item-rest/item-rest-services/src/test/resources/catalog-notification-config-empty.yaml
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/openecomp-be/api/openecomp-sdc-rest-webapp/item-rest/item-rest-services/src/test/resources/catalog-notification-config-without-notification-section.yaml b/openecomp-be/api/openecomp-sdc-rest-webapp/item-rest/item-rest-services/src/test/resources/catalog-notification-config-without-notification-section.yaml
new file mode 100644 (file)
index 0000000..0d8e794
--- /dev/null
@@ -0,0 +1 @@
+hello: world
\ No newline at end of file
index b729ae7..a8158c9 100644 (file)
@@ -5,17 +5,21 @@ template "onboard-be-config" do
    group "jetty"
    mode "0755"
    variables({
-      :onboard_ip             => node['ONBOARDING_BE_VIP'],
-      :onboard_port           => node['ONBOARDING_BE'][:http_port],
-      :ssl_port               => node['ONBOARDING_BE'][:https_port],
-      :cassandra_ip           => node['Nodes']['CS'].join(",").gsub(/[|]/,''),
-      :DC_NAME                => node['cassandra']['datacenter_name']+node.chef_environment,
-      :socket_connect_timeout => node['cassandra']['socket_connect_timeout'],
-      :socket_read_timeout    => node['cassandra']['socket_read_timeout'],
-      :cassandra_pwd          => node['cassandra'][:cassandra_password],
-      :cassandra_usr          => node['cassandra'][:cassandra_user],
-      :cassandra_truststore_password => node['cassandra'][:truststore_password],
-      :cassandra_ssl_enabled => "#{ENV['cassandra_ssl_enabled']}"
+      :onboard_ip                      => node['ONBOARDING_BE_VIP'],
+      :onboard_port                    => node['ONBOARDING_BE'][:http_port],
+      :ssl_port                        => node['ONBOARDING_BE'][:https_port],
+      :cassandra_ip                    => node['Nodes']['CS'].join(",").gsub(/[|]/,''),
+      :DC_NAME                         => node['cassandra']['datacenter_name']+node.chef_environment,
+      :socket_connect_timeout          => node['cassandra']['socket_connect_timeout'],
+      :socket_read_timeout             => node['cassandra']['socket_read_timeout'],
+      :cassandra_pwd                   => node['cassandra'][:cassandra_password],
+      :cassandra_usr                   => node['cassandra'][:cassandra_user],
+      :cassandra_truststore_password   => node['cassandra'][:truststore_password],
+      :cassandra_ssl_enabled           => "#{ENV['cassandra_ssl_enabled']}",
+      :catalog_notification_url        => node['ONBOARDING_BE']['catalog_notification_url'],
+      :catalog_be_http_port            => node['BE'][:http_port],
+      :catalog_be_ssl_port             => node['BE'][:https_port],
+      :catalog_be_fqdn                 => node['Nodes']['BE']
    })
 end
 
index a2f0bbd..c0f3e1b 100644 (file)
@@ -1,3 +1,16 @@
+catalogNotificationsConfig:
+    # catalog backend protocol
+    <% if node[:disableHttp] -%>
+    catalogBeProtocol: https
+    <% else %>
+    catalogBeProtocol: http
+    <% end -%>
+    catalogBeHttpPort: <%= @catalog_be_http_port %>
+    catalogBeSslPort: <%= @catalog_be_ssl_port %>
+    catalogBeFqdn: <%= @catalog_be_fqdn %>
+    # do not remove the "" from catalog_notification_url. it is escaping % characters coming from AUTO.json
+    catalogNotificationUrl: "<%= @catalog_notification_url %>"
+
 notifications:
     pollingIntervalMsec: 2000
     selectionSize: 100
index 8654339..68f7804 100644 (file)
@@ -49,7 +49,7 @@ public class ConfigurationManagerTest {
     public void testGetInstanceSystemProperty() throws Throwable {
 
         expectedException.expect(IOException.class);
-        expectedException.expectMessage(containsString(NON_EXISTENT));
+        expectedException.expectMessage((NON_EXISTENT));
 
         try (ConfigurationSystemPropertyUpdater updater = new ConfigurationSystemPropertyUpdater(NON_EXISTENT)) {
             ConfigurationManager.getInstance();
index d212d1e..d738939 100644 (file)
@@ -72,7 +72,8 @@
         },
         "ONBOARDING_BE": {
             "http_port": "8081",
-            "https_port": "8445"
+            "https_port": "8445",
+            "catalog_notification_url": "%s://%s:%s/sdc2/rest/v1/catalog/notif/vsp/"
         },
         "elasticsearch": {
             "cluster_name": "SDC-ES-",