From bd591af78a4ab97af4f65d6b7094d9c60f7879c4 Mon Sep 17 00:00:00 2001 From: "waqas.ikram" Date: Mon, 23 Jan 2023 11:58:29 +0000 Subject: [PATCH] Adding common classes for workflows Change-Id: Id86256ee21ee8c78da82ae3141f7936191593597 Issue-ID: SO-4068 Signed-off-by: waqas.ikram --- pom.xml | 3 + so-cnfm/so-cnfm-lcm/pom.xml | 1 + so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/pom.xml | 145 ++++++++ .../lcm/bpmn/flows/CamundaCustomConfiguration.java | 49 +++ .../bpmn/flows/CamundaDatabaseConfiguration.java | 89 +++++ .../bpmn/flows/CamundaVariableNameConstants.java | 53 +++ .../org/onap/so/cnfm/lcm/bpmn/flows/Constants.java | 45 +++ .../onap/so/cnfm/lcm/bpmn/flows/GsonProvider.java | 45 +++ .../exceptions/AsRequestProcessingException.java | 55 +++ .../flows/exceptions/BasicAuthConfigException.java | 37 ++ .../exceptions/FileNotFoundInCsarException.java | 44 +++ .../exceptions/PropertyNotFoundException.java | 44 +++ .../SdcPackageRequestFailureException.java | 42 +++ .../flows/extclients/aai/AaiClientProvider.java | 34 ++ .../flows/extclients/aai/AaiPropertiesImpl.java | 108 ++++++ .../flows/extclients/aai/AaiServiceProvider.java | 68 ++++ .../extclients/aai/AaiServiceProviderImpl.java | 211 ++++++++++++ .../extclients/kubernetes/KubernetesResource.java | 209 ++++++++++++ .../bpmn/flows/extclients/sdc/DeploymentItem.java | 143 ++++++++ .../sdc/SdcClientConfigurationProvider.java | 79 +++++ .../flows/extclients/sdc/SdcCsarPackageParser.java | 201 +++++++++++ .../extclients/sdc/SdcCsarPropertiesConstants.java | 37 ++ .../SdcHttpRestServiceProviderConfiguration.java | 108 ++++++ .../flows/extclients/sdc/SdcPackageProvider.java | 33 ++ .../extclients/sdc/SdcPackageProviderImpl.java | 80 +++++ .../lcm/bpmn/flows/service/JobExecutorService.java | 376 +++++++++++++++++++++ .../flows/service/WorkflowExecutorService.java | 59 ++++ .../bpmn/flows/service/WorkflowQueryService.java | 111 ++++++ .../bpmn/flows/utils/LocalDateTimeTypeAdapter.java | 59 ++++ .../flows/utils/OffsetDateTimeTypeAdapter.java | 68 ++++ .../flows/utils/PropertiesToYamlConverter.java | 59 ++++ .../services/org.onap.so.client.RestProperties | 1 + .../extclients/sdc/SdcCsarPackageParserTest.java | 83 +++++ .../flows/utils/LocalDateTimeTypeAdapterTest.java | 64 ++++ .../flows/utils/OffsetDateTimeTypeAdapterTest.java | 66 ++++ .../flows/utils/PropertiesToYamlConverterTest.java | 45 +++ .../src/test/resources/application.yaml | 52 +++ .../src/test/resources/request.json | 25 ++ .../resource-Generatedasdpackage-csar.csar | Bin 0 -> 72173 bytes .../cnfm/lcm/database/beans/AsLifecycleParam.java | 2 +- .../so/cnfm/lcm/database/beans/utils/Utils.java | 21 +- .../database/config/CnfmDatabaseConfiguration.java | 5 +- .../cnfm/lcm/database/beans/utils/UtilsTest.java | 5 + .../service/DatabaseServiceProviderTest.java | 2 +- 44 files changed, 3050 insertions(+), 16 deletions(-) create mode 100644 so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/pom.xml create mode 100644 so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/java/org/onap/so/cnfm/lcm/bpmn/flows/CamundaCustomConfiguration.java create mode 100644 so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/java/org/onap/so/cnfm/lcm/bpmn/flows/CamundaDatabaseConfiguration.java create mode 100644 so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/java/org/onap/so/cnfm/lcm/bpmn/flows/CamundaVariableNameConstants.java create mode 100644 so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/java/org/onap/so/cnfm/lcm/bpmn/flows/Constants.java create mode 100644 so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/java/org/onap/so/cnfm/lcm/bpmn/flows/GsonProvider.java create mode 100644 so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/java/org/onap/so/cnfm/lcm/bpmn/flows/exceptions/AsRequestProcessingException.java create mode 100644 so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/java/org/onap/so/cnfm/lcm/bpmn/flows/exceptions/BasicAuthConfigException.java create mode 100644 so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/java/org/onap/so/cnfm/lcm/bpmn/flows/exceptions/FileNotFoundInCsarException.java create mode 100644 so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/java/org/onap/so/cnfm/lcm/bpmn/flows/exceptions/PropertyNotFoundException.java create mode 100644 so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/java/org/onap/so/cnfm/lcm/bpmn/flows/exceptions/SdcPackageRequestFailureException.java create mode 100644 so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/java/org/onap/so/cnfm/lcm/bpmn/flows/extclients/aai/AaiClientProvider.java create mode 100644 so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/java/org/onap/so/cnfm/lcm/bpmn/flows/extclients/aai/AaiPropertiesImpl.java create mode 100644 so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/java/org/onap/so/cnfm/lcm/bpmn/flows/extclients/aai/AaiServiceProvider.java create mode 100644 so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/java/org/onap/so/cnfm/lcm/bpmn/flows/extclients/aai/AaiServiceProviderImpl.java create mode 100644 so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/java/org/onap/so/cnfm/lcm/bpmn/flows/extclients/kubernetes/KubernetesResource.java create mode 100644 so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/java/org/onap/so/cnfm/lcm/bpmn/flows/extclients/sdc/DeploymentItem.java create mode 100644 so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/java/org/onap/so/cnfm/lcm/bpmn/flows/extclients/sdc/SdcClientConfigurationProvider.java create mode 100644 so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/java/org/onap/so/cnfm/lcm/bpmn/flows/extclients/sdc/SdcCsarPackageParser.java create mode 100644 so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/java/org/onap/so/cnfm/lcm/bpmn/flows/extclients/sdc/SdcCsarPropertiesConstants.java create mode 100644 so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/java/org/onap/so/cnfm/lcm/bpmn/flows/extclients/sdc/SdcHttpRestServiceProviderConfiguration.java create mode 100644 so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/java/org/onap/so/cnfm/lcm/bpmn/flows/extclients/sdc/SdcPackageProvider.java create mode 100644 so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/java/org/onap/so/cnfm/lcm/bpmn/flows/extclients/sdc/SdcPackageProviderImpl.java create mode 100644 so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/java/org/onap/so/cnfm/lcm/bpmn/flows/service/JobExecutorService.java create mode 100644 so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/java/org/onap/so/cnfm/lcm/bpmn/flows/service/WorkflowExecutorService.java create mode 100644 so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/java/org/onap/so/cnfm/lcm/bpmn/flows/service/WorkflowQueryService.java create mode 100644 so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/java/org/onap/so/cnfm/lcm/bpmn/flows/utils/LocalDateTimeTypeAdapter.java create mode 100644 so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/java/org/onap/so/cnfm/lcm/bpmn/flows/utils/OffsetDateTimeTypeAdapter.java create mode 100644 so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/java/org/onap/so/cnfm/lcm/bpmn/flows/utils/PropertiesToYamlConverter.java create mode 100644 so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/resources/META-INF/services/org.onap.so.client.RestProperties create mode 100644 so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/test/java/org/onap/so/cnfm/lcm/bpmn/flows/extclients/sdc/SdcCsarPackageParserTest.java create mode 100644 so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/test/java/org/onap/so/cnfm/lcm/bpmn/flows/utils/LocalDateTimeTypeAdapterTest.java create mode 100644 so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/test/java/org/onap/so/cnfm/lcm/bpmn/flows/utils/OffsetDateTimeTypeAdapterTest.java create mode 100644 so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/test/java/org/onap/so/cnfm/lcm/bpmn/flows/utils/PropertiesToYamlConverterTest.java create mode 100644 so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/test/resources/application.yaml create mode 100644 so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/test/resources/request.json create mode 100644 so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/test/resources/resource-Generatedasdpackage-csar.csar diff --git a/pom.xml b/pom.xml index ffb9e8f..5f0dcce 100644 --- a/pom.xml +++ b/pom.xml @@ -22,6 +22,9 @@ 3.0.33 1.9.0-SNAPSHOT 3.4.1 + 0.11 + 16.0.0 + 1.3.70 diff --git a/so-cnfm/so-cnfm-lcm/pom.xml b/so-cnfm/so-cnfm-lcm/pom.xml index 4d6df9e..ad758d1 100644 --- a/so-cnfm/so-cnfm-lcm/pom.xml +++ b/so-cnfm/so-cnfm-lcm/pom.xml @@ -14,5 +14,6 @@ so-cnfm-lcm-api so-cnfm-lcm-database-service + so-cnfm-lcm-bpmn-flows \ No newline at end of file diff --git a/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/pom.xml b/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/pom.xml new file mode 100644 index 0000000..3982680 --- /dev/null +++ b/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/pom.xml @@ -0,0 +1,145 @@ + + 4.0.0 + + org.onap.so.adapters.so-cnf-adapter.so-cnfm.lcm + so-cnfm-lcm + 1.9.0-SNAPSHOT + + + so-cnfm-lcm-bpmn-flows + SO CNFM LCM BPMN Flows + + + + + org.jacoco + jacoco-maven-plugin + + + org.apache.maven.plugins + maven-surefire-plugin + + + DEBUG + + 2 + suites + false + 1 + + + + + + + + org.onap.so.adapters.so-cnf-adapter.so-cnfm.lcm + so-cnfm-lcm-api + ${project.version} + + + org.onap.so.adapters.so-cnf-adapter.so-cnfm.lcm + so-cnfm-lcm-database-service + ${project.version} + + + org.onap.so + aai-client + ${so-core-version} + + + org.onap.aai.schema-service + aai-schema + + + org.camunda.bpm.springboot + camunda-bpm-spring-boot-starter-rest + + + org.camunda.bpmn + camunda-engine-rest-core + + + + + org.yaml + snakeyaml + + + com.shazam + shazamcrest + ${snakeyaml-version} + + + com.google.guava + guava + + + org.apache.commons + commons-lang3 + + + com.vaadin.external.google + android-json + + + + + com.h2database + h2 + test + + + org.hamcrest + hamcrest + test + + + nl.jqno.equalsverifier + equalsverifier + ${equalsverifier-version} + test + + + org.springframework.cloud + spring-cloud-contract-wiremock + + + org.springframework.boot + spring-boot-starter-test + test + + + org.onap.so + common + ${so-core-version} + + + log4j + log4j + + + org.apache.tomcat + tomcat-catalina + + + + + io.kubernetes + client-java + ${kubernetes-client-version} + + + org.jetbrains.kotlin + kotlin-stdlib-common + + + + + org.jetbrains.kotlin + kotlin-stdlib + ${kotlin-stdlib-version} + + + \ No newline at end of file diff --git a/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/java/org/onap/so/cnfm/lcm/bpmn/flows/CamundaCustomConfiguration.java b/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/java/org/onap/so/cnfm/lcm/bpmn/flows/CamundaCustomConfiguration.java new file mode 100644 index 0000000..765d25b --- /dev/null +++ b/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/java/org/onap/so/cnfm/lcm/bpmn/flows/CamundaCustomConfiguration.java @@ -0,0 +1,49 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2023 Nordix Foundation. + * ================================================================================ + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ +package org.onap.so.cnfm.lcm.bpmn.flows; + +import static org.onap.so.cnfm.lcm.bpmn.flows.Constants.AS_WORKFLOW_ENGINE; +import static org.onap.so.cnfm.lcm.bpmn.flows.Constants.TENANT_ID; +import static org.slf4j.LoggerFactory.getLogger; +import org.camunda.bpm.engine.spring.SpringProcessEngineConfiguration; +import org.camunda.bpm.spring.boot.starter.configuration.Ordering; +import org.camunda.bpm.spring.boot.starter.configuration.impl.AbstractCamundaConfiguration; +import org.slf4j.Logger; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; + +/** + * @author Waqas Ikram (waqas.ikram@est.tech) + * + */ +@Component +@Order(Ordering.DEFAULT_ORDER + 1) +public class CamundaCustomConfiguration extends AbstractCamundaConfiguration { + private static final Logger logger = getLogger(CamundaCustomConfiguration.class); + + @Override + public void preInit(final SpringProcessEngineConfiguration processEngineConfiguration) { + logger.info("Setting DeploymentTenantId to {} and DeploymentName to {}", TENANT_ID, AS_WORKFLOW_ENGINE); + processEngineConfiguration.setDeploymentTenantId(TENANT_ID); + processEngineConfiguration.setDeploymentName(AS_WORKFLOW_ENGINE); + super.preInit(processEngineConfiguration); + } + +} diff --git a/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/java/org/onap/so/cnfm/lcm/bpmn/flows/CamundaDatabaseConfiguration.java b/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/java/org/onap/so/cnfm/lcm/bpmn/flows/CamundaDatabaseConfiguration.java new file mode 100644 index 0000000..cc8bb01 --- /dev/null +++ b/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/java/org/onap/so/cnfm/lcm/bpmn/flows/CamundaDatabaseConfiguration.java @@ -0,0 +1,89 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2023 Nordix Foundation. + * ================================================================================ + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ +package org.onap.so.cnfm.lcm.bpmn.flows; + + +import static org.slf4j.LoggerFactory.getLogger; +import javax.sql.DataSource; +import org.camunda.bpm.engine.spring.SpringProcessEngineConfiguration; +import org.camunda.bpm.spring.boot.starter.util.SpringBootProcessEnginePlugin; +import org.slf4j.Logger; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.jdbc.datasource.DataSourceTransactionManager; +import org.springframework.jmx.export.MBeanExporter; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.annotation.EnableTransactionManagement; +import com.zaxxer.hikari.HikariConfig; +import com.zaxxer.hikari.HikariDataSource; + +/** + * @author Waqas Ikram (waqas.ikram@est.tech) + * + */ +@Configuration +@EnableTransactionManagement +public class CamundaDatabaseConfiguration { + + private static final String CAMUNDA_TRANSACTION_MANAGER_BEAN_NAME = "camundaTransactionManager"; + + private static final String CAMUNDA_DATA_SOURCE_BEAN_NAME = "camundaBpmDataSource"; + + private static final Logger logger = getLogger(CamundaDatabaseConfiguration.class); + + @Bean + @ConfigurationProperties(prefix = "spring.datasource.hikari.camunda") + public HikariConfig camundaDbConfig() { + logger.debug("Creating Camunda HikariConfig bean ... "); + return new HikariConfig(); + } + + @Bean(name = CAMUNDA_DATA_SOURCE_BEAN_NAME) + public DataSource camundaDataSource(@Autowired(required = false) final MBeanExporter mBeanExporter) { + if (mBeanExporter != null) { + mBeanExporter.addExcludedBean(CAMUNDA_DATA_SOURCE_BEAN_NAME); + } + logger.debug("Creating Camunda HikariDataSource bean ... "); + final HikariConfig hikariConfig = this.camundaDbConfig(); + return new HikariDataSource(hikariConfig); + } + + @Bean(name = CAMUNDA_TRANSACTION_MANAGER_BEAN_NAME) + public PlatformTransactionManager camundaTransactionManager( + @Qualifier(CAMUNDA_DATA_SOURCE_BEAN_NAME) final DataSource dataSource) { + return new DataSourceTransactionManager(dataSource); + } + + @Bean + public SpringBootProcessEnginePlugin transactionManagerProcessEnginePlugin( + @Qualifier(CAMUNDA_TRANSACTION_MANAGER_BEAN_NAME) final PlatformTransactionManager camundaTransactionManager) { + return new SpringBootProcessEnginePlugin() { + @Override + public void preInit(final SpringProcessEngineConfiguration processEngineConfiguration) { + logger.info("Setting Camunda TransactionManager ..."); + processEngineConfiguration.setTransactionManager(camundaTransactionManager); + + } + }; + } +} diff --git a/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/java/org/onap/so/cnfm/lcm/bpmn/flows/CamundaVariableNameConstants.java b/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/java/org/onap/so/cnfm/lcm/bpmn/flows/CamundaVariableNameConstants.java new file mode 100644 index 0000000..47c5795 --- /dev/null +++ b/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/java/org/onap/so/cnfm/lcm/bpmn/flows/CamundaVariableNameConstants.java @@ -0,0 +1,53 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2023 Nordix Foundation. + * ================================================================================ + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ +package org.onap.so.cnfm.lcm.bpmn.flows; + +/** + * @author Waqas Ikram (waqas.ikram@est.tech) + * + */ +public class CamundaVariableNameConstants { + + public static final String AS_DEPLOYMENT_ITEM_INST_ID_PARAM_NAME = "asDeploymentItemInstId"; + public static final String KUBE_KINDS_RESULT_PARAM_NAME = "kubeKindsResult"; + public static final String KUBE_CONFIG_FILE_PATH_PARAM_NAME = "kubeConfigFilePath"; + public static final String KUBE_KINDS_PARAM_NAME = "kubeKinds"; + public static final String KIND_PARAM_NAME = "kind"; + public static final String RELEASE_NAME_PARAM_NAME = "releaseName"; + public static final String JOB_ID_PARAM_NAME = "jobId"; + public static final String JOB_BUSINESS_KEY_PARAM_NAME = "jobBusinessKey"; + public static final String CREATE_AS_REQUEST_PARAM_NAME = "createAsRequest"; + + public static final String AS_PACKAGE_MODEL_PARAM_NAME = "AsPackageModel"; + public static final String AS_WORKFLOW_PROCESSING_EXCEPTION_PARAM_NAME = "AsWorkflowProcessingException"; + public static final String CREATE_AS_RESPONSE_PARAM_NAME = "createAsResponse"; + + public static final String INSTANTIATE_AS_REQUEST_PARAM_NAME = "instantiateAsRequest"; + public static final String AS_INSTANCE_ID_PARAM_NAME = "AsInstanceId"; + public static final String OCC_ID_PARAM_NAME = "occId"; + + public static final String DEPLOYMENT_ITEM_INSTANTIATE_REQUESTS = "deploymentItemInstantiateRequests"; + public static final String DEPLOYMENT_ITEM_TERMINATE_REQUESTS = "deploymentItemTerminateRequests"; + + public static final String TERMINATE_AS_REQUEST_PARAM_NAME = "terminateAsRequest"; + + private CamundaVariableNameConstants() {} + +} diff --git a/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/java/org/onap/so/cnfm/lcm/bpmn/flows/Constants.java b/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/java/org/onap/so/cnfm/lcm/bpmn/flows/Constants.java new file mode 100644 index 0000000..45c36be --- /dev/null +++ b/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/java/org/onap/so/cnfm/lcm/bpmn/flows/Constants.java @@ -0,0 +1,45 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2023 Nordix Foundation. + * ================================================================================ + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ +package org.onap.so.cnfm.lcm.bpmn.flows; + +/** + * @author Waqas Ikram (waqas.ikram@est.tech) + * + */ +public class Constants { + + public static final String TENANT_ID = "as-workflow-engine-tenant"; + public static final String AS_WORKFLOW_ENGINE = "AS-WORKFLOW-ENGINE"; + public static final String CREATE_AS_WORKFLOW_NAME = "CreateAs"; + public static final String INSTANTIATE_AS_WORKFLOW_NAME = "InstantiateAs"; + public static final String TERMINATE_AS_WORKFLOW_NAME = "TerminateAs"; + public static final String DELETE_AS_WORKFLOW_NAME = "DeleteAs"; + + public static final String KIND_STATEFUL_SET = "StatefulSet"; + public static final String KIND_DAEMON_SET = "DaemonSet"; + public static final String KIND_REPLICA_SET = "ReplicaSet"; + public static final String KIND_DEPLOYMENT = "Deployment"; + public static final String KIND_SERVICE = "Service"; + public static final String KIND_POD = "Pod"; + public static final String KIND_JOB = "Job"; + + private Constants() {} + +} diff --git a/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/java/org/onap/so/cnfm/lcm/bpmn/flows/GsonProvider.java b/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/java/org/onap/so/cnfm/lcm/bpmn/flows/GsonProvider.java new file mode 100644 index 0000000..486c528 --- /dev/null +++ b/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/java/org/onap/so/cnfm/lcm/bpmn/flows/GsonProvider.java @@ -0,0 +1,45 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2023 Nordix Foundation. + * ================================================================================ + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ +package org.onap.so.cnfm.lcm.bpmn.flows; + +import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import org.onap.so.cnfm.lcm.bpmn.flows.utils.LocalDateTimeTypeAdapter; +import org.onap.so.cnfm.lcm.bpmn.flows.utils.OffsetDateTimeTypeAdapter; +import org.springframework.stereotype.Component; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +/** + * @author Waqas Ikram (waqas.ikram@est.tech) + * + */ +@Component +public class GsonProvider { + + private final OffsetDateTimeTypeAdapter offsetDateTimeTypeAdapter = new OffsetDateTimeTypeAdapter(); + + public Gson getGson() { + return new GsonBuilder().registerTypeAdapter(OffsetDateTime.class, offsetDateTimeTypeAdapter) + .registerTypeAdapter(LocalDateTime.class, new LocalDateTimeTypeAdapter()).create(); + } + + +} diff --git a/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/java/org/onap/so/cnfm/lcm/bpmn/flows/exceptions/AsRequestProcessingException.java b/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/java/org/onap/so/cnfm/lcm/bpmn/flows/exceptions/AsRequestProcessingException.java new file mode 100644 index 0000000..991c3c3 --- /dev/null +++ b/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/java/org/onap/so/cnfm/lcm/bpmn/flows/exceptions/AsRequestProcessingException.java @@ -0,0 +1,55 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2023 Nordix Foundation. + * ================================================================================ + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ +package org.onap.so.cnfm.lcm.bpmn.flows.exceptions; + +import org.onap.so.cnfm.lcm.model.ErrorDetails; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +/** + * + * @author Waqas Ikram (waqas.ikram@est.tech) + */ +@ResponseStatus(code = HttpStatus.INTERNAL_SERVER_ERROR) +public class AsRequestProcessingException extends RuntimeException { + + private static final long serialVersionUID = 66862444537194516L; + private final ErrorDetails errorDetails; + + public AsRequestProcessingException(final String message) { + super(message); + errorDetails = null; + } + + public AsRequestProcessingException(final String message, final ErrorDetails errorContents) { + super(message); + this.errorDetails = errorContents; + } + + @Override + public synchronized Throwable fillInStackTrace() { + return this; + } + + public ErrorDetails getErrorDetails() { + return errorDetails; + } + +} diff --git a/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/java/org/onap/so/cnfm/lcm/bpmn/flows/exceptions/BasicAuthConfigException.java b/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/java/org/onap/so/cnfm/lcm/bpmn/flows/exceptions/BasicAuthConfigException.java new file mode 100644 index 0000000..4b9bfb1 --- /dev/null +++ b/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/java/org/onap/so/cnfm/lcm/bpmn/flows/exceptions/BasicAuthConfigException.java @@ -0,0 +1,37 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2023 Nordix Foundation. + * ================================================================================ + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ +package org.onap.so.cnfm.lcm.bpmn.flows.exceptions; + +/** + * + * @author Waqas Ikram (waqas.ikram@est.tech) + */ +public class BasicAuthConfigException extends RuntimeException { + + private static final long serialVersionUID = -6317913344782441364L; + + public BasicAuthConfigException(final String message) { + super(message); + } + + public BasicAuthConfigException(final String message, final Throwable cause) { + super(message, cause); + } +} diff --git a/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/java/org/onap/so/cnfm/lcm/bpmn/flows/exceptions/FileNotFoundInCsarException.java b/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/java/org/onap/so/cnfm/lcm/bpmn/flows/exceptions/FileNotFoundInCsarException.java new file mode 100644 index 0000000..4bad50c --- /dev/null +++ b/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/java/org/onap/so/cnfm/lcm/bpmn/flows/exceptions/FileNotFoundInCsarException.java @@ -0,0 +1,44 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2023 Nordix Foundation. + * ================================================================================ + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ +package org.onap.so.cnfm.lcm.bpmn.flows.exceptions; + +/** + * + * @author Waqas Ikram (waqas.ikram@est.tech) + * + */ +public class FileNotFoundInCsarException extends RuntimeException { + + private static final long serialVersionUID = -3294117029811603499L; + + public FileNotFoundInCsarException(final String message) { + super(message); + } + + public FileNotFoundInCsarException(final String message, final Throwable cause) { + super(message); + } + + @Override + public synchronized Throwable fillInStackTrace() { + return this; + } + +} diff --git a/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/java/org/onap/so/cnfm/lcm/bpmn/flows/exceptions/PropertyNotFoundException.java b/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/java/org/onap/so/cnfm/lcm/bpmn/flows/exceptions/PropertyNotFoundException.java new file mode 100644 index 0000000..908a04d --- /dev/null +++ b/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/java/org/onap/so/cnfm/lcm/bpmn/flows/exceptions/PropertyNotFoundException.java @@ -0,0 +1,44 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2023 Nordix Foundation. + * ================================================================================ + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ +package org.onap.so.cnfm.lcm.bpmn.flows.exceptions; + +/** + * + * @author Waqas Ikram (waqas.ikram@est.tech) + * + */ +public class PropertyNotFoundException extends RuntimeException { + + private static final long serialVersionUID = 684205374040870233L; + + public PropertyNotFoundException(final String message) { + super(message); + } + + public PropertyNotFoundException(final String message, final Throwable cause) { + super(message); + } + + @Override + public synchronized Throwable fillInStackTrace() { + return this; + } + +} diff --git a/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/java/org/onap/so/cnfm/lcm/bpmn/flows/exceptions/SdcPackageRequestFailureException.java b/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/java/org/onap/so/cnfm/lcm/bpmn/flows/exceptions/SdcPackageRequestFailureException.java new file mode 100644 index 0000000..d33c51f --- /dev/null +++ b/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/java/org/onap/so/cnfm/lcm/bpmn/flows/exceptions/SdcPackageRequestFailureException.java @@ -0,0 +1,42 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2023 Nordix Foundation. + * ================================================================================ + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ +package org.onap.so.cnfm.lcm.bpmn.flows.exceptions; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +/** + * + * @author Waqas Ikram (waqas.ikram@est.tech) + * + */ +@ResponseStatus(code = HttpStatus.INTERNAL_SERVER_ERROR) +public class SdcPackageRequestFailureException extends RuntimeException { + + private static final long serialVersionUID = 5816306976965772635L; + + public SdcPackageRequestFailureException(final String message) { + super(message); + } + + public SdcPackageRequestFailureException(final String message, final Throwable cause) { + super(message, cause); + } +} diff --git a/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/java/org/onap/so/cnfm/lcm/bpmn/flows/extclients/aai/AaiClientProvider.java b/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/java/org/onap/so/cnfm/lcm/bpmn/flows/extclients/aai/AaiClientProvider.java new file mode 100644 index 0000000..aceafe7 --- /dev/null +++ b/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/java/org/onap/so/cnfm/lcm/bpmn/flows/extclients/aai/AaiClientProvider.java @@ -0,0 +1,34 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2023 Nordix Foundation. + * ================================================================================ + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.so.cnfm.lcm.bpmn.flows.extclients.aai; + +import org.onap.aaiclient.client.aai.AAIResourcesClient; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class AaiClientProvider { + + @Bean + public AAIResourcesClient getAaiClient() { + return new AAIResourcesClient(); + } +} diff --git a/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/java/org/onap/so/cnfm/lcm/bpmn/flows/extclients/aai/AaiPropertiesImpl.java b/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/java/org/onap/so/cnfm/lcm/bpmn/flows/extclients/aai/AaiPropertiesImpl.java new file mode 100644 index 0000000..b0986d8 --- /dev/null +++ b/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/java/org/onap/so/cnfm/lcm/bpmn/flows/extclients/aai/AaiPropertiesImpl.java @@ -0,0 +1,108 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2023 Nordix Foundation. + * ================================================================================ + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.so.cnfm.lcm.bpmn.flows.extclients.aai; + +import org.onap.aaiclient.client.aai.AAIProperties; +import org.onap.aaiclient.client.aai.AAIVersion; +import org.onap.so.client.CacheProperties; +import org.onap.so.spring.SpringContextHelper; +import org.springframework.context.ApplicationContext; +import java.net.MalformedURLException; +import java.net.URL; + +public class AaiPropertiesImpl implements AAIProperties { + + private final String endpoint; + private final String encryptedBasicAuth; + private final String encryptionKey; + private final String aaiVersion; + private final Long readTimeout; + private final Long connectionTimeout; + private final boolean enableCaching; + private final Long cacheMaxAge; + + public AaiPropertiesImpl() { + final ApplicationContext context = SpringContextHelper.getAppContext(); + this.endpoint = context.getEnvironment().getProperty("aai.endpoint"); + this.encryptedBasicAuth = context.getEnvironment().getProperty("aai.auth"); + this.encryptionKey = context.getEnvironment().getProperty("mso.key"); + this.aaiVersion = context.getEnvironment().getProperty("aai.version"); + this.readTimeout = context.getEnvironment().getProperty("aai.readTimeout", Long.class, 60000L); + this.connectionTimeout = context.getEnvironment().getProperty("aai.connectionTimeout", Long.class, 60000L); + this.enableCaching = context.getEnvironment().getProperty("aai.caching.enabled", Boolean.class, false); + this.cacheMaxAge = context.getEnvironment().getProperty("aai.caching.maxAge", Long.class, 60000L); + } + + @Override + public URL getEndpoint() throws MalformedURLException { + return new URL(endpoint); + } + + @Override + public String getSystemName() { + return "MSO"; + } + + @Override + public AAIVersion getDefaultVersion() { + for (final AAIVersion version : AAIVersion.values()) { + if (version.toString().equalsIgnoreCase(this.aaiVersion)) { + return version; + } + } + return AAIVersion.LATEST; + } + + @Override + public String getAuth() { + return encryptedBasicAuth; + } + + @Override + public String getKey() { + return encryptionKey; + } + + @Override + public Long getReadTimeout() { + return this.readTimeout; + } + + @Override + public Long getConnectionTimeout() { + return this.connectionTimeout; + } + + @Override + public boolean isCachingEnabled() { + return this.enableCaching; + } + + @Override + public CacheProperties getCacheProperties() { + return new AAICacheProperties() { + @Override + public Long getMaxAge() { + return cacheMaxAge; + } + }; + } +} diff --git a/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/java/org/onap/so/cnfm/lcm/bpmn/flows/extclients/aai/AaiServiceProvider.java b/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/java/org/onap/so/cnfm/lcm/bpmn/flows/extclients/aai/AaiServiceProvider.java new file mode 100644 index 0000000..fae677d --- /dev/null +++ b/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/java/org/onap/so/cnfm/lcm/bpmn/flows/extclients/aai/AaiServiceProvider.java @@ -0,0 +1,68 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2023 Nordix Foundation. + * ================================================================================ + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.so.cnfm.lcm.bpmn.flows.extclients.aai; + +import java.util.List; +import java.util.Optional; +import org.onap.aai.domain.yang.GenericVnf; +import org.onap.aai.domain.yang.K8SResource; +import org.onap.aai.domain.yang.VfModule; +import org.onap.so.beans.nsmf.OrchestrationStatusEnum; +import org.onap.so.cnfm.lcm.bpmn.flows.extclients.kubernetes.KubernetesResource; + +/** + * @author Waqas Ikram (waqas.ikram@est.tech) + * + */ +public interface AaiServiceProvider { + + void createGenericVnfAndConnectServiceInstance(final String serviceInstanceId, final String vnfId, + final GenericVnf genericVnf); + + void connectGenericVnfToTenant(final String vnfId, final String cloudOwner, final String cloudRegion, + final String tenantId); + + Optional getGenericVnf(final String vnfId); + + void deleteGenericVnf(final String vnfId); + + void updateGenericVnf(final String vnfId, final GenericVnf vnf); + + void createVfModule(final String vnfId, final String vfModuleId, final VfModule vfModule); + + void createK8sResource(final String k8sResourceId, final String cloudOwner, final String cloudRegion, + final String tenantId, K8SResource k8sResource); + + void connectK8sResourceToVfModule(final String k8sResourceId, final String cloudOwner, final String cloudRegion, + final String tenantId, final String vnfId, final String vfModuleId); + + void connectK8sResourceToGenericVnf(final String k8sResourceId, final String cloudOwner, final String cloudRegion, + final String tenantId, final String vnfId); + + List getK8sResources(final String vnfId, final String vfModuleId); + + void deleteK8SResource(final String k8sResourceId, final String cloudOwner, final String cloudRegion, + final String tenantId); + + void deleteVfModule(final String vnfId, final String vfModuleId); + + boolean updateGenericVnfStatus(final String vnfId, final OrchestrationStatusEnum status); +} diff --git a/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/java/org/onap/so/cnfm/lcm/bpmn/flows/extclients/aai/AaiServiceProviderImpl.java b/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/java/org/onap/so/cnfm/lcm/bpmn/flows/extclients/aai/AaiServiceProviderImpl.java new file mode 100644 index 0000000..4f5ec8d --- /dev/null +++ b/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/java/org/onap/so/cnfm/lcm/bpmn/flows/extclients/aai/AaiServiceProviderImpl.java @@ -0,0 +1,211 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2023 Nordix Foundation. + * ================================================================================ + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.so.cnfm.lcm.bpmn.flows.extclients.aai; + +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; +import org.onap.aai.domain.yang.GenericVnf; +import org.onap.aai.domain.yang.K8SResource; +import org.onap.aai.domain.yang.VfModule; +import org.onap.aaiclient.client.aai.entities.AAIResultWrapper; +import org.onap.aaiclient.client.aai.entities.uri.AAIResourceUri; +import org.onap.aaiclient.client.aai.entities.uri.AAIUriFactory; +import org.onap.aaiclient.client.generated.fluentbuilders.AAIFluentTypeBuilder; +import org.onap.aaiclient.client.generated.fluentbuilders.AAIFluentTypeBuilder.Types; +import org.onap.so.beans.nsmf.OrchestrationStatusEnum; +import org.onap.so.cnfm.lcm.bpmn.flows.GsonProvider; +import org.onap.so.cnfm.lcm.bpmn.flows.extclients.kubernetes.KubernetesResource; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import com.google.gson.Gson; + +/** + * @author Waqas Ikram (waqas.ikram@est.tech) + */ +@Service +public class AaiServiceProviderImpl implements AaiServiceProvider { + private static final Logger logger = LoggerFactory.getLogger(AaiServiceProviderImpl.class); + private final AaiClientProvider aaiClientProvider; + private final Gson gson; + + @Autowired + public AaiServiceProviderImpl(final AaiClientProvider aaiClientProvider, final GsonProvider gsonProvider) { + this.aaiClientProvider = aaiClientProvider; + this.gson = gsonProvider.getGson(); + } + + @Override + public void createGenericVnfAndConnectServiceInstance(final String serviceInstanceId, final String vnfId, + final GenericVnf genericVnf) { + logger.info("Creating GenericVnf in AAI: {}", genericVnf); + final AAIResourceUri genericVnfURI = + AAIUriFactory.createResourceUri(AAIFluentTypeBuilder.network().genericVnf(vnfId)); + final AAIResourceUri serviceInstanceURI = + AAIUriFactory.createResourceUri(Types.SERVICE_INSTANCE.getFragment(serviceInstanceId)); + aaiClientProvider.getAaiClient().createIfNotExists(genericVnfURI, Optional.of(genericVnf)) + .connect(genericVnfURI, serviceInstanceURI); + + } + + @Override + public void connectGenericVnfToTenant(final String vnfId, final String cloudOwner, final String cloudRegion, + final String tenantId) { + logger.info("Connecting GenericVnf {} to {}/{}/{} in AAI", vnfId, cloudOwner, cloudRegion, tenantId); + final AAIResourceUri tenantURI = AAIUriFactory.createResourceUri( + AAIFluentTypeBuilder.cloudInfrastructure().cloudRegion(cloudOwner, cloudRegion).tenant(tenantId)); + final AAIResourceUri genericVnfURI = + AAIUriFactory.createResourceUri(AAIFluentTypeBuilder.network().genericVnf(vnfId)); + aaiClientProvider.getAaiClient().connect(tenantURI, genericVnfURI); + } + + @Override + public Optional getGenericVnf(final String vnfId) { + return aaiClientProvider.getAaiClient().get(GenericVnf.class, + AAIUriFactory.createResourceUri(AAIFluentTypeBuilder.network().genericVnf(vnfId))); + } + + @Override + public void deleteGenericVnf(final String vnfId) { + logger.info("Deleting GenericVnf with id: {} from AAI.", vnfId); + final AAIResourceUri aaiResourceUri = + AAIUriFactory.createResourceUri(AAIFluentTypeBuilder.network().genericVnf(vnfId)); + aaiClientProvider.getAaiClient().delete(aaiResourceUri); + } + + @Override + public void updateGenericVnf(final String vnfId, final GenericVnf vnf) { + logger.info("Updating GenericVnf of id: {} in AAI.", vnfId); + final AAIResourceUri aaiResourceUri = + AAIUriFactory.createResourceUri(AAIFluentTypeBuilder.network().genericVnf(vnfId)); + aaiClientProvider.getAaiClient().update(aaiResourceUri, vnf); + } + + @Override + public void createVfModule(final String vnfId, final String vfModuleId, final VfModule vfModule) { + logger.info("Creating VfModule in AAI: {}", vfModule); + + final AAIResourceUri vfModuleURI = + AAIUriFactory.createResourceUri(AAIFluentTypeBuilder.network().genericVnf(vnfId).vfModule(vfModuleId)); + aaiClientProvider.getAaiClient().createIfNotExists(vfModuleURI, Optional.of(vfModule)); + + } + + @Override + public void createK8sResource(final String k8sResourceId, final String cloudOwner, final String cloudRegion, + final String tenantId, final K8SResource k8sResource) { + logger.info("Creating K8SResource in AAI: {} using {}/{}/{}/{}", k8sResource, k8sResourceId, cloudOwner, + cloudRegion, tenantId); + + final AAIResourceUri k8sResourceURI = AAIUriFactory.createResourceUri(AAIFluentTypeBuilder.cloudInfrastructure() + .cloudRegion(cloudOwner, cloudRegion).tenant(tenantId).k8sResource(k8sResourceId)); + + final String payload = gson.toJson(k8sResource); + logger.debug("Creating K8sResource in A&AI: {}", payload); + + aaiClientProvider.getAaiClient().createIfNotExists(k8sResourceURI, Optional.of(payload)); + + } + + @Override + public void connectK8sResourceToVfModule(final String k8sResourceId, final String cloudOwner, + final String cloudRegion, final String tenantId, final String vnfId, final String vfModuleId) { + logger.info("Connecting K8sResource {}/{}/{}/{} to VF Moudle {}/{} in AAI", cloudOwner, cloudRegion, tenantId, + k8sResourceId, vnfId, vfModuleId); + + final AAIResourceUri k8sResourceURI = AAIUriFactory.createResourceUri(AAIFluentTypeBuilder.cloudInfrastructure() + .cloudRegion(cloudOwner, cloudRegion).tenant(tenantId).k8sResource(k8sResourceId)); + + final AAIResourceUri vfModuleURI = + AAIUriFactory.createResourceUri(AAIFluentTypeBuilder.network().genericVnf(vnfId).vfModule(vfModuleId)); + aaiClientProvider.getAaiClient().connect(k8sResourceURI, vfModuleURI); + + } + + @Override + public void connectK8sResourceToGenericVnf(final String k8sResourceId, final String cloudOwner, + final String cloudRegion, final String tenantId, final String vnfId) { + logger.info("Connecting K8sResource {}/{}/{}/{} to Generic Vnf {} in AAI", cloudOwner, cloudRegion, tenantId, + k8sResourceId, vnfId); + final AAIResourceUri k8sResourceURI = AAIUriFactory.createResourceUri(AAIFluentTypeBuilder.cloudInfrastructure() + .cloudRegion(cloudOwner, cloudRegion).tenant(tenantId).k8sResource(k8sResourceId)); + final AAIResourceUri genericVnfURI = + AAIUriFactory.createResourceUri(AAIFluentTypeBuilder.network().genericVnf(vnfId)); + aaiClientProvider.getAaiClient().connect(k8sResourceURI, genericVnfURI); + + } + + @Override + public List getK8sResources(final String vnfId, final String vfModuleId) { + logger.info("Getting K8S resources related to VfModule: {} from AAI", vfModuleId); + final AAIResourceUri vfModuleURI = + AAIUriFactory.createResourceUri(AAIFluentTypeBuilder.network().genericVnf(vnfId).vfModule(vfModuleId)); + final AAIResultWrapper vfModuleInstance = aaiClientProvider.getAaiClient().get(vfModuleURI); + if (vfModuleInstance.hasRelationshipsTo(Types.K8S_RESOURCE) + && vfModuleInstance.getRelationships().isPresent()) { + logger.debug("VfModule has relations of type K8SResource"); + return vfModuleInstance.getRelationships().get().getByType(Types.K8S_RESOURCE).stream() + .map(relation -> relation.asBean(KubernetesResource.class)).filter(Optional::isPresent) + .map(Optional::get).collect(Collectors.toList()); + } + logger.info("No K8S resources found for VfModule :{}", vfModuleId); + return Collections.emptyList(); + } + + @Override + public void deleteK8SResource(final String k8sResourceId, final String cloudOwner, final String cloudRegion, + final String tenantId) { + logger.info("Deleting K8sResource {} from AAI", k8sResourceId); + + final AAIResourceUri k8sResourceURI = AAIUriFactory.createResourceUri(AAIFluentTypeBuilder.cloudInfrastructure() + .cloudRegion(cloudOwner, cloudRegion).tenant(tenantId).k8sResource(k8sResourceId)); + aaiClientProvider.getAaiClient().deleteIfExists(k8sResourceURI); + + logger.info("K8S resource removed from AAI using URI: {}", k8sResourceURI); + } + + @Override + public void deleteVfModule(final String vnfId, final String vfModuleId) { + logger.info("Deleting VfModule {} from AAI", vfModuleId); + + final AAIResourceUri vfModuleURI = + AAIUriFactory.createResourceUri(AAIFluentTypeBuilder.network().genericVnf(vnfId).vfModule(vfModuleId)); + aaiClientProvider.getAaiClient().deleteIfExists(vfModuleURI); + logger.info("VfModule deleted from AAI using URI: {}", vfModuleId); + } + + @Override + public boolean updateGenericVnfStatus(final String vnfId, final OrchestrationStatusEnum status) { + logger.info("Updating GenericVnf status to deactivated for vnfID: {}", vnfId); + final Optional optionalVnf = getGenericVnf(vnfId); + if (optionalVnf.isPresent()) { + final GenericVnf vnf = optionalVnf.get(); + vnf.setOrchestrationStatus(status.getValue()); + updateGenericVnf(vnfId, vnf); + return true; + } + return false; + } + +} diff --git a/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/java/org/onap/so/cnfm/lcm/bpmn/flows/extclients/kubernetes/KubernetesResource.java b/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/java/org/onap/so/cnfm/lcm/bpmn/flows/extclients/kubernetes/KubernetesResource.java new file mode 100644 index 0000000..7ec50f0 --- /dev/null +++ b/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/java/org/onap/so/cnfm/lcm/bpmn/flows/extclients/kubernetes/KubernetesResource.java @@ -0,0 +1,209 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2023 Nordix Foundation. + * ================================================================================ + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ +package org.onap.so.cnfm.lcm.bpmn.flows.extclients.kubernetes; + +import static org.onap.so.cnfm.lcm.database.beans.utils.Utils.toIndentedString; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * + * @author Waqas Ikram (waqas.ikram@est.tech) + * + */ +public class KubernetesResource implements Serializable { + + private static final long serialVersionUID = -4342437130757578686L; + + private String id; + private String name; + private String group; + private String version; + private String kind; + private String namespace; + private String selflink; + private String resourceVersion; + private List labels; + + public String getId() { + return id; + } + + public void setId(final String id) { + this.id = id; + } + + public KubernetesResource id(final String id) { + this.id = id; + return this; + } + + public String getName() { + return name; + } + + public void setName(final String name) { + this.name = name; + } + + public KubernetesResource name(final String name) { + this.name = name; + return this; + } + + public String getGroup() { + return group; + } + + public void setGroup(final String group) { + this.group = group; + } + + public KubernetesResource group(final String group) { + this.group = group; + return this; + } + + public String getVersion() { + return version; + } + + public void setVersion(final String version) { + this.version = version; + } + + public KubernetesResource version(final String version) { + this.version = version; + return this; + } + + public String getKind() { + return kind; + } + + public void setKind(final String kind) { + this.kind = kind; + } + + public KubernetesResource kind(final String kind) { + this.kind = kind; + return this; + } + + public String getNamespace() { + return namespace; + } + + public void setNamespace(final String namespace) { + this.namespace = namespace; + } + + public KubernetesResource namespace(final String namespace) { + this.namespace = namespace; + return this; + } + + public String getSelflink() { + return selflink; + } + + public void setSelflink(final String selflink) { + this.selflink = selflink; + } + + public KubernetesResource selflink(final String selflink) { + this.selflink = selflink; + return this; + } + + public String getResourceVersion() { + return resourceVersion; + } + + public void setResourceVersion(final String resourceVersion) { + this.resourceVersion = resourceVersion; + } + + public KubernetesResource resourceVersion(final String resourceVersion) { + this.resourceVersion = resourceVersion; + return this; + } + + public List getLabels() { + return labels; + } + + public void setLabels(final List labels) { + this.labels = labels; + } + + public KubernetesResource labels(final List labels) { + this.labels = labels; + return this; + } + + public KubernetesResource label(final String label) { + if (this.labels == null) { + this.labels = new ArrayList<>(); + } + + this.labels.add(label); + return this; + } + + @Override + public int hashCode() { + return Objects.hash(id, name, group, version, kind, namespace, selflink, resourceVersion, labels); + } + + @Override + public boolean equals(final Object obj) { + if (obj instanceof KubernetesResource) { + final KubernetesResource other = (KubernetesResource) obj; + return Objects.equals(id, other.id) && Objects.equals(name, other.name) + && Objects.equals(group, other.group) && Objects.equals(version, other.version) + && Objects.equals(kind, other.kind) && Objects.equals(namespace, other.namespace) + && Objects.equals(selflink, other.selflink) + && Objects.equals(resourceVersion, other.resourceVersion) && Objects.equals(labels, other.labels); + } + return false; + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder(); + sb.append("class KubernetesResource {\n"); + sb.append(" id: ").append(toIndentedString(id)).append("\n"); + sb.append(" name: ").append(toIndentedString(name)).append("\n"); + sb.append(" group: ").append(toIndentedString(group)).append("\n"); + sb.append(" version: ").append(toIndentedString(version)).append("\n"); + sb.append(" kind: ").append(toIndentedString(kind)).append("\n"); + sb.append(" namespace: ").append(toIndentedString(namespace)).append("\n"); + sb.append(" selflink: ").append(toIndentedString(selflink)).append("\n"); + sb.append(" resourceVersion: ").append(toIndentedString(resourceVersion)).append("\n"); + sb.append(" labels: ").append(toIndentedString(labels)).append("\n"); + + sb.append("}"); + return sb.toString(); + } + +} diff --git a/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/java/org/onap/so/cnfm/lcm/bpmn/flows/extclients/sdc/DeploymentItem.java b/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/java/org/onap/so/cnfm/lcm/bpmn/flows/extclients/sdc/DeploymentItem.java new file mode 100644 index 0000000..1062cb7 --- /dev/null +++ b/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/java/org/onap/so/cnfm/lcm/bpmn/flows/extclients/sdc/DeploymentItem.java @@ -0,0 +1,143 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2023 Nordix Foundation. + * ================================================================================ + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ +package org.onap.so.cnfm.lcm.bpmn.flows.extclients.sdc; + +import static org.onap.so.cnfm.lcm.database.beans.utils.Utils.toIndentedString; +import java.io.Serializable; +import java.util.List; +import java.util.Objects; + +/** + * + * @author Waqas Ikram (waqas.ikram@est.tech) + * + */ +public class DeploymentItem implements Serializable { + + private static final long serialVersionUID = -1974244669409099225L; + private String name; + private String file; + private String itemId; + private String deploymentOrder; + + private List lifecycleParameters; + + + public String getName() { + return name; + } + + public void setName(final String name) { + this.name = name; + } + + public DeploymentItem name(final String name) { + this.name = name; + return this; + } + + + public String getFile() { + return file; + } + + public void setFile(final String file) { + this.file = file; + } + + public DeploymentItem file(final String file) { + this.file = file; + return this; + } + + + public String getItemId() { + return itemId; + } + + public void setItemId(final String itemId) { + this.itemId = itemId; + } + + public DeploymentItem itemId(final String itemId) { + this.itemId = itemId; + return this; + } + + public String getDeploymentOrder() { + return deploymentOrder; + } + + public void setDeploymentOrder(String deploymentOrder) { + this.deploymentOrder = deploymentOrder; + } + + public DeploymentItem deploymentOrder(final String deploymentOrder) { + this.deploymentOrder = deploymentOrder; + return this; + } + + + public List getLifecycleParameters() { + return lifecycleParameters; + } + + public void setLifecycleParameters(final List lifecycleParameters) { + this.lifecycleParameters = lifecycleParameters; + } + + public DeploymentItem lifecycleParameters(final List lifecycleParameters) { + this.lifecycleParameters = lifecycleParameters; + return this; + } + + @Override + public int hashCode() { + return Objects.hash(name, file, itemId, deploymentOrder, lifecycleParameters); + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) + return true; + if (obj == null || getClass() != obj.getClass()) + return false; + if (obj instanceof DeploymentItem) { + final DeploymentItem other = (DeploymentItem) obj; + return Objects.equals(name, other.name) && Objects.equals(file, other.file) + && Objects.equals(itemId, other.itemId) && Objects.equals(deploymentOrder, other.deploymentOrder) + && Objects.equals(lifecycleParameters, other.lifecycleParameters); + } + return false; + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder(); + sb.append("class DeploymentItem {\n"); + sb.append(" name: ").append(toIndentedString(name)).append("\n"); + sb.append(" file: ").append(toIndentedString(file)).append("\n"); + sb.append(" itemId: ").append(toIndentedString(itemId)).append("\n"); + sb.append(" deploymentOrder: ").append(toIndentedString(deploymentOrder)).append("\n"); + sb.append(" lifecycleParameters: ").append(toIndentedString(lifecycleParameters)).append("\n"); + sb.append("}"); + return sb.toString(); + } +} diff --git a/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/java/org/onap/so/cnfm/lcm/bpmn/flows/extclients/sdc/SdcClientConfigurationProvider.java b/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/java/org/onap/so/cnfm/lcm/bpmn/flows/extclients/sdc/SdcClientConfigurationProvider.java new file mode 100644 index 0000000..4ae1d43 --- /dev/null +++ b/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/java/org/onap/so/cnfm/lcm/bpmn/flows/extclients/sdc/SdcClientConfigurationProvider.java @@ -0,0 +1,79 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2023 Nordix Foundation. + * ================================================================================ + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ +package org.onap.so.cnfm.lcm.bpmn.flows.extclients.sdc; + +import java.nio.charset.StandardCharsets; +import java.security.GeneralSecurityException; +import org.apache.commons.codec.binary.Base64; +import org.onap.so.cnfm.lcm.bpmn.flows.exceptions.BasicAuthConfigException; +import org.onap.so.utils.CryptoUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; + +/** + * @author Waqas Ikram (waqas.ikram@est.tech) + * + */ +@Configuration +public class SdcClientConfigurationProvider { + + private static final Logger logger = LoggerFactory.getLogger(SdcClientConfigurationProvider.class); + + @Value("${sdc.username:mso}") + private String sdcUsername; + + @Value("${sdc.password:76966BDD3C7414A03F7037264FF2E6C8EEC6C28F2B67F2840A1ED857C0260FEE731D73F47F828E5527125D29FD25D3E0DE39EE44C058906BF1657DE77BF897EECA93BDC07FA64F}") + private String sdcPassword; + + @Value("${sdc.key:566B754875657232314F5548556D3665}") + private String sdcKey; + + @Value("${sdc.endpoint:https://sdc-be.onap:8443}") + private String baseUrl; + + private static String basicAuth = null; + + + public String getBasicAuth() { + if (basicAuth == null) { + synchronized (this) { + if (basicAuth == null) { + try { + final String auth = sdcUsername + ":" + CryptoUtils.decrypt(sdcPassword, sdcKey); + final byte[] encodedAuth = Base64.encodeBase64(auth.getBytes(StandardCharsets.ISO_8859_1)); + basicAuth = "Basic " + new String(encodedAuth); + } catch (final GeneralSecurityException exception) { + logger.error("Unable to process basic auth information", exception); + throw new BasicAuthConfigException("Unable to process basic auth information", exception); + } + } + } + } + return basicAuth; + } + + public String getSdcPackageUrl(final String packageId) { + return baseUrl + "/sdc/v1/catalog/resources/" + packageId + "/toscaModel"; + + } + +} diff --git a/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/java/org/onap/so/cnfm/lcm/bpmn/flows/extclients/sdc/SdcCsarPackageParser.java b/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/java/org/onap/so/cnfm/lcm/bpmn/flows/extclients/sdc/SdcCsarPackageParser.java new file mode 100644 index 0000000..0ade7bf --- /dev/null +++ b/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/java/org/onap/so/cnfm/lcm/bpmn/flows/extclients/sdc/SdcCsarPackageParser.java @@ -0,0 +1,201 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2023 Nordix Foundation. + * ================================================================================ + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ +package org.onap.so.cnfm.lcm.bpmn.flows.extclients.sdc; + +import static com.google.common.base.Splitter.on; +import static com.google.common.collect.Iterables.filter; +import static org.onap.so.cnfm.lcm.bpmn.flows.extclients.sdc.SdcCsarPropertiesConstants.APPLICATION_NAME_PARAM_NAME; +import static org.onap.so.cnfm.lcm.bpmn.flows.extclients.sdc.SdcCsarPropertiesConstants.APPLICATION_VERSION_PARAM_NAME; +import static org.onap.so.cnfm.lcm.bpmn.flows.extclients.sdc.SdcCsarPropertiesConstants.DEPLOYMENT_ITEMS_PARAM_NAME; +import static org.onap.so.cnfm.lcm.bpmn.flows.extclients.sdc.SdcCsarPropertiesConstants.DESCRIPTOR_ID_PARAM_NAME; +import static org.onap.so.cnfm.lcm.bpmn.flows.extclients.sdc.SdcCsarPropertiesConstants.DESCRIPTOR_INVARIANT_ID_PARAM_NAME; +import static org.onap.so.cnfm.lcm.bpmn.flows.extclients.sdc.SdcCsarPropertiesConstants.PROVIDER_PARAM_NAME; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Set; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + +import com.google.gson.JsonArray; +import org.onap.so.cnfm.lcm.bpmn.flows.exceptions.FileNotFoundInCsarException; +import org.onap.so.cnfm.lcm.bpmn.flows.exceptions.PropertyNotFoundException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; +import org.yaml.snakeyaml.Yaml; +import com.google.common.io.ByteStreams; +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; + +/** + * + * @author Waqas Ikram (waqas.ikram@est.tech) + * + */ +@Service +public class SdcCsarPackageParser { + private static final String TOCSA_METADATA_FILE_PATH = "TOSCA-Metadata/TOSCA.meta"; + private static final String ENTRY_DEFINITIONS_ENTRY = "Entry-Definitions"; + + private static final Logger logger = LoggerFactory.getLogger(SdcCsarPackageParser.class); + + public Map getAsdProperties(final byte[] onapPackage) { + + try (final ByteArrayInputStream stream = new ByteArrayInputStream(onapPackage); + final ZipInputStream zipInputStream = new ZipInputStream(stream);) { + final String asdLocation = getAsdLocation(zipInputStream); + final String onapAsdContent = getFileInZip(zipInputStream, asdLocation).toString(); + logger.debug("ASD CONTENTS: {}", onapAsdContent); + final JsonObject root = new Gson().toJsonTree(new Yaml().load(onapAsdContent)).getAsJsonObject(); + + final JsonObject topologyTemplates = child(root, "topology_template"); + final JsonObject nodeTemplates = child(topologyTemplates, "node_templates"); + for (final JsonObject child : children(nodeTemplates)) { + final String type = childElement(child, "type").getAsString(); + if ("tosca.nodes.asd".equals(type)) { + final JsonObject properties = child(child, "properties"); + logger.debug("properties: {}", properties.toString()); + final Map propertiesValues = new HashMap<>(); + propertiesValues.put(DESCRIPTOR_ID_PARAM_NAME, + getStringValue(properties, DESCRIPTOR_ID_PARAM_NAME)); + propertiesValues.put(DESCRIPTOR_INVARIANT_ID_PARAM_NAME, + getStringValue(properties, DESCRIPTOR_INVARIANT_ID_PARAM_NAME)); + propertiesValues.put(PROVIDER_PARAM_NAME, getStringValue(properties, PROVIDER_PARAM_NAME)); + propertiesValues.put(APPLICATION_NAME_PARAM_NAME, + getStringValue(properties, APPLICATION_NAME_PARAM_NAME)); + propertiesValues.put(APPLICATION_VERSION_PARAM_NAME, + getStringValue(properties, APPLICATION_VERSION_PARAM_NAME)); + propertiesValues.put(DEPLOYMENT_ITEMS_PARAM_NAME, getDeploymentItems(child)); + + return propertiesValues; + + } + } + + + } catch (final Exception exception) { + throw new IllegalArgumentException("Unable to parser CSAR package", exception); + } + return Collections.emptyMap(); + } + + private String getStringValue(final JsonObject properties, final String key) { + final JsonElement element = properties.get(key); + if (element != null && element.isJsonPrimitive()) { + return element.getAsString(); + } + logger.warn("'{}' value is not Primitive or null val:{}", key, element != null ? element.toString() : null); + return null; + } + + private List getDeploymentItems(final JsonObject child) { + final List items = new ArrayList<>(); + + final JsonObject artifacts = child(child, "artifacts"); + artifacts.keySet().forEach(key -> { + final JsonObject element = artifacts.getAsJsonObject(key); + final JsonObject artifactsProperties = child(element, "properties"); + final List lcp = getLifecycleParameters(artifactsProperties); + items.add(new DeploymentItem().name(key).itemId(getStringValue(artifactsProperties, "itemId")) + .file(getStringValue(element, "file")) + .deploymentOrder(getStringValue(artifactsProperties, "deployment_order")).lifecycleParameters(lcp)); + }); + return items; + } + + private List getLifecycleParameters(final JsonObject artifactsProperties) { + final JsonArray lcParameters = childElement(artifactsProperties, "lifecycle_parameters").getAsJsonArray(); + final List lifecycleParameters = new ArrayList<>(); + if(lcParameters != null) { + final Iterator it = lcParameters.iterator(); + while(it.hasNext()){ + lifecycleParameters.add(it.next().getAsString()); + } + } + return lifecycleParameters; + } + + private String getAsdLocation(final ZipInputStream zipInputStream) throws IOException { + + try (final ByteArrayOutputStream fileContent = getFileInZip(zipInputStream, TOCSA_METADATA_FILE_PATH);) { + final String toscaMetadata = new String(fileContent.toByteArray()); + if (!toscaMetadata.isEmpty()) { + final String entry = + filter(on("\n").split(toscaMetadata), line -> line.contains(ENTRY_DEFINITIONS_ENTRY)).iterator() + .next(); + return entry.replace(ENTRY_DEFINITIONS_ENTRY + ":", "").trim(); + } + final String message = "Unable to find valid Tosca Path"; + logger.error(message); + throw new FileNotFoundInCsarException(message); + } + } + + public ByteArrayOutputStream getFileInZip(final ZipInputStream zipInputStream, final String path) + throws IOException { + ZipEntry zipEntry; + final Set items = new HashSet<>(); + while ((zipEntry = zipInputStream.getNextEntry()) != null) { + items.add(zipEntry.getName()); + if (zipEntry.getName().matches(path)) { + final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + ByteStreams.copy(zipInputStream, byteArrayOutputStream); + return byteArrayOutputStream; + } + } + logger.error("Unable to find the {} in archive found: {}", path, items); + throw new NoSuchElementException("Unable to find the " + path + " in archive found: " + items); + } + + private JsonObject child(final JsonObject parent, final String name) { + return childElement(parent, name).getAsJsonObject(); + } + + private JsonElement childElement(final JsonObject parent, final String name) { + final JsonElement child = parent.get(name); + if (child == null) { + final String message = "Missing child " + name; + logger.error(message); + throw new PropertyNotFoundException(message); + } + return child; + } + + private Collection children(final JsonObject parent) { + final ArrayList childElements = new ArrayList<>(); + parent.keySet().stream().forEach(childKey -> { + if (parent.get(childKey).isJsonObject()) { + childElements.add(parent.get(childKey).getAsJsonObject()); + } + }); + return childElements; + } +} diff --git a/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/java/org/onap/so/cnfm/lcm/bpmn/flows/extclients/sdc/SdcCsarPropertiesConstants.java b/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/java/org/onap/so/cnfm/lcm/bpmn/flows/extclients/sdc/SdcCsarPropertiesConstants.java new file mode 100644 index 0000000..40a0d0d --- /dev/null +++ b/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/java/org/onap/so/cnfm/lcm/bpmn/flows/extclients/sdc/SdcCsarPropertiesConstants.java @@ -0,0 +1,37 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2023 Nordix Foundation. + * ================================================================================ + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ +package org.onap.so.cnfm.lcm.bpmn.flows.extclients.sdc; + +/** + * + * @author Waqas Ikram (waqas.ikram@est.tech) + * + */ +public class SdcCsarPropertiesConstants { + public static final String DEPLOYMENT_ITEMS_PARAM_NAME = "deployment_items"; + public static final String APPLICATION_VERSION_PARAM_NAME = "application_version"; + public static final String APPLICATION_NAME_PARAM_NAME = "application_name"; + public static final String PROVIDER_PARAM_NAME = "provider"; + public static final String DESCRIPTOR_INVARIANT_ID_PARAM_NAME = "descriptor_invariant_id"; + public static final String DESCRIPTOR_ID_PARAM_NAME = "descriptor_id"; + + private SdcCsarPropertiesConstants() {} + +} diff --git a/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/java/org/onap/so/cnfm/lcm/bpmn/flows/extclients/sdc/SdcHttpRestServiceProviderConfiguration.java b/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/java/org/onap/so/cnfm/lcm/bpmn/flows/extclients/sdc/SdcHttpRestServiceProviderConfiguration.java new file mode 100644 index 0000000..f727e07 --- /dev/null +++ b/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/java/org/onap/so/cnfm/lcm/bpmn/flows/extclients/sdc/SdcHttpRestServiceProviderConfiguration.java @@ -0,0 +1,108 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2023 Nordix Foundation. + * ================================================================================ + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.so.cnfm.lcm.bpmn.flows.extclients.sdc; + +import java.util.Iterator; +import javax.net.ssl.SSLContext; +import org.apache.http.client.HttpClient; +import org.apache.http.conn.ssl.SSLConnectionSocketFactory; +import org.apache.http.impl.client.HttpClients; +import org.onap.logging.filter.spring.SpringClientPayloadFilter; +import org.onap.so.cnfm.lcm.bpmn.flows.GsonProvider; +import org.onap.so.configuration.rest.HttpComponentsClientConfiguration; +import org.onap.so.logging.jaxrs.filter.SOSpringClientFilter; +import org.onap.so.rest.service.HttpRestServiceProvider; +import org.onap.so.rest.service.HttpRestServiceProviderImpl; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.client.BufferingClientHttpRequestFactory; +import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.converter.json.GsonHttpMessageConverter; +import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; +import org.springframework.web.client.RestTemplate; + +/** + * @author Waqas Ikram (waqas.ikram@est.tech) + * + */ +@Configuration +public class SdcHttpRestServiceProviderConfiguration { + + private static final Logger logger = LoggerFactory.getLogger(SdcHttpRestServiceProviderConfiguration.class); + + public static final String SDC_REST_TEMPLATE_CLIENT_BEAN = "SdcRestTemplateClientBean"; + public static final String SDC_HTTP_REST_SERVICE_PROVIDER_BEAN = "SdcHttpRestServiceProviderBean"; + + @Autowired + private GsonProvider gsonProvider; + + @Bean + @Qualifier(SDC_REST_TEMPLATE_CLIENT_BEAN) + public RestTemplate sdcAdapterRestTemplate( + @Autowired final HttpComponentsClientConfiguration httpComponentsClientConfiguration) { + + final HttpComponentsClientHttpRequestFactory clientHttpRequestFactory = + httpComponentsClientConfiguration.httpComponentsClientHttpRequestFactory(); + + final RestTemplate restTemplate = + new RestTemplate(new BufferingClientHttpRequestFactory(clientHttpRequestFactory)); + restTemplate.getInterceptors().add(new SOSpringClientFilter()); + restTemplate.getInterceptors().add((new SpringClientPayloadFilter())); + return restTemplate; + + } + + @Bean + @Qualifier(SDC_HTTP_REST_SERVICE_PROVIDER_BEAN) + public HttpRestServiceProvider sdcHttpRestServiceProvider( + @Qualifier(SDC_REST_TEMPLATE_CLIENT_BEAN) @Autowired final RestTemplate restTemplate) { + + try { + logger.info("Setting SSLConnectionSocketFactory with Default SSL ..."); + final SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(SSLContext.getDefault()); + final HttpClient httpClient = HttpClients.custom().setSSLSocketFactory(socketFactory).build(); + final HttpComponentsClientHttpRequestFactory factory = + new HttpComponentsClientHttpRequestFactory(httpClient); + restTemplate.setRequestFactory(new BufferingClientHttpRequestFactory(factory)); + } catch (final Exception exception) { + logger.error("Error reading truststore, TLS connection to SDC will fail.", exception); + } + setGsonMessageConverter(restTemplate); + + + return new HttpRestServiceProviderImpl(restTemplate); + } + + private void setGsonMessageConverter(final RestTemplate restTemplate) { + final Iterator> iterator = restTemplate.getMessageConverters().iterator(); + while (iterator.hasNext()) { + if (iterator.next() instanceof MappingJackson2HttpMessageConverter) { + iterator.remove(); + } + } + restTemplate.getMessageConverters().add(new GsonHttpMessageConverter(gsonProvider.getGson())); + } +} diff --git a/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/java/org/onap/so/cnfm/lcm/bpmn/flows/extclients/sdc/SdcPackageProvider.java b/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/java/org/onap/so/cnfm/lcm/bpmn/flows/extclients/sdc/SdcPackageProvider.java new file mode 100644 index 0000000..f75d5f5 --- /dev/null +++ b/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/java/org/onap/so/cnfm/lcm/bpmn/flows/extclients/sdc/SdcPackageProvider.java @@ -0,0 +1,33 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2023 Nordix Foundation. + * ================================================================================ + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ +package org.onap.so.cnfm.lcm.bpmn.flows.extclients.sdc; + +import java.util.Optional; + +/** + * + * @author Waqas Ikram (waqas.ikram@est.tech) + * + */ +public interface SdcPackageProvider { + + Optional getSdcResourcePackage(final String packageId); + +} diff --git a/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/java/org/onap/so/cnfm/lcm/bpmn/flows/extclients/sdc/SdcPackageProviderImpl.java b/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/java/org/onap/so/cnfm/lcm/bpmn/flows/extclients/sdc/SdcPackageProviderImpl.java new file mode 100644 index 0000000..5e69661 --- /dev/null +++ b/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/java/org/onap/so/cnfm/lcm/bpmn/flows/extclients/sdc/SdcPackageProviderImpl.java @@ -0,0 +1,80 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2023 Nordix Foundation. + * ================================================================================ + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ +package org.onap.so.cnfm.lcm.bpmn.flows.extclients.sdc; + +import static org.onap.so.cnfm.lcm.bpmn.flows.extclients.sdc.SdcHttpRestServiceProviderConfiguration.SDC_HTTP_REST_SERVICE_PROVIDER_BEAN; +import java.util.Optional; +import org.onap.so.cnfm.lcm.bpmn.flows.exceptions.SdcPackageRequestFailureException; +import org.onap.so.rest.service.HttpRestServiceProvider; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; + +/** + * + * @author Waqas Ikram (waqas.ikram@est.tech) + * + */ +@Service +public class SdcPackageProviderImpl implements SdcPackageProvider { + private static final Logger logger = LoggerFactory.getLogger(SdcPackageProviderImpl.class); + private final SdcClientConfigurationProvider sdcClientConfigurationProvider; + private final HttpRestServiceProvider httpServiceProvider; + private static final String SERVICE_NAME = "SO-CNFM"; + + @Autowired + public SdcPackageProviderImpl(final SdcClientConfigurationProvider sdcClientConfigurationProvider, + @Qualifier(SDC_HTTP_REST_SERVICE_PROVIDER_BEAN) final HttpRestServiceProvider httpServiceProvider) { + this.sdcClientConfigurationProvider = sdcClientConfigurationProvider; + this.httpServiceProvider = httpServiceProvider; + } + + @Override + public Optional getSdcResourcePackage(final String packageId) { + try { + final HttpHeaders headers = new HttpHeaders(); + headers.add(HttpHeaders.AUTHORIZATION, sdcClientConfigurationProvider.getBasicAuth()); + headers.add(HttpHeaders.ACCEPT, MediaType.APPLICATION_OCTET_STREAM_VALUE); + headers.add("X-ECOMP-InstanceID", SERVICE_NAME); + headers.add("X-FromAppId", SERVICE_NAME); + + logger.info("Will retrieve resource package with id: {} from SDC", packageId); + final String url = sdcClientConfigurationProvider.getSdcPackageUrl(packageId); + + final ResponseEntity response = httpServiceProvider.getHttpResponse(url, headers, byte[].class); + if (response.getStatusCode().is2xxSuccessful()) { + if (response.hasBody()) { + return Optional.of(response.getBody()); + } + logger.error("Received response without body ..."); + } + return Optional.empty(); + } catch (final Exception restProcessingException) { + final String message = "Caught exception while getting resource package content for: " + packageId; + throw new SdcPackageRequestFailureException(message, restProcessingException); + } + } + +} diff --git a/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/java/org/onap/so/cnfm/lcm/bpmn/flows/service/JobExecutorService.java b/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/java/org/onap/so/cnfm/lcm/bpmn/flows/service/JobExecutorService.java new file mode 100644 index 0000000..c4bd210 --- /dev/null +++ b/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/java/org/onap/so/cnfm/lcm/bpmn/flows/service/JobExecutorService.java @@ -0,0 +1,376 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2023 Nordix Foundation. + * ================================================================================ + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ +package org.onap.so.cnfm.lcm.bpmn.flows.service; + +import static org.onap.so.cnfm.lcm.bpmn.flows.CamundaVariableNameConstants.AS_INSTANCE_ID_PARAM_NAME; +import static org.onap.so.cnfm.lcm.bpmn.flows.CamundaVariableNameConstants.CREATE_AS_REQUEST_PARAM_NAME; +import static org.onap.so.cnfm.lcm.bpmn.flows.CamundaVariableNameConstants.INSTANTIATE_AS_REQUEST_PARAM_NAME; +import static org.onap.so.cnfm.lcm.bpmn.flows.CamundaVariableNameConstants.JOB_ID_PARAM_NAME; +import static org.onap.so.cnfm.lcm.bpmn.flows.CamundaVariableNameConstants.OCC_ID_PARAM_NAME; +import static org.onap.so.cnfm.lcm.bpmn.flows.CamundaVariableNameConstants.TERMINATE_AS_REQUEST_PARAM_NAME; +import static org.onap.so.cnfm.lcm.bpmn.flows.Constants.CREATE_AS_WORKFLOW_NAME; +import static org.onap.so.cnfm.lcm.bpmn.flows.Constants.DELETE_AS_WORKFLOW_NAME; +import static org.onap.so.cnfm.lcm.bpmn.flows.Constants.INSTANTIATE_AS_WORKFLOW_NAME; +import static org.onap.so.cnfm.lcm.bpmn.flows.Constants.TERMINATE_AS_WORKFLOW_NAME; +import static org.onap.so.cnfm.lcm.database.beans.JobStatusEnum.ERROR; +import static org.onap.so.cnfm.lcm.database.beans.JobStatusEnum.FINISHED; +import static org.onap.so.cnfm.lcm.database.beans.JobStatusEnum.FINISHED_WITH_ERROR; +import static org.onap.so.cnfm.lcm.database.beans.JobStatusEnum.IN_PROGRESS; +import static org.slf4j.LoggerFactory.getLogger; +import java.time.Instant; +import java.time.LocalDateTime; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.TimeUnit; +import org.apache.commons.lang3.tuple.ImmutablePair; +import org.onap.so.cnfm.lcm.bpmn.flows.GsonProvider; +import org.onap.so.cnfm.lcm.bpmn.flows.exceptions.AsRequestProcessingException; +import org.onap.so.cnfm.lcm.database.beans.AsInst; +import org.onap.so.cnfm.lcm.database.beans.AsLcmOpOcc; +import org.onap.so.cnfm.lcm.database.beans.AsLcmOpType; +import org.onap.so.cnfm.lcm.database.beans.Job; +import org.onap.so.cnfm.lcm.database.beans.JobAction; +import org.onap.so.cnfm.lcm.database.beans.JobStatusEnum; +import org.onap.so.cnfm.lcm.database.beans.OperationStateEnum; +import org.onap.so.cnfm.lcm.database.beans.State; +import org.onap.so.cnfm.lcm.database.service.DatabaseServiceProvider; +import org.onap.so.cnfm.lcm.model.AsInstance; +import org.onap.so.cnfm.lcm.model.CreateAsRequest; +import org.onap.so.cnfm.lcm.model.ErrorDetails; +import org.onap.so.cnfm.lcm.model.InstantiateAsRequest; +import org.onap.so.cnfm.lcm.model.TerminateAsRequest; +import org.slf4j.Logger; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import com.google.common.collect.ImmutableSet; +import com.google.gson.Gson; + +/** + * @author Waqas Ikram (waqas.ikram@est.tech) + * + */ +@Service +public class JobExecutorService { + + private static final Logger logger = getLogger(JobExecutorService.class); + + private static final ImmutableSet JOB_FINISHED_STATES = + ImmutableSet.of(FINISHED, ERROR, FINISHED_WITH_ERROR); + + private static final int SLEEP_TIME_IN_SECONDS = 5; + + @Value("${so-cnfm-lcm.requesttimeout.timeoutInSeconds:300}") + private int timeOutInSeconds; + + private final DatabaseServiceProvider databaseServiceProvider; + private final WorkflowExecutorService workflowExecutorService; + private final WorkflowQueryService workflowQueryService; + private final Gson gson; + + @Autowired + public JobExecutorService(final DatabaseServiceProvider databaseServiceProvider, + final WorkflowExecutorService workflowExecutorService, final WorkflowQueryService workflowQueryService, + final GsonProvider gsonProvider) { + this.databaseServiceProvider = databaseServiceProvider; + this.workflowExecutorService = workflowExecutorService; + this.workflowQueryService = workflowQueryService; + this.gson = gsonProvider.getGson(); + } + + public AsInstance runCreateAsJob(final CreateAsRequest createAsRequest) { + logger.info("Starting 'Create AS' workflow job for request:\n{}", createAsRequest); + final Job newJob = new Job().startTime(LocalDateTime.now()).jobType("AS").jobAction(JobAction.CREATE) + .resourceId(createAsRequest.getAsdId()).resourceName(createAsRequest.getAsInstanceName()) + .status(JobStatusEnum.STARTING); + databaseServiceProvider.addJob(newJob); + + logger.info("New job created in database :\n{}", newJob); + + workflowExecutorService.executeWorkflow(newJob.getJobId(), CREATE_AS_WORKFLOW_NAME, + getVariables(newJob.getJobId(), createAsRequest)); + + final ImmutablePair immutablePair = + waitForJobToFinish(newJob.getJobId(), JOB_FINISHED_STATES); + + if (immutablePair.getRight() == null) { + final String message = "Failed to create AS for request: \n" + createAsRequest; + logger.error(message); + throw new AsRequestProcessingException(message); + } + final JobStatusEnum finalJobStatus = immutablePair.getRight(); + final String processInstanceId = immutablePair.getLeft(); + + if (!FINISHED.equals(finalJobStatus)) { + + final Optional optional = workflowQueryService.getErrorDetails(processInstanceId); + if (optional.isPresent()) { + final ErrorDetails errorDetails = optional.get(); + final String message = + "Failed to create AS for request: \n" + createAsRequest + " due to \n" + errorDetails; + logger.error(message); + throw new AsRequestProcessingException(message, errorDetails); + } + + final String message = "Received unexpected Job Status: " + finalJobStatus + + " Failed to Create AS for request: \n" + createAsRequest; + logger.error(message); + throw new AsRequestProcessingException(message); + } + + logger.debug("Will query for CreateAsResponse using processInstanceId:{}", processInstanceId); + final Optional optional = workflowQueryService.getCreateNsResponse(processInstanceId); + if (optional.isEmpty()) { + final String message = + "Unable to find CreateAsReponse in Camunda History for process instance: " + processInstanceId; + logger.error(message); + throw new AsRequestProcessingException(message); + } + return optional.get(); + } + + public String runInstantiateAsJob(final String asInstanceId, final InstantiateAsRequest instantiateAsRequest) { + final Job newJob = new Job().startTime(LocalDateTime.now()).jobType("AS").jobAction(JobAction.INSTANTIATE) + .resourceId(asInstanceId).status(JobStatusEnum.STARTING); + databaseServiceProvider.addJob(newJob); + logger.info("New job created in database :\n{}", newJob); + + final LocalDateTime currentDateTime = LocalDateTime.now(); + final AsLcmOpOcc newAsLcmOpOcc = new AsLcmOpOcc().id(asInstanceId).operation(AsLcmOpType.INSTANTIATE) + .operationState(OperationStateEnum.PROCESSING).stateEnteredTime(currentDateTime) + .startTime(currentDateTime).asInst(getAsInst(asInstanceId)).isAutoInvocation(false) + .isCancelPending(false).operationParams(gson.toJson(instantiateAsRequest)); + databaseServiceProvider.addAsLcmOpOcc(newAsLcmOpOcc); + logger.info("New AsLcmOpOcc created in database :\n{}", newAsLcmOpOcc); + + workflowExecutorService.executeWorkflow(newJob.getJobId(), INSTANTIATE_AS_WORKFLOW_NAME, + getVariables(asInstanceId, newJob.getJobId(), newAsLcmOpOcc.getId(), instantiateAsRequest)); + + final ImmutableSet jobFinishedStates = + ImmutableSet.of(FINISHED, ERROR, FINISHED_WITH_ERROR, IN_PROGRESS); + final ImmutablePair immutablePair = + waitForJobToFinish(newJob.getJobId(), jobFinishedStates); + + if (immutablePair.getRight() == null) { + final String message = "Failed to Instantiate AS for request: \n" + instantiateAsRequest; + logger.error(message); + throw new AsRequestProcessingException(message); + } + + final JobStatusEnum finalJobStatus = immutablePair.getRight(); + if (IN_PROGRESS.equals(finalJobStatus) || FINISHED.equals(finalJobStatus)) { + logger.info("Instantiation Job status: {}", finalJobStatus); + return newAsLcmOpOcc.getId(); + } + + final String message = "Received unexpected Job Status: " + finalJobStatus + + " Failed to instantiate AS for request: \n" + instantiateAsRequest; + logger.error(message); + throw new AsRequestProcessingException(message); + } + + public String runTerminateAsJob(final String asInstanceId, final TerminateAsRequest terminateAsRequest) { + doInitialTerminateChecks(asInstanceId, terminateAsRequest); + + final Job newJob = new Job().startTime(LocalDateTime.now()).jobType("AS").jobAction(JobAction.TERMINATE) + .resourceId(asInstanceId).status(JobStatusEnum.STARTING); + databaseServiceProvider.addJob(newJob); + logger.info("New job created in database :\n{}", newJob); + + final LocalDateTime currentDateTime = LocalDateTime.now(); + final AsLcmOpOcc newAsLcmOpOcc = new AsLcmOpOcc().id(asInstanceId).operation(AsLcmOpType.TERMINATE) + .operationState(OperationStateEnum.PROCESSING).stateEnteredTime(currentDateTime) + .startTime(currentDateTime).asInst(getAsInst(asInstanceId)).isAutoInvocation(false) + .isCancelPending(false).operationParams(gson.toJson(terminateAsRequest)); + databaseServiceProvider.addAsLcmOpOcc(newAsLcmOpOcc); + logger.info("New AsLcmOpOcc created in database :\n{}", newAsLcmOpOcc); + + workflowExecutorService.executeWorkflow(newJob.getJobId(), TERMINATE_AS_WORKFLOW_NAME, + getVariables(asInstanceId, newJob.getJobId(), newAsLcmOpOcc.getId(), terminateAsRequest)); + + final ImmutableSet jobFinishedStates = + ImmutableSet.of(FINISHED, ERROR, FINISHED_WITH_ERROR, IN_PROGRESS); + final ImmutablePair immutablePair = + waitForJobToFinish(newJob.getJobId(), jobFinishedStates); + + if (immutablePair.getRight() == null) { + final String message = + "Failed to Terminate AS with id: " + asInstanceId + " for request: \n" + terminateAsRequest; + logger.error(message); + throw new AsRequestProcessingException(message); + } + + final JobStatusEnum finalJobStatus = immutablePair.getRight(); + + if (IN_PROGRESS.equals(finalJobStatus) || FINISHED.equals(finalJobStatus)) { + logger.info("Termination Job status: {}", finalJobStatus); + return newAsLcmOpOcc.getId(); + } + + final String message = "Received unexpected Job Status: " + finalJobStatus + " Failed to Terminate AS with id: " + + asInstanceId + " for request: \n" + terminateAsRequest; + logger.error(message); + throw new AsRequestProcessingException(message); + } + + public void runDeleteAsJob(final String asInstanceId) { + final Job newJob = new Job().startTime(LocalDateTime.now()).jobType("AS").jobAction(JobAction.DELETE) + .resourceId(asInstanceId).status(JobStatusEnum.STARTING); + databaseServiceProvider.addJob(newJob); + logger.info("New job created in database :\n{}", newJob); + + workflowExecutorService.executeWorkflow(newJob.getJobId(), DELETE_AS_WORKFLOW_NAME, + getVariables(asInstanceId, newJob.getJobId())); + + final ImmutablePair immutablePair = + waitForJobToFinish(newJob.getJobId(), JOB_FINISHED_STATES); + + if (immutablePair.getRight() == null) { + final String message = "Failed to Delete AS with id: " + asInstanceId; + logger.error(message); + throw new AsRequestProcessingException(message); + } + + final JobStatusEnum finalJobStatus = immutablePair.getRight(); + final String processInstanceId = immutablePair.getLeft(); + + logger.info("Delete Job status: {}", finalJobStatus); + + if (!FINISHED.equals(finalJobStatus)) { + + final Optional optional = workflowQueryService.getErrorDetails(processInstanceId); + if (optional.isPresent()) { + final ErrorDetails errorDetails = optional.get(); + final String message = "Failed to Delete AS with id: " + asInstanceId + " due to \n" + errorDetails; + logger.error(message); + throw new AsRequestProcessingException(message, errorDetails); + } + + final String message = "Received unexpected Job Status: " + finalJobStatus + + " Failed to Delete AS with id: " + asInstanceId; + logger.error(message); + throw new AsRequestProcessingException(message); + } + + logger.debug("Delete AS finished successfully ..."); + } + + private AsInst getAsInst(final String asInstId) { + logger.info("Getting AsInst with nsInstId: {}", asInstId); + final Optional optionalNfvoNsInst = databaseServiceProvider.getAsInst(asInstId); + + if (optionalNfvoNsInst.isEmpty()) { + final String message = "No matching AS Instance for id: " + asInstId + " found in database."; + throw new AsRequestProcessingException(message); + } + + return optionalNfvoNsInst.get(); + } + + private ImmutablePair waitForJobToFinish(final String jobId, + final ImmutableSet jobFinishedStates) { + try { + final long startTimeInMillis = System.currentTimeMillis(); + final long timeOutTime = startTimeInMillis + TimeUnit.SECONDS.toMillis(timeOutInSeconds); + + logger.info("Will wait till {} for {} job to finish", Instant.ofEpochMilli(timeOutTime).toString(), jobId); + JobStatusEnum currentJobStatus = null; + while (timeOutTime > System.currentTimeMillis()) { + + final Optional optional = databaseServiceProvider.getRefreshedJob(jobId); + + if (optional.isEmpty()) { + logger.error("Unable to find Job using jobId: {}", jobId); + return ImmutablePair.nullPair(); + } + + final Job job = optional.get(); + currentJobStatus = job.getStatus(); + logger.debug("Received job status response: \n {}", job); + if (jobFinishedStates.contains(currentJobStatus)) { + logger.info("Job finished \n {}", currentJobStatus); + return ImmutablePair.of(job.getProcessInstanceId(), currentJobStatus); + } + + logger.info("Haven't received one of finish state {} yet, will try again in {} seconds", + jobFinishedStates, SLEEP_TIME_IN_SECONDS); + TimeUnit.SECONDS.sleep(SLEEP_TIME_IN_SECONDS); + + } + logger.warn("Timeout current job status: {}", currentJobStatus); + return ImmutablePair.nullPair(); + } catch (final InterruptedException interruptedException) { + Thread.currentThread().interrupt(); + logger.error("Sleep was interrupted", interruptedException); + return ImmutablePair.nullPair(); + } + } + + private void doInitialTerminateChecks(final String asInstanceId, final TerminateAsRequest terminateAsRequest) { + final AsInst asInst = getAsInst(asInstanceId); + if (isNotInstantiated(asInst)) { + final String message = "TerminateAsRequest received: " + terminateAsRequest + " for asInstanceId: " + + asInstanceId + "\nUnable to terminate. AS Instance is already in " + State.NOT_INSTANTIATED + + " state." + "\nThis method can only be used with an AS instance in the " + State.INSTANTIATED + + " state."; + logger.error(message); + throw new AsRequestProcessingException(message); + } + } + + private boolean isNotInstantiated(final AsInst asInst) { + return State.NOT_INSTANTIATED.equals(asInst.getStatus()); + } + + private Map getVariables(final String jobId, final CreateAsRequest createAsRequest) { + final Map variables = new HashMap<>(); + variables.put(JOB_ID_PARAM_NAME, jobId); + variables.put(CREATE_AS_REQUEST_PARAM_NAME, createAsRequest); + return variables; + } + + private Map getVariables(final String asInstanceId, final String jobId, final String occId, + final InstantiateAsRequest instantiateAsRequest) { + final Map variables = new HashMap<>(); + variables.put(AS_INSTANCE_ID_PARAM_NAME, asInstanceId); + variables.put(JOB_ID_PARAM_NAME, jobId); + variables.put(OCC_ID_PARAM_NAME, occId); + variables.put(INSTANTIATE_AS_REQUEST_PARAM_NAME, instantiateAsRequest); + return variables; + } + + private Map getVariables(final String asInstanceId, final String jobId, final String occId, + final TerminateAsRequest terminateAsRequest) { + final Map variables = new HashMap<>(); + variables.put(AS_INSTANCE_ID_PARAM_NAME, asInstanceId); + variables.put(JOB_ID_PARAM_NAME, jobId); + variables.put(OCC_ID_PARAM_NAME, occId); + variables.put(TERMINATE_AS_REQUEST_PARAM_NAME, terminateAsRequest); + return variables; + } + + private Map getVariables(final String asInstanceId, final String jobId) { + final Map variables = new HashMap<>(); + variables.put(AS_INSTANCE_ID_PARAM_NAME, asInstanceId); + variables.put(JOB_ID_PARAM_NAME, jobId); + return variables; + } +} diff --git a/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/java/org/onap/so/cnfm/lcm/bpmn/flows/service/WorkflowExecutorService.java b/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/java/org/onap/so/cnfm/lcm/bpmn/flows/service/WorkflowExecutorService.java new file mode 100644 index 0000000..57a8a41 --- /dev/null +++ b/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/java/org/onap/so/cnfm/lcm/bpmn/flows/service/WorkflowExecutorService.java @@ -0,0 +1,59 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2023 Nordix Foundation. + * ================================================================================ + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ +package org.onap.so.cnfm.lcm.bpmn.flows.service; + +import static org.onap.so.cnfm.lcm.bpmn.flows.Constants.TENANT_ID; +import static org.slf4j.LoggerFactory.getLogger; +import java.util.Map; +import org.camunda.bpm.engine.RuntimeService; +import org.camunda.bpm.engine.runtime.ProcessInstance; +import org.slf4j.Logger; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; + +/** + * @author Waqas Ikram (waqas.ikram@est.tech) + * + */ +@Service +public class WorkflowExecutorService { + + private static final Logger logger = getLogger(WorkflowExecutorService.class); + + private final RuntimeService runtimeService; + + @Autowired + public WorkflowExecutorService(final RuntimeService runtimeService) { + this.runtimeService = runtimeService; + } + + @Async + public void executeWorkflow(final String jobId, final String processDefinitionKey, + final Map variables) { + logger.info("Executing {} workflow with business key: {}", processDefinitionKey, jobId); + final ProcessInstance processInstance = runtimeService.createProcessInstanceByKey(processDefinitionKey) + .businessKey(jobId).setVariables(variables).processDefinitionTenantId(TENANT_ID).execute(); + + logger.info("Workflow running with processInstanceId: {} and business key: {}", + processInstance.getProcessInstanceId(), processInstance.getBusinessKey()); + } + +} diff --git a/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/java/org/onap/so/cnfm/lcm/bpmn/flows/service/WorkflowQueryService.java b/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/java/org/onap/so/cnfm/lcm/bpmn/flows/service/WorkflowQueryService.java new file mode 100644 index 0000000..b6dbd77 --- /dev/null +++ b/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/java/org/onap/so/cnfm/lcm/bpmn/flows/service/WorkflowQueryService.java @@ -0,0 +1,111 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2023 Nordix Foundation. + * ================================================================================ + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ +package org.onap.so.cnfm.lcm.bpmn.flows.service; + +import static org.onap.so.cnfm.lcm.bpmn.flows.CamundaVariableNameConstants.AS_WORKFLOW_PROCESSING_EXCEPTION_PARAM_NAME; +import static org.onap.so.cnfm.lcm.bpmn.flows.CamundaVariableNameConstants.CREATE_AS_RESPONSE_PARAM_NAME; +import static org.onap.so.cnfm.lcm.bpmn.flows.Constants.TENANT_ID; +import static org.slf4j.LoggerFactory.getLogger; +import java.util.Optional; +import org.camunda.bpm.engine.HistoryService; +import org.camunda.bpm.engine.ProcessEngineException; +import org.camunda.bpm.engine.history.HistoricVariableInstance; +import org.onap.so.cnfm.lcm.model.AsInstance; +import org.onap.so.cnfm.lcm.model.ErrorDetails; +import org.slf4j.Logger; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import com.google.common.base.Strings; + +/** + * @author Waqas Ikram (waqas.ikram@est.tech) + * + */ +@Service +public class WorkflowQueryService { + + private static final Logger logger = getLogger(WorkflowQueryService.class); + + private final HistoryService camundaHistoryService; + + @Autowired + public WorkflowQueryService(final HistoryService camundaHistoryService) { + this.camundaHistoryService = camundaHistoryService; + } + + public Optional getCreateNsResponse(final String processInstanceId) { + try { + + if (Strings.isNullOrEmpty(processInstanceId)) { + logger.error("Invalid processInstanceId: {}", processInstanceId); + return Optional.empty(); + } + + final HistoricVariableInstance historicVariableInstance = + getVariable(processInstanceId, CREATE_AS_RESPONSE_PARAM_NAME); + + if (historicVariableInstance != null) { + logger.info("Found HistoricVariableInstance : {}", historicVariableInstance); + final Object variableValue = historicVariableInstance.getValue(); + if (variableValue instanceof AsInstance) { + return Optional.ofNullable((AsInstance) variableValue); + } + logger.error("Unknown CreateAsResponse object type {} received value: {}", + historicVariableInstance.getValue() != null ? variableValue.getClass() : null, variableValue); + } + } catch (final ProcessEngineException processEngineException) { + logger.error("Unable to find {} variable using processInstanceId: {}", CREATE_AS_RESPONSE_PARAM_NAME, + processInstanceId, processEngineException); + } + logger.error("Unable to find {} variable using processInstanceId: {}", CREATE_AS_RESPONSE_PARAM_NAME, + processInstanceId); + return Optional.empty(); + + } + + public Optional getErrorDetails(final String processInstanceId) { + try { + final HistoricVariableInstance historicVariableInstance = + getVariable(processInstanceId, AS_WORKFLOW_PROCESSING_EXCEPTION_PARAM_NAME); + + logger.info("Found HistoricVariableInstance : {}", historicVariableInstance); + if (historicVariableInstance != null) { + final Object variableValue = historicVariableInstance.getValue(); + if (variableValue instanceof ErrorDetails) { + return Optional.ofNullable((ErrorDetails) variableValue); + } + logger.error("Unknown ErrorContents object type {} received value: {}", + historicVariableInstance.getValue() != null ? variableValue.getClass() : null, variableValue); + } + logger.error("Unable to retrieve HistoricVariableInstance value was null"); + } catch (final ProcessEngineException processEngineException) { + logger.error("Unable to find {} variable using processInstanceId: {}", + AS_WORKFLOW_PROCESSING_EXCEPTION_PARAM_NAME, processInstanceId, processEngineException); + } + return Optional.empty(); + } + + + private HistoricVariableInstance getVariable(final String processInstanceId, final String name) { + return camundaHistoryService.createHistoricVariableInstanceQuery().processInstanceId(processInstanceId) + .variableName(name).tenantIdIn(TENANT_ID).singleResult(); + } + +} diff --git a/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/java/org/onap/so/cnfm/lcm/bpmn/flows/utils/LocalDateTimeTypeAdapter.java b/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/java/org/onap/so/cnfm/lcm/bpmn/flows/utils/LocalDateTimeTypeAdapter.java new file mode 100644 index 0000000..57a4dd6 --- /dev/null +++ b/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/java/org/onap/so/cnfm/lcm/bpmn/flows/utils/LocalDateTimeTypeAdapter.java @@ -0,0 +1,59 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2023 Nordix Foundation. + * ================================================================================ + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ +package org.onap.so.cnfm.lcm.bpmn.flows.utils; + +import java.io.IOException; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import com.google.gson.TypeAdapter; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonToken; +import com.google.gson.stream.JsonWriter; + +/** + * @author Waqas Ikram (waqas.ikram@est.tech) + * + */ +public class LocalDateTimeTypeAdapter extends TypeAdapter { + + private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + + @Override + public void write(final JsonWriter out, final LocalDateTime localDateTime) throws IOException { + if (localDateTime == null) { + out.nullValue(); + } else { + out.value(FORMATTER.format(localDateTime)); + } + } + + @Override + public LocalDateTime read(final JsonReader in) throws IOException { + if (JsonToken.NULL == in.peek()) { + in.nextNull(); + return null; + + } + final String dateTime = in.nextString(); + return LocalDateTime.parse(dateTime, FORMATTER); + + } + +} diff --git a/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/java/org/onap/so/cnfm/lcm/bpmn/flows/utils/OffsetDateTimeTypeAdapter.java b/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/java/org/onap/so/cnfm/lcm/bpmn/flows/utils/OffsetDateTimeTypeAdapter.java new file mode 100644 index 0000000..1f63cc1 --- /dev/null +++ b/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/java/org/onap/so/cnfm/lcm/bpmn/flows/utils/OffsetDateTimeTypeAdapter.java @@ -0,0 +1,68 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2023 Nordix Foundation. + * ================================================================================ + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ +package org.onap.so.cnfm.lcm.bpmn.flows.utils; + +import java.io.IOException; +import java.time.OffsetDateTime; +import java.time.format.DateTimeFormatter; +import com.google.gson.TypeAdapter; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonToken; +import com.google.gson.stream.JsonWriter; + +/** + * @author Waqas Ikram (waqas.ikram@est.tech) + * + */ +public class OffsetDateTimeTypeAdapter extends TypeAdapter { + + private DateTimeFormatter formatter; + + public OffsetDateTimeTypeAdapter() { + this(DateTimeFormatter.ISO_OFFSET_DATE_TIME); + } + + public OffsetDateTimeTypeAdapter(final DateTimeFormatter formatter) { + this.formatter = formatter; + } + + @Override + public void write(final JsonWriter out, final OffsetDateTime date) throws IOException { + if (date == null) { + out.nullValue(); + } else { + out.value(formatter.format(date)); + } + } + + @Override + public OffsetDateTime read(final JsonReader in) throws IOException { + if (JsonToken.NULL == in.peek()) { + in.nextNull(); + return null; + + } + String date = in.nextString(); + if (date.endsWith("+0000")) { + date = date.substring(0, date.length() - 5) + "Z"; + } + return OffsetDateTime.parse(date, formatter); + } +} diff --git a/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/java/org/onap/so/cnfm/lcm/bpmn/flows/utils/PropertiesToYamlConverter.java b/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/java/org/onap/so/cnfm/lcm/bpmn/flows/utils/PropertiesToYamlConverter.java new file mode 100644 index 0000000..4cc1f8a --- /dev/null +++ b/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/java/org/onap/so/cnfm/lcm/bpmn/flows/utils/PropertiesToYamlConverter.java @@ -0,0 +1,59 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2023 Nordix Foundation. + * ================================================================================ + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ +package org.onap.so.cnfm.lcm.bpmn.flows.utils; + +import java.util.Map; +import java.util.TreeMap; +import org.springframework.stereotype.Service; +import org.yaml.snakeyaml.Yaml; + +/** + * @author Waqas Ikram (waqas.ikram@est.tech) + * + */ + +@Service +public class PropertiesToYamlConverter { + public String getValuesYamlFileContent(final Map lifeCycleParams) { + final Map root = new TreeMap<>(); + lifeCycleParams.entrySet().stream().forEach(entry -> processProperty(root, entry.getKey(), entry.getValue())); + final Yaml yaml = new Yaml(); + return yaml.dumpAsMap(root); + } + + @SuppressWarnings("unchecked") + private void processProperty(final Map root, final String key, final String value) { + Map local = root; + final String[] keys = key.split("\\."); + final int lastIndex = keys.length - 1; + for (int index = 0; index < lastIndex; index++) { + final String currentKey = keys[index]; + if (!local.containsKey(currentKey)) { + final Map subMap = new TreeMap<>(); + local.put(currentKey, subMap); + local = subMap; + continue; + } else { + local = (Map) local.get(currentKey); + } + } + local.put(keys[lastIndex], value); + } +} diff --git a/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/resources/META-INF/services/org.onap.so.client.RestProperties b/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/resources/META-INF/services/org.onap.so.client.RestProperties new file mode 100644 index 0000000..90c1ca7 --- /dev/null +++ b/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/main/resources/META-INF/services/org.onap.so.client.RestProperties @@ -0,0 +1 @@ +org.onap.so.cnfm.lcm.bpmn.flows.extclients.aai.AaiPropertiesImpl \ No newline at end of file diff --git a/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/test/java/org/onap/so/cnfm/lcm/bpmn/flows/extclients/sdc/SdcCsarPackageParserTest.java b/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/test/java/org/onap/so/cnfm/lcm/bpmn/flows/extclients/sdc/SdcCsarPackageParserTest.java new file mode 100644 index 0000000..7de9bdc --- /dev/null +++ b/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/test/java/org/onap/so/cnfm/lcm/bpmn/flows/extclients/sdc/SdcCsarPackageParserTest.java @@ -0,0 +1,83 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2023 Nordix Foundation. + * ================================================================================ + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ +package org.onap.so.cnfm.lcm.bpmn.flows.extclients.sdc; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.onap.so.cnfm.lcm.bpmn.flows.extclients.sdc.SdcCsarPropertiesConstants.APPLICATION_NAME_PARAM_NAME; +import static org.onap.so.cnfm.lcm.bpmn.flows.extclients.sdc.SdcCsarPropertiesConstants.DESCRIPTOR_ID_PARAM_NAME; +import static org.onap.so.cnfm.lcm.bpmn.flows.extclients.sdc.SdcCsarPropertiesConstants.DESCRIPTOR_INVARIANT_ID_PARAM_NAME; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.Map; +import org.junit.Test; + +/** + * + * @author Waqas Ikram (waqas.ikram@est.tech) + * + */ +public class SdcCsarPackageParserTest { + + private static final String RESOURCE_ASD_PACKAGE_CSAR_PATH = + "src/test/resources/resource-Generatedasdpackage-csar.csar"; + + @Test + public void testResourceAsdCsar() throws IOException { + final SdcCsarPackageParser objUnderTest = new SdcCsarPackageParser(); + + final byte[] content = getFileContent(Paths.get(getAbsolutePath(RESOURCE_ASD_PACKAGE_CSAR_PATH))); + + final Map properties = objUnderTest.getAsdProperties(content); + assertEquals("123e4567-e89b-12d3-a456-426614174000", properties.get(DESCRIPTOR_ID_PARAM_NAME)); + assertEquals("123e4yyy-e89b-12d3-a456-426614174abc", properties.get(DESCRIPTOR_INVARIANT_ID_PARAM_NAME)); + assertEquals("SampleApp", properties.get(APPLICATION_NAME_PARAM_NAME)); + assertEquals("2.3", properties.get(SdcCsarPropertiesConstants.APPLICATION_VERSION_PARAM_NAME)); + assertEquals("MyCompany", properties.get(SdcCsarPropertiesConstants.PROVIDER_PARAM_NAME)); + + @SuppressWarnings("unchecked") + final List items = + (List) properties.get(SdcCsarPropertiesConstants.DEPLOYMENT_ITEMS_PARAM_NAME); + assertNotNull(items); + assertTrue(items.size() == 2); + + DeploymentItem deploymentItem = items.get(0); + assertEquals("sampleapp-db", deploymentItem.getName()); + assertEquals("1", deploymentItem.getItemId()); + assertEquals("1", deploymentItem.getDeploymentOrder()); + assertEquals("Artifacts/Deployment/HELM/sampleapp-db-operator-helm.tgz", deploymentItem.getFile()); + + + } + + private String getAbsolutePath(final String path) { + final File file = new File(path); + return file.getAbsolutePath(); + } + + private byte[] getFileContent(final Path path) throws IOException { + return Files.readAllBytes(path); + } +} diff --git a/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/test/java/org/onap/so/cnfm/lcm/bpmn/flows/utils/LocalDateTimeTypeAdapterTest.java b/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/test/java/org/onap/so/cnfm/lcm/bpmn/flows/utils/LocalDateTimeTypeAdapterTest.java new file mode 100644 index 0000000..3015d87 --- /dev/null +++ b/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/test/java/org/onap/so/cnfm/lcm/bpmn/flows/utils/LocalDateTimeTypeAdapterTest.java @@ -0,0 +1,64 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2023 Nordix Foundation. + * ================================================================================ + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ +package org.onap.so.cnfm.lcm.bpmn.flows.utils; + +import static org.junit.Assert.assertEquals; +import java.io.IOException; +import java.io.Reader; +import java.io.StringReader; +import java.io.StringWriter; +import java.time.LocalDateTime; +import org.junit.Test; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +/** + * + * @author Waqas Ikram (waqas.ikram@est.tech) + * + */ +public class LocalDateTimeTypeAdapterTest { + private static final LocalDateTime LOCAL_DATETIME_VALUE = LocalDateTime.of(2023, 1, 1, 12, 0, 15); + private static final String STRING_VALUE = "\"2023-01-01 12:00:15\""; + + @Test + public void testReadWithValidLocalDateTimeString() throws IOException { + final LocalDateTimeTypeAdapter objUnderTest = new LocalDateTimeTypeAdapter(); + + final Reader reader = new StringReader(STRING_VALUE); + final JsonReader jsonReader = new JsonReader(reader); + + final LocalDateTime actual = objUnderTest.read(jsonReader); + assertEquals(LOCAL_DATETIME_VALUE, actual); + + } + + @Test + public void testWritedWithValidLocalDateTime() throws IOException { + final LocalDateTimeTypeAdapter objUnderTest = new LocalDateTimeTypeAdapter(); + + final StringWriter writer = new StringWriter(); + final JsonWriter jsonWriter = new JsonWriter(writer); + + objUnderTest.write(jsonWriter, LOCAL_DATETIME_VALUE); + assertEquals(STRING_VALUE, writer.toString()); + + } +} diff --git a/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/test/java/org/onap/so/cnfm/lcm/bpmn/flows/utils/OffsetDateTimeTypeAdapterTest.java b/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/test/java/org/onap/so/cnfm/lcm/bpmn/flows/utils/OffsetDateTimeTypeAdapterTest.java new file mode 100644 index 0000000..0adbef0 --- /dev/null +++ b/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/test/java/org/onap/so/cnfm/lcm/bpmn/flows/utils/OffsetDateTimeTypeAdapterTest.java @@ -0,0 +1,66 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2023 Nordix Foundation. + * ================================================================================ + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ +package org.onap.so.cnfm.lcm.bpmn.flows.utils; + +import static org.junit.Assert.assertEquals; +import java.io.IOException; +import java.io.Reader; +import java.io.StringReader; +import java.io.StringWriter; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; +import org.junit.Test; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +/** + * + * @author Waqas Ikram (waqas.ikram@est.tech) + * + */ +public class OffsetDateTimeTypeAdapterTest { + private static final OffsetDateTime OFF_SET_DATETIME_VALUE = + OffsetDateTime.of(2023, 1, 1, 12, 00, 15, 0, ZoneOffset.UTC); + private static final String STRING_VALUE = "\"2023-01-01T12:00:15Z\""; + + @Test + public void testReadWithValidOffsetDateTimeString() throws IOException { + final OffsetDateTimeTypeAdapter objUnderTest = new OffsetDateTimeTypeAdapter(); + + final Reader reader = new StringReader(STRING_VALUE); + final JsonReader jsonReader = new JsonReader(reader); + + final OffsetDateTime actual = objUnderTest.read(jsonReader); + assertEquals(OFF_SET_DATETIME_VALUE, actual); + + } + + @Test + public void testWritedWithValidOffsetDateTime() throws IOException { + final OffsetDateTimeTypeAdapter objUnderTest = new OffsetDateTimeTypeAdapter(); + + final StringWriter writer = new StringWriter(); + final JsonWriter jsonWriter = new JsonWriter(writer); + + objUnderTest.write(jsonWriter, OFF_SET_DATETIME_VALUE); + assertEquals(STRING_VALUE, writer.toString()); + + } +} diff --git a/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/test/java/org/onap/so/cnfm/lcm/bpmn/flows/utils/PropertiesToYamlConverterTest.java b/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/test/java/org/onap/so/cnfm/lcm/bpmn/flows/utils/PropertiesToYamlConverterTest.java new file mode 100644 index 0000000..2410d73 --- /dev/null +++ b/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/test/java/org/onap/so/cnfm/lcm/bpmn/flows/utils/PropertiesToYamlConverterTest.java @@ -0,0 +1,45 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2023 Nordix Foundation. + * ================================================================================ + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ +package org.onap.so.cnfm.lcm.bpmn.flows.utils; + +import static org.junit.Assert.assertEquals; +import java.util.Map; +import org.junit.Test; + +/** + * + * @author Waqas Ikram (waqas.ikram@est.tech) + * + */ +public class PropertiesToYamlConverterTest { + + @Test + public void testGetValuesYamlFileContent() { + final String expected = "primary:\n" + " service:\n" + " nodePorts:\n" + " mysql: '1234'\n" + + " ports:\n" + " mysql: dummy\n"; + final PropertiesToYamlConverter objUnderTest = new PropertiesToYamlConverter(); + final Map lifeCycleParams = + Map.of("primary.service.ports.mysql", "dummy", "primary.service.nodePorts.mysql", "1234"); + final String actual = objUnderTest.getValuesYamlFileContent(lifeCycleParams); + + assertEquals(expected, actual); + + } +} diff --git a/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/test/resources/application.yaml b/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/test/resources/application.yaml new file mode 100644 index 0000000..404bbdb --- /dev/null +++ b/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/test/resources/application.yaml @@ -0,0 +1,52 @@ +# Copyright © 2022 Nordix Foundation +# +# 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. +spring: + main: + allow-bean-definition-overriding: true + datasource: + hikari: + camunda: + jdbcUrl: jdbc:h2:mem:example-simple;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE + driver-class-name: org.h2.Driver + pool-name: cnfm-lcm-bpmn-pool + registerMbeans: true + cnfm: + jdbcUrl: jdbc:h2:mem:nfvo;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE;INIT=CREATE SCHEMA IF NOT EXISTS cnfm;MODE=MYSQL;DATABASE_TO_LOWER=TRUE;CASE_INSENSITIVE_IDENTIFIERS=TRUE + driver-class-name: org.h2.Driver + pool-name: cnfm-lcm-bpmn-pool + registerMbeans: true + jpa: + generate-ddl: true + hibernate: + ddl-auto: create +hibernate: + dialect: org.hibernate.dialect.H2Dialect + hbm2ddl: + auto: create +aai: + version: v19 + endpoint: http://localhost:${wiremock.server.port} +sdc: + endpoint: http://localhost:${wiremock.server.port} +logging: + level: + org.reflections.Reflections: ERROR +cnfm: + kube-configs-dir: ${java.io.tmpdir}/kube-configs + csar: + dir: ${java.io.tmpdir} +kubernetes: + client: + http-request: + timeoutSeconds: 1 \ No newline at end of file diff --git a/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/test/resources/request.json b/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/test/resources/request.json new file mode 100644 index 0000000..042247f --- /dev/null +++ b/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/test/resources/request.json @@ -0,0 +1,25 @@ +{ + "deploymentItems": [ + { + "deploymentItemsId": "1", + "lifecycleParameterKeyValues": { + ".Values.primary.service.ports.mysql": "dummy", + ".Values.primary.service.nodePorts.mysql": "dummy" + } + }, + { + "deploymentItemsId": "2", + "lifecycleParameterKeyValues": { + + } + } + ], + "asdExtCpdInputParams": { + "extCpdId": null, + "loadbalanceIP": null, + "externalIPs": [], + "nadNames": [], + "nadNamespace": null + }, + "additionalParams": {} +} \ No newline at end of file diff --git a/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/test/resources/resource-Generatedasdpackage-csar.csar b/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-bpmn-flows/src/test/resources/resource-Generatedasdpackage-csar.csar new file mode 100644 index 0000000000000000000000000000000000000000..b3c629dab2a27207b87d24c97a500a8f5874971f GIT binary patch literal 72173 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

Rd@t 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 literal 0 HcmV?d00001 diff --git a/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-database-service/src/main/java/org/onap/so/cnfm/lcm/database/beans/AsLifecycleParam.java b/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-database-service/src/main/java/org/onap/so/cnfm/lcm/database/beans/AsLifecycleParam.java index 607886c..ed8b098 100644 --- a/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-database-service/src/main/java/org/onap/so/cnfm/lcm/database/beans/AsLifecycleParam.java +++ b/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-database-service/src/main/java/org/onap/so/cnfm/lcm/database/beans/AsLifecycleParam.java @@ -54,7 +54,7 @@ public class AsLifecycleParam { private String lifecycleParam; public AsLifecycleParam() { - + // default constructor } public void setAsLifecycleParamId(final Integer asLifecycleParamId) { diff --git a/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-database-service/src/main/java/org/onap/so/cnfm/lcm/database/beans/utils/Utils.java b/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-database-service/src/main/java/org/onap/so/cnfm/lcm/database/beans/utils/Utils.java index 4d40087..5e1c638 100644 --- a/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-database-service/src/main/java/org/onap/so/cnfm/lcm/database/beans/utils/Utils.java +++ b/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-database-service/src/main/java/org/onap/so/cnfm/lcm/database/beans/utils/Utils.java @@ -40,19 +40,20 @@ public class Utils { return second == null; } - if (first.isEmpty()) { - return second.isEmpty(); + if (second == null) { + return false; + } + + if (first.size() != second.size()) { + return false; } - if ((first != null && second != null) && (first.size() == second.size())) { - for (int index = 0; index < first.size(); index++) { - if (!Objects.equals(first.get(index), second.get(index))) { - return false; - } - } - return true; + for (int index = 0; index < first.size(); index++) { + if (!Objects.equals(first.get(index), second.get(index))) { + return false; + } } - return false; + return true; } diff --git a/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-database-service/src/main/java/org/onap/so/cnfm/lcm/database/config/CnfmDatabaseConfiguration.java b/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-database-service/src/main/java/org/onap/so/cnfm/lcm/database/config/CnfmDatabaseConfiguration.java index 62a90f8..043b846 100644 --- a/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-database-service/src/main/java/org/onap/so/cnfm/lcm/database/config/CnfmDatabaseConfiguration.java +++ b/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-database-service/src/main/java/org/onap/so/cnfm/lcm/database/config/CnfmDatabaseConfiguration.java @@ -55,9 +55,6 @@ public class CnfmDatabaseConfiguration { private static final String PERSISTENCE_UNIT = "cnfm"; private static final String CNFM_DATA_SOURCE_QUALIFIER = "cnfmDataSource"; - @Autowired(required = false) - private MBeanExporter mBeanExporter; - @Bean @ConfigurationProperties(prefix = "spring.datasource.hikari.cnfm") public HikariConfig cnfmDbConfig() { @@ -68,7 +65,7 @@ public class CnfmDatabaseConfiguration { @Primary @FlywayDataSource @Bean(name = CNFM_DATA_SOURCE_QUALIFIER) - public DataSource dataSource() { + public DataSource dataSource(@Autowired(required = false) final MBeanExporter mBeanExporter) { if (mBeanExporter != null) { mBeanExporter.addExcludedBean(CNFM_DATA_SOURCE_QUALIFIER); } diff --git a/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-database-service/src/test/java/org/onap/so/cnfm/lcm/database/beans/utils/UtilsTest.java b/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-database-service/src/test/java/org/onap/so/cnfm/lcm/database/beans/utils/UtilsTest.java index ab24f94..9d9714e 100644 --- a/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-database-service/src/test/java/org/onap/so/cnfm/lcm/database/beans/utils/UtilsTest.java +++ b/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-database-service/src/test/java/org/onap/so/cnfm/lcm/database/beans/utils/UtilsTest.java @@ -50,6 +50,11 @@ public class UtilsTest { public void testNullListAndEmptyList_notEqual() { assertFalse(Utils.isEquals(null, Collections.emptyList())); } + + @Test + public void testEmptyListAndNullList_notEqual() { + assertFalse(Utils.isEquals(Collections.emptyList(), null)); + } @Test public void testTwoNotEmptyListsContainSameObjects_equal() { diff --git a/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-database-service/src/test/java/org/onap/so/cnfm/lcm/database/service/DatabaseServiceProviderTest.java b/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-database-service/src/test/java/org/onap/so/cnfm/lcm/database/service/DatabaseServiceProviderTest.java index d353ea6..4397fb5 100644 --- a/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-database-service/src/test/java/org/onap/so/cnfm/lcm/database/service/DatabaseServiceProviderTest.java +++ b/so-cnfm/so-cnfm-lcm/so-cnfm-lcm-database-service/src/test/java/org/onap/so/cnfm/lcm/database/service/DatabaseServiceProviderTest.java @@ -1,6 +1,6 @@ /*- * ============LICENSE_START======================================================= - * Copyright (C) 2022 Nordix Foundation. + * Copyright (C) 2023 Nordix Foundation. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. -- 2.16.6