diff options
Diffstat (limited to 'runtime-acm/src/main')
43 files changed, 5742 insertions, 0 deletions
diff --git a/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/Application.java b/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/Application.java new file mode 100644 index 000000000..d9298b15e --- /dev/null +++ b/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/Application.java @@ -0,0 +1,53 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2021 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.policy.clamp.acm.runtime; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.domain.EntityScan; +import org.springframework.boot.context.properties.ConfigurationPropertiesScan; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; +import org.springframework.scheduling.annotation.EnableScheduling; + +// @formatter:off +@EnableScheduling +@SpringBootApplication +@EnableJpaRepositories({ + "org.onap.policy.clamp.models.acm.persistence.repository" +}) +@ComponentScan({ + "org.onap.policy.clamp.models.acm.persistence.provider", + "org.onap.policy.clamp.acm.runtime", + "org.onap.policy.clamp.common.acm.rest" +}) +@ConfigurationPropertiesScan("org.onap.policy.clamp.acm.runtime.main.parameters") +@EntityScan({ + "org.onap.policy.models.tosca.simple.concepts", + "org.onap.policy.clamp.models.acm.persistence.concepts" +}) +//@formatter:on +public class Application { + + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } +} diff --git a/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/commissioning/CommissioningProvider.java b/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/commissioning/CommissioningProvider.java new file mode 100644 index 000000000..dfb9d151b --- /dev/null +++ b/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/commissioning/CommissioningProvider.java @@ -0,0 +1,383 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2021 Nordix Foundation. + * Modifications Copyright (C) 2021 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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.policy.clamp.acm.runtime.commissioning; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.module.jsonSchema.factories.SchemaFactoryWrapper; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import javax.ws.rs.core.Response.Status; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.collections4.MapUtils; +import org.onap.policy.clamp.acm.runtime.supervision.SupervisionHandler; +import org.onap.policy.clamp.models.acm.concepts.Participant; +import org.onap.policy.clamp.models.acm.messages.rest.commissioning.CommissioningResponse; +import org.onap.policy.clamp.models.acm.persistence.provider.AutomationCompositionProvider; +import org.onap.policy.clamp.models.acm.persistence.provider.ParticipantProvider; +import org.onap.policy.clamp.models.acm.persistence.provider.ServiceTemplateProvider; +import org.onap.policy.models.base.PfModelException; +import org.onap.policy.models.tosca.authorative.concepts.ToscaCapabilityType; +import org.onap.policy.models.tosca.authorative.concepts.ToscaConceptIdentifier; +import org.onap.policy.models.tosca.authorative.concepts.ToscaDataType; +import org.onap.policy.models.tosca.authorative.concepts.ToscaNodeTemplate; +import org.onap.policy.models.tosca.authorative.concepts.ToscaNodeType; +import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicyType; +import org.onap.policy.models.tosca.authorative.concepts.ToscaRelationshipType; +import org.onap.policy.models.tosca.authorative.concepts.ToscaServiceTemplate; +import org.onap.policy.models.tosca.authorative.concepts.ToscaServiceTemplates; +import org.onap.policy.models.tosca.authorative.concepts.ToscaTopologyTemplate; +import org.onap.policy.models.tosca.authorative.concepts.ToscaTypedEntityFilter; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +/** + * This class provides the create, read and delete actions on Commissioning of automation composition concepts in the + * database to the callers. + */ +@Service +@Transactional +public class CommissioningProvider { + public static final String AUTOMATION_COMPOSITION_NODE_TYPE = "org.onap.policy.clamp.acm.AutomationComposition"; + private static final String INSTANCE_TEXT = "_Instance"; + + private final ServiceTemplateProvider serviceTemplateProvider; + private final AutomationCompositionProvider acProvider; + private final ObjectMapper mapper = new ObjectMapper(); + private final ParticipantProvider participantProvider; + private final SupervisionHandler supervisionHandler; + + /** + * Create a commissioning provider. + * + * @param serviceTemplateProvider the ServiceTemplate Provider + * @param acProvider the AutomationComposition Provider + * @param supervisionHandler the Supervision Handler + * @param participantProvider the Participant Provider + */ + public CommissioningProvider(ServiceTemplateProvider serviceTemplateProvider, + AutomationCompositionProvider acProvider, SupervisionHandler supervisionHandler, + ParticipantProvider participantProvider) { + this.serviceTemplateProvider = serviceTemplateProvider; + this.acProvider = acProvider; + this.supervisionHandler = supervisionHandler; + this.participantProvider = participantProvider; + mapper.setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE); + } + + /** + * Create automation compositions from a service template. + * + * @param serviceTemplate the service template + * @return the result of the commissioning operation + * @throws PfModelException on creation errors + */ + public CommissioningResponse createAutomationCompositionDefinitions(ToscaServiceTemplate serviceTemplate) + throws PfModelException { + + if (verifyIfInstancePropertiesExists()) { + throw new PfModelException(Status.BAD_REQUEST, + "Delete instances, to commission automation composition definitions"); + } + serviceTemplate = serviceTemplateProvider.createServiceTemplate(serviceTemplate); + List<Participant> participantList = participantProvider.getParticipants(); + if (!participantList.isEmpty()) { + supervisionHandler.handleSendCommissionMessage(serviceTemplate.getName(), serviceTemplate.getVersion()); + } + var response = new CommissioningResponse(); + // @formatter:off + response.setAffectedAutomationCompositionDefinitions( + serviceTemplate.getToscaTopologyTemplate().getNodeTemplates() + .values() + .stream() + .map(template -> template.getKey().asIdentifier()) + .collect(Collectors.toList())); + // @formatter:on + + return response; + } + + /** + * Delete the automation composition definition with the given name and version. + * + * @param name the name of the automation composition definition to delete + * @param version the version of the automation composition to delete + * @return the result of the deletion + * @throws PfModelException on deletion errors + */ + public CommissioningResponse deleteAutomationCompositionDefinition(String name, String version) + throws PfModelException { + + if (verifyIfInstancePropertiesExists()) { + throw new PfModelException(Status.BAD_REQUEST, + "Delete instances, to commission automation composition definitions"); + } + List<Participant> participantList = participantProvider.getParticipants(); + if (!participantList.isEmpty()) { + supervisionHandler.handleSendDeCommissionMessage(); + } + serviceTemplateProvider.deleteServiceTemplate(name, version); + var response = new CommissioningResponse(); + response.setAffectedAutomationCompositionDefinitions(List.of(new ToscaConceptIdentifier(name, version))); + + return response; + } + + /** + * Get automation composition node templates. + * + * @param acName the name of the automation composition, null for all + * @param acVersion the version of the automation composition, null for all + * @return list of automation composition node templates + * @throws PfModelException on errors getting automation composition definitions + */ + @Transactional(readOnly = true) + public List<ToscaNodeTemplate> getAutomationCompositionDefinitions(String acName, String acVersion) + throws PfModelException { + + // @formatter:off + ToscaTypedEntityFilter<ToscaNodeTemplate> nodeTemplateFilter = ToscaTypedEntityFilter + .<ToscaNodeTemplate>builder() + .name(acName) + .version(acVersion) + .type(AUTOMATION_COMPOSITION_NODE_TYPE) + .build(); + // @formatter:on + + return acProvider.getFilteredNodeTemplates(nodeTemplateFilter); + } + + /** + * Get the automation composition elements from a automation composition node template. + * + * @param automationCompositionNodeTemplate the automation composition node template + * @return a list of the automation composition element node templates in a automation composition node template + * @throws PfModelException on errors get automation composition element node templates + */ + @Transactional(readOnly = true) + public List<ToscaNodeTemplate> getAutomationCompositionElementDefinitions( + ToscaNodeTemplate automationCompositionNodeTemplate) throws PfModelException { + if (!AUTOMATION_COMPOSITION_NODE_TYPE.equals(automationCompositionNodeTemplate.getType())) { + return Collections.emptyList(); + } + + if (MapUtils.isEmpty(automationCompositionNodeTemplate.getProperties())) { + return Collections.emptyList(); + } + + @SuppressWarnings("unchecked") + List<Map<String, String>> automationCompositionElements = + (List<Map<String, String>>) automationCompositionNodeTemplate.getProperties().get("elements"); + + if (CollectionUtils.isEmpty(automationCompositionElements)) { + return Collections.emptyList(); + } + + List<ToscaNodeTemplate> automationCompositionElementList = new ArrayList<>(); + // @formatter:off + automationCompositionElementList.addAll( + automationCompositionElements + .stream() + .map(elementMap -> acProvider.getNodeTemplates(elementMap.get("name"), + elementMap.get("version"))) + .flatMap(List::stream) + .collect(Collectors.toList()) + ); + // @formatter:on + + return automationCompositionElementList; + } + + /** + * Get node templates with common properties added. + * + * @param common boolean indicating common or instance properties to be used + * @param name the name of the definition to use, null for all definitions + * @param version the version of the definition to use, null for all definitions + * @return the nodes templates with common or instance properties + * @throws PfModelException on errors getting common or instance properties from node_templates + */ + @Transactional(readOnly = true) + public Map<String, ToscaNodeTemplate> getNodeTemplatesWithCommonOrInstanceProperties(boolean common, String name, + String version) throws PfModelException { + + if (common && verifyIfInstancePropertiesExists()) { + throw new PfModelException(Status.BAD_REQUEST, + "Cannot create or edit common properties, delete all the instantiations first"); + } + + var serviceTemplateList = serviceTemplateProvider.getServiceTemplateList(name, version); + var commonOrInstanceNodeTypeProps = + serviceTemplateProvider.getCommonOrInstancePropertiesFromNodeTypes(common, serviceTemplateList.get(0)); + + var serviceTemplates = new ToscaServiceTemplates(); + serviceTemplates.setServiceTemplates(filterToscaNodeTemplateInstance(serviceTemplateList)); + + return serviceTemplateProvider.getDerivedCommonOrInstanceNodeTemplates( + serviceTemplates.getServiceTemplates().get(0).getToscaTopologyTemplate().getNodeTemplates(), + commonOrInstanceNodeTypeProps); + } + + /** + * Get the requested automation composition definitions. + * + * @param name the name of the definition to get, null for all definitions + * @param version the version of the definition to get, null for all definitions + * @return the automation composition definitions + * @throws PfModelException on errors getting automation composition definitions + */ + @Transactional(readOnly = true) + public ToscaServiceTemplate getToscaServiceTemplate(String name, String version) throws PfModelException { + return serviceTemplateProvider.getToscaServiceTemplate(name, version); + } + + /** + * Get All the requested automation composition definitions. + * + * @return the automation composition definitions + * @throws PfModelException on errors getting automation composition definitions + */ + @Transactional(readOnly = true) + public List<ToscaServiceTemplate> getAllToscaServiceTemplate() throws PfModelException { + return serviceTemplateProvider.getAllServiceTemplates(); + } + + /** + * Get the tosca service template with only required sections. + * + * @param name the name of the template to get, null for all definitions + * @param version the version of the template to get, null for all definitions + * @return the tosca service template + * @throws PfModelException on errors getting tosca service template + */ + @Transactional(readOnly = true) + public String getToscaServiceTemplateReduced(String name, String version) throws PfModelException { + var serviceTemplateList = serviceTemplateProvider.getServiceTemplateList(name, version); + + List<ToscaServiceTemplate> filteredServiceTemplateList = filterToscaNodeTemplateInstance(serviceTemplateList); + + if (filteredServiceTemplateList.isEmpty()) { + throw new PfModelException(Status.BAD_REQUEST, "Invalid Service Template"); + } + + ToscaServiceTemplate fullTemplate = filteredServiceTemplateList.get(0); + + var template = new HashMap<String, Object>(); + template.put("tosca_definitions_version", fullTemplate.getToscaDefinitionsVersion()); + template.put("data_types", fullTemplate.getDataTypes()); + template.put("policy_types", fullTemplate.getPolicyTypes()); + template.put("node_types", fullTemplate.getNodeTypes()); + template.put("topology_template", fullTemplate.getToscaTopologyTemplate()); + + try { + return mapper.writerWithDefaultPrettyPrinter().writeValueAsString(template); + + } catch (JsonProcessingException e) { + throw new PfModelException(Status.BAD_REQUEST, "Converion to Json Schema failed", e); + } + } + + /** + * Get the requested json schema. + * + * @param section section of the tosca service template to get schema for + * @return the specified tosca service template or section Json Schema + * @throws PfModelException on errors with retrieving the classes + */ + @Transactional(readOnly = true) + public String getToscaServiceTemplateSchema(String section) throws PfModelException { + var visitor = new SchemaFactoryWrapper(); + + try { + switch (section) { + case "data_types": + mapper.acceptJsonFormatVisitor(mapper.constructType(ToscaDataType.class), visitor); + break; + case "capability_types": + mapper.acceptJsonFormatVisitor(mapper.constructType(ToscaCapabilityType.class), visitor); + break; + case "node_types": + mapper.acceptJsonFormatVisitor(mapper.constructType(ToscaNodeType.class), visitor); + break; + case "relationship_types": + mapper.acceptJsonFormatVisitor(mapper.constructType(ToscaRelationshipType.class), visitor); + break; + case "policy_types": + mapper.acceptJsonFormatVisitor(mapper.constructType(ToscaPolicyType.class), visitor); + break; + case "topology_template": + mapper.acceptJsonFormatVisitor(mapper.constructType(ToscaTopologyTemplate.class), visitor); + break; + case "node_templates": + mapper.acceptJsonFormatVisitor( + mapper.getTypeFactory().constructCollectionType(List.class, ToscaNodeTemplate.class), visitor); + break; + default: + mapper.acceptJsonFormatVisitor(mapper.constructType(ToscaServiceTemplate.class), visitor); + } + + var jsonSchema = visitor.finalSchema(); + return mapper.writerWithDefaultPrettyPrinter().writeValueAsString(jsonSchema); + } catch (JsonProcessingException e) { + throw new PfModelException(Status.BAD_REQUEST, "Converion to Json Schema failed", e); + } + } + + private List<ToscaServiceTemplate> filterToscaNodeTemplateInstance(List<ToscaServiceTemplate> serviceTemplates) { + + List<ToscaServiceTemplate> toscaServiceTemplates = new ArrayList<>(); + + serviceTemplates.stream().forEach(serviceTemplate -> { + + Map<String, ToscaNodeTemplate> toscaNodeTemplates = new HashMap<>(); + + serviceTemplate.getToscaTopologyTemplate().getNodeTemplates().forEach((key, nodeTemplate) -> { + if (!nodeTemplate.getName().contains(INSTANCE_TEXT)) { + toscaNodeTemplates.put(key, nodeTemplate); + } + }); + + serviceTemplate.getToscaTopologyTemplate().getNodeTemplates().clear(); + serviceTemplate.getToscaTopologyTemplate().setNodeTemplates(toscaNodeTemplates); + + toscaServiceTemplates.add(serviceTemplate); + }); + + return toscaServiceTemplates; + } + + /** + * Validates to see if there is any instance properties saved. + * + * @return true if exists instance properties + */ + private boolean verifyIfInstancePropertiesExists() { + return acProvider.getAllNodeTemplates().stream() + .anyMatch(nodeTemplate -> nodeTemplate.getKey().getName().contains(INSTANCE_TEXT)); + + } +} diff --git a/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/config/AafConfiguration.java b/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/config/AafConfiguration.java new file mode 100644 index 000000000..b1f408048 --- /dev/null +++ b/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/config/AafConfiguration.java @@ -0,0 +1,43 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2021 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. + * ============LICENSE_END============================================ + * =================================================================== + * + */ + +package org.onap.policy.clamp.acm.runtime.config; + +import javax.servlet.Filter; +import org.onap.policy.clamp.acm.runtime.main.web.AutomationConfiguraitonAafFilter; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; + +@Configuration +@Profile("clamp-aaf-authentication") +public class AafConfiguration { + + /** + * Method to return AAF filter. + * + * @return Filter + */ + @Bean + public Filter aafFilter() { + return new AutomationConfiguraitonAafFilter(); + } + +} diff --git a/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/config/ConverterConfiguration.java b/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/config/ConverterConfiguration.java new file mode 100644 index 000000000..f51497266 --- /dev/null +++ b/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/config/ConverterConfiguration.java @@ -0,0 +1,44 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2021 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.policy.clamp.acm.runtime.config; + +import java.util.Arrays; +import java.util.List; +import org.onap.policy.clamp.common.acm.rest.CoderHttpMesageConverter; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.MediaType; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.converter.StringHttpMessageConverter; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +public class ConverterConfiguration implements WebMvcConfigurer { + + @Override + public void extendMessageConverters(List<HttpMessageConverter<?>> converters) { + converters.add(new CoderHttpMesageConverter<>("yaml")); + converters.add(new CoderHttpMesageConverter<>("json")); + + StringHttpMessageConverter converter = new StringHttpMessageConverter(); + converter.setSupportedMediaTypes(Arrays.asList(MediaType.TEXT_PLAIN)); + converters.add(converter); + } +} diff --git a/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/config/FilterConfig.java b/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/config/FilterConfig.java new file mode 100644 index 000000000..4dcd94c25 --- /dev/null +++ b/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/config/FilterConfig.java @@ -0,0 +1,45 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2021 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.policy.clamp.acm.runtime.config; + +import org.onap.policy.clamp.common.acm.rest.RequestResponseLoggingFilter; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class FilterConfig { + + /** + * Logging Filter configuration. + * + * @return FilterRegistrationBean + */ + @Bean + public FilterRegistrationBean<RequestResponseLoggingFilter> loggingFilter() { + FilterRegistrationBean<RequestResponseLoggingFilter> registrationBean = new FilterRegistrationBean<>(); + + registrationBean.setFilter(new RequestResponseLoggingFilter()); + registrationBean.addUrlPatterns("/onap/policy/clamp/acm/v2/*"); + + return registrationBean; + } +} diff --git a/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/config/SecurityConfig.java b/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/config/SecurityConfig.java new file mode 100644 index 000000000..ade7c56b3 --- /dev/null +++ b/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/config/SecurityConfig.java @@ -0,0 +1,47 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2021 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.policy.clamp.acm.runtime.config; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; + +@Configuration +public class SecurityConfig extends WebSecurityConfigurerAdapter { + + @Value("${security.enable-csrf:true}") + private boolean csrfEnabled = true; + + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http.authorizeRequests() + .antMatchers().authenticated() + .anyRequest().authenticated() + .and().httpBasic(); + // @formatter:on + + if (!csrfEnabled) { + http.csrf().disable(); + } + } +} diff --git a/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/config/SpringFoxConfig.java b/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/config/SpringFoxConfig.java new file mode 100644 index 000000000..94c8bce06 --- /dev/null +++ b/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/config/SpringFoxConfig.java @@ -0,0 +1,45 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2021 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.policy.clamp.acm.runtime.config; + +import org.onap.policy.clamp.acm.runtime.main.rest.MonitoringQueryController; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import springfox.documentation.builders.PathSelectors; +import springfox.documentation.builders.RequestHandlerSelectors; +import springfox.documentation.spi.DocumentationType; +import springfox.documentation.spring.web.plugins.Docket; + +@Configuration +public class SpringFoxConfig { + + /** + * Docket Spring Fox Config. + * + * @return Docket + */ + @Bean + public Docket api() { + return new Docket(DocumentationType.SWAGGER_2).select() + .apis(RequestHandlerSelectors.basePackage(MonitoringQueryController.class.getPackageName())) + .paths(PathSelectors.any()).build(); + } +} diff --git a/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/config/messaging/Listener.java b/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/config/messaging/Listener.java new file mode 100644 index 000000000..23240ab8a --- /dev/null +++ b/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/config/messaging/Listener.java @@ -0,0 +1,41 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2021 Nordix Foundation. + * Modifications Copyright (C) 2021 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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.policy.clamp.acm.runtime.config.messaging; + +import org.onap.policy.common.endpoints.listeners.ScoListener; + +public interface Listener<T> { + + /** + * Get the type of message of interest to the listener. + * + * @return type of message of interest to the listener + */ + String getType(); + + /** + * Get listener to register. + * + * @return listener to register + */ + ScoListener<T> getScoListener(); +} diff --git a/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/config/messaging/MessageDispatcherActivator.java b/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/config/messaging/MessageDispatcherActivator.java new file mode 100644 index 000000000..0d9de205e --- /dev/null +++ b/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/config/messaging/MessageDispatcherActivator.java @@ -0,0 +1,137 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2021 Nordix Foundation. + * Modifications Copyright (C) 2021 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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.policy.clamp.acm.runtime.config.messaging; + +import java.io.Closeable; +import java.io.IOException; +import java.util.List; +import lombok.Getter; +import org.onap.policy.clamp.acm.runtime.main.parameters.AcRuntimeParameterGroup; +import org.onap.policy.clamp.common.acm.exception.AutomationCompositionRuntimeException; +import org.onap.policy.common.endpoints.event.comm.TopicEndpointManager; +import org.onap.policy.common.endpoints.event.comm.TopicSink; +import org.onap.policy.common.endpoints.event.comm.TopicSource; +import org.onap.policy.common.endpoints.listeners.MessageTypeDispatcher; +import org.onap.policy.common.utils.services.ServiceManagerContainer; +import org.springframework.context.event.ContextClosedEvent; +import org.springframework.context.event.ContextRefreshedEvent; +import org.springframework.context.event.EventListener; +import org.springframework.stereotype.Component; + +@Component +public class MessageDispatcherActivator extends ServiceManagerContainer implements Closeable { + + private static final String[] MSG_TYPE_NAMES = {"messageType"}; + + // Topics from which the application receives and to which the application sends messages + private List<TopicSink> topicSinks; + private List<TopicSource> topicSources; + + @Getter + private final MessageTypeDispatcher msgDispatcher; + + /** + * Constructor. + * + * @param acRuntimeParameterGroup the parameters for the automation composition runtime service + * @param publishers list of Publishers + * @param listeners list of Listeners + * @throws AutomationCompositionRuntimeException if the activator does not start + */ + public <T> MessageDispatcherActivator(final AcRuntimeParameterGroup acRuntimeParameterGroup, + List<Publisher> publishers, List<Listener<T>> listeners) { + topicSinks = TopicEndpointManager.getManager() + .addTopicSinks(acRuntimeParameterGroup.getTopicParameterGroup().getTopicSinks()); + + topicSources = TopicEndpointManager.getManager() + .addTopicSources(acRuntimeParameterGroup.getTopicParameterGroup().getTopicSources()); + + msgDispatcher = new MessageTypeDispatcher(MSG_TYPE_NAMES); + + // @formatter:off + addAction("Topic endpoint management", + () -> TopicEndpointManager.getManager().start(), + () -> TopicEndpointManager.getManager().shutdown()); + + publishers.forEach(publisher -> + addAction("Publisher " + publisher.getClass().getSimpleName(), + () -> publisher.active(topicSinks), + publisher::stop)); + + listeners.forEach(listener -> + addAction("Listener " + listener.getClass().getSimpleName(), + () -> msgDispatcher.register(listener.getType(), listener.getScoListener()), + () -> msgDispatcher.unregister(listener.getType()))); + + addAction("Topic Message Dispatcher", this::registerMsgDispatcher, this::unregisterMsgDispatcher); + // @formatter:on + } + + /** + * Registers the dispatcher with the topic source(s). + */ + private void registerMsgDispatcher() { + for (final TopicSource source : topicSources) { + source.register(msgDispatcher); + } + } + + /** + * Unregisters the dispatcher from the topic source(s). + */ + private void unregisterMsgDispatcher() { + for (final TopicSource source : topicSources) { + source.unregister(msgDispatcher); + } + } + + /** + * Start Manager after the application is Started. + * + * @param cre Refreshed Event + */ + @EventListener + public void handleContextStart(ContextRefreshedEvent cre) { + if (!isAlive()) { + start(); + } + } + + /** + * Handle ContextClosedEvent. + * + * @param ctxClosedEvent ContextClosedEvent + */ + @EventListener + public void handleContextClosedEvent(ContextClosedEvent ctxClosedEvent) { + if (isAlive()) { + stop(); + } + } + + @Override + public void close() throws IOException { + if (isAlive()) { + super.shutdown(); + } + } +} diff --git a/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/config/messaging/Publisher.java b/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/config/messaging/Publisher.java new file mode 100644 index 000000000..a7acc47b3 --- /dev/null +++ b/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/config/messaging/Publisher.java @@ -0,0 +1,34 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2021 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.policy.clamp.acm.runtime.config.messaging; + +import java.util.List; +import org.onap.policy.common.endpoints.event.comm.TopicSink; + +/** + * Publisher. + */ +public interface Publisher { + + void active(List<TopicSink> topicSinks); + + void stop(); +} diff --git a/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/instantiation/AutomationCompositionInstantiationProvider.java b/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/instantiation/AutomationCompositionInstantiationProvider.java new file mode 100644 index 000000000..39d84026b --- /dev/null +++ b/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/instantiation/AutomationCompositionInstantiationProvider.java @@ -0,0 +1,637 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2021 Nordix Foundation. + * Modifications Copyright (C) 2021 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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.policy.clamp.acm.runtime.instantiation; + +import com.google.gson.Gson; +import com.google.gson.internal.LinkedTreeMap; +import com.google.gson.reflect.TypeToken; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.function.Function; +import java.util.function.UnaryOperator; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.Response.Status; +import lombok.AllArgsConstructor; +import org.onap.policy.clamp.acm.runtime.commissioning.CommissioningProvider; +import org.onap.policy.clamp.acm.runtime.supervision.SupervisionHandler; +import org.onap.policy.clamp.common.acm.exception.AutomationCompositionException; +import org.onap.policy.clamp.models.acm.concepts.AutomationComposition; +import org.onap.policy.clamp.models.acm.concepts.AutomationCompositionElement; +import org.onap.policy.clamp.models.acm.concepts.AutomationCompositionOrderedState; +import org.onap.policy.clamp.models.acm.concepts.AutomationCompositionState; +import org.onap.policy.clamp.models.acm.concepts.AutomationCompositions; +import org.onap.policy.clamp.models.acm.concepts.Participant; +import org.onap.policy.clamp.models.acm.messages.rest.GenericNameVersion; +import org.onap.policy.clamp.models.acm.messages.rest.instantiation.AutomationCompositionOrderStateResponse; +import org.onap.policy.clamp.models.acm.messages.rest.instantiation.AutomationCompositionPrimed; +import org.onap.policy.clamp.models.acm.messages.rest.instantiation.AutomationCompositionPrimedResponse; +import org.onap.policy.clamp.models.acm.messages.rest.instantiation.InstancePropertiesResponse; +import org.onap.policy.clamp.models.acm.messages.rest.instantiation.InstantiationCommand; +import org.onap.policy.clamp.models.acm.messages.rest.instantiation.InstantiationResponse; +import org.onap.policy.clamp.models.acm.persistence.provider.AutomationCompositionProvider; +import org.onap.policy.clamp.models.acm.persistence.provider.ParticipantProvider; +import org.onap.policy.common.parameters.BeanValidationResult; +import org.onap.policy.common.parameters.ObjectValidationResult; +import org.onap.policy.common.parameters.ValidationResult; +import org.onap.policy.common.parameters.ValidationStatus; +import org.onap.policy.models.base.PfModelException; +import org.onap.policy.models.tosca.authorative.concepts.ToscaConceptIdentifier; +import org.onap.policy.models.tosca.authorative.concepts.ToscaNameVersion; +import org.onap.policy.models.tosca.authorative.concepts.ToscaNodeTemplate; +import org.onap.policy.models.tosca.authorative.concepts.ToscaServiceTemplate; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +/** + * This class is dedicated to the Instantiation of Commissioned automation composition. + */ +@Service +@Transactional +@AllArgsConstructor +public class AutomationCompositionInstantiationProvider { + private static final String AUTOMATION_COMPOSITION_NODE_TYPE = "org.onap.policy.clamp.acm.AutomationComposition"; + private static final String AUTOMATION_COMPOSITION_NODE_ELEMENT_TYPE = "AutomationCompositionElement"; + private static final String PARTICIPANT_ID_PROPERTY_KEY = "participant_id"; + private static final String PARTICIPANT_TYPE_PROPERTY_KEY = "participantType"; + private static final String AC_ELEMENT_NAME = "name"; + private static final String AC_ELEMENT_VERSION = "version"; + private static final String INSTANCE_TEXT = "_Instance"; + + private static final Gson GSON = new Gson(); + + private final AutomationCompositionProvider automationCompositionProvider; + private final CommissioningProvider commissioningProvider; + private final SupervisionHandler supervisionHandler; + private final ParticipantProvider participantProvider; + private static final String ENTRY = "entry "; + + /** + * Creates Instance Properties and automation composition. + * + * @param serviceTemplate the service template + * @return the result of the instantiation operation + * @throws PfModelException on creation errors + */ + public InstancePropertiesResponse createInstanceProperties(ToscaServiceTemplate serviceTemplate) + throws PfModelException { + + String instanceName = generateSequentialInstanceName(); + AutomationComposition automationComposition = new AutomationComposition(); + Map<UUID, AutomationCompositionElement> automationCompositionElements = new HashMap<>(); + + ToscaServiceTemplate toscaServiceTemplate = commissioningProvider.getAllToscaServiceTemplate().get(0); + + Map<String, ToscaNodeTemplate> persistedNodeTemplateMap = + toscaServiceTemplate.getToscaTopologyTemplate().getNodeTemplates(); + + Map<String, ToscaNodeTemplate> nodeTemplates = deepCloneNodeTemplate(serviceTemplate); + + nodeTemplates.forEach((key, template) -> { + ToscaNodeTemplate newNodeTemplate = new ToscaNodeTemplate(); + String name = key + instanceName; + String version = template.getVersion(); + String description = template.getDescription() + instanceName; + newNodeTemplate.setName(name); + newNodeTemplate.setVersion(version); + newNodeTemplate.setDescription(description); + newNodeTemplate.setProperties(new HashMap<>(template.getProperties())); + newNodeTemplate.setType(template.getType()); + newNodeTemplate.setTypeVersion(template.getTypeVersion()); + newNodeTemplate.setMetadata(template.getMetadata()); + + crateNewAutomationCompositionInstance(instanceName, automationComposition, automationCompositionElements, + template, newNodeTemplate); + + persistedNodeTemplateMap.put(name, newNodeTemplate); + }); + + AutomationCompositions automationCompositions = new AutomationCompositions(); + + serviceTemplate.getToscaTopologyTemplate().getNodeTemplates().putAll(persistedNodeTemplateMap); + + automationComposition.setElements(automationCompositionElements); + automationCompositions.getAutomationCompositionList().add(automationComposition); + + return saveInstancePropertiesAndAutomationComposition(serviceTemplate, automationCompositions); + } + + /** + * Deletes Instance Properties. + * + * @param name the name of the automation composition to delete + * @param version the version of the automation composition to delete + * @return the result of the deletion + * @throws PfModelException on deletion errors + */ + public InstantiationResponse deleteInstanceProperties(String name, String version) throws PfModelException { + + String instanceName = getInstancePropertyName(name, version); + + Map<String, ToscaNodeTemplate> filteredToscaNodeTemplateMap = new HashMap<>(); + + ToscaServiceTemplate toscaServiceTemplate = commissioningProvider.getAllToscaServiceTemplate().get(0); + + toscaServiceTemplate.getToscaTopologyTemplate().getNodeTemplates().forEach((key, nodeTemplate) -> { + if (!nodeTemplate.getName().contains(instanceName)) { + filteredToscaNodeTemplateMap.put(key, nodeTemplate); + } + }); + + List<ToscaNodeTemplate> filteredToscaNodeTemplateList = + toscaServiceTemplate.getToscaTopologyTemplate().getNodeTemplates().values().stream() + .filter(nodeTemplate -> nodeTemplate.getName().contains(instanceName)).collect(Collectors.toList()); + + InstantiationResponse response = this.deleteAutomationComposition(name, version); + + automationCompositionProvider.deleteInstanceProperties(filteredToscaNodeTemplateMap, + filteredToscaNodeTemplateList); + + return response; + } + + /** + * Create automation compositions. + * + * @param automationCompositions the automation composition + * @return the result of the instantiation operation + * @throws PfModelException on creation errors + */ + public InstantiationResponse createAutomationCompositions(AutomationCompositions automationCompositions) + throws PfModelException { + for (AutomationComposition automationComposition : automationCompositions.getAutomationCompositionList()) { + var checkAutomationCompositionOpt = + automationCompositionProvider.findAutomationComposition(automationComposition.getKey().asIdentifier()); + if (checkAutomationCompositionOpt.isPresent()) { + throw new PfModelException(Response.Status.BAD_REQUEST, + automationComposition.getKey().asIdentifier() + " already defined"); + } + } + BeanValidationResult validationResult = validateAutomationCompositions(automationCompositions); + if (!validationResult.isValid()) { + throw new PfModelException(Response.Status.BAD_REQUEST, validationResult.getResult()); + } + automationCompositionProvider.saveAutomationCompositions(automationCompositions.getAutomationCompositionList()); + + var response = new InstantiationResponse(); + response.setAffectedAutomationCompositions(automationCompositions.getAutomationCompositionList().stream() + .map(ac -> ac.getKey().asIdentifier()).collect(Collectors.toList())); + + return response; + } + + /** + * Update automation compositions. + * + * @param automationCompositions the automation composition + * @return the result of the instantiation operation + * @throws PfModelException on update errors + */ + public InstantiationResponse updateAutomationCompositions(AutomationCompositions automationCompositions) + throws PfModelException { + BeanValidationResult validationResult = validateAutomationCompositions(automationCompositions); + if (!validationResult.isValid()) { + throw new PfModelException(Response.Status.BAD_REQUEST, validationResult.getResult()); + } + automationCompositionProvider.saveAutomationCompositions(automationCompositions.getAutomationCompositionList()); + + var response = new InstantiationResponse(); + response.setAffectedAutomationCompositions(automationCompositions.getAutomationCompositionList().stream() + .map(ac -> ac.getKey().asIdentifier()).collect(Collectors.toList())); + + return response; + } + + /** + * Validate AutomationCompositions. + * + * @param automationCompositions AutomationCompositions to validate + * @return the result of validation + * @throws PfModelException if automationCompositions is not valid + */ + private BeanValidationResult validateAutomationCompositions(AutomationCompositions automationCompositions) + throws PfModelException { + + var result = new BeanValidationResult("AutomationCompositions", automationCompositions); + + for (AutomationComposition automationComposition : automationCompositions.getAutomationCompositionList()) { + var subResult = new BeanValidationResult(ENTRY + automationComposition.getDefinition().getName(), + automationComposition); + + List<ToscaNodeTemplate> toscaNodeTemplates = commissioningProvider.getAutomationCompositionDefinitions( + automationComposition.getDefinition().getName(), automationComposition.getDefinition().getVersion()); + + if (toscaNodeTemplates.isEmpty()) { + subResult.addResult( + new ObjectValidationResult("AutomationComposition", automationComposition.getDefinition().getName(), + ValidationStatus.INVALID, "Commissioned automation composition definition not found")); + } else if (toscaNodeTemplates.size() > 1) { + subResult.addResult( + new ObjectValidationResult("AutomationComposition", automationComposition.getDefinition().getName(), + ValidationStatus.INVALID, "Commissioned automation composition definition not valid")); + } else { + + List<ToscaNodeTemplate> acElementDefinitions = + commissioningProvider.getAutomationCompositionElementDefinitions(toscaNodeTemplates.get(0)); + + // @formatter:off + Map<String, ToscaConceptIdentifier> definitions = acElementDefinitions + .stream() + .map(nodeTemplate -> nodeTemplate.getKey().asIdentifier()) + .collect(Collectors.toMap(ToscaConceptIdentifier::getName, UnaryOperator.identity())); + // @formatter:on + + for (AutomationCompositionElement element : automationComposition.getElements().values()) { + subResult.addResult(validateDefinition(definitions, element.getDefinition())); + } + } + result.addResult(subResult); + } + return result; + } + + /** + * Validate ToscaConceptIdentifier, checking if exist in ToscaConceptIdentifiers map. + * + * @param definitions map of all ToscaConceptIdentifiers + * @param definition ToscaConceptIdentifier to validate + * @return the validation result + */ + private ValidationResult validateDefinition(Map<String, ToscaConceptIdentifier> definitions, + ToscaConceptIdentifier definition) { + var result = new BeanValidationResult(ENTRY + definition.getName(), definition); + ToscaConceptIdentifier identifier = definitions.get(definition.getName()); + if (identifier == null) { + result.setResult(ValidationStatus.INVALID, "Not found"); + } else if (!identifier.equals(definition)) { + result.setResult(ValidationStatus.INVALID, "Version not matching"); + } + return (result.isClean() ? null : result); + } + + /** + * Delete the automation composition with the given name and version. + * + * @param name the name of the automation composition to delete + * @param version the version of the automation composition to delete + * @return the result of the deletion + * @throws PfModelException on deletion errors + */ + public InstantiationResponse deleteAutomationComposition(String name, String version) throws PfModelException { + var automationCompositionOpt = automationCompositionProvider.findAutomationComposition(name, version); + if (automationCompositionOpt.isEmpty()) { + throw new PfModelException(Response.Status.NOT_FOUND, "Automation composition not found"); + } + var automationComposition = automationCompositionOpt.get(); + if (!AutomationCompositionState.UNINITIALISED.equals(automationComposition.getState())) { + throw new PfModelException(Response.Status.BAD_REQUEST, + "Automation composition state is still " + automationComposition.getState()); + } + var response = new InstantiationResponse(); + response.setAffectedAutomationCompositions( + List.of(automationCompositionProvider.deleteAutomationComposition(name, version).getKey().asIdentifier())); + return response; + } + + /** + * Get the requested automation compositions. + * + * @param name the name of the automation composition to get, null for all automation compositions + * @param version the version of the automation composition to get, null for all automation compositions + * @return the automation compositions + * @throws PfModelException on errors getting automation compositions + */ + @Transactional(readOnly = true) + public AutomationCompositions getAutomationCompositions(String name, String version) throws PfModelException { + var automationCompositions = new AutomationCompositions(); + automationCompositions + .setAutomationCompositionList(automationCompositionProvider.getAutomationCompositions(name, version)); + + return automationCompositions; + } + + /** + * Issue a command to automation compositions, setting their ordered state. + * + * @param command the command to issue to automation compositions + * @return the result of the initiation command + * @throws PfModelException on errors setting the ordered state on the automation compositions + * @throws AutomationCompositionException on ordered state invalid + */ + public InstantiationResponse issueAutomationCompositionCommand(InstantiationCommand command) + throws AutomationCompositionException, PfModelException { + + if (command.getOrderedState() == null) { + throw new AutomationCompositionException(Status.BAD_REQUEST, + "ordered state invalid or not specified on command"); + } + + var participants = participantProvider.getParticipants(); + if (participants.isEmpty()) { + throw new AutomationCompositionException(Status.BAD_REQUEST, "No participants registered"); + } + var validationResult = new BeanValidationResult("InstantiationCommand", command); + List<AutomationComposition> automationCompositions = + new ArrayList<>(command.getAutomationCompositionIdentifierList().size()); + for (ToscaConceptIdentifier id : command.getAutomationCompositionIdentifierList()) { + var automationCompositionOpt = automationCompositionProvider.findAutomationComposition(id); + if (automationCompositionOpt.isEmpty()) { + validationResult.addResult("ToscaConceptIdentifier", id, ValidationStatus.INVALID, + "AutomationComposition with id " + id + " not found"); + } else { + var automationComposition = automationCompositionOpt.get(); + automationComposition.setCascadedOrderedState(command.getOrderedState()); + automationCompositions.add(automationComposition); + } + } + if (validationResult.isValid()) { + validationResult = validateIssueAutomationCompositions(automationCompositions, participants); + } + if (!validationResult.isValid()) { + throw new PfModelException(Response.Status.BAD_REQUEST, validationResult.getResult()); + } + automationCompositionProvider.saveAutomationCompositions(automationCompositions); + + supervisionHandler.triggerAutomationCompositionSupervision(command.getAutomationCompositionIdentifierList()); + var response = new InstantiationResponse(); + response.setAffectedAutomationCompositions(command.getAutomationCompositionIdentifierList()); + + return response; + } + + private BeanValidationResult validateIssueAutomationCompositions(List<AutomationComposition> automationCompositions, + List<Participant> participants) { + var result = new BeanValidationResult("AutomationCompositions", automationCompositions); + + Map<ToscaConceptIdentifier, Participant> participantMap = participants.stream() + .collect(Collectors.toMap(participant -> participant.getKey().asIdentifier(), Function.identity())); + + for (AutomationComposition automationComposition : automationCompositions) { + + for (var element : automationComposition.getElements().values()) { + + var subResult = new BeanValidationResult(ENTRY + element.getDefinition().getName(), element); + Participant p = participantMap.get(element.getParticipantId()); + if (p == null) { + subResult.addResult(new ObjectValidationResult(AUTOMATION_COMPOSITION_NODE_ELEMENT_TYPE, + element.getDefinition().getName(), ValidationStatus.INVALID, + "Participant with ID " + element.getParticipantId() + " is not registered")); + } else if (!p.getParticipantType().equals(element.getParticipantType())) { + subResult.addResult(new ObjectValidationResult(AUTOMATION_COMPOSITION_NODE_ELEMENT_TYPE, + element.getDefinition().getName(), ValidationStatus.INVALID, + "Participant with ID " + element.getParticipantType() + " - " + element.getParticipantId() + + " is not registered")); + } + result.addResult(subResult); + } + + } + + return result; + } + + /** + * Gets a list of automation compositions with it's ordered state. + * + * @param name the name of the automation composition to get, null for all automation compositions + * @param version the version of the automation composition to get, null for all automation compositions + * @return a list of Instantiation Command + * @throws PfModelException on errors getting automation compositions + */ + @Transactional(readOnly = true) + public AutomationCompositionOrderStateResponse getInstantiationOrderState(String name, String version) + throws PfModelException { + + List<AutomationComposition> automationCompositions = + automationCompositionProvider.getAutomationCompositions(name, version); + + var response = new AutomationCompositionOrderStateResponse(); + + automationCompositions.forEach(automationComposition -> { + var genericNameVersion = new GenericNameVersion(); + genericNameVersion.setName(automationComposition.getName()); + genericNameVersion.setVersion(automationComposition.getVersion()); + response.getAutomationCompositionIdentifierList().add(genericNameVersion); + }); + + return response; + } + + /** + * Saves Instance Properties and automation composition. + * Gets a list of automation compositions which are primed or de-primed. + * + * @param name the name of the automation composition to get, null for all automation compositions + * @param version the version of the automation composition to get, null for all automation compositions + * @return a list of Instantiation Command + * @throws PfModelException on errors getting automation compositions + */ + @Transactional(readOnly = true) + public AutomationCompositionPrimedResponse getAutomationCompositionPriming(String name, String version) + throws PfModelException { + + List<AutomationComposition> automationCompositions = + automationCompositionProvider.getAutomationCompositions(name, version); + + var response = new AutomationCompositionPrimedResponse(); + + automationCompositions.forEach(automationComposition -> { + var primed = new AutomationCompositionPrimed(); + primed.setName(automationComposition.getName()); + primed.setVersion(automationComposition.getVersion()); + primed.setPrimed(automationComposition.getPrimed()); + response.getPrimedAutomationCompositionsList().add(primed); + }); + + return response; + } + + /** + * Creates instance element name. + * + * @param serviceTemplate the service template + * @param automationCompositions a list of automation compositions + * @return the result of the instance properties and instantiation operation + * @throws PfModelException on creation errors + */ + private InstancePropertiesResponse saveInstancePropertiesAndAutomationComposition( + ToscaServiceTemplate serviceTemplate, AutomationCompositions automationCompositions) throws PfModelException { + + for (var automationComposition : automationCompositions.getAutomationCompositionList()) { + var checkAutomationCompositionOpt = + automationCompositionProvider.findAutomationComposition(automationComposition.getKey().asIdentifier()); + if (checkAutomationCompositionOpt.isPresent()) { + throw new PfModelException(Response.Status.BAD_REQUEST, "Automation composition with id " + + automationComposition.getKey().asIdentifier() + " already defined"); + } + } + Map<String, ToscaNodeTemplate> toscaSavedNodeTemplate = + automationCompositionProvider.saveInstanceProperties(serviceTemplate); + automationCompositionProvider.saveAutomationCompositions(automationCompositions.getAutomationCompositionList()); + List<ToscaConceptIdentifier> affectedAutomationCompositions = automationCompositions + .getAutomationCompositionList().stream().map(ac -> ac.getKey().asIdentifier()).collect(Collectors.toList()); + + List<ToscaConceptIdentifier> toscaAffectedProperties = toscaSavedNodeTemplate.values().stream() + .map(template -> template.getKey().asIdentifier()).collect(Collectors.toList()); + + var response = new InstancePropertiesResponse(); + response.setAffectedInstanceProperties(Stream.of(affectedAutomationCompositions, toscaAffectedProperties) + .flatMap(Collection::stream).collect(Collectors.toList())); + + return response; + } + + /** + * Crates a new automation composition instance. + * + * @param instanceName automation composition Instance name + * @param automationComposition empty automation composition + * @param automationCompositionElements new automation composition Element map + * @param template original Cloned Tosca Node Template + * @param newNodeTemplate new Tosca Node Template + */ + private void crateNewAutomationCompositionInstance(String instanceName, AutomationComposition automationComposition, + Map<UUID, AutomationCompositionElement> automationCompositionElements, ToscaNodeTemplate template, + ToscaNodeTemplate newNodeTemplate) { + if (template.getType().equals(AUTOMATION_COMPOSITION_NODE_TYPE)) { + automationComposition.setDefinition(getAutomationCompositionDefinition(newNodeTemplate)); + } + + if (template.getType().contains(AUTOMATION_COMPOSITION_NODE_ELEMENT_TYPE)) { + AutomationCompositionElement automationCompositionElement = + getAutomationCompositionElement(newNodeTemplate); + automationCompositionElements.put(automationCompositionElement.getId(), automationCompositionElement); + } + + automationComposition.setName("PMSH" + instanceName); + automationComposition.setVersion(template.getVersion()); + automationComposition.setDescription("PMSH automation composition " + instanceName); + automationComposition.setState(AutomationCompositionState.UNINITIALISED); + automationComposition.setOrderedState(AutomationCompositionOrderedState.UNINITIALISED); + } + + /** + * Get's the instance property name of the automation composition. + * + * @param name the name of the automation composition to get, null for all automation compositions + * @param version the version of the automation composition to get, null for all automation compositions + * @return the instance name of the automation composition instance properties + * @throws PfModelException on errors getting automation compositions + */ + private String getInstancePropertyName(String name, String version) throws PfModelException { + List<String> toscaDefinitionsNames = + automationCompositionProvider.getAutomationCompositions(name, version).stream() + .map(AutomationComposition::getDefinition).map(ToscaNameVersion::getName).collect(Collectors.toList()); + + return toscaDefinitionsNames.stream().reduce("", (s1, s2) -> { + + if (s2.contains(INSTANCE_TEXT)) { + String[] instances = s2.split(INSTANCE_TEXT); + + return INSTANCE_TEXT + instances[1]; + } + + return s1; + }); + } + + /** + * Generates Instance Name in sequential order and return it to append to the Node Template Name. + * + * @return instanceName + */ + private String generateSequentialInstanceName() { + List<ToscaNodeTemplate> nodeTemplates = automationCompositionProvider.getAllNodeTemplates(); + + int instanceNumber = nodeTemplates.stream().map(ToscaNodeTemplate::getName) + .filter(name -> name.contains(INSTANCE_TEXT)).map(n -> { + String[] defNameArr = n.split(INSTANCE_TEXT); + + return Integer.parseInt(defNameArr[1]); + }).reduce(0, Math::max); + + return INSTANCE_TEXT + (instanceNumber + 1); + } + + /** + * Retrieves automation composition Definition. + * + * @param template tosca node template + * @return automation composition definition + */ + private ToscaConceptIdentifier getAutomationCompositionDefinition(ToscaNodeTemplate template) { + ToscaConceptIdentifier definition = new ToscaConceptIdentifier(); + definition.setName(template.getName()); + definition.setVersion(template.getVersion()); + return definition; + } + + /** + * Retrieves automation composition Element. + * + * @param template tosca node template + * @return a automation composition element + */ + @SuppressWarnings("unchecked") + private AutomationCompositionElement getAutomationCompositionElement(ToscaNodeTemplate template) { + AutomationCompositionElement automationCompositionElement = new AutomationCompositionElement(); + ToscaConceptIdentifier definition = new ToscaConceptIdentifier(); + definition.setName(template.getName()); + definition.setVersion(template.getVersion()); + automationCompositionElement.setDefinition(definition); + LinkedTreeMap<String, Object> participantId = + (LinkedTreeMap<String, Object>) template.getProperties().get(PARTICIPANT_ID_PROPERTY_KEY); + if (participantId != null) { + ToscaConceptIdentifier participantIdProperty = new ToscaConceptIdentifier(); + participantIdProperty.setName(String.valueOf(participantId.get(AC_ELEMENT_NAME))); + participantIdProperty.setVersion(String.valueOf(participantId.get(AC_ELEMENT_VERSION))); + automationCompositionElement.setParticipantId(participantIdProperty); + } + LinkedTreeMap<String, Object> participantType = + (LinkedTreeMap<String, Object>) template.getProperties().get(PARTICIPANT_TYPE_PROPERTY_KEY); + if (participantType != null) { + ToscaConceptIdentifier participantTypeProperty = new ToscaConceptIdentifier(); + participantTypeProperty.setName(String.valueOf(participantType.get(AC_ELEMENT_NAME))); + participantTypeProperty.setVersion(participantType.get(AC_ELEMENT_VERSION).toString()); + automationCompositionElement.setParticipantType(participantTypeProperty); + } + return automationCompositionElement; + } + + /** + * Deep clones ToscaNodeTemplate. + * + * @param serviceTemplate ToscaServiceTemplate + * @return a cloned Hash Map of ToscaNodeTemplate + */ + private Map<String, ToscaNodeTemplate> deepCloneNodeTemplate(ToscaServiceTemplate serviceTemplate) { + String jsonString = GSON.toJson(serviceTemplate.getToscaTopologyTemplate().getNodeTemplates()); + Type type = new TypeToken<HashMap<String, ToscaNodeTemplate>>() {}.getType(); + return GSON.fromJson(jsonString, type); + } +} diff --git a/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/main/parameters/AcRuntimeParameterGroup.java b/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/main/parameters/AcRuntimeParameterGroup.java new file mode 100644 index 000000000..563da1268 --- /dev/null +++ b/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/main/parameters/AcRuntimeParameterGroup.java @@ -0,0 +1,49 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2021 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.policy.clamp.acm.runtime.main.parameters; + +import javax.validation.Valid; +import javax.validation.constraints.NotNull; +import lombok.Getter; +import lombok.Setter; +import org.onap.policy.common.endpoints.parameters.TopicParameterGroup; +import org.onap.policy.common.parameters.validation.ParameterGroupConstraint; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.validation.annotation.Validated; + +/** + * Class to hold all parameters needed for the ACM runtime component. + * + */ +@Validated +@Getter +@Setter +@ConfigurationProperties(prefix = "runtime") +public class AcRuntimeParameterGroup { + + @Valid + @NotNull + private ParticipantParameters participantParameters; + + @NotNull + @ParameterGroupConstraint + private TopicParameterGroup topicParameterGroup; +} diff --git a/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/main/parameters/ParticipantParameters.java b/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/main/parameters/ParticipantParameters.java new file mode 100644 index 000000000..248824f11 --- /dev/null +++ b/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/main/parameters/ParticipantParameters.java @@ -0,0 +1,45 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2021 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. + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.clamp.acm.runtime.main.parameters; + +import javax.validation.Valid; +import javax.validation.constraints.Min; +import javax.validation.constraints.NotNull; +import lombok.Getter; +import lombok.Setter; +import org.springframework.validation.annotation.Validated; + +/** + * Parameters for communicating with participants. + */ +@Getter +@Setter +@Validated +public class ParticipantParameters { + + @Min(100) + private long heartBeatMs; + + @Min(100) + private long maxStatusWaitMs; + + @Valid + @NotNull + private ParticipantUpdateParameters updateParameters; +} diff --git a/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/main/parameters/ParticipantUpdateParameters.java b/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/main/parameters/ParticipantUpdateParameters.java new file mode 100644 index 000000000..5ffaf39c0 --- /dev/null +++ b/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/main/parameters/ParticipantUpdateParameters.java @@ -0,0 +1,46 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2021 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. + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.clamp.acm.runtime.main.parameters; + +import javax.validation.constraints.Min; +import lombok.Getter; +import lombok.Setter; +import org.springframework.validation.annotation.Validated; + +/** + * Parameters for Participant UPDATE requests. + */ +@Getter +@Setter +@Validated +public class ParticipantUpdateParameters { + + /** + * Maximum number of times to re-send a request to a PDP. + */ + @Min(value = 1) + private int maxRetryCount; + + /** + * Maximum time to wait, in milliseconds, for a PDP response. + */ + @Min(value = 100) + private long maxWaitMs; + +} diff --git a/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/main/rest/CommissioningController.java b/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/main/rest/CommissioningController.java new file mode 100644 index 000000000..0fd8661b4 --- /dev/null +++ b/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/main/rest/CommissioningController.java @@ -0,0 +1,501 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2021 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.policy.clamp.acm.runtime.main.rest; + +import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.ApiParam; +import io.swagger.annotations.ApiResponse; +import io.swagger.annotations.ApiResponses; +import io.swagger.annotations.Authorization; +import io.swagger.annotations.Extension; +import io.swagger.annotations.ExtensionProperty; +import io.swagger.annotations.ResponseHeader; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import javax.ws.rs.core.Response.Status; +import lombok.RequiredArgsConstructor; +import org.onap.policy.clamp.acm.runtime.commissioning.CommissioningProvider; +import org.onap.policy.clamp.acm.runtime.main.web.AbstractRestController; +import org.onap.policy.clamp.common.acm.exception.AutomationCompositionException; +import org.onap.policy.clamp.models.acm.messages.rest.commissioning.CommissioningResponse; +import org.onap.policy.models.base.PfModelException; +import org.onap.policy.models.tosca.authorative.concepts.ToscaNodeTemplate; +import org.onap.policy.models.tosca.authorative.concepts.ToscaServiceTemplate; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +/** + * Class to provide REST end points for creating, deleting, querying commissioned automation compositions. + */ +@RestController +@RequiredArgsConstructor +public class CommissioningController extends AbstractRestController { + + private static final String TAGS = "Clamp Automation Composition Commissioning API"; + + private final CommissioningProvider provider; + + /** + * Creates a automation composition definition. + * + * @param requestId request ID used in ONAP logging + * @param body the body of automation composition following TOSCA definition + * @return a response + * @throws PfModelException on errors creating a automation composition definition + */ + // @formatter:off + @PostMapping(value = "/commission", + consumes = {MediaType.APPLICATION_JSON_VALUE, APPLICATION_YAML}, + produces = {MediaType.APPLICATION_JSON_VALUE, APPLICATION_YAML}) + @ApiOperation( + value = "Commissions automation composition definitions", + notes = "Commissions automation composition definitions, returning commissioned definition IDs", + response = CommissioningResponse.class, + tags = {TAGS}, + authorizations = @Authorization(value = AUTHORIZATION_TYPE), + responseHeaders = { + @ResponseHeader( + name = VERSION_MINOR_NAME, + description = VERSION_MINOR_DESCRIPTION, + response = String.class), + @ResponseHeader( + name = VERSION_PATCH_NAME, + description = VERSION_PATCH_DESCRIPTION, + response = String.class), + @ResponseHeader( + name = VERSION_LATEST_NAME, + description = VERSION_LATEST_DESCRIPTION, + response = String.class), + @ResponseHeader( + name = REQUEST_ID_NAME, + description = REQUEST_ID_HDR_DESCRIPTION, + response = UUID.class) + }, + extensions = { + @Extension + ( + name = EXTENSION_NAME, + properties = { + @ExtensionProperty(name = API_VERSION_NAME, value = API_VERSION), + @ExtensionProperty(name = LAST_MOD_NAME, value = LAST_MOD_RELEASE) + } + ) + } + ) + @ApiResponses( + value = { + @ApiResponse(code = AUTHENTICATION_ERROR_CODE, message = AUTHENTICATION_ERROR_MESSAGE), + @ApiResponse(code = AUTHORIZATION_ERROR_CODE, message = AUTHORIZATION_ERROR_MESSAGE), + @ApiResponse(code = SERVER_ERROR_CODE, message = SERVER_ERROR_MESSAGE) + } + ) + // @formatter:on + public ResponseEntity<CommissioningResponse> create( + @RequestHeader(name = REQUEST_ID_NAME, required = false) @ApiParam(REQUEST_ID_PARAM_DESCRIPTION) UUID requestId, + @ApiParam( + value = "Entity Body of Automation Composition", + required = true) @RequestBody ToscaServiceTemplate body) + throws PfModelException { + + return ResponseEntity.ok().body(provider.createAutomationCompositionDefinitions(body)); + } + + /** + * Deletes a automation composition definition. + * + * @param requestId request ID used in ONAP logging + * @param name the name of the automation composition definition to delete + * @param version the version of the automation composition definition to delete + * @return a response + * @throws PfModelException on errors deleting a automation composition definition + */ + // @formatter:off + @DeleteMapping(value = "/commission", + produces = {MediaType.APPLICATION_JSON_VALUE, APPLICATION_YAML}) + @ApiOperation(value = "Delete a commissioned automation composition", + notes = "Deletes a Commissioned Automation Composition, returning optional error details", + response = CommissioningResponse.class, + tags = {TAGS}, + authorizations = @Authorization(value = AUTHORIZATION_TYPE), + responseHeaders = { + @ResponseHeader( + name = VERSION_MINOR_NAME, + description = VERSION_MINOR_DESCRIPTION, + response = String.class), + @ResponseHeader( + name = VERSION_PATCH_NAME, + description = VERSION_PATCH_DESCRIPTION, + response = String.class), + @ResponseHeader( + name = VERSION_LATEST_NAME, + description = VERSION_LATEST_DESCRIPTION, + response = String.class), + @ResponseHeader( + name = REQUEST_ID_NAME, + description = REQUEST_ID_HDR_DESCRIPTION, + response = UUID.class)}, + extensions = { + @Extension + ( + name = EXTENSION_NAME, + properties = { + @ExtensionProperty(name = API_VERSION_NAME, value = API_VERSION), + @ExtensionProperty(name = LAST_MOD_NAME, value = LAST_MOD_RELEASE) + } + ) + } + ) + @ApiResponses( + value = { + @ApiResponse(code = AUTHENTICATION_ERROR_CODE, message = AUTHENTICATION_ERROR_MESSAGE), + @ApiResponse(code = AUTHORIZATION_ERROR_CODE, message = AUTHORIZATION_ERROR_MESSAGE), + @ApiResponse(code = SERVER_ERROR_CODE, message = SERVER_ERROR_MESSAGE) + } + ) + // @formatter:on + public ResponseEntity<CommissioningResponse> delete( + @RequestHeader(name = REQUEST_ID_NAME, required = false) @ApiParam(REQUEST_ID_PARAM_DESCRIPTION) UUID requestId, + @ApiParam(value = "Automation composition definition name", required = true) @RequestParam( + value = "name") String name, + @ApiParam( + value = "Automation composition definition version", + required = true) @RequestParam("version") String version) + throws PfModelException { + + return ResponseEntity.ok().body(provider.deleteAutomationCompositionDefinition(name, version)); + } + + /** + * Queries details of all or specific automation composition definitions. + * + * @param requestId request ID used in ONAP logging + * @param name the name of the automation composition definition to get, null for all definitions + * @param version the version of the automation composition definition to get, null for all definitions + * @return the automation composition definitions + * @throws PfModelException on errors getting details of all or specific automation composition definitions + */ + // @formatter:off + @GetMapping(value = "/commission", + produces = {MediaType.APPLICATION_JSON_VALUE, APPLICATION_YAML}) + @ApiOperation(value = "Query details of the requested commissioned automation composition definitions", + notes = "Queries details of the requested commissioned automation composition definitions, " + + "returning all automation composition details", + response = ToscaNodeTemplate.class, + tags = {TAGS}, + authorizations = @Authorization(value = AUTHORIZATION_TYPE), + responseHeaders = { + @ResponseHeader( + name = VERSION_MINOR_NAME, description = VERSION_MINOR_DESCRIPTION, + response = String.class), + @ResponseHeader(name = VERSION_PATCH_NAME, description = VERSION_PATCH_DESCRIPTION, + response = String.class), + @ResponseHeader(name = VERSION_LATEST_NAME, description = VERSION_LATEST_DESCRIPTION, + response = String.class), + @ResponseHeader(name = REQUEST_ID_NAME, description = REQUEST_ID_HDR_DESCRIPTION, + response = UUID.class)}, + extensions = { + @Extension + ( + name = EXTENSION_NAME, + properties = { + @ExtensionProperty(name = API_VERSION_NAME, value = API_VERSION), + @ExtensionProperty(name = LAST_MOD_NAME, value = LAST_MOD_RELEASE) + } + ) + } + ) + @ApiResponses( + value = { + @ApiResponse(code = AUTHENTICATION_ERROR_CODE, message = AUTHENTICATION_ERROR_MESSAGE), + @ApiResponse(code = AUTHORIZATION_ERROR_CODE, message = AUTHORIZATION_ERROR_MESSAGE), + @ApiResponse(code = SERVER_ERROR_CODE, message = SERVER_ERROR_MESSAGE) + } + ) + // @formatter:on + public ResponseEntity<List<ToscaNodeTemplate>> query( + @RequestHeader(name = REQUEST_ID_NAME, required = false) @ApiParam(REQUEST_ID_PARAM_DESCRIPTION) UUID requestId, + @ApiParam(value = "Automation composition definition name", required = false) @RequestParam( + value = "name", + required = false) String name, + @ApiParam(value = "Automation composition definition version", required = false) @RequestParam( + value = "version", + required = false) String version) + throws PfModelException { + + return ResponseEntity.ok().body(provider.getAutomationCompositionDefinitions(name, version)); + } + + /** + * Retrieves the Tosca Service Template. + * + * @param requestId request ID used in ONAP logging + * @param name the name of the tosca service template to retrieve + * @param version the version of the tosca service template to get + * @return the specified tosca service template + * @throws PfModelException on errors getting the Tosca Service Template + */ + // @formatter:off + @GetMapping(value = "/commission/toscaservicetemplate", + produces = {MediaType.APPLICATION_JSON_VALUE, APPLICATION_YAML}) + @ApiOperation(value = "Query details of the requested tosca service templates", + notes = "Queries details of the requested commissioned tosca service template, " + + "returning all tosca service template details", + response = ToscaServiceTemplate.class, + tags = {TAGS}, + authorizations = @Authorization(value = AUTHORIZATION_TYPE), + responseHeaders = { + @ResponseHeader( + name = VERSION_MINOR_NAME, description = VERSION_MINOR_DESCRIPTION, + response = String.class), + @ResponseHeader(name = VERSION_PATCH_NAME, description = VERSION_PATCH_DESCRIPTION, + response = String.class), + @ResponseHeader(name = VERSION_LATEST_NAME, description = VERSION_LATEST_DESCRIPTION, + response = String.class), + @ResponseHeader(name = REQUEST_ID_NAME, description = REQUEST_ID_HDR_DESCRIPTION, + response = UUID.class)}, + extensions = { + @Extension + ( + name = EXTENSION_NAME, + properties = { + @ExtensionProperty(name = API_VERSION_NAME, value = API_VERSION), + @ExtensionProperty(name = LAST_MOD_NAME, value = LAST_MOD_RELEASE) + } + ) + } + ) + @ApiResponses( + value = { + @ApiResponse(code = AUTHENTICATION_ERROR_CODE, message = AUTHENTICATION_ERROR_MESSAGE), + @ApiResponse(code = AUTHORIZATION_ERROR_CODE, message = AUTHORIZATION_ERROR_MESSAGE), + @ApiResponse(code = SERVER_ERROR_CODE, message = SERVER_ERROR_MESSAGE) + } + ) + // @formatter:on + public ResponseEntity<String> queryToscaServiceTemplate( + @RequestHeader(name = REQUEST_ID_NAME, required = false) @ApiParam(REQUEST_ID_PARAM_DESCRIPTION) UUID requestId, + @ApiParam(value = "Tosca service template name", required = false) @RequestParam( + value = "name", + required = false) String name, + @ApiParam(value = "Tosca service template version", required = false) @RequestParam( + value = "version", + required = false) String version) + throws PfModelException { + + return ResponseEntity.ok().body(provider.getToscaServiceTemplateReduced(name, version)); + } + + /** + * Retrieves the Json Schema for the specified Tosca Service Template. + * + * @param requestId request ID used in ONAP logging + * @param section section of the tosca service template to get schema for + * @return the specified tosca service template or section Json Schema + * @throws PfModelException on errros getting the Json Schema for the specified Tosca Service Template + */ + // @formatter:off + @GetMapping(value = "/commission/toscaServiceTemplateSchema", + produces = {MediaType.APPLICATION_JSON_VALUE, APPLICATION_YAML}) + @ApiOperation(value = "Query details of the requested tosca service template json schema", + notes = "Queries details of the requested commissioned tosca service template json schema, " + + "returning all tosca service template json schema details", + response = ToscaServiceTemplate.class, + tags = {TAGS}, + authorizations = @Authorization(value = AUTHORIZATION_TYPE), + responseHeaders = { + @ResponseHeader( + name = VERSION_MINOR_NAME, description = VERSION_MINOR_DESCRIPTION, + response = String.class), + @ResponseHeader(name = VERSION_PATCH_NAME, description = VERSION_PATCH_DESCRIPTION, + response = String.class), + @ResponseHeader(name = VERSION_LATEST_NAME, description = VERSION_LATEST_DESCRIPTION, + response = String.class), + @ResponseHeader(name = REQUEST_ID_NAME, description = REQUEST_ID_HDR_DESCRIPTION, + response = UUID.class)}, + extensions = { + @Extension + ( + name = EXTENSION_NAME, + properties = { + @ExtensionProperty(name = API_VERSION_NAME, value = API_VERSION), + @ExtensionProperty(name = LAST_MOD_NAME, value = LAST_MOD_RELEASE) + } + ) + } + ) + @ApiResponses( + value = { + @ApiResponse(code = AUTHENTICATION_ERROR_CODE, message = AUTHENTICATION_ERROR_MESSAGE), + @ApiResponse(code = AUTHORIZATION_ERROR_CODE, message = AUTHORIZATION_ERROR_MESSAGE), + @ApiResponse(code = SERVER_ERROR_CODE, message = SERVER_ERROR_MESSAGE) + } + ) + // @formatter:on + public ResponseEntity<String> queryToscaServiceTemplateJsonSchema( + @RequestHeader(name = REQUEST_ID_NAME, required = false) @ApiParam(REQUEST_ID_PARAM_DESCRIPTION) UUID requestId, + @ApiParam( + value = "Section of Template schema is desired for", + required = false) @RequestParam(value = "section", required = false, defaultValue = "all") String section) + throws PfModelException { + + return ResponseEntity.ok().body(provider.getToscaServiceTemplateSchema(section)); + } + + /** + * Retrieves the Common or Instance Properties for the specified Tosca Service Template. + * + * @param requestId request ID used in ONAP logging + * @param common a flag, true to get common properties, false to get instance properties + * @param name the name of the tosca service template to retrieve + * @param version the version of the tosca service template to get + * @return the specified tosca service template or section Json Schema + * @throws PfModelException on errors getting the Common or Instance Properties + * @throws AutomationCompositionException on error getting the Common or Instance Properties + */ + // @formatter:off + @GetMapping(value = "/commission/getCommonOrInstanceProperties", + produces = {MediaType.APPLICATION_JSON_VALUE, APPLICATION_YAML}) + @ApiOperation(value = "Query details of the requested tosca service template common or instance properties", + notes = "Queries details of the requested commissioned tosca service template json common" + + "or instance properties, returning all tosca service template common or instance property details", + response = ToscaServiceTemplate.class, + tags = {"Clamp Automation Composition Commissioning API"}, + authorizations = @Authorization(value = AUTHORIZATION_TYPE), + responseHeaders = { + @ResponseHeader( + name = VERSION_MINOR_NAME, description = VERSION_MINOR_DESCRIPTION, + response = String.class), + @ResponseHeader(name = VERSION_PATCH_NAME, description = VERSION_PATCH_DESCRIPTION, + response = String.class), + @ResponseHeader(name = VERSION_LATEST_NAME, description = VERSION_LATEST_DESCRIPTION, + response = String.class), + @ResponseHeader(name = REQUEST_ID_NAME, description = REQUEST_ID_HDR_DESCRIPTION, + response = UUID.class)}, + extensions = { + @Extension + ( + name = EXTENSION_NAME, + properties = { + @ExtensionProperty(name = API_VERSION_NAME, value = API_VERSION), + @ExtensionProperty(name = LAST_MOD_NAME, value = LAST_MOD_RELEASE) + } + ) + } + ) + @ApiResponses( + value = { + @ApiResponse(code = AUTHENTICATION_ERROR_CODE, message = AUTHENTICATION_ERROR_MESSAGE), + @ApiResponse(code = AUTHORIZATION_ERROR_CODE, message = AUTHORIZATION_ERROR_MESSAGE), + @ApiResponse(code = SERVER_ERROR_CODE, message = SERVER_ERROR_MESSAGE) + } + ) + // @formatter:on + public ResponseEntity<Map<String, ToscaNodeTemplate>> queryToscaServiceCommonOrInstanceProperties( + @RequestHeader(name = REQUEST_ID_NAME, required = false) @ApiParam(REQUEST_ID_PARAM_DESCRIPTION) UUID requestId, + @ApiParam( + value = "Flag, true for common properties, false for instance", + required = false) @RequestParam(value = "common", defaultValue = "false", required = false) boolean common, + @ApiParam(value = "Tosca service template name", required = false) @RequestParam( + value = "name", + required = false) String name, + @ApiParam(value = "Tosca service template version", required = false) @RequestParam( + value = "version", + required = false) String version) + throws PfModelException { + + return ResponseEntity.ok().body(provider.getNodeTemplatesWithCommonOrInstanceProperties(common, name, version)); + } + + /** + * Queries the elements of a specific automation composition. + * + * @param requestId request ID used in ONAP logging + * @param name the name of the automation composition definition to get + * @param version the version of the automation composition definition to get + * @return the automation composition element definitions + * @throws PfModelException on errors getting the elements of a specific automation composition + */ + // @formatter:off + @GetMapping(value = "/commission/elements", + produces = {MediaType.APPLICATION_JSON_VALUE, APPLICATION_YAML}) + @ApiOperation(value = "Query details of the requested commissioned automation composition element definitions", + notes = "Queries details of the requested commissioned automation composition element definitions, " + + "returning all automation composition elements' details", + response = ToscaNodeTemplate.class, + tags = {TAGS}, + authorizations = @Authorization(value = AUTHORIZATION_TYPE), + responseHeaders = { + @ResponseHeader( + name = VERSION_MINOR_NAME, description = VERSION_MINOR_DESCRIPTION, + response = String.class), + @ResponseHeader(name = VERSION_PATCH_NAME, description = VERSION_PATCH_DESCRIPTION, + response = String.class), + @ResponseHeader(name = VERSION_LATEST_NAME, description = VERSION_LATEST_DESCRIPTION, + response = String.class), + @ResponseHeader(name = REQUEST_ID_NAME, description = REQUEST_ID_HDR_DESCRIPTION, + response = UUID.class)}, + extensions = { + @Extension + ( + name = EXTENSION_NAME, + properties = { + @ExtensionProperty(name = API_VERSION_NAME, value = API_VERSION), + @ExtensionProperty(name = LAST_MOD_NAME, value = LAST_MOD_RELEASE) + } + ) + } + ) + @ApiResponses( + value = { + @ApiResponse(code = AUTHENTICATION_ERROR_CODE, message = AUTHENTICATION_ERROR_MESSAGE), + @ApiResponse(code = AUTHORIZATION_ERROR_CODE, message = AUTHORIZATION_ERROR_MESSAGE), + @ApiResponse(code = SERVER_ERROR_CODE, message = SERVER_ERROR_MESSAGE) + } + ) + // @formatter:on + public ResponseEntity<List<ToscaNodeTemplate>> queryElements( + @RequestHeader(name = REQUEST_ID_NAME, required = false) @ApiParam(REQUEST_ID_PARAM_DESCRIPTION) UUID requestId, + @ApiParam(value = "Automation composition definition name", required = false) @RequestParam( + value = "name", + required = false) String name, + @ApiParam(value = "Automation composition definition version", required = false) @RequestParam( + value = "version", + required = false) String version) + throws PfModelException { + + List<ToscaNodeTemplate> nodeTemplate = provider.getAutomationCompositionDefinitions(name, version); + // Prevent ambiguous queries with multiple returns + if (nodeTemplate.size() > 1) { + throw new PfModelException(Status.NOT_ACCEPTABLE, "Multiple automation compositions are not supported"); + } + + List<ToscaNodeTemplate> response = provider.getAutomationCompositionElementDefinitions(nodeTemplate.get(0)); + return ResponseEntity.ok().body(response); + } +} diff --git a/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/main/rest/InstantiationController.java b/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/main/rest/InstantiationController.java new file mode 100644 index 000000000..dc56c77e7 --- /dev/null +++ b/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/main/rest/InstantiationController.java @@ -0,0 +1,623 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2021 Nordix Foundation. + * Modifications Copyright (C) 2021 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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.policy.clamp.acm.runtime.main.rest; + +import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.ApiParam; +import io.swagger.annotations.ApiResponse; +import io.swagger.annotations.ApiResponses; +import io.swagger.annotations.Authorization; +import io.swagger.annotations.Extension; +import io.swagger.annotations.ExtensionProperty; +import io.swagger.annotations.ResponseHeader; +import java.util.UUID; +import lombok.RequiredArgsConstructor; +import org.onap.policy.clamp.acm.runtime.instantiation.AutomationCompositionInstantiationProvider; +import org.onap.policy.clamp.acm.runtime.main.web.AbstractRestController; +import org.onap.policy.clamp.common.acm.exception.AutomationCompositionException; +import org.onap.policy.clamp.models.acm.concepts.AutomationCompositions; +import org.onap.policy.clamp.models.acm.messages.rest.instantiation.AutomationCompositionOrderStateResponse; +import org.onap.policy.clamp.models.acm.messages.rest.instantiation.AutomationCompositionPrimedResponse; +import org.onap.policy.clamp.models.acm.messages.rest.instantiation.InstancePropertiesResponse; +import org.onap.policy.clamp.models.acm.messages.rest.instantiation.InstantiationCommand; +import org.onap.policy.clamp.models.acm.messages.rest.instantiation.InstantiationResponse; +import org.onap.policy.models.base.PfModelException; +import org.onap.policy.models.tosca.authorative.concepts.ToscaServiceTemplate; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +/** + * Class to provide REST end points for creating, deleting, query and commanding a automation composition definition. + */ +@RestController +@RequiredArgsConstructor +public class InstantiationController extends AbstractRestController { + + private static final String TAGS = "Clamp Automation Composition Instantiation API"; + + // The Automation Composition provider for instantiation requests + private final AutomationCompositionInstantiationProvider provider; + + /** + * Creates a automation composition. + * + * @param requestId request ID used in ONAP logging + * @param automationCompositions the automation compositions + * @return a response + * @throws PfModelException on errors creating a automation composition + */ + // @formatter:off + @PostMapping(value = "/instantiation", + produces = {MediaType.APPLICATION_JSON_VALUE, APPLICATION_YAML}, + consumes = {MediaType.APPLICATION_JSON_VALUE, APPLICATION_YAML}) + @ApiOperation( + value = "Commissions automation composition definitions", + notes = "Commissions automation composition definitions, returning the automation composition IDs", + response = InstantiationResponse.class, + tags = {TAGS}, + authorizations = @Authorization(value = AUTHORIZATION_TYPE), + responseHeaders = { + @ResponseHeader( + name = VERSION_MINOR_NAME, + description = VERSION_MINOR_DESCRIPTION, + response = String.class), + @ResponseHeader( + name = VERSION_PATCH_NAME, + description = VERSION_PATCH_DESCRIPTION, + response = String.class), + @ResponseHeader( + name = VERSION_LATEST_NAME, + description = VERSION_LATEST_DESCRIPTION, + response = String.class), + @ResponseHeader( + name = REQUEST_ID_NAME, + description = REQUEST_ID_HDR_DESCRIPTION, + response = UUID.class) + }, + extensions = { + @Extension + ( + name = EXTENSION_NAME, + properties = { + @ExtensionProperty(name = API_VERSION_NAME, value = API_VERSION), + @ExtensionProperty(name = LAST_MOD_NAME, value = LAST_MOD_RELEASE) + } + ) + } + ) + @ApiResponses( + value = { + @ApiResponse(code = AUTHENTICATION_ERROR_CODE, message = AUTHENTICATION_ERROR_MESSAGE), + @ApiResponse(code = AUTHORIZATION_ERROR_CODE, message = AUTHORIZATION_ERROR_MESSAGE), + @ApiResponse(code = SERVER_ERROR_CODE, message = SERVER_ERROR_MESSAGE) + } + ) + // @formatter:on + public ResponseEntity<InstantiationResponse> create( + @RequestHeader(name = REQUEST_ID_NAME, required = false) @ApiParam(REQUEST_ID_PARAM_DESCRIPTION) UUID requestId, + @ApiParam( + value = "Entity Body of automation composition", + required = true) @RequestBody AutomationCompositions automationCompositions) + throws PfModelException { + + return ResponseEntity.ok().body(provider.createAutomationCompositions(automationCompositions)); + } + + /** + * Saves instance properties. + * + * @param requestId request ID used in ONAP logging + * @param body the body of automation composition following TOSCA definition + * @return a response + */ + // @formatter:off + @PostMapping(value = "/instanceProperties", + consumes = {MediaType.APPLICATION_JSON_VALUE, APPLICATION_YAML}, + produces = {MediaType.APPLICATION_JSON_VALUE, APPLICATION_YAML}) + @ApiOperation( + value = "Saves instance properties", + notes = "Saves instance properties, returning the saved instances properties and it's version", + response = InstancePropertiesResponse.class, + tags = {TAGS}, + authorizations = @Authorization(value = AUTHORIZATION_TYPE), + responseHeaders = { + @ResponseHeader( + name = VERSION_MINOR_NAME, + description = VERSION_MINOR_DESCRIPTION, + response = String.class), + @ResponseHeader( + name = VERSION_PATCH_NAME, + description = VERSION_PATCH_DESCRIPTION, + response = String.class), + @ResponseHeader( + name = VERSION_LATEST_NAME, + description = VERSION_LATEST_DESCRIPTION, + response = String.class), + @ResponseHeader( + name = REQUEST_ID_NAME, + description = REQUEST_ID_HDR_DESCRIPTION, + response = UUID.class) + }, + extensions = { + @Extension + ( + name = EXTENSION_NAME, + properties = { + @ExtensionProperty(name = API_VERSION_NAME, value = API_VERSION), + @ExtensionProperty(name = LAST_MOD_NAME, value = LAST_MOD_RELEASE) + } + ) + } + ) + @ApiResponses( + value = { + @ApiResponse(code = AUTHENTICATION_ERROR_CODE, message = AUTHENTICATION_ERROR_MESSAGE), + @ApiResponse(code = AUTHORIZATION_ERROR_CODE, message = AUTHORIZATION_ERROR_MESSAGE), + @ApiResponse(code = SERVER_ERROR_CODE, message = SERVER_ERROR_MESSAGE) + } + ) + // @formatter:on + public ResponseEntity<InstancePropertiesResponse> createInstanceProperties( + @RequestHeader(name = REQUEST_ID_NAME, required = false) @ApiParam(REQUEST_ID_PARAM_DESCRIPTION) UUID requestId, + @ApiParam(value = "Body of instance properties", required = true) @RequestBody ToscaServiceTemplate body) + throws PfModelException { + + return ResponseEntity.ok().body(provider.createInstanceProperties(body)); + } + + /** + * Deletes a automation composition definition and instance properties. + * + * @param requestId request ID used in ONAP logging + * @param name the name of the automation composition to delete + * @param version the version of the automation composition to delete + * @return a response + * @throws PfModelException on errors deleting of automation composition and instance properties + */ + // @formatter:off + @DeleteMapping(value = "/instanceProperties", + produces = {MediaType.APPLICATION_JSON_VALUE, APPLICATION_YAML}) + @ApiOperation(value = "Delete a automation composition and instance properties", + notes = "Deletes a automation composition and instance properties, returning optional error details", + response = InstantiationResponse.class, + tags = {TAGS}, + authorizations = @Authorization(value = AUTHORIZATION_TYPE), + responseHeaders = { + @ResponseHeader( + name = VERSION_MINOR_NAME, + description = VERSION_MINOR_DESCRIPTION, + response = String.class), + @ResponseHeader( + name = VERSION_PATCH_NAME, + description = VERSION_PATCH_DESCRIPTION, + response = String.class), + @ResponseHeader( + name = VERSION_LATEST_NAME, + description = VERSION_LATEST_DESCRIPTION, + response = String.class), + @ResponseHeader( + name = REQUEST_ID_NAME, + description = REQUEST_ID_HDR_DESCRIPTION, + response = UUID.class)}, + extensions = { + @Extension + ( + name = EXTENSION_NAME, + properties = { + @ExtensionProperty(name = API_VERSION_NAME, value = API_VERSION), + @ExtensionProperty(name = LAST_MOD_NAME, value = LAST_MOD_RELEASE) + } + ) + } + ) + @ApiResponses( + value = { + @ApiResponse(code = AUTHENTICATION_ERROR_CODE, message = AUTHENTICATION_ERROR_MESSAGE), + @ApiResponse(code = AUTHORIZATION_ERROR_CODE, message = AUTHORIZATION_ERROR_MESSAGE), + @ApiResponse(code = SERVER_ERROR_CODE, message = SERVER_ERROR_MESSAGE) + } + ) + // @formatter:on + + public ResponseEntity<InstantiationResponse> deleteInstanceProperties( + @RequestHeader(name = REQUEST_ID_NAME, required = false) @ApiParam(REQUEST_ID_PARAM_DESCRIPTION) UUID requestId, + @ApiParam(value = "Automation composition definition name", required = true) @RequestParam("name") String name, + @ApiParam(value = "Automation composition definition version") @RequestParam( + value = "version", + required = true) String version) + throws PfModelException { + + return ResponseEntity.ok().body(provider.deleteInstanceProperties(name, version)); + } + + /** + * Queries details of all automation compositions. + * + * @param requestId request ID used in ONAP logging + * @param name the name of the automation composition to get, null for all automation compositions + * @param version the version of the automation composition to get, null for all automation compositions + * @return the automation compositions + * @throws PfModelException on errors getting commissioning of automation composition + */ + // @formatter:off + @GetMapping(value = "/instantiation", + produces = {MediaType.APPLICATION_JSON_VALUE, APPLICATION_YAML}) + @ApiOperation(value = "Query details of the requested automation compositions", + notes = "Queries details of the requested automation compositions, returning all composition details", + response = AutomationCompositions.class, + tags = {TAGS}, + authorizations = @Authorization(value = AUTHORIZATION_TYPE), + responseHeaders = { + @ResponseHeader( + name = VERSION_MINOR_NAME, description = VERSION_MINOR_DESCRIPTION, + response = String.class), + @ResponseHeader(name = VERSION_PATCH_NAME, description = VERSION_PATCH_DESCRIPTION, + response = String.class), + @ResponseHeader(name = VERSION_LATEST_NAME, description = VERSION_LATEST_DESCRIPTION, + response = String.class), + @ResponseHeader(name = REQUEST_ID_NAME, description = REQUEST_ID_HDR_DESCRIPTION, + response = UUID.class)}, + extensions = { + @Extension + ( + name = EXTENSION_NAME, + properties = { + @ExtensionProperty(name = API_VERSION_NAME, value = API_VERSION), + @ExtensionProperty(name = LAST_MOD_NAME, value = LAST_MOD_RELEASE) + } + ) + } + ) + @ApiResponses( + value = { + @ApiResponse(code = AUTHENTICATION_ERROR_CODE, message = AUTHENTICATION_ERROR_MESSAGE), + @ApiResponse(code = AUTHORIZATION_ERROR_CODE, message = AUTHORIZATION_ERROR_MESSAGE), + @ApiResponse(code = SERVER_ERROR_CODE, message = SERVER_ERROR_MESSAGE) + } + ) + // @formatter:on + public ResponseEntity<AutomationCompositions> query( + @RequestHeader(name = REQUEST_ID_NAME, required = false) @ApiParam(REQUEST_ID_PARAM_DESCRIPTION) UUID requestId, + @ApiParam(value = "Automation composition definition name", required = false) @RequestParam( + value = "name", + required = false) String name, + @ApiParam(value = "Automation composition definition version", required = false) @RequestParam( + value = "version", + required = false) String version) + throws PfModelException { + + return ResponseEntity.ok().body(provider.getAutomationCompositions(name, version)); + } + + /** + * Updates a automation composition. + * + * @param requestId request ID used in ONAP logging + * @param automationCompositions the automation compositions + * @return a response + * @throws PfModelException on errors updating of automation compositions + */ + // @formatter:off + @PutMapping(value = "/instantiation", + produces = {MediaType.APPLICATION_JSON_VALUE, APPLICATION_YAML}, + consumes = {MediaType.APPLICATION_JSON_VALUE, APPLICATION_YAML}) + @ApiOperation( + value = "Updates automation composition definitions", + notes = "Updates automation composition definitions, returning the updated composition definition IDs", + response = InstantiationResponse.class, + tags = {TAGS}, + authorizations = @Authorization(value = AUTHORIZATION_TYPE), + responseHeaders = { + @ResponseHeader( + name = VERSION_MINOR_NAME, + description = VERSION_MINOR_DESCRIPTION, + response = String.class), + @ResponseHeader( + name = VERSION_PATCH_NAME, + description = VERSION_PATCH_DESCRIPTION, + response = String.class), + @ResponseHeader( + name = VERSION_LATEST_NAME, + description = VERSION_LATEST_DESCRIPTION, + response = String.class), + @ResponseHeader( + name = REQUEST_ID_NAME, + description = REQUEST_ID_HDR_DESCRIPTION, + response = UUID.class) + }, + extensions = { + @Extension + ( + name = EXTENSION_NAME, + properties = { + @ExtensionProperty(name = API_VERSION_NAME, value = API_VERSION), + @ExtensionProperty(name = LAST_MOD_NAME, value = LAST_MOD_RELEASE) + } + ) + } + ) + @ApiResponses( + value = { + @ApiResponse(code = AUTHENTICATION_ERROR_CODE, message = AUTHENTICATION_ERROR_MESSAGE), + @ApiResponse(code = AUTHORIZATION_ERROR_CODE, message = AUTHORIZATION_ERROR_MESSAGE), + @ApiResponse(code = SERVER_ERROR_CODE, message = SERVER_ERROR_MESSAGE) + } + ) + // @formatter:on + public ResponseEntity<InstantiationResponse> update( + @RequestHeader(name = REQUEST_ID_NAME, required = false) @ApiParam(REQUEST_ID_PARAM_DESCRIPTION) UUID requestId, + @ApiParam( + value = "Entity Body of Automation Composition", + required = true) @RequestBody AutomationCompositions automationCompositions) + throws PfModelException { + + return ResponseEntity.ok().body(provider.updateAutomationCompositions(automationCompositions)); + } + + /** + * Deletes a automation composition definition. + * + * @param requestId request ID used in ONAP logging + * @param name the name of the automation composition to delete + * @param version the version of the automation composition to delete + * @return a response + * @throws PfModelException on errors deleting of automation composition + */ + // @formatter:off + @DeleteMapping(value = "/instantiation", + produces = {MediaType.APPLICATION_JSON_VALUE, APPLICATION_YAML}) + @ApiOperation(value = "Delete a automation composition", + notes = "Deletes a automation composition, returning optional error details", + response = InstantiationResponse.class, + tags = {TAGS}, + authorizations = @Authorization(value = AUTHORIZATION_TYPE), + responseHeaders = { + @ResponseHeader( + name = VERSION_MINOR_NAME, + description = VERSION_MINOR_DESCRIPTION, + response = String.class), + @ResponseHeader( + name = VERSION_PATCH_NAME, + description = VERSION_PATCH_DESCRIPTION, + response = String.class), + @ResponseHeader( + name = VERSION_LATEST_NAME, + description = VERSION_LATEST_DESCRIPTION, + response = String.class), + @ResponseHeader( + name = REQUEST_ID_NAME, + description = REQUEST_ID_HDR_DESCRIPTION, + response = UUID.class)}, + extensions = { + @Extension + ( + name = EXTENSION_NAME, + properties = { + @ExtensionProperty(name = API_VERSION_NAME, value = API_VERSION), + @ExtensionProperty(name = LAST_MOD_NAME, value = LAST_MOD_RELEASE) + } + ) + } + ) + @ApiResponses( + value = { + @ApiResponse(code = AUTHENTICATION_ERROR_CODE, message = AUTHENTICATION_ERROR_MESSAGE), + @ApiResponse(code = AUTHORIZATION_ERROR_CODE, message = AUTHORIZATION_ERROR_MESSAGE), + @ApiResponse(code = SERVER_ERROR_CODE, message = SERVER_ERROR_MESSAGE) + } + ) + // @formatter:on + + public ResponseEntity<InstantiationResponse> delete( + @RequestHeader(name = REQUEST_ID_NAME, required = false) @ApiParam(REQUEST_ID_PARAM_DESCRIPTION) UUID requestId, + @ApiParam(value = "Automation composition definition name", required = true) @RequestParam("name") String name, + @ApiParam(value = "Automation composition definition version") @RequestParam( + value = "version", + required = true) String version) + throws PfModelException { + + return ResponseEntity.ok().body(provider.deleteAutomationComposition(name, version)); + } + + /** + * Issues automation composition commands to automation compositions. + * + * @param requestId request ID used in ONAP logging + * @param command the command to issue to automation compositions + * @return the automation composition definitions + * @throws PfModelException on errors issuing a command + * @throws AutomationCompositionException on errors issuing a command + */ + // @formatter:off + @PutMapping(value = "/instantiation/command", + produces = {MediaType.APPLICATION_JSON_VALUE, APPLICATION_YAML}, + consumes = {MediaType.APPLICATION_JSON_VALUE, APPLICATION_YAML}) + @ApiOperation(value = "Issue a command to the requested automation compositions", + notes = "Issues a command to an automation composition, ordering a state change on the composition", + response = InstantiationResponse.class, + tags = {TAGS}, + authorizations = @Authorization(value = AUTHORIZATION_TYPE), + responseHeaders = { + @ResponseHeader( + name = VERSION_MINOR_NAME, description = VERSION_MINOR_DESCRIPTION, + response = String.class), + @ResponseHeader(name = VERSION_PATCH_NAME, description = VERSION_PATCH_DESCRIPTION, + response = String.class), + @ResponseHeader(name = VERSION_LATEST_NAME, description = VERSION_LATEST_DESCRIPTION, + response = String.class), + @ResponseHeader(name = REQUEST_ID_NAME, description = REQUEST_ID_HDR_DESCRIPTION, + response = UUID.class)}, + extensions = { + @Extension + ( + name = EXTENSION_NAME, + properties = { + @ExtensionProperty(name = API_VERSION_NAME, value = API_VERSION), + @ExtensionProperty(name = LAST_MOD_NAME, value = LAST_MOD_RELEASE) + } + ) + } + ) + @ApiResponses( + value = { + @ApiResponse(code = AUTHENTICATION_ERROR_CODE, message = AUTHENTICATION_ERROR_MESSAGE), + @ApiResponse(code = AUTHORIZATION_ERROR_CODE, message = AUTHORIZATION_ERROR_MESSAGE), + @ApiResponse(code = SERVER_ERROR_CODE, message = SERVER_ERROR_MESSAGE) + } + ) + // @formatter:on + public ResponseEntity<InstantiationResponse> issueAutomationCompositionCommand( + @RequestHeader(name = REQUEST_ID_NAME, required = false) @ApiParam(REQUEST_ID_PARAM_DESCRIPTION) UUID requestId, + @ApiParam( + value = "Entity Body of automation composition command", + required = true) @RequestBody InstantiationCommand command) + throws AutomationCompositionException, PfModelException { + + return ResponseEntity.accepted().body(provider.issueAutomationCompositionCommand(command)); + } + + /** + * Queries details of all automation compositions. + * + * @param requestId request ID used in ONAP logging + * @param name the name of the automation composition to get, null for all automation compositions + * @param version the version of the automation composition to get, null for all automation compositions + * @return the automation compositions + * @throws PfModelException on errors getting commissioning of automation composition + */ + // @formatter:off + @GetMapping(value = "/instantiationState", + produces = {MediaType.APPLICATION_JSON_VALUE, APPLICATION_YAML}) + @ApiOperation(value = "Query details of the requested automation compositions", + notes = "Queries details of requested automation compositions, returning all automation composition details", + response = AutomationCompositions.class, + tags = {TAGS}, + authorizations = @Authorization(value = AUTHORIZATION_TYPE), + responseHeaders = { + @ResponseHeader( + name = VERSION_MINOR_NAME, description = VERSION_MINOR_DESCRIPTION, + response = String.class), + @ResponseHeader(name = VERSION_PATCH_NAME, description = VERSION_PATCH_DESCRIPTION, + response = String.class), + @ResponseHeader(name = VERSION_LATEST_NAME, description = VERSION_LATEST_DESCRIPTION, + response = String.class), + @ResponseHeader(name = REQUEST_ID_NAME, description = REQUEST_ID_HDR_DESCRIPTION, + response = UUID.class)}, + extensions = { + @Extension + ( + name = EXTENSION_NAME, + properties = { + @ExtensionProperty(name = API_VERSION_NAME, value = API_VERSION), + @ExtensionProperty(name = LAST_MOD_NAME, value = LAST_MOD_RELEASE) + } + ) + } + ) + @ApiResponses( + value = { + @ApiResponse(code = AUTHENTICATION_ERROR_CODE, message = AUTHENTICATION_ERROR_MESSAGE), + @ApiResponse(code = AUTHORIZATION_ERROR_CODE, message = AUTHORIZATION_ERROR_MESSAGE), + @ApiResponse(code = SERVER_ERROR_CODE, message = SERVER_ERROR_MESSAGE) + } + ) + // @formatter:on + public ResponseEntity<AutomationCompositionOrderStateResponse> getInstantiationOrderState( + @RequestHeader(name = REQUEST_ID_NAME, required = false) @ApiParam(REQUEST_ID_PARAM_DESCRIPTION) UUID requestId, + @ApiParam(value = "Automation composition name", required = false) @RequestParam( + value = "name", + required = false) String name, + @ApiParam(value = "Automation composition version", required = false) @RequestParam( + value = "version", + required = false) String version) + throws PfModelException { + + return ResponseEntity.ok().body(provider.getInstantiationOrderState(name, version)); + } + + /** + * Queries Primed/De-Primed status of a automation composition. + * + * @param requestId request ID used in ONAP logging + * @param name the name of the automation composition to get, null for all automation compositions + * @param version the version of the automation composition to get, null for all automation compositions + * @return the automation compositions + * @throws PfModelException on errors getting priming of automation composition + */ + // @formatter:off + @GetMapping(value = "/automationCompositionPriming", + produces = {MediaType.APPLICATION_JSON_VALUE, APPLICATION_YAML}) + @ApiOperation(value = "Query priming details of the requested automation compositions", + notes = "Queries priming details of requested automation compositions, returning primed/deprimed compositions", + response = AutomationCompositionPrimedResponse.class, + tags = {TAGS}, + authorizations = @Authorization(value = AUTHORIZATION_TYPE), + responseHeaders = { + @ResponseHeader( + name = VERSION_MINOR_NAME, description = VERSION_MINOR_DESCRIPTION, + response = String.class), + @ResponseHeader(name = VERSION_PATCH_NAME, description = VERSION_PATCH_DESCRIPTION, + response = String.class), + @ResponseHeader(name = VERSION_LATEST_NAME, description = VERSION_LATEST_DESCRIPTION, + response = String.class), + @ResponseHeader(name = REQUEST_ID_NAME, description = REQUEST_ID_HDR_DESCRIPTION, + response = UUID.class)}, + extensions = { + @Extension + ( + name = EXTENSION_NAME, + properties = { + @ExtensionProperty(name = API_VERSION_NAME, value = API_VERSION), + @ExtensionProperty(name = LAST_MOD_NAME, value = LAST_MOD_RELEASE) + } + ) + } + ) + @ApiResponses( + value = { + @ApiResponse(code = AUTHENTICATION_ERROR_CODE, message = AUTHENTICATION_ERROR_MESSAGE), + @ApiResponse(code = AUTHORIZATION_ERROR_CODE, message = AUTHORIZATION_ERROR_MESSAGE), + @ApiResponse(code = SERVER_ERROR_CODE, message = SERVER_ERROR_MESSAGE) + } + ) + // @formatter:on + public ResponseEntity<AutomationCompositionPrimedResponse> getAutomationCompositionPriming( + @RequestHeader(name = REQUEST_ID_NAME, required = false) @ApiParam(REQUEST_ID_PARAM_DESCRIPTION) UUID requestId, + @ApiParam(value = "Automation composition definition name", required = false) @RequestParam( + value = "name", + required = false) String name, + @ApiParam(value = "Automation composition definition version", required = false) @RequestParam( + value = "version", + required = false) String version) + throws PfModelException { + + return ResponseEntity.ok().body(provider.getAutomationCompositionPriming(name, version)); + } +} diff --git a/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/main/rest/MonitoringQueryController.java b/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/main/rest/MonitoringQueryController.java new file mode 100644 index 000000000..30c1d5dc9 --- /dev/null +++ b/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/main/rest/MonitoringQueryController.java @@ -0,0 +1,334 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2021 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.policy.clamp.acm.runtime.main.rest; + +import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.ApiParam; +import io.swagger.annotations.ApiResponse; +import io.swagger.annotations.ApiResponses; +import io.swagger.annotations.Authorization; +import io.swagger.annotations.Extension; +import io.swagger.annotations.ExtensionProperty; +import io.swagger.annotations.ResponseHeader; +import java.time.Instant; +import java.util.UUID; +import lombok.RequiredArgsConstructor; +import org.onap.policy.clamp.acm.runtime.main.web.AbstractRestController; +import org.onap.policy.clamp.acm.runtime.monitoring.MonitoringProvider; +import org.onap.policy.clamp.models.acm.concepts.AcElementStatisticsList; +import org.onap.policy.clamp.models.acm.concepts.ParticipantStatisticsList; +import org.onap.policy.models.base.PfModelException; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +/** + * This class handles REST endpoints for ACM Statistics monitoring. + */ +@RestController +@RequiredArgsConstructor +public class MonitoringQueryController extends AbstractRestController { + + private static final String TAGS = "Clamp Automation Composition Monitoring API"; + private final MonitoringProvider provider; + + /** + * Queries details of automation composition participants statistics. + * + * @param requestId request ID used in ONAP logging + * @param name the name of the participant to get, null for all participants statistics + * @param version the version of the participant to get, null for all participants with the given name + * @param recordCount the record count to be fetched + * @param startTime the time from which to get statistics + * @param endTime the time to which to get statistics + * @return the participant statistics + */ + // @formatter:off + @GetMapping(value = "/monitoring/participant", + produces = {MediaType.APPLICATION_JSON_VALUE, APPLICATION_YAML}) + @ApiOperation(value = "Query details of the requested participant stats", + notes = "Queries details of the requested participant stats, returning all participant stats", + response = ParticipantStatisticsList.class, + tags = {TAGS}, + authorizations = @Authorization(value = AUTHORIZATION_TYPE), + responseHeaders = { + @ResponseHeader( + name = VERSION_MINOR_NAME, description = VERSION_MINOR_DESCRIPTION, + response = String.class), + @ResponseHeader(name = VERSION_PATCH_NAME, description = VERSION_PATCH_DESCRIPTION, + response = String.class), + @ResponseHeader(name = VERSION_LATEST_NAME, description = VERSION_LATEST_DESCRIPTION, + response = String.class), + @ResponseHeader(name = REQUEST_ID_NAME, description = REQUEST_ID_HDR_DESCRIPTION, + response = UUID.class)}, + extensions = { + @Extension + ( + name = EXTENSION_NAME, + properties = { + @ExtensionProperty(name = API_VERSION_NAME, value = API_VERSION), + @ExtensionProperty(name = LAST_MOD_NAME, value = LAST_MOD_RELEASE) + } + ) + } + ) + @ApiResponses( + value = { + @ApiResponse(code = AUTHENTICATION_ERROR_CODE, message = AUTHENTICATION_ERROR_MESSAGE), + @ApiResponse(code = AUTHORIZATION_ERROR_CODE, message = AUTHORIZATION_ERROR_MESSAGE), + @ApiResponse(code = SERVER_ERROR_CODE, message = SERVER_ERROR_MESSAGE) + } + ) + // @formatter:on + public ResponseEntity<ParticipantStatisticsList> queryParticipantStatistics( + @RequestHeader(name = REQUEST_ID_NAME, required = false) @ApiParam(REQUEST_ID_PARAM_DESCRIPTION) UUID requestId, + @ApiParam(value = "Automation composition participant name") @RequestParam( + value = "name", + required = false) final String name, + @ApiParam(value = "Automation composition participant version", required = false) @RequestParam( + value = "version", + required = false) final String version, + @ApiParam(value = "Record count", required = false) @RequestParam( + value = "recordCount", + required = false, + defaultValue = "0") final int recordCount, + @ApiParam(value = "start time", required = false) @RequestParam( + value = "startTime", + required = false) final String startTime, + @ApiParam(value = "end time", required = false) @RequestParam( + value = "endTime", + required = false) final String endTime) { + + Instant startTimestamp = null; + Instant endTimestamp = null; + + if (startTime != null) { + startTimestamp = Instant.parse(startTime); + } + if (endTime != null) { + endTimestamp = Instant.parse(endTime); + } + return ResponseEntity.ok().body( + provider.fetchFilteredParticipantStatistics(name, version, recordCount, startTimestamp, endTimestamp)); + } + + /** + * Queries details of all participant statistics per automation composition. + * + * @param requestId request ID used in ONAP logging + * @param name the name of the automation composition + * @param version version of the automation composition + * @return the automation composition element statistics + */ + // @formatter:off + @GetMapping(value = "/monitoring/participants/automationcomposition", + produces = {MediaType.APPLICATION_JSON_VALUE, APPLICATION_YAML}) + @ApiOperation(value = "Query details of all the participant stats in a automation composition", + notes = "Queries details of the participant stats, returning all participant stats", + response = AcElementStatisticsList.class, + tags = {TAGS}, + authorizations = @Authorization(value = AUTHORIZATION_TYPE), + responseHeaders = { + @ResponseHeader( + name = VERSION_MINOR_NAME, description = VERSION_MINOR_DESCRIPTION, + response = String.class), + @ResponseHeader(name = VERSION_PATCH_NAME, description = VERSION_PATCH_DESCRIPTION, + response = String.class), + @ResponseHeader(name = VERSION_LATEST_NAME, description = VERSION_LATEST_DESCRIPTION, + response = String.class), + @ResponseHeader(name = REQUEST_ID_NAME, description = REQUEST_ID_HDR_DESCRIPTION, + response = UUID.class)}, + extensions = { + @Extension + ( + name = EXTENSION_NAME, + properties = { + @ExtensionProperty(name = API_VERSION_NAME, value = API_VERSION), + @ExtensionProperty(name = LAST_MOD_NAME, value = LAST_MOD_RELEASE) + } + ) + }) + @ApiResponses( + value = { + @ApiResponse(code = AUTHENTICATION_ERROR_CODE, message = AUTHENTICATION_ERROR_MESSAGE), + @ApiResponse(code = AUTHORIZATION_ERROR_CODE, message = AUTHORIZATION_ERROR_MESSAGE), + @ApiResponse(code = SERVER_ERROR_CODE, message = SERVER_ERROR_MESSAGE) + } + ) + // @formatter:on + public ResponseEntity<ParticipantStatisticsList> queryParticipantStatisticsPerAutomationComposition( + @RequestHeader(name = REQUEST_ID_NAME, required = false) @ApiParam(REQUEST_ID_PARAM_DESCRIPTION) UUID requestId, + @ApiParam(value = "Automation composition name", required = true) @RequestParam( + value = "name", + required = false) final String name, + @ApiParam(value = "Automation composition version", required = true) @RequestParam( + value = "version", + required = false) final String version) { + + return ResponseEntity.ok().body(provider.fetchParticipantStatsPerAutomationComposition(name, version)); + } + + /** + * Queries details of all automation composition element statistics per automation composition. + * + * @param requestId request ID used in ONAP logging + * @param name the name of the automation composition + * @param version version of the automation composition + * @return the automation composition element statistics + */ + // @formatter:off + @GetMapping(value = "/monitoring/acelements/automationcomposition", + produces = {MediaType.APPLICATION_JSON_VALUE, APPLICATION_YAML}) + @ApiOperation(value = "Query details of the requested acElement stats in a automation composition", + notes = "Queries details of the requested acElement stats, returning all acElement stats", + response = AcElementStatisticsList.class, + tags = {TAGS}, + authorizations = @Authorization(value = AUTHORIZATION_TYPE), + responseHeaders = { + @ResponseHeader( + name = VERSION_MINOR_NAME, description = VERSION_MINOR_DESCRIPTION, + response = String.class), + @ResponseHeader(name = VERSION_PATCH_NAME, description = VERSION_PATCH_DESCRIPTION, + response = String.class), + @ResponseHeader(name = VERSION_LATEST_NAME, description = VERSION_LATEST_DESCRIPTION, + response = String.class), + @ResponseHeader(name = REQUEST_ID_NAME, description = REQUEST_ID_HDR_DESCRIPTION, + response = UUID.class)}, + extensions = { + @Extension + ( + name = EXTENSION_NAME, + properties = { + @ExtensionProperty(name = API_VERSION_NAME, value = API_VERSION), + @ExtensionProperty(name = LAST_MOD_NAME, value = LAST_MOD_RELEASE) + } + ) + }) + @ApiResponses( + value = { + @ApiResponse(code = AUTHENTICATION_ERROR_CODE, message = AUTHENTICATION_ERROR_MESSAGE), + @ApiResponse(code = AUTHORIZATION_ERROR_CODE, message = AUTHORIZATION_ERROR_MESSAGE), + @ApiResponse(code = SERVER_ERROR_CODE, message = SERVER_ERROR_MESSAGE) + } + ) + // @formatter:on + public ResponseEntity<AcElementStatisticsList> queryElementStatisticsPerAutomationComposition( + @RequestHeader(name = REQUEST_ID_NAME, required = false) @ApiParam(REQUEST_ID_PARAM_DESCRIPTION) UUID requestId, + @ApiParam(value = "Automation composition name", required = true) @RequestParam( + value = "name", + required = false) final String name, + @ApiParam(value = "Automation composition version", required = true) @RequestParam( + value = "version", + required = false) final String version) { + + return ResponseEntity.ok().body(provider.fetchAcElementStatsPerAutomationComposition(name, version)); + } + + /** + * Queries details of all automation composition element statistics per automation composition. + * + * @param requestId request ID used in ONAP logging + * @param name the name of the automation composition + * @param version version of the automation composition + * @param id Id of the automation composition element + * @param recordCount the record count to be fetched + * @param startTime the time from which to get statistics + * @param endTime the time to which to get statistics + * @return the automation composition element statistics + * @throws PfModelException on errors getting details of all automation composition element statistics per + * automation composition + */ + // @formatter:off + @GetMapping(value = "/monitoring/acelement", + produces = {MediaType.APPLICATION_JSON_VALUE, APPLICATION_YAML}) + @ApiOperation(value = "Query details of the requested acElement stats", + notes = "Queries details of the requested acElement stats, returning all acElement stats", + response = AcElementStatisticsList.class, + tags = {TAGS}, + authorizations = @Authorization(value = AUTHORIZATION_TYPE), + responseHeaders = { + @ResponseHeader( + name = VERSION_MINOR_NAME, description = VERSION_MINOR_DESCRIPTION, + response = String.class), + @ResponseHeader(name = VERSION_PATCH_NAME, description = VERSION_PATCH_DESCRIPTION, + response = String.class), + @ResponseHeader(name = VERSION_LATEST_NAME, description = VERSION_LATEST_DESCRIPTION, + response = String.class), + @ResponseHeader(name = REQUEST_ID_NAME, description = REQUEST_ID_HDR_DESCRIPTION, + response = UUID.class)}, + extensions = { + @Extension + ( + name = EXTENSION_NAME, + properties = { + @ExtensionProperty(name = API_VERSION_NAME, value = API_VERSION), + @ExtensionProperty(name = LAST_MOD_NAME, value = LAST_MOD_RELEASE) + } + ) + }) + @ApiResponses( + value = { + @ApiResponse(code = AUTHENTICATION_ERROR_CODE, message = AUTHENTICATION_ERROR_MESSAGE), + @ApiResponse(code = AUTHORIZATION_ERROR_CODE, message = AUTHORIZATION_ERROR_MESSAGE), + @ApiResponse(code = SERVER_ERROR_CODE, message = SERVER_ERROR_MESSAGE) + } + ) + // @formatter:on + public ResponseEntity<AcElementStatisticsList> queryElementStatistics( + @RequestHeader(name = REQUEST_ID_NAME, required = false) @ApiParam(REQUEST_ID_PARAM_DESCRIPTION) UUID requestId, + @ApiParam(value = "Participant name", required = true) @RequestParam( + value = "name", + required = false) final String name, + @ApiParam(value = "Participant version", required = true) @RequestParam( + value = "version", + required = false) final String version, + @ApiParam(value = "Record count", required = false) @RequestParam( + value = "recordCount", + required = false, + defaultValue = "0") final int recordCount, + @ApiParam(value = "Automation composition element id", required = false) @RequestParam( + value = "id", + required = false) final String id, + @ApiParam(value = "start time", required = false) @RequestParam( + value = "startTime", + required = false) final String startTime, + @ApiParam(value = "end time", required = false) @RequestParam( + value = "endTime", + required = false) final String endTime) + throws PfModelException { + + Instant startTimestamp = null; + Instant endTimestamp = null; + + if (startTime != null) { + startTimestamp = Instant.parse(startTime); + } + if (endTime != null) { + endTimestamp = Instant.parse(endTime); + } + return ResponseEntity.ok().body( + provider.fetchFilteredAcElementStatistics(name, version, id, startTimestamp, endTimestamp, recordCount)); + } + +} diff --git a/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/main/web/AbstractRestController.java b/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/main/web/AbstractRestController.java new file mode 100644 index 000000000..7907de7be --- /dev/null +++ b/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/main/web/AbstractRestController.java @@ -0,0 +1,91 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2021 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.policy.clamp.acm.runtime.main.web; + +import io.swagger.annotations.Api; +import io.swagger.annotations.BasicAuthDefinition; +import io.swagger.annotations.Info; +import io.swagger.annotations.SecurityDefinition; +import io.swagger.annotations.SwaggerDefinition; +import io.swagger.annotations.Tag; +import java.net.HttpURLConnection; +import javax.ws.rs.core.MediaType; +import org.springframework.web.bind.annotation.RequestMapping; + +/** + * Common superclass to provide REST endpoints for the participant simulator. + */ +// @formatter:off +@RequestMapping(value = "/v2", produces = {MediaType.APPLICATION_JSON, AbstractRestController.APPLICATION_YAML}) +@Api(value = "Automation Composition Commissioning API") +@SwaggerDefinition( + info = @Info(description = + "Automation Composition Service", version = "v1.0", + title = "Automation Composition"), + consumes = {MediaType.APPLICATION_JSON, AbstractRestController.APPLICATION_YAML}, + produces = {MediaType.APPLICATION_JSON, AbstractRestController.APPLICATION_YAML}, + schemes = {SwaggerDefinition.Scheme.HTTP, SwaggerDefinition.Scheme.HTTPS}, + tags = {@Tag(name = "automationcomposition", description = "Automation Composition Service")}, + securityDefinition = @SecurityDefinition(basicAuthDefinitions = {@BasicAuthDefinition(key = "basicAuth")})) +// @formatter:on +public abstract class AbstractRestController { + public static final String APPLICATION_YAML = "application/yaml"; + + public static final String EXTENSION_NAME = "interface info"; + + public static final String API_VERSION_NAME = "api-version"; + public static final String API_VERSION = "1.0.0"; + + public static final String LAST_MOD_NAME = "last-mod-release"; + public static final String LAST_MOD_RELEASE = "Istanbul"; + + public static final String VERSION_MINOR_NAME = "X-MinorVersion"; + public static final String VERSION_MINOR_DESCRIPTION = + "Used to request or communicate a MINOR version back from the client" + + " to the server, and from the server back to the client"; + + public static final String VERSION_PATCH_NAME = "X-PatchVersion"; + public static final String VERSION_PATCH_DESCRIPTION = "Used only to communicate a PATCH version in a response for" + + " troubleshooting purposes only, and will not be provided by" + " the client on request"; + + public static final String VERSION_LATEST_NAME = "X-LatestVersion"; + public static final String VERSION_LATEST_DESCRIPTION = "Used only to communicate an API's latest version"; + + public static final String REQUEST_ID_NAME = "X-ONAP-RequestID"; + public static final String REQUEST_ID_HDR_DESCRIPTION = "Used to track REST transactions for logging purpose"; + public static final String REQUEST_ID_PARAM_DESCRIPTION = "RequestID for http transaction"; + + public static final String AUTHORIZATION_TYPE = "basicAuth"; + + public static final int AUTHENTICATION_ERROR_CODE = HttpURLConnection.HTTP_UNAUTHORIZED; + public static final int AUTHORIZATION_ERROR_CODE = HttpURLConnection.HTTP_FORBIDDEN; + public static final int SERVER_ERROR_CODE = HttpURLConnection.HTTP_INTERNAL_ERROR; + + public static final String AUTHENTICATION_ERROR_MESSAGE = "Authentication Error"; + public static final String AUTHORIZATION_ERROR_MESSAGE = "Authorization Error"; + public static final String SERVER_ERROR_MESSAGE = "Internal Server Error"; + + /** + * Constructor. + */ + protected AbstractRestController() { + } +} diff --git a/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/main/web/AutomationConfiguraitonAafFilter.java b/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/main/web/AutomationConfiguraitonAafFilter.java new file mode 100644 index 000000000..ed49e3b44 --- /dev/null +++ b/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/main/web/AutomationConfiguraitonAafFilter.java @@ -0,0 +1,38 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2021 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.policy.clamp.acm.runtime.main.web; + +import org.onap.policy.common.endpoints.http.server.aaf.AafGranularAuthFilter; +import org.onap.policy.common.utils.resources.MessageConstants; + +/** + * Class to manage AAF filters for the automation composition runtime component. + */ +public class AutomationConfiguraitonAafFilter extends AafGranularAuthFilter { + + public static final String AAF_NODETYPE = MessageConstants.POLICY_CLAMP; + public static final String AAF_ROOT_PERMISSION = DEFAULT_NAMESPACE + "." + AAF_NODETYPE; + + @Override + public String getPermissionTypeRoot() { + return AAF_ROOT_PERMISSION; + } +} diff --git a/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/main/web/GlobalControllerExceptionHandler.java b/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/main/web/GlobalControllerExceptionHandler.java new file mode 100644 index 000000000..fef358bb1 --- /dev/null +++ b/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/main/web/GlobalControllerExceptionHandler.java @@ -0,0 +1,67 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2021 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.policy.clamp.acm.runtime.main.web; + +import org.onap.policy.clamp.common.acm.exception.AutomationCompositionException; +import org.onap.policy.clamp.models.acm.messages.rest.SimpleResponse; +import org.onap.policy.clamp.models.acm.rest.RestUtils; +import org.onap.policy.models.base.PfModelException; +import org.onap.policy.models.base.PfModelRuntimeException; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +@RestControllerAdvice +public class GlobalControllerExceptionHandler { + + /** + * Handle AutomationCompositionException. + * + * @param ex AutomationCompositionException + * @return ResponseEntity + */ + @ExceptionHandler(AutomationCompositionException.class) + public ResponseEntity<SimpleResponse> handleBadRequest(AutomationCompositionException ex) { + return RestUtils.toSimpleResponse(ex); + } + + /** + * Handle PfModelRuntimeException. + * + * @param ex PfModelRuntimeException + * @return ResponseEntity + */ + @ExceptionHandler(PfModelRuntimeException.class) + public ResponseEntity<SimpleResponse> handleBadRequest(PfModelRuntimeException ex) { + return RestUtils.toSimpleResponse(ex); + } + + /** + * Handle PfModelException. + * + * @param ex PfModelException + * @return ResponseEntity + */ + @ExceptionHandler(PfModelException.class) + public ResponseEntity<SimpleResponse> handleBadRequest(PfModelException ex) { + return RestUtils.toSimpleResponse(ex); + } +} diff --git a/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/main/web/RuntimeErrorController.java b/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/main/web/RuntimeErrorController.java new file mode 100644 index 000000000..5eecb92dd --- /dev/null +++ b/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/main/web/RuntimeErrorController.java @@ -0,0 +1,104 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2021 Nordix Foundation. + * ================================================================================ + * Modifications Copyright (C) 2021 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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.policy.clamp.acm.runtime.main.web; + +import io.swagger.v3.oas.annotations.Hidden; +import java.util.Map; +import javax.servlet.RequestDispatcher; +import javax.servlet.http.HttpServletRequest; +import org.onap.policy.clamp.models.acm.messages.rest.SimpleResponse; +import org.onap.policy.clamp.models.acm.messages.rest.TypedSimpleResponse; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.web.error.ErrorAttributeOptions; +import org.springframework.boot.web.servlet.error.ErrorAttributes; +import org.springframework.boot.web.servlet.error.ErrorController; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.context.request.ServletWebRequest; + +@Controller +@Hidden +public class RuntimeErrorController implements ErrorController { + + private static final Logger LOGGER = LoggerFactory.getLogger(RuntimeErrorController.class); + + private final ErrorAttributes errorAttributes; + + @Value("${server.error.path}") + private String path; + + /** + * Constructor. + * + * @param errorAttributes ErrorAttributes + */ + public RuntimeErrorController(ErrorAttributes errorAttributes) { + this.errorAttributes = errorAttributes; + } + + protected HttpStatus getStatus(HttpServletRequest request) { + Integer statusCode = (Integer) request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE); + if (statusCode == null) { + return HttpStatus.INTERNAL_SERVER_ERROR; + } + try { + return HttpStatus.valueOf(statusCode); + } catch (Exception ex) { + LOGGER.error("statusCode {} Not Valid", statusCode, ex); + return HttpStatus.INTERNAL_SERVER_ERROR; + } + } + + /** + * Handle Errors not handled to GlobalControllerExceptionHandler. + * + * @param request HttpServletRequest + * @return ResponseEntity + */ + @RequestMapping(value = "${server.error.path}", produces = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity<TypedSimpleResponse<SimpleResponse>> handleError(HttpServletRequest request) { + Map<String, Object> map = this.errorAttributes.getErrorAttributes(new ServletWebRequest(request), + ErrorAttributeOptions.defaults()); + + var sb = new StringBuilder(); + final Object error = map.get("error"); + if (error != null) { + sb.append(error.toString()).append(" "); + } + final Object message = map.get("message"); + if (message != null) { + sb.append(message.toString()); + } + + TypedSimpleResponse<SimpleResponse> resp = new TypedSimpleResponse<>(); + resp.setErrorDetails(sb.toString()); + + return ResponseEntity.status(getStatus(request)).body(resp); + + } +} diff --git a/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/monitoring/MonitoringProvider.java b/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/monitoring/MonitoringProvider.java new file mode 100644 index 000000000..2950ad9da --- /dev/null +++ b/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/monitoring/MonitoringProvider.java @@ -0,0 +1,247 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2021 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.policy.clamp.acm.runtime.monitoring; + +import java.time.Instant; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import lombok.AllArgsConstructor; +import lombok.NonNull; +import org.onap.policy.clamp.models.acm.concepts.AcElementStatistics; +import org.onap.policy.clamp.models.acm.concepts.AcElementStatisticsList; +import org.onap.policy.clamp.models.acm.concepts.AutomationCompositionElement; +import org.onap.policy.clamp.models.acm.concepts.ParticipantStatistics; +import org.onap.policy.clamp.models.acm.concepts.ParticipantStatisticsList; +import org.onap.policy.clamp.models.acm.persistence.provider.AcElementStatisticsProvider; +import org.onap.policy.clamp.models.acm.persistence.provider.AutomationCompositionProvider; +import org.onap.policy.clamp.models.acm.persistence.provider.ParticipantStatisticsProvider; +import org.onap.policy.models.base.PfModelException; +import org.onap.policy.models.base.PfModelRuntimeException; +import org.onap.policy.models.tosca.authorative.concepts.ToscaConceptIdentifier; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +/** + * This class provides information about statistics data of Automation Composition elements and Participants in database + * to callers. + */ +@Service +@Transactional +@AllArgsConstructor +public class MonitoringProvider { + + private static final String DESC_ORDER = "DESC"; + private final ParticipantStatisticsProvider participantStatisticsProvider; + private final AcElementStatisticsProvider acElementStatisticsProvider; + private final AutomationCompositionProvider automationCompositionProvider; + + /** + * Create participant statistics. + * + * @param participantStatistics the participant statistics + * @return the result of create operation + * @throws PfModelException on creation errors + */ + public ParticipantStatisticsList createParticipantStatistics(List<ParticipantStatistics> participantStatistics) + throws PfModelException { + var participantStatisticsList = new ParticipantStatisticsList(); + participantStatisticsList + .setStatisticsList(participantStatisticsProvider.createParticipantStatistics(participantStatistics)); + + return participantStatisticsList; + } + + /** + * Create AcElement statistics. + * + * @param acElementStatisticsList the AcElement statistics + * @return the result of create operation + * @throws PfModelException on creation errors + */ + public AcElementStatisticsList createAcElementStatistics(List<AcElementStatistics> acElementStatisticsList) + throws PfModelException { + var elementStatisticsList = new AcElementStatisticsList(); + elementStatisticsList + .setAcElementStatistics(acElementStatisticsProvider.createAcElementStatistics(acElementStatisticsList)); + + return elementStatisticsList; + } + + /** + * Get participant statistics based on specific filters. + * + * @param name the name of the participant statistics to get, null to get all statistics + * @param version the version of the participant statistics to get, null to get all statistics + * @param recordCount number of records to be fetched. + * @param startTime start of the timestamp, from statistics to be filtered + * @param endTime end of the timestamp up to which statistics to be filtered + * @return the participant found + */ + @Transactional(readOnly = true) + public ParticipantStatisticsList fetchFilteredParticipantStatistics(@NonNull final String name, + final String version, int recordCount, Instant startTime, Instant endTime) { + var participantStatisticsList = new ParticipantStatisticsList(); + + // Additional parameters can be added in filterMap for filtering data. + Map<String, Object> filterMap = null; + participantStatisticsList.setStatisticsList(participantStatisticsProvider.getFilteredParticipantStatistics(name, + version, startTime, endTime, filterMap, DESC_ORDER, recordCount)); + + return participantStatisticsList; + } + + /** + * Get all participant statistics records found for a specific automation composition. + * + * @param automationCompositionName name of the automation composition + * @param automationCompositionVersion version of the automation composition + * @return All the participant statistics found + * @throws PfModelRuntimeException on errors getting participant statistics + */ + @Transactional(readOnly = true) + public ParticipantStatisticsList fetchParticipantStatsPerAutomationComposition( + @NonNull final String automationCompositionName, @NonNull final String automationCompositionVersion) { + var statisticsList = new ParticipantStatisticsList(); + List<ParticipantStatistics> participantStatistics = new ArrayList<>(); + try { + // Fetch all participantIds for a specific automation composition + List<ToscaConceptIdentifier> participantIds = + getAllParticipantIdsPerAutomationComposition(automationCompositionName, automationCompositionVersion); + for (ToscaConceptIdentifier id : participantIds) { + participantStatistics.addAll(participantStatisticsProvider + .getFilteredParticipantStatistics(id.getName(), id.getVersion(), null, null, null, DESC_ORDER, 0)); + } + statisticsList.setStatisticsList(participantStatistics); + } catch (PfModelException e) { + throw new PfModelRuntimeException(e); + } + return statisticsList; + } + + /** + * Get AcElement statistics based on specific filters. + * + * @param name the name of the AcElement statistics to get, null to get all statistics + * @param version the version of the AcElement statistics to get, null to get all statistics + * @param id UUID of the automation composition element + * @param startTime start of the timestamp, from statistics to be filtered + * @param endTime end of the timestamp up to which statistics to be filtered + * @param recordCount number of records to be fetched. + * @return the participant found + * @throws PfModelException on errors getting automation composition statistics + */ + @Transactional(readOnly = true) + public AcElementStatisticsList fetchFilteredAcElementStatistics(@NonNull final String name, final String version, + final String id, Instant startTime, Instant endTime, int recordCount) throws PfModelException { + var acElementStatisticsList = new AcElementStatisticsList(); + Map<String, Object> filterMap = new HashMap<>(); + // Adding UUID in filter if present + if (id != null) { + filterMap.put("localName", id); + } + acElementStatisticsList.setAcElementStatistics(acElementStatisticsProvider.getFilteredAcElementStatistics(name, + version, startTime, endTime, filterMap, DESC_ORDER, recordCount)); + + return acElementStatisticsList; + } + + /** + * Get AcElement statistics per automation composition. + * + * @param name the name of the automation composition + * @param version the version of the automation composition + * @return the AcElement statistics found + * @throws PfModelRuntimeException on errors getting automation composition statistics + */ + @Transactional(readOnly = true) + public AcElementStatisticsList fetchAcElementStatsPerAutomationComposition(@NonNull final String name, + @NonNull final String version) { + var acElementStatisticsList = new AcElementStatisticsList(); + List<AcElementStatistics> acElementStats = new ArrayList<>(); + try { + List<AutomationCompositionElement> acElements = new ArrayList<>(); + // Fetch all automation composition elements for the automation composition + var automationCompositionOpt = + automationCompositionProvider.findAutomationComposition(new ToscaConceptIdentifier(name, version)); + if (automationCompositionOpt.isPresent()) { + acElements.addAll(automationCompositionOpt.get().getElements().values()); + // Collect automation composition element statistics for each acElement. + for (AutomationCompositionElement acElement : acElements) { + acElementStats.addAll(fetchFilteredAcElementStatistics(acElement.getParticipantId().getName(), + acElement.getParticipantId().getVersion(), acElement.getId().toString(), null, null, 0) + .getAcElementStatistics()); + } + } + acElementStatisticsList.setAcElementStatistics(acElementStats); + } catch (PfModelException e) { + throw new PfModelRuntimeException(e); + } + return acElementStatisticsList; + } + + /** + * If required, REST end point can be defined for this method to fetch associated participant Ids + * for a automation composition. + * + * @param name the name of the automation composition + * @param version the version of the automation composition + * @return List of participant Id + * @throws PfModelException on errors + */ + @Transactional(readOnly = true) + public List<ToscaConceptIdentifier> getAllParticipantIdsPerAutomationComposition(String name, String version) + throws PfModelException { + List<ToscaConceptIdentifier> participantIds = new ArrayList<>(); + var automationCompositionOpt = + automationCompositionProvider.findAutomationComposition(new ToscaConceptIdentifier(name, version)); + if (automationCompositionOpt.isPresent()) { + for (AutomationCompositionElement acElement : automationCompositionOpt.get().getElements().values()) { + participantIds.add(acElement.getParticipantId()); + } + } + return participantIds; + } + + /** + * If required, REST end point can be defined for this method to fetch associated automation composition element Ids + * for a automation composition. + * + * @param name the name of the automation composition + * @param version the version of the automation composition + * @return Map of automation composition Id and participant details + * @throws PfModelException on errors + */ + @Transactional(readOnly = true) + public Map<String, ToscaConceptIdentifier> getAllAcElementsIdPerAutomationComposition(String name, String version) + throws PfModelException { + Map<String, ToscaConceptIdentifier> acElementId = new HashMap<>(); + var automationCompositionOpt = + automationCompositionProvider.findAutomationComposition(new ToscaConceptIdentifier(name, version)); + if (automationCompositionOpt.isPresent()) { + for (AutomationCompositionElement acElement : automationCompositionOpt.get().getElements().values()) { + acElementId.put(acElement.getId().toString(), acElement.getParticipantId()); + } + } + return acElementId; + } +} diff --git a/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/supervision/HandleCounter.java b/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/supervision/HandleCounter.java new file mode 100644 index 000000000..9949f3c89 --- /dev/null +++ b/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/supervision/HandleCounter.java @@ -0,0 +1,106 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2021 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.policy.clamp.acm.runtime.supervision; + +import java.time.Instant; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import lombok.Getter; +import lombok.Setter; + +public class HandleCounter<K> { + @Getter + @Setter + private long maxWaitMs; + + @Getter + @Setter + private int maxRetryCount; + + private Map<K, Integer> mapCounter = new HashMap<>(); + private Set<K> mapFault = new HashSet<>(); + private Map<K, Long> mapTimer = new HashMap<>(); + + public long getDuration(K id) { + mapTimer.putIfAbsent(id, getEpochMilli()); + return getEpochMilli() - mapTimer.get(id); + } + + /** + * Reset timer and clear counter and fault by id. + * + * @param id the id + */ + public void clear(K id) { + mapFault.remove(id); + mapCounter.put(id, 0); + mapTimer.put(id, getEpochMilli()); + } + + /** + * Remove counter, timer and fault by id. + * + * @param id the id + */ + public void remove(K id) { + mapFault.remove(id); + mapCounter.remove(id); + mapTimer.remove(id); + } + + public void setFault(K id) { + mapCounter.put(id, 0); + mapFault.add(id); + } + + /** + * Increment RetryCount by id e return true if minor or equal of maxRetryCount. + * + * @param id the identifier + * @return false if count is major of maxRetryCount + */ + public boolean count(K id) { + int counter = mapCounter.getOrDefault(id, 0) + 1; + if (counter <= maxRetryCount) { + mapCounter.put(id, counter); + return true; + } + return false; + } + + public boolean isFault(K id) { + return mapFault.contains(id); + } + + public int getCounter(K id) { + return mapCounter.getOrDefault(id, 0); + } + + protected long getEpochMilli() { + return Instant.now().toEpochMilli(); + } + + public Set<K> keySet() { + return mapCounter.keySet(); + } +} diff --git a/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/supervision/MessageIntercept.java b/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/supervision/MessageIntercept.java new file mode 100644 index 000000000..5b861ce96 --- /dev/null +++ b/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/supervision/MessageIntercept.java @@ -0,0 +1,32 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2021 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.policy.clamp.acm.runtime.supervision; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface MessageIntercept { + +} diff --git a/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/supervision/SupervisionAspect.java b/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/supervision/SupervisionAspect.java new file mode 100644 index 000000000..ea851da81 --- /dev/null +++ b/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/supervision/SupervisionAspect.java @@ -0,0 +1,105 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2021 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.policy.clamp.acm.runtime.supervision; + +import java.io.Closeable; +import java.io.IOException; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import lombok.RequiredArgsConstructor; +import org.apache.commons.lang3.tuple.ImmutablePair; +import org.aspectj.lang.annotation.After; +import org.aspectj.lang.annotation.AfterReturning; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Before; +import org.onap.policy.clamp.models.acm.messages.dmaap.participant.ParticipantRegister; +import org.onap.policy.clamp.models.acm.messages.dmaap.participant.ParticipantStatus; +import org.onap.policy.clamp.models.acm.messages.dmaap.participant.ParticipantUpdateAck; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +@Aspect +@Component +@RequiredArgsConstructor +public class SupervisionAspect implements Closeable { + + private static final Logger LOGGER = LoggerFactory.getLogger(SupervisionAspect.class); + + private final SupervisionScanner supervisionScanner; + + private ThreadPoolExecutor executor = + new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>()); + + @Scheduled( + fixedRateString = "${runtime.participantParameters.heartBeatMs}", + initialDelayString = "${runtime.participantParameters.heartBeatMs}") + public void schedule() { + LOGGER.info("Add scheduled scanning"); + executor.execute(() -> supervisionScanner.run(true)); + } + + /** + * Intercept Messages from participant and run Supervision Scan. + */ + @After("@annotation(MessageIntercept)") + public void doCheck() { + if (executor.getQueue().size() < 2) { + LOGGER.debug("Add scanning Message"); + executor.execute(() -> supervisionScanner.run(false)); + } + } + + @Before("@annotation(MessageIntercept) && args(participantStatusMessage,..)") + public void handleParticipantStatus(ParticipantStatus participantStatusMessage) { + executor.execute(() -> supervisionScanner.handleParticipantStatus(participantStatusMessage.getParticipantId())); + } + + /** + * Intercepts participant Register Message + * if there is a Commissioning starts an execution of handleParticipantRegister. + * + * @param participantRegisterMessage the ParticipantRegister message + * @param isCommissioning is Commissioning + */ + @AfterReturning( + value = "@annotation(MessageIntercept) && args(participantRegisterMessage,..)", + returning = "isCommissioning") + public void handleParticipantRegister(ParticipantRegister participantRegisterMessage, boolean isCommissioning) { + if (isCommissioning) { + executor.execute(() -> supervisionScanner.handleParticipantRegister(new ImmutablePair<>( + participantRegisterMessage.getParticipantId(), participantRegisterMessage.getParticipantType()))); + } + } + + @Before("@annotation(MessageIntercept) && args(participantUpdateAckMessage,..)") + public void handleParticipantUpdateAck(ParticipantUpdateAck participantUpdateAckMessage) { + executor.execute(() -> supervisionScanner.handleParticipantUpdateAck(new ImmutablePair<>( + participantUpdateAckMessage.getParticipantId(), participantUpdateAckMessage.getParticipantType()))); + } + + @Override + public void close() throws IOException { + executor.shutdown(); + } +} diff --git a/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/supervision/SupervisionHandler.java b/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/supervision/SupervisionHandler.java new file mode 100644 index 000000000..055acb28f --- /dev/null +++ b/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/supervision/SupervisionHandler.java @@ -0,0 +1,518 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2021 Nordix Foundation. + * Modifications Copyright (C) 2021 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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.policy.clamp.acm.runtime.supervision; + +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.Response.Status; +import lombok.AllArgsConstructor; +import org.apache.commons.collections4.CollectionUtils; +import org.onap.policy.clamp.acm.runtime.monitoring.MonitoringProvider; +import org.onap.policy.clamp.acm.runtime.supervision.comm.AutomationCompositionStateChangePublisher; +import org.onap.policy.clamp.acm.runtime.supervision.comm.AutomationCompositionUpdatePublisher; +import org.onap.policy.clamp.acm.runtime.supervision.comm.ParticipantDeregisterAckPublisher; +import org.onap.policy.clamp.acm.runtime.supervision.comm.ParticipantRegisterAckPublisher; +import org.onap.policy.clamp.acm.runtime.supervision.comm.ParticipantUpdatePublisher; +import org.onap.policy.clamp.common.acm.exception.AutomationCompositionException; +import org.onap.policy.clamp.models.acm.concepts.AutomationComposition; +import org.onap.policy.clamp.models.acm.concepts.AutomationCompositionElementAck; +import org.onap.policy.clamp.models.acm.concepts.AutomationCompositionInfo; +import org.onap.policy.clamp.models.acm.concepts.AutomationCompositionState; +import org.onap.policy.clamp.models.acm.concepts.Participant; +import org.onap.policy.clamp.models.acm.concepts.ParticipantHealthStatus; +import org.onap.policy.clamp.models.acm.concepts.ParticipantState; +import org.onap.policy.clamp.models.acm.concepts.ParticipantUtils; +import org.onap.policy.clamp.models.acm.messages.dmaap.participant.AutomationCompositionAck; +import org.onap.policy.clamp.models.acm.messages.dmaap.participant.ParticipantDeregister; +import org.onap.policy.clamp.models.acm.messages.dmaap.participant.ParticipantMessage; +import org.onap.policy.clamp.models.acm.messages.dmaap.participant.ParticipantRegister; +import org.onap.policy.clamp.models.acm.messages.dmaap.participant.ParticipantStatus; +import org.onap.policy.clamp.models.acm.messages.dmaap.participant.ParticipantUpdateAck; +import org.onap.policy.clamp.models.acm.persistence.provider.AutomationCompositionProvider; +import org.onap.policy.clamp.models.acm.persistence.provider.ParticipantProvider; +import org.onap.policy.clamp.models.acm.persistence.provider.ServiceTemplateProvider; +import org.onap.policy.models.base.PfModelException; +import org.onap.policy.models.base.PfModelRuntimeException; +import org.onap.policy.models.tosca.authorative.concepts.ToscaConceptIdentifier; +import org.onap.policy.models.tosca.authorative.concepts.ToscaServiceTemplate; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +/** + * This class handles supervision of automation composition instances, so only one object of this type should be built + * at a time. + * + * <p/> + * It is effectively a singleton that is started at system start. + */ +@Component +@AllArgsConstructor +public class SupervisionHandler { + private static final Logger LOGGER = LoggerFactory.getLogger(SupervisionHandler.class); + + private static final String AUTOMATION_COMPOSITION_CANNOT_TRANSITION_FROM_STATE = + "Automation composition can't transition from state "; + private static final String AUTOMATION_COMPOSITION_IS_ALREADY_IN_STATE = + "Automation composition is already in state "; + private static final String TO_STATE = " to state "; + private static final String AND_TRANSITIONING_TO_STATE = " and transitioning to state "; + + private final AutomationCompositionProvider automationCompositionProvider; + private final ParticipantProvider participantProvider; + private final MonitoringProvider monitoringProvider; + private final ServiceTemplateProvider serviceTemplateProvider; + + // Publishers for participant communication + private final AutomationCompositionUpdatePublisher automationCompositionUpdatePublisher; + private final AutomationCompositionStateChangePublisher automationCompositionStateChangePublisher; + private final ParticipantRegisterAckPublisher participantRegisterAckPublisher; + private final ParticipantDeregisterAckPublisher participantDeregisterAckPublisher; + private final ParticipantUpdatePublisher participantUpdatePublisher; + + /** + * Supervision trigger called when a command is issued on automation compositions. + * + * <p/> + * Causes supervision to start or continue supervision on the automation compositions in question. + * + * @param automationCompositionIdentifierList the automation compositions for which the supervision command has been + * issued + * @throws AutomationCompositionException on supervision triggering exceptions + */ + public void triggerAutomationCompositionSupervision( + List<ToscaConceptIdentifier> automationCompositionIdentifierList) throws AutomationCompositionException { + + LOGGER.debug("triggering automation composition supervision on automation compositions {}", + automationCompositionIdentifierList); + + if (CollectionUtils.isEmpty(automationCompositionIdentifierList)) { + // This is just to force throwing of the exception in certain circumstances. + exceptionOccured(Response.Status.NOT_ACCEPTABLE, + "The list of automation compositions for supervision is empty"); + } + + for (ToscaConceptIdentifier automationCompositionId : automationCompositionIdentifierList) { + try { + var automationComposition = + automationCompositionProvider.getAutomationComposition(automationCompositionId); + + superviseAutomationComposition(automationComposition); + + automationCompositionProvider.saveAutomationComposition(automationComposition); + } catch (PfModelException pfme) { + throw new AutomationCompositionException(pfme.getErrorResponse().getResponseCode(), pfme.getMessage(), + pfme); + } + } + } + + /** + * Handle a ParticipantStatus message from a participant. + * + * @param participantStatusMessage the ParticipantStatus message received from a participant + */ + @MessageIntercept + public void handleParticipantMessage(ParticipantStatus participantStatusMessage) { + LOGGER.debug("Participant Status received {}", participantStatusMessage); + try { + superviseParticipant(participantStatusMessage); + } catch (PfModelException | AutomationCompositionException svExc) { + LOGGER.warn("error supervising participant {}", participantStatusMessage.getParticipantId(), svExc); + return; + } + + try { + superviseAutomationCompositions(participantStatusMessage); + } catch (PfModelException | AutomationCompositionException svExc) { + LOGGER.warn("error supervising participant {}", participantStatusMessage.getParticipantId(), svExc); + } + } + + /** + * Handle a ParticipantRegister message from a participant. + * + * @param participantRegisterMessage the ParticipantRegister message received from a participant + */ + @MessageIntercept + public boolean handleParticipantMessage(ParticipantRegister participantRegisterMessage) { + LOGGER.debug("Participant Register received {}", participantRegisterMessage); + try { + checkParticipant(participantRegisterMessage, ParticipantState.UNKNOWN, ParticipantHealthStatus.UNKNOWN); + } catch (PfModelException | AutomationCompositionException svExc) { + LOGGER.warn("error saving participant {}", participantRegisterMessage.getParticipantId(), svExc); + } + + var isCommissioning = participantUpdatePublisher.sendCommissioning(null, null, + participantRegisterMessage.getParticipantId(), participantRegisterMessage.getParticipantType()); + + participantRegisterAckPublisher.send(participantRegisterMessage.getMessageId(), + participantRegisterMessage.getParticipantId(), participantRegisterMessage.getParticipantType()); + return isCommissioning; + } + + /** + * Handle a ParticipantDeregister message from a participant. + * + * @param participantDeregisterMessage the ParticipantDeregister message received from a participant + */ + @MessageIntercept + public void handleParticipantMessage(ParticipantDeregister participantDeregisterMessage) { + LOGGER.debug("Participant Deregister received {}", participantDeregisterMessage); + try { + var participantOpt = + participantProvider.findParticipant(participantDeregisterMessage.getParticipantId().getName(), + participantDeregisterMessage.getParticipantId().getVersion()); + + if (participantOpt.isPresent()) { + var participant = participantOpt.get(); + participant.setParticipantState(ParticipantState.TERMINATED); + participant.setHealthStatus(ParticipantHealthStatus.OFF_LINE); + participantProvider.saveParticipant(participant); + } + } catch (PfModelException pfme) { + LOGGER.warn("Model exception occured with participant id {}", + participantDeregisterMessage.getParticipantId()); + } + + participantDeregisterAckPublisher.send(participantDeregisterMessage.getMessageId()); + } + + /** + * Handle a ParticipantUpdateAck message from a participant. + * + * @param participantUpdateAckMessage the ParticipantUpdateAck message received from a participant + */ + @MessageIntercept + public void handleParticipantMessage(ParticipantUpdateAck participantUpdateAckMessage) { + LOGGER.debug("Participant Update Ack received {}", participantUpdateAckMessage); + try { + var participantOpt = + participantProvider.findParticipant(participantUpdateAckMessage.getParticipantId().getName(), + participantUpdateAckMessage.getParticipantId().getVersion()); + + if (participantOpt.isPresent()) { + var participant = participantOpt.get(); + participant.setParticipantState(participantUpdateAckMessage.getState()); + participantProvider.saveParticipant(participant); + } else { + LOGGER.warn("Participant not found in database {}", participantUpdateAckMessage.getParticipantId()); + } + } catch (PfModelException pfme) { + LOGGER.warn("Model exception occured with participant id {}", + participantUpdateAckMessage.getParticipantId()); + } + } + + /** + * Send commissioning update message to dmaap. + * + * @param name the ToscaServiceTemplate name + * @param version the ToscaServiceTemplate version + */ + public void handleSendCommissionMessage(String name, String version) { + LOGGER.debug("Participant update message with serviveTemplate {} {} being sent to all participants", name, + version); + participantUpdatePublisher.sendComissioningBroadcast(name, version); + } + + /** + * Send decommissioning update message to dmaap. + * + */ + public void handleSendDeCommissionMessage() { + LOGGER.debug("Participant update message being sent"); + participantUpdatePublisher.sendDecomisioning(); + } + + /** + * Handle a AutomationComposition update acknowledge message from a participant. + * + * @param automationCompositionAckMessage the AutomationCompositionAck message received from a participant + */ + @MessageIntercept + public void handleAutomationCompositionUpdateAckMessage(AutomationCompositionAck automationCompositionAckMessage) { + LOGGER.debug("AutomationComposition Update Ack message received {}", automationCompositionAckMessage); + setAcElementStateInDb(automationCompositionAckMessage); + } + + /** + * Handle a AutomationComposition statechange acknowledge message from a participant. + * + * @param automationCompositionAckMessage the AutomationCompositionAck message received from a participant + */ + @MessageIntercept + public void handleAutomationCompositionStateChangeAckMessage( + AutomationCompositionAck automationCompositionAckMessage) { + LOGGER.debug("AutomationComposition StateChange Ack message received {}", automationCompositionAckMessage); + setAcElementStateInDb(automationCompositionAckMessage); + } + + private void setAcElementStateInDb(AutomationCompositionAck automationCompositionAckMessage) { + if (automationCompositionAckMessage.getAutomationCompositionResultMap() != null) { + try { + var automationComposition = automationCompositionProvider + .getAutomationComposition(automationCompositionAckMessage.getAutomationCompositionId()); + if (automationComposition != null) { + var updated = updateState(automationComposition, + automationCompositionAckMessage.getAutomationCompositionResultMap().entrySet()); + updated |= setPrimed(automationComposition); + if (updated) { + automationCompositionProvider.saveAutomationComposition(automationComposition); + } + } else { + LOGGER.warn("AutomationComposition not found in database {}", + automationCompositionAckMessage.getAutomationCompositionId()); + } + } catch (PfModelException pfme) { + LOGGER.warn("Model exception occured with AutomationComposition Id {}", + automationCompositionAckMessage.getAutomationCompositionId()); + } + } + } + + private boolean updateState(AutomationComposition automationComposition, + Set<Map.Entry<UUID, AutomationCompositionElementAck>> automationCompositionResultSet) { + var updated = false; + for (var acElementAck : automationCompositionResultSet) { + var element = automationComposition.getElements().get(acElementAck.getKey()); + if (element != null) { + element.setState(acElementAck.getValue().getState()); + updated = true; + } + } + return updated; + } + + private boolean setPrimed(AutomationComposition automationComposition) { + var acElements = automationComposition.getElements().values(); + if (acElements != null) { + Boolean primedFlag = true; + var checkOpt = automationComposition.getElements().values().stream() + .filter(acElement -> (!acElement.getState().equals(AutomationCompositionState.PASSIVE) + || !acElement.getState().equals(AutomationCompositionState.RUNNING))) + .findAny(); + if (checkOpt.isEmpty()) { + primedFlag = false; + } + automationComposition.setPrimed(primedFlag); + return true; + } + + return false; + } + + /** + * Supervise a automation composition, performing whatever actions need to be performed on the automation + * composition. + * + * @param automationComposition the automation composition to supervises + * @throws AutomationCompositionException on supervision errors + */ + private void superviseAutomationComposition(AutomationComposition automationComposition) + throws AutomationCompositionException { + switch (automationComposition.getOrderedState()) { + case UNINITIALISED: + superviseAutomationCompositionUninitialization(automationComposition); + break; + + case PASSIVE: + superviseAutomationCompositionPassivation(automationComposition); + break; + + case RUNNING: + superviseAutomationCompositionActivation(automationComposition); + break; + + default: + exceptionOccured(Response.Status.NOT_ACCEPTABLE, + "A automation composition cannot be commanded to go into state " + + automationComposition.getOrderedState().name()); + } + } + + /** + * Supervise a automation composition uninitialisation, performing whatever actions need to be performed on the + * automation composition, + * automation composition ordered state is UNINITIALIZED. + * + * @param automationComposition the automation composition to supervises + * @throws AutomationCompositionException on supervision errors + */ + private void superviseAutomationCompositionUninitialization(AutomationComposition automationComposition) + throws AutomationCompositionException { + switch (automationComposition.getState()) { + case UNINITIALISED: + exceptionOccured(Response.Status.NOT_ACCEPTABLE, + AUTOMATION_COMPOSITION_IS_ALREADY_IN_STATE + automationComposition.getState().name()); + break; + + case UNINITIALISED2PASSIVE: + case PASSIVE: + automationComposition.setState(AutomationCompositionState.PASSIVE2UNINITIALISED); + automationCompositionStateChangePublisher.send(automationComposition, + getFirstStartPhase(automationComposition)); + break; + + case PASSIVE2UNINITIALISED: + exceptionOccured(Response.Status.NOT_ACCEPTABLE, + AUTOMATION_COMPOSITION_IS_ALREADY_IN_STATE + automationComposition.getState().name() + + AND_TRANSITIONING_TO_STATE + automationComposition.getOrderedState()); + break; + + default: + exceptionOccured(Response.Status.NOT_ACCEPTABLE, AUTOMATION_COMPOSITION_CANNOT_TRANSITION_FROM_STATE + + automationComposition.getState().name() + TO_STATE + automationComposition.getOrderedState()); + break; + } + } + + private void superviseAutomationCompositionPassivation(AutomationComposition automationComposition) + throws AutomationCompositionException { + switch (automationComposition.getState()) { + case PASSIVE: + exceptionOccured(Response.Status.NOT_ACCEPTABLE, + AUTOMATION_COMPOSITION_IS_ALREADY_IN_STATE + automationComposition.getState().name()); + break; + case UNINITIALISED: + automationComposition.setState(AutomationCompositionState.UNINITIALISED2PASSIVE); + automationCompositionUpdatePublisher.send(automationComposition); + break; + + case UNINITIALISED2PASSIVE: + case RUNNING2PASSIVE: + exceptionOccured(Response.Status.NOT_ACCEPTABLE, + AUTOMATION_COMPOSITION_IS_ALREADY_IN_STATE + automationComposition.getState().name() + + AND_TRANSITIONING_TO_STATE + automationComposition.getOrderedState()); + break; + + case RUNNING: + automationComposition.setState(AutomationCompositionState.RUNNING2PASSIVE); + automationCompositionStateChangePublisher.send(automationComposition, + getFirstStartPhase(automationComposition)); + break; + + default: + exceptionOccured(Response.Status.NOT_ACCEPTABLE, AUTOMATION_COMPOSITION_CANNOT_TRANSITION_FROM_STATE + + automationComposition.getState().name() + TO_STATE + automationComposition.getOrderedState()); + break; + } + } + + private void superviseAutomationCompositionActivation(AutomationComposition automationComposition) + throws AutomationCompositionException { + switch (automationComposition.getState()) { + case RUNNING: + exceptionOccured(Response.Status.NOT_ACCEPTABLE, + AUTOMATION_COMPOSITION_IS_ALREADY_IN_STATE + automationComposition.getState().name()); + break; + + case PASSIVE2RUNNING: + exceptionOccured(Response.Status.NOT_ACCEPTABLE, + AUTOMATION_COMPOSITION_IS_ALREADY_IN_STATE + automationComposition.getState().name() + + AND_TRANSITIONING_TO_STATE + automationComposition.getOrderedState()); + break; + + case PASSIVE: + automationComposition.setState(AutomationCompositionState.PASSIVE2RUNNING); + automationCompositionStateChangePublisher.send(automationComposition, + getFirstStartPhase(automationComposition)); + break; + + default: + exceptionOccured(Response.Status.NOT_ACCEPTABLE, AUTOMATION_COMPOSITION_CANNOT_TRANSITION_FROM_STATE + + automationComposition.getState().name() + TO_STATE + automationComposition.getOrderedState()); + break; + } + } + + private int getFirstStartPhase(AutomationComposition automationComposition) { + ToscaServiceTemplate toscaServiceTemplate = null; + try { + toscaServiceTemplate = serviceTemplateProvider.getAllServiceTemplates().get(0); + } catch (PfModelException e) { + throw new PfModelRuntimeException(Status.BAD_REQUEST, "Canont load ToscaServiceTemplate from DB", e); + } + return ParticipantUtils.getFirstStartPhase(automationComposition, toscaServiceTemplate); + } + + private void checkParticipant(ParticipantMessage participantMessage, ParticipantState participantState, + ParticipantHealthStatus healthStatus) throws AutomationCompositionException, PfModelException { + if (participantMessage.getParticipantId() == null) { + exceptionOccured(Response.Status.NOT_FOUND, "Participant ID on PARTICIPANT_STATUS message is null"); + } + var participantOpt = participantProvider.findParticipant(participantMessage.getParticipantId().getName(), + participantMessage.getParticipantId().getVersion()); + + if (participantOpt.isEmpty()) { + var participant = new Participant(); + participant.setName(participantMessage.getParticipantId().getName()); + participant.setVersion(participantMessage.getParticipantId().getVersion()); + participant.setDefinition(participantMessage.getParticipantId()); + participant.setParticipantType(participantMessage.getParticipantType()); + participant.setParticipantState(participantState); + participant.setHealthStatus(healthStatus); + + participantProvider.saveParticipant(participant); + } else { + var participant = participantOpt.get(); + participant.setParticipantState(participantState); + participant.setHealthStatus(healthStatus); + + participantProvider.saveParticipant(participant); + } + } + + private void superviseParticipant(ParticipantStatus participantStatusMessage) + throws PfModelException, AutomationCompositionException { + + checkParticipant(participantStatusMessage, participantStatusMessage.getState(), + participantStatusMessage.getHealthStatus()); + + monitoringProvider.createParticipantStatistics(List.of(participantStatusMessage.getParticipantStatistics())); + } + + private void superviseAutomationCompositions(ParticipantStatus participantStatusMessage) + throws PfModelException, AutomationCompositionException { + if (participantStatusMessage.getAutomationCompositionInfoList() != null) { + for (AutomationCompositionInfo acEntry : participantStatusMessage.getAutomationCompositionInfoList()) { + var dbAutomationComposition = automationCompositionProvider + .getAutomationComposition(new ToscaConceptIdentifier(acEntry.getAutomationCompositionId())); + if (dbAutomationComposition == null) { + exceptionOccured(Response.Status.NOT_FOUND, + "PARTICIPANT_STATUS automation composition not found in database: " + + acEntry.getAutomationCompositionId()); + } + dbAutomationComposition.setState(acEntry.getState()); + monitoringProvider.createAcElementStatistics( + acEntry.getAutomationCompositionStatistics().getAcElementStatisticsList().getAcElementStatistics()); + } + } + } + + private void exceptionOccured(Response.Status status, String reason) throws AutomationCompositionException { + throw new AutomationCompositionException(status, reason); + } +} diff --git a/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/supervision/SupervisionScanner.java b/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/supervision/SupervisionScanner.java new file mode 100644 index 000000000..ce7195d93 --- /dev/null +++ b/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/supervision/SupervisionScanner.java @@ -0,0 +1,307 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2021 Nordix Foundation. + * ================================================================================ + * Modifications Copyright (C) 2021 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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.policy.clamp.acm.runtime.supervision; + +import java.util.HashMap; +import java.util.Map; +import org.apache.commons.lang3.tuple.Pair; +import org.onap.policy.clamp.acm.runtime.main.parameters.AcRuntimeParameterGroup; +import org.onap.policy.clamp.acm.runtime.supervision.comm.AutomationCompositionStateChangePublisher; +import org.onap.policy.clamp.acm.runtime.supervision.comm.AutomationCompositionUpdatePublisher; +import org.onap.policy.clamp.acm.runtime.supervision.comm.ParticipantStatusReqPublisher; +import org.onap.policy.clamp.acm.runtime.supervision.comm.ParticipantUpdatePublisher; +import org.onap.policy.clamp.models.acm.concepts.AutomationComposition; +import org.onap.policy.clamp.models.acm.concepts.AutomationCompositionElement; +import org.onap.policy.clamp.models.acm.concepts.AutomationCompositionState; +import org.onap.policy.clamp.models.acm.concepts.Participant; +import org.onap.policy.clamp.models.acm.concepts.ParticipantHealthStatus; +import org.onap.policy.clamp.models.acm.concepts.ParticipantUtils; +import org.onap.policy.clamp.models.acm.persistence.provider.AutomationCompositionProvider; +import org.onap.policy.clamp.models.acm.persistence.provider.ParticipantProvider; +import org.onap.policy.clamp.models.acm.persistence.provider.ServiceTemplateProvider; +import org.onap.policy.models.base.PfModelException; +import org.onap.policy.models.tosca.authorative.concepts.ToscaConceptIdentifier; +import org.onap.policy.models.tosca.authorative.concepts.ToscaNodeTemplate; +import org.onap.policy.models.tosca.authorative.concepts.ToscaServiceTemplate; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +/** + * This class is used to scan the automation compositions in the database and check if they are in the correct state. + */ +@Component +public class SupervisionScanner { + private static final Logger LOGGER = LoggerFactory.getLogger(SupervisionScanner.class); + + private final HandleCounter<ToscaConceptIdentifier> automationCompositionCounter = new HandleCounter<>(); + private final HandleCounter<ToscaConceptIdentifier> participantStatusCounter = new HandleCounter<>(); + private final HandleCounter<Pair<ToscaConceptIdentifier, ToscaConceptIdentifier>> participantUpdateCounter = + new HandleCounter<>(); + + private final Map<ToscaConceptIdentifier, Integer> phaseMap = new HashMap<>(); + + private final AutomationCompositionProvider automationCompositionProvider; + private final ServiceTemplateProvider serviceTemplateProvider; + private final AutomationCompositionStateChangePublisher automationCompositionStateChangePublisher; + private final AutomationCompositionUpdatePublisher automationCompositionUpdatePublisher; + private final ParticipantProvider participantProvider; + private final ParticipantStatusReqPublisher participantStatusReqPublisher; + private final ParticipantUpdatePublisher participantUpdatePublisher; + + /** + * Constructor for instantiating SupervisionScanner. + * + * @param automationCompositionProvider the provider to use to read automation compositions from the database + * @param serviceTemplateProvider the Policy Models Provider + * @param automationCompositionStateChangePublisher the AutomationComposition StateChange Publisher + * @param automationCompositionUpdatePublisher the AutomationCompositionUpdate Publisher + * @param participantProvider the Participant Provider + * @param participantStatusReqPublisher the Participant StatusReq Publisher + * @param participantUpdatePublisher the Participant Update Publisher + * @param acRuntimeParameterGroup the parameters for the automation composition runtime + */ + public SupervisionScanner(final AutomationCompositionProvider automationCompositionProvider, + ServiceTemplateProvider serviceTemplateProvider, + final AutomationCompositionStateChangePublisher automationCompositionStateChangePublisher, + AutomationCompositionUpdatePublisher automationCompositionUpdatePublisher, + ParticipantProvider participantProvider, ParticipantStatusReqPublisher participantStatusReqPublisher, + ParticipantUpdatePublisher participantUpdatePublisher, final AcRuntimeParameterGroup acRuntimeParameterGroup) { + this.automationCompositionProvider = automationCompositionProvider; + this.serviceTemplateProvider = serviceTemplateProvider; + this.automationCompositionStateChangePublisher = automationCompositionStateChangePublisher; + this.automationCompositionUpdatePublisher = automationCompositionUpdatePublisher; + this.participantProvider = participantProvider; + this.participantStatusReqPublisher = participantStatusReqPublisher; + this.participantUpdatePublisher = participantUpdatePublisher; + + automationCompositionCounter.setMaxRetryCount( + acRuntimeParameterGroup.getParticipantParameters().getUpdateParameters().getMaxRetryCount()); + automationCompositionCounter + .setMaxWaitMs(acRuntimeParameterGroup.getParticipantParameters().getMaxStatusWaitMs()); + + participantUpdateCounter.setMaxRetryCount( + acRuntimeParameterGroup.getParticipantParameters().getUpdateParameters().getMaxRetryCount()); + participantUpdateCounter + .setMaxWaitMs(acRuntimeParameterGroup.getParticipantParameters().getUpdateParameters().getMaxWaitMs()); + + participantStatusCounter.setMaxRetryCount( + acRuntimeParameterGroup.getParticipantParameters().getUpdateParameters().getMaxRetryCount()); + participantStatusCounter.setMaxWaitMs(acRuntimeParameterGroup.getParticipantParameters().getMaxStatusWaitMs()); + } + + /** + * Run Scanning. + * + * @param counterCheck if true activate counter and retry + */ + public void run(boolean counterCheck) { + LOGGER.debug("Scanning automation compositions in the database . . ."); + + if (counterCheck) { + try { + for (var participant : participantProvider.getParticipants()) { + scanParticipantStatus(participant); + } + } catch (PfModelException pfme) { + LOGGER.warn("error reading participant from database", pfme); + return; + } + } + + try { + var list = serviceTemplateProvider.getAllServiceTemplates(); + if (list != null && !list.isEmpty()) { + ToscaServiceTemplate toscaServiceTemplate = list.get(0); + + for (AutomationComposition automationComposition : automationCompositionProvider + .getAutomationCompositions()) { + scanAutomationComposition(automationComposition, toscaServiceTemplate, counterCheck); + } + } + } catch (PfModelException pfme) { + LOGGER.warn("error reading automation compositions from database", pfme); + } + + if (counterCheck) { + scanParticipantUpdate(); + } + + LOGGER.debug("Automation composition scan complete . . ."); + } + + private void scanParticipantUpdate() { + LOGGER.debug("Scanning participants to update . . ."); + + for (var id : participantUpdateCounter.keySet()) { + if (participantUpdateCounter.isFault(id)) { + LOGGER.debug("report Participant Update fault"); + + } else if (participantUpdateCounter.getDuration(id) > participantUpdateCounter.getMaxWaitMs()) { + + if (participantUpdateCounter.count(id)) { + LOGGER.debug("retry message ParticipantUpdate"); + participantUpdatePublisher.sendCommissioning(null, null, id.getLeft(), id.getRight()); + } else { + LOGGER.debug("report Participant Update fault"); + participantUpdateCounter.setFault(id); + } + } + } + + LOGGER.debug("Participants to update scan complete . . ."); + } + + private void scanParticipantStatus(Participant participant) throws PfModelException { + ToscaConceptIdentifier id = participant.getKey().asIdentifier(); + if (participantStatusCounter.isFault(id)) { + LOGGER.debug("report Participant fault"); + return; + } + if (participantStatusCounter.getDuration(id) > participantStatusCounter.getMaxWaitMs()) { + if (participantStatusCounter.count(id)) { + LOGGER.debug("retry message ParticipantStatusReq"); + participantStatusReqPublisher.send(id); + participant.setHealthStatus(ParticipantHealthStatus.NOT_HEALTHY); + } else { + LOGGER.debug("report Participant fault"); + participantStatusCounter.setFault(id); + participant.setHealthStatus(ParticipantHealthStatus.OFF_LINE); + } + participantProvider.saveParticipant(participant); + } + } + + /** + * handle participant Status message. + */ + public void handleParticipantStatus(ToscaConceptIdentifier id) { + participantStatusCounter.clear(id); + } + + public void handleParticipantRegister(Pair<ToscaConceptIdentifier, ToscaConceptIdentifier> id) { + participantUpdateCounter.clear(id); + } + + public void handleParticipantUpdateAck(Pair<ToscaConceptIdentifier, ToscaConceptIdentifier> id) { + participantUpdateCounter.remove(id); + } + + private void scanAutomationComposition(final AutomationComposition automationComposition, + ToscaServiceTemplate toscaServiceTemplate, boolean counterCheck) throws PfModelException { + LOGGER.debug("scanning automation composition {} . . .", automationComposition.getKey().asIdentifier()); + + if (automationComposition.getState().equals(automationComposition.getOrderedState().asState())) { + LOGGER.debug("automation composition {} scanned, OK", automationComposition.getKey().asIdentifier()); + + // Clear missed report counter on automation composition + clearFaultAndCounter(automationComposition); + return; + } + + var completed = true; + var minSpNotCompleted = 1000; // min startPhase not completed + var maxSpNotCompleted = 0; // max startPhase not completed + var defaultMin = 1000; // min startPhase + var defaultMax = 0; // max startPhase + for (AutomationCompositionElement element : automationComposition.getElements().values()) { + ToscaNodeTemplate toscaNodeTemplate = toscaServiceTemplate.getToscaTopologyTemplate().getNodeTemplates() + .get(element.getDefinition().getName()); + int startPhase = ParticipantUtils.findStartPhase(toscaNodeTemplate.getProperties()); + defaultMin = Math.min(defaultMin, startPhase); + defaultMax = Math.max(defaultMax, startPhase); + if (!element.getState().equals(element.getOrderedState().asState())) { + completed = false; + minSpNotCompleted = Math.min(minSpNotCompleted, startPhase); + maxSpNotCompleted = Math.max(maxSpNotCompleted, startPhase); + } + } + + if (completed) { + LOGGER.debug("automation composition scan: transition from state {} to {} completed", + automationComposition.getState(), automationComposition.getOrderedState()); + + automationComposition.setState(automationComposition.getOrderedState().asState()); + automationCompositionProvider.saveAutomationComposition(automationComposition); + + // Clear missed report counter on automation composition + clearFaultAndCounter(automationComposition); + } else { + LOGGER.debug("automation composition scan: transition from state {} to {} not completed", + automationComposition.getState(), automationComposition.getOrderedState()); + + var nextSpNotCompleted = + AutomationCompositionState.UNINITIALISED2PASSIVE.equals(automationComposition.getState()) + || AutomationCompositionState.PASSIVE2RUNNING.equals(automationComposition.getState()) + ? minSpNotCompleted + : maxSpNotCompleted; + + var firstStartPhase = + AutomationCompositionState.UNINITIALISED2PASSIVE.equals(automationComposition.getState()) + || AutomationCompositionState.PASSIVE2RUNNING.equals(automationComposition.getState()) ? defaultMin + : defaultMax; + + if (nextSpNotCompleted != phaseMap.getOrDefault(automationComposition.getKey().asIdentifier(), + firstStartPhase)) { + phaseMap.put(automationComposition.getKey().asIdentifier(), nextSpNotCompleted); + sendAutomationCompositionMsg(automationComposition, nextSpNotCompleted); + } else if (counterCheck) { + phaseMap.put(automationComposition.getKey().asIdentifier(), nextSpNotCompleted); + handleCounter(automationComposition, nextSpNotCompleted); + } + } + } + + private void clearFaultAndCounter(AutomationComposition automationComposition) { + automationCompositionCounter.clear(automationComposition.getKey().asIdentifier()); + phaseMap.clear(); + } + + private void handleCounter(AutomationComposition automationComposition, int startPhase) { + ToscaConceptIdentifier id = automationComposition.getKey().asIdentifier(); + if (automationCompositionCounter.isFault(id)) { + LOGGER.debug("report AutomationComposition fault"); + return; + } + + if (automationCompositionCounter.getDuration(id) > automationCompositionCounter.getMaxWaitMs()) { + if (automationCompositionCounter.count(id)) { + phaseMap.put(id, startPhase); + sendAutomationCompositionMsg(automationComposition, startPhase); + } else { + LOGGER.debug("report AutomationComposition fault"); + automationCompositionCounter.setFault(id); + } + } + } + + private void sendAutomationCompositionMsg(AutomationComposition automationComposition, int startPhase) { + if (AutomationCompositionState.UNINITIALISED2PASSIVE.equals(automationComposition.getState())) { + LOGGER.debug("retry message AutomationCompositionUpdate"); + automationCompositionUpdatePublisher.send(automationComposition, startPhase); + } else { + LOGGER.debug("retry message AutomationCompositionStateChange"); + automationCompositionStateChangePublisher.send(automationComposition, startPhase); + } + } +} diff --git a/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/supervision/comm/AbstractParticipantAckPublisher.java b/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/supervision/comm/AbstractParticipantAckPublisher.java new file mode 100644 index 000000000..22284a4eb --- /dev/null +++ b/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/supervision/comm/AbstractParticipantAckPublisher.java @@ -0,0 +1,62 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2021 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.policy.clamp.acm.runtime.supervision.comm; + +import java.util.List; +import javax.ws.rs.core.Response.Status; +import org.onap.policy.clamp.acm.runtime.config.messaging.Publisher; +import org.onap.policy.clamp.common.acm.exception.AutomationCompositionRuntimeException; +import org.onap.policy.clamp.models.acm.messages.dmaap.participant.ParticipantAckMessage; +import org.onap.policy.common.endpoints.event.comm.TopicSink; +import org.onap.policy.common.endpoints.event.comm.client.TopicSinkClient; + +public abstract class AbstractParticipantAckPublisher<E extends ParticipantAckMessage> implements Publisher { + + private TopicSinkClient topicSinkClient; + private boolean active = false; + + /** + * Method to send Participant message to participants on demand. + * + * @param participantMessage the Participant message + */ + public void send(final E participantMessage) { + if (!active) { + throw new AutomationCompositionRuntimeException(Status.NOT_ACCEPTABLE, "Not Active!"); + } + topicSinkClient.send(participantMessage); + } + + + @Override + public void active(List<TopicSink> topicSinks) { + if (topicSinks.size() != 1) { + throw new IllegalArgumentException("Topic Sink must be one"); + } + this.topicSinkClient = new TopicSinkClient(topicSinks.get(0)); + active = true; + } + + @Override + public void stop() { + active = false; + } +} diff --git a/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/supervision/comm/AbstractParticipantPublisher.java b/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/supervision/comm/AbstractParticipantPublisher.java new file mode 100644 index 000000000..054eaf7b5 --- /dev/null +++ b/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/supervision/comm/AbstractParticipantPublisher.java @@ -0,0 +1,62 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2021 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.policy.clamp.acm.runtime.supervision.comm; + +import java.util.List; +import javax.ws.rs.core.Response.Status; +import org.onap.policy.clamp.acm.runtime.config.messaging.Publisher; +import org.onap.policy.clamp.common.acm.exception.AutomationCompositionRuntimeException; +import org.onap.policy.clamp.models.acm.messages.dmaap.participant.ParticipantMessage; +import org.onap.policy.common.endpoints.event.comm.TopicSink; +import org.onap.policy.common.endpoints.event.comm.client.TopicSinkClient; + +public abstract class AbstractParticipantPublisher<E extends ParticipantMessage> implements Publisher { + + private TopicSinkClient topicSinkClient; + private boolean active = false; + + /** + * Method to send Participant message to participants on demand. + * + * @param participantMessage the Participant message + */ + public void send(final E participantMessage) { + if (!active) { + throw new AutomationCompositionRuntimeException(Status.NOT_ACCEPTABLE, "Not Active!"); + } + topicSinkClient.send(participantMessage); + } + + + @Override + public void active(List<TopicSink> topicSinks) { + if (topicSinks.size() != 1) { + throw new IllegalArgumentException("Topic Sink must be one"); + } + this.topicSinkClient = new TopicSinkClient(topicSinks.get(0)); + active = true; + } + + @Override + public void stop() { + active = false; + } +} diff --git a/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/supervision/comm/AutomationCompositionStateChangeAckListener.java b/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/supervision/comm/AutomationCompositionStateChangeAckListener.java new file mode 100644 index 000000000..dd07be680 --- /dev/null +++ b/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/supervision/comm/AutomationCompositionStateChangeAckListener.java @@ -0,0 +1,70 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2021 Nordix Foundation. + * Modifications Copyright (C) 2021 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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.policy.clamp.acm.runtime.supervision.comm; + +import org.onap.policy.clamp.acm.runtime.config.messaging.Listener; +import org.onap.policy.clamp.acm.runtime.supervision.SupervisionHandler; +import org.onap.policy.clamp.models.acm.messages.dmaap.participant.AutomationCompositionAck; +import org.onap.policy.clamp.models.acm.messages.dmaap.participant.ParticipantMessageType; +import org.onap.policy.common.endpoints.event.comm.Topic.CommInfrastructure; +import org.onap.policy.common.endpoints.listeners.ScoListener; +import org.onap.policy.common.utils.coder.StandardCoderObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +/** + * Listener for AutomationCompositionStateChangeAck messages sent by participants. + */ +@Component +public class AutomationCompositionStateChangeAckListener extends ScoListener<AutomationCompositionAck> + implements Listener<AutomationCompositionAck> { + private static final Logger LOGGER = LoggerFactory.getLogger(AutomationCompositionStateChangeAckListener.class); + + private final SupervisionHandler supervisionHandler; + + /** + * Constructs the object. + */ + public AutomationCompositionStateChangeAckListener(SupervisionHandler supervisionHandler) { + super(AutomationCompositionAck.class); + this.supervisionHandler = supervisionHandler; + } + + @Override + public void onTopicEvent(final CommInfrastructure infra, final String topic, final StandardCoderObject sco, + final AutomationCompositionAck automationCompositionStateChangeAckMessage) { + LOGGER.debug("AutomationCompositionStateChangeAck received from participant - {}", + automationCompositionStateChangeAckMessage); + supervisionHandler.handleAutomationCompositionStateChangeAckMessage(automationCompositionStateChangeAckMessage); + } + + @Override + public ScoListener<AutomationCompositionAck> getScoListener() { + return this; + } + + @Override + public String getType() { + return ParticipantMessageType.AUTOMATION_COMPOSITION_STATECHANGE_ACK.name(); + } +} diff --git a/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/supervision/comm/AutomationCompositionStateChangePublisher.java b/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/supervision/comm/AutomationCompositionStateChangePublisher.java new file mode 100644 index 000000000..4e0d12bf6 --- /dev/null +++ b/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/supervision/comm/AutomationCompositionStateChangePublisher.java @@ -0,0 +1,50 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2021 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.policy.clamp.acm.runtime.supervision.comm; + +import java.util.UUID; +import org.onap.policy.clamp.models.acm.concepts.AutomationComposition; +import org.onap.policy.clamp.models.acm.messages.dmaap.participant.AutomationCompositionStateChange; +import org.springframework.stereotype.Component; + +/** + * This class is used to send AutomationCompositionStateChangePublisher messages to participants on DMaaP. + */ +@Component +public class AutomationCompositionStateChangePublisher + extends AbstractParticipantPublisher<AutomationCompositionStateChange> { + + /** + * Send AutomationCompositionStateChange to Participant. + * + * @param automationComposition the AutomationComposition + * @param startPhase the startPhase + */ + public void send(AutomationComposition automationComposition, int startPhase) { + var acsc = new AutomationCompositionStateChange(); + acsc.setAutomationCompositionId(automationComposition.getKey().asIdentifier()); + acsc.setMessageId(UUID.randomUUID()); + acsc.setOrderedState(automationComposition.getOrderedState()); + acsc.setStartPhase(startPhase); + + super.send(acsc); + } +} diff --git a/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/supervision/comm/AutomationCompositionUpdateAckListener.java b/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/supervision/comm/AutomationCompositionUpdateAckListener.java new file mode 100644 index 000000000..7a1d5294c --- /dev/null +++ b/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/supervision/comm/AutomationCompositionUpdateAckListener.java @@ -0,0 +1,70 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2021 Nordix Foundation. + * Modifications Copyright (C) 2021 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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.policy.clamp.acm.runtime.supervision.comm; + +import org.onap.policy.clamp.acm.runtime.config.messaging.Listener; +import org.onap.policy.clamp.acm.runtime.supervision.SupervisionHandler; +import org.onap.policy.clamp.models.acm.messages.dmaap.participant.AutomationCompositionAck; +import org.onap.policy.clamp.models.acm.messages.dmaap.participant.ParticipantMessageType; +import org.onap.policy.common.endpoints.event.comm.Topic.CommInfrastructure; +import org.onap.policy.common.endpoints.listeners.ScoListener; +import org.onap.policy.common.utils.coder.StandardCoderObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +/** + * Listener for AutomationCompositionUpdateAck messages sent by participants. + */ +@Component +public class AutomationCompositionUpdateAckListener extends ScoListener<AutomationCompositionAck> + implements Listener<AutomationCompositionAck> { + private static final Logger LOGGER = LoggerFactory.getLogger(AutomationCompositionUpdateAckListener.class); + + private final SupervisionHandler supervisionHandler; + + /** + * Constructs the object. + */ + public AutomationCompositionUpdateAckListener(SupervisionHandler supervisionHandler) { + super(AutomationCompositionAck.class); + this.supervisionHandler = supervisionHandler; + } + + @Override + public void onTopicEvent(final CommInfrastructure infra, final String topic, final StandardCoderObject sco, + final AutomationCompositionAck automationCompositionUpdateAckMessage) { + LOGGER.debug("AutomationCompositionUpdateAck message received from participant - {}", + automationCompositionUpdateAckMessage); + supervisionHandler.handleAutomationCompositionUpdateAckMessage(automationCompositionUpdateAckMessage); + } + + @Override + public ScoListener<AutomationCompositionAck> getScoListener() { + return this; + } + + @Override + public String getType() { + return ParticipantMessageType.AUTOMATION_COMPOSITION_UPDATE_ACK.name(); + } +} diff --git a/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/supervision/comm/AutomationCompositionUpdatePublisher.java b/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/supervision/comm/AutomationCompositionUpdatePublisher.java new file mode 100644 index 000000000..ac5a998b4 --- /dev/null +++ b/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/supervision/comm/AutomationCompositionUpdatePublisher.java @@ -0,0 +1,91 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2021 Nordix Foundation. + * ================================================================================ + * Modifications Copyright (C) 2021 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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.policy.clamp.acm.runtime.supervision.comm; + +import java.time.Instant; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import lombok.AllArgsConstructor; +import org.onap.policy.clamp.models.acm.concepts.AutomationComposition; +import org.onap.policy.clamp.models.acm.concepts.AutomationCompositionElement; +import org.onap.policy.clamp.models.acm.concepts.ParticipantUpdates; +import org.onap.policy.clamp.models.acm.messages.dmaap.participant.AutomationCompositionUpdate; +import org.onap.policy.clamp.models.acm.persistence.provider.ServiceTemplateProvider; +import org.onap.policy.clamp.models.acm.utils.AcmUtils; +import org.onap.policy.models.base.PfModelException; +import org.onap.policy.models.tosca.authorative.concepts.ToscaServiceTemplate; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +/** + * This class is used to send AutomationCompositionUpdate messages to participants on DMaaP. + */ +@Component +@AllArgsConstructor +public class AutomationCompositionUpdatePublisher extends AbstractParticipantPublisher<AutomationCompositionUpdate> { + + private static final Logger LOGGER = LoggerFactory.getLogger(AutomationCompositionUpdatePublisher.class); + private final ServiceTemplateProvider serviceTemplateProvider; + + /** + * Send AutomationCompositionUpdate to Participant. + * + * @param automationComposition the AutomationComposition + */ + public void send(AutomationComposition automationComposition) { + send(automationComposition, 0); + } + + /** + * Send AutomationCompositionUpdate to Participant. + * + * @param automationComposition the AutomationComposition + * @param startPhase the Start Phase + */ + public void send(AutomationComposition automationComposition, int startPhase) { + var automationCompositionUpdateMsg = new AutomationCompositionUpdate(); + automationCompositionUpdateMsg.setStartPhase(startPhase); + automationCompositionUpdateMsg.setAutomationCompositionId(automationComposition.getKey().asIdentifier()); + automationCompositionUpdateMsg.setMessageId(UUID.randomUUID()); + automationCompositionUpdateMsg.setTimestamp(Instant.now()); + ToscaServiceTemplate toscaServiceTemplate; + try { + toscaServiceTemplate = serviceTemplateProvider.getAllServiceTemplates().get(0); + } catch (PfModelException pfme) { + LOGGER.warn("Get of tosca service template failed, cannot send participantupdate", pfme); + return; + } + + List<ParticipantUpdates> participantUpdates = new ArrayList<>(); + for (AutomationCompositionElement element : automationComposition.getElements().values()) { + AcmUtils.setServiceTemplatePolicyInfo(element, toscaServiceTemplate); + AcmUtils.prepareParticipantUpdate(element, participantUpdates); + } + automationCompositionUpdateMsg.setParticipantUpdatesList(participantUpdates); + + LOGGER.debug("AutomationCompositionUpdate message sent {}", automationCompositionUpdateMsg); + super.send(automationCompositionUpdateMsg); + } +} diff --git a/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/supervision/comm/ParticipantDeregisterAckPublisher.java b/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/supervision/comm/ParticipantDeregisterAckPublisher.java new file mode 100644 index 000000000..34881b557 --- /dev/null +++ b/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/supervision/comm/ParticipantDeregisterAckPublisher.java @@ -0,0 +1,45 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2021 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.policy.clamp.acm.runtime.supervision.comm; + +import java.util.UUID; +import org.onap.policy.clamp.models.acm.messages.dmaap.participant.ParticipantDeregisterAck; +import org.springframework.stereotype.Component; + +/** + * This class is used to send ParticipantDeregisterAck messages to participants on DMaaP. + */ +@Component +public class ParticipantDeregisterAckPublisher extends AbstractParticipantAckPublisher<ParticipantDeregisterAck> { + + /** + * Sent ParticipantDeregisterAck to Participant. + * + * @param responseTo the original request id in the request. + */ + public void send(UUID responseTo) { + var message = new ParticipantDeregisterAck(); + message.setResponseTo(responseTo); + message.setMessage("Participant Deregister Ack"); + message.setResult(true); + super.send(message); + } +} diff --git a/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/supervision/comm/ParticipantDeregisterListener.java b/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/supervision/comm/ParticipantDeregisterListener.java new file mode 100644 index 000000000..eec21235f --- /dev/null +++ b/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/supervision/comm/ParticipantDeregisterListener.java @@ -0,0 +1,69 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2021 Nordix Foundation. + * Modifications Copyright (C) 2021 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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.policy.clamp.acm.runtime.supervision.comm; + +import org.onap.policy.clamp.acm.runtime.config.messaging.Listener; +import org.onap.policy.clamp.acm.runtime.supervision.SupervisionHandler; +import org.onap.policy.clamp.models.acm.messages.dmaap.participant.ParticipantDeregister; +import org.onap.policy.clamp.models.acm.messages.dmaap.participant.ParticipantMessageType; +import org.onap.policy.common.endpoints.event.comm.Topic.CommInfrastructure; +import org.onap.policy.common.endpoints.listeners.ScoListener; +import org.onap.policy.common.utils.coder.StandardCoderObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +/** + * Listener for ParticipantDeregister messages sent by participants. + */ +@Component +public class ParticipantDeregisterListener extends ScoListener<ParticipantDeregister> + implements Listener<ParticipantDeregister> { + private static final Logger LOGGER = LoggerFactory.getLogger(ParticipantDeregisterListener.class); + + private final SupervisionHandler supervisionHandler; + + /** + * Constructs the object. + */ + public ParticipantDeregisterListener(SupervisionHandler supervisionHandler) { + super(ParticipantDeregister.class); + this.supervisionHandler = supervisionHandler; + } + + @Override + public void onTopicEvent(final CommInfrastructure infra, final String topic, final StandardCoderObject sco, + final ParticipantDeregister participantDeregisterMessage) { + LOGGER.debug("ParticipantDeregister message received from participant - {}", participantDeregisterMessage); + supervisionHandler.handleParticipantMessage(participantDeregisterMessage); + } + + @Override + public String getType() { + return ParticipantMessageType.PARTICIPANT_DEREGISTER.name(); + } + + @Override + public ScoListener<ParticipantDeregister> getScoListener() { + return this; + } +} diff --git a/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/supervision/comm/ParticipantRegisterAckPublisher.java b/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/supervision/comm/ParticipantRegisterAckPublisher.java new file mode 100644 index 000000000..8344837c1 --- /dev/null +++ b/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/supervision/comm/ParticipantRegisterAckPublisher.java @@ -0,0 +1,50 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2021 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.policy.clamp.acm.runtime.supervision.comm; + +import java.util.UUID; +import org.onap.policy.clamp.models.acm.messages.dmaap.participant.ParticipantRegisterAck; +import org.onap.policy.models.tosca.authorative.concepts.ToscaConceptIdentifier; +import org.springframework.stereotype.Component; + +/** + * This class is used to send ParticipantRegisterAck messages to participants on DMaaP. + */ +@Component +public class ParticipantRegisterAckPublisher extends AbstractParticipantAckPublisher<ParticipantRegisterAck> { + + /** + * Send ParticipantRegisterAck to Participant. + * + * @param responseTo the original request id in the request. + * @param participantId the participant Id + * @param participantType the participant Type + */ + public void send(UUID responseTo, ToscaConceptIdentifier participantId, ToscaConceptIdentifier participantType) { + var message = new ParticipantRegisterAck(); + message.setParticipantId(participantId); + message.setParticipantType(participantType); + message.setResponseTo(responseTo); + message.setMessage("Participant Register Ack"); + message.setResult(true); + super.send(message); + } +} diff --git a/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/supervision/comm/ParticipantRegisterListener.java b/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/supervision/comm/ParticipantRegisterListener.java new file mode 100644 index 000000000..852340000 --- /dev/null +++ b/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/supervision/comm/ParticipantRegisterListener.java @@ -0,0 +1,69 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2021 Nordix Foundation. + * Modifications Copyright (C) 2021 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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.policy.clamp.acm.runtime.supervision.comm; + +import org.onap.policy.clamp.acm.runtime.config.messaging.Listener; +import org.onap.policy.clamp.acm.runtime.supervision.SupervisionHandler; +import org.onap.policy.clamp.models.acm.messages.dmaap.participant.ParticipantMessageType; +import org.onap.policy.clamp.models.acm.messages.dmaap.participant.ParticipantRegister; +import org.onap.policy.common.endpoints.event.comm.Topic.CommInfrastructure; +import org.onap.policy.common.endpoints.listeners.ScoListener; +import org.onap.policy.common.utils.coder.StandardCoderObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +/** + * Listener for ParticipantRegister messages sent by participants. + */ +@Component +public class ParticipantRegisterListener extends ScoListener<ParticipantRegister> + implements Listener<ParticipantRegister> { + private static final Logger LOGGER = LoggerFactory.getLogger(ParticipantRegisterListener.class); + + private final SupervisionHandler supervisionHandler; + + /** + * Constructs the object. + */ + public ParticipantRegisterListener(SupervisionHandler supervisionHandler) { + super(ParticipantRegister.class); + this.supervisionHandler = supervisionHandler; + } + + @Override + public void onTopicEvent(final CommInfrastructure infra, final String topic, final StandardCoderObject sco, + final ParticipantRegister participantRegisterMessage) { + LOGGER.debug("ParticipantRegister message received from participant - {}", participantRegisterMessage); + supervisionHandler.handleParticipantMessage(participantRegisterMessage); + } + + @Override + public String getType() { + return ParticipantMessageType.PARTICIPANT_REGISTER.name(); + } + + @Override + public ScoListener<ParticipantRegister> getScoListener() { + return this; + } +} diff --git a/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/supervision/comm/ParticipantStatusListener.java b/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/supervision/comm/ParticipantStatusListener.java new file mode 100644 index 000000000..4ae1a1a2d --- /dev/null +++ b/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/supervision/comm/ParticipantStatusListener.java @@ -0,0 +1,68 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2021 Nordix Foundation. + * Modifications Copyright (C) 2021 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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.policy.clamp.acm.runtime.supervision.comm; + +import org.onap.policy.clamp.acm.runtime.config.messaging.Listener; +import org.onap.policy.clamp.acm.runtime.supervision.SupervisionHandler; +import org.onap.policy.clamp.models.acm.messages.dmaap.participant.ParticipantMessageType; +import org.onap.policy.clamp.models.acm.messages.dmaap.participant.ParticipantStatus; +import org.onap.policy.common.endpoints.event.comm.Topic.CommInfrastructure; +import org.onap.policy.common.endpoints.listeners.ScoListener; +import org.onap.policy.common.utils.coder.StandardCoderObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +/** + * Listener for ParticipantStatus messages sent by participants. + */ +@Component +public class ParticipantStatusListener extends ScoListener<ParticipantStatus> implements Listener<ParticipantStatus> { + private static final Logger LOGGER = LoggerFactory.getLogger(ParticipantStatusListener.class); + + private final SupervisionHandler supervisionHandler; + + /** + * Constructs the object. + */ + public ParticipantStatusListener(SupervisionHandler supervisionHandler) { + super(ParticipantStatus.class); + this.supervisionHandler = supervisionHandler; + } + + @Override + public void onTopicEvent(final CommInfrastructure infra, final String topic, final StandardCoderObject sco, + final ParticipantStatus participantStatusMessage) { + LOGGER.debug("ParticipantStatus message received from participant - {}", participantStatusMessage); + supervisionHandler.handleParticipantMessage(participantStatusMessage); + } + + @Override + public String getType() { + return ParticipantMessageType.PARTICIPANT_STATUS.name(); + } + + @Override + public ScoListener<ParticipantStatus> getScoListener() { + return this; + } +} diff --git a/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/supervision/comm/ParticipantStatusReqPublisher.java b/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/supervision/comm/ParticipantStatusReqPublisher.java new file mode 100644 index 000000000..0de8ff063 --- /dev/null +++ b/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/supervision/comm/ParticipantStatusReqPublisher.java @@ -0,0 +1,48 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2021 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.policy.clamp.acm.runtime.supervision.comm; + +import java.time.Instant; +import org.onap.policy.clamp.models.acm.messages.dmaap.participant.ParticipantStatusReq; +import org.onap.policy.models.tosca.authorative.concepts.ToscaConceptIdentifier; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +@Component +public class ParticipantStatusReqPublisher extends AbstractParticipantPublisher<ParticipantStatusReq> { + + private static final Logger LOGGER = LoggerFactory.getLogger(ParticipantStatusReqPublisher.class); + + /** + * Send ParticipantStatusReq to Participant. + * + * @param participantId the participant Id + */ + public void send(ToscaConceptIdentifier participantId) { + ParticipantStatusReq message = new ParticipantStatusReq(); + message.setParticipantId(participantId); + message.setTimestamp(Instant.now()); + + LOGGER.debug("Participant StatusReq sent {}", message); + super.send(message); + } +} diff --git a/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/supervision/comm/ParticipantUpdateAckListener.java b/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/supervision/comm/ParticipantUpdateAckListener.java new file mode 100644 index 000000000..d75de775b --- /dev/null +++ b/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/supervision/comm/ParticipantUpdateAckListener.java @@ -0,0 +1,69 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2021 Nordix Foundation. + * Modifications Copyright (C) 2021 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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.policy.clamp.acm.runtime.supervision.comm; + +import org.onap.policy.clamp.acm.runtime.config.messaging.Listener; +import org.onap.policy.clamp.acm.runtime.supervision.SupervisionHandler; +import org.onap.policy.clamp.models.acm.messages.dmaap.participant.ParticipantMessageType; +import org.onap.policy.clamp.models.acm.messages.dmaap.participant.ParticipantUpdateAck; +import org.onap.policy.common.endpoints.event.comm.Topic.CommInfrastructure; +import org.onap.policy.common.endpoints.listeners.ScoListener; +import org.onap.policy.common.utils.coder.StandardCoderObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +/** + * Listener for ParticipantUpdateAck messages sent by participants. + */ +@Component +public class ParticipantUpdateAckListener extends ScoListener<ParticipantUpdateAck> + implements Listener<ParticipantUpdateAck> { + private static final Logger LOGGER = LoggerFactory.getLogger(ParticipantUpdateAckListener.class); + + private final SupervisionHandler supervisionHandler; + + /** + * Constructs the object. + */ + public ParticipantUpdateAckListener(SupervisionHandler supervisionHandler) { + super(ParticipantUpdateAck.class); + this.supervisionHandler = supervisionHandler; + } + + @Override + public void onTopicEvent(final CommInfrastructure infra, final String topic, final StandardCoderObject sco, + final ParticipantUpdateAck participantUpdateAckMessage) { + LOGGER.debug("ParticipantUpdateAck message received from participant - {}", participantUpdateAckMessage); + supervisionHandler.handleParticipantMessage(participantUpdateAckMessage); + } + + @Override + public String getType() { + return ParticipantMessageType.PARTICIPANT_UPDATE_ACK.name(); + } + + @Override + public ScoListener<ParticipantUpdateAck> getScoListener() { + return this; + } +} diff --git a/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/supervision/comm/ParticipantUpdatePublisher.java b/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/supervision/comm/ParticipantUpdatePublisher.java new file mode 100644 index 000000000..47a66c10e --- /dev/null +++ b/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/supervision/comm/ParticipantUpdatePublisher.java @@ -0,0 +1,127 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2021 Nordix Foundation. + * ================================================================================ + * Modifications Copyright (C) 2021 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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.policy.clamp.acm.runtime.supervision.comm; + +import java.time.Instant; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import lombok.AllArgsConstructor; +import org.onap.policy.clamp.models.acm.concepts.ParticipantDefinition; +import org.onap.policy.clamp.models.acm.concepts.ParticipantUtils; +import org.onap.policy.clamp.models.acm.messages.dmaap.participant.ParticipantUpdate; +import org.onap.policy.clamp.models.acm.persistence.provider.ServiceTemplateProvider; +import org.onap.policy.clamp.models.acm.utils.AcmUtils; +import org.onap.policy.models.base.PfModelException; +import org.onap.policy.models.tosca.authorative.concepts.ToscaConceptIdentifier; +import org.onap.policy.models.tosca.authorative.concepts.ToscaNodeType; +import org.onap.policy.models.tosca.authorative.concepts.ToscaServiceTemplate; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +/** + * This class is used to send ParticipantUpdate messages to participants on DMaaP. + */ +@Component +@AllArgsConstructor +public class ParticipantUpdatePublisher extends AbstractParticipantPublisher<ParticipantUpdate> { + + private static final Logger LOGGER = LoggerFactory.getLogger(ParticipantUpdatePublisher.class); + + private final ServiceTemplateProvider serviceTemplateProvider; + + /** + * Send ParticipantUpdate to all Participants. + * + * @param name the ToscaServiceTemplate name + * @param version the ToscaServiceTemplate version + */ + public void sendComissioningBroadcast(String name, String version) { + sendCommissioning(name, version, null, null); + } + + /** + * Send ParticipantUpdate to Participant + * if participantType and participantId are null then message is broadcast. + * + * @param name the ToscaServiceTemplate name + * @param version the ToscaServiceTemplate version + * @param participantType the ParticipantType + * @param participantId the ParticipantId + */ + public boolean sendCommissioning(String name, String version, ToscaConceptIdentifier participantType, + ToscaConceptIdentifier participantId) { + var message = new ParticipantUpdate(); + message.setParticipantType(participantType); + message.setParticipantId(participantId); + message.setTimestamp(Instant.now()); + + ToscaServiceTemplate toscaServiceTemplate = null; + Map<String, ToscaNodeType> commonPropertiesMap = null; + try { + var list = serviceTemplateProvider.getServiceTemplateList(name, version); + if (!list.isEmpty()) { + toscaServiceTemplate = list.get(0); + commonPropertiesMap = + serviceTemplateProvider.getCommonOrInstancePropertiesFromNodeTypes(true, toscaServiceTemplate); + } else { + LOGGER.warn("No tosca service template found, cannot send participantupdate {} {}", name, version); + return false; + } + } catch (PfModelException pfme) { + LOGGER.warn("Get of tosca service template failed, cannot send participantupdate", pfme); + return false; + } + + List<ParticipantDefinition> participantDefinitionUpdates = new ArrayList<>(); + for (var toscaInputEntry : toscaServiceTemplate.getToscaTopologyTemplate().getNodeTemplates().entrySet()) { + if (ParticipantUtils.checkIfNodeTemplateIsAutomationCompositionElement(toscaInputEntry.getValue(), + toscaServiceTemplate)) { + AcmUtils.prepareParticipantDefinitionUpdate( + ParticipantUtils.findParticipantType(toscaInputEntry.getValue().getProperties()), + toscaInputEntry.getKey(), toscaInputEntry.getValue(), + participantDefinitionUpdates, commonPropertiesMap); + } + } + + // Commission the automation composition but sending participantdefinitions to participants + message.setParticipantDefinitionUpdates(participantDefinitionUpdates); + LOGGER.debug("Participant Update sent {}", message); + super.send(message); + return true; + } + + /** + * Send ParticipantUpdate to Participant after that commissioning has been removed. + */ + public void sendDecomisioning() { + var message = new ParticipantUpdate(); + message.setTimestamp(Instant.now()); + // DeCommission the automation composition but deleting participantdefinitions on participants + message.setParticipantDefinitionUpdates(null); + + LOGGER.debug("Participant Update sent {}", message); + super.send(message); + } +} diff --git a/runtime-acm/src/main/resources/application.yaml b/runtime-acm/src/main/resources/application.yaml new file mode 100644 index 000000000..9f60211c8 --- /dev/null +++ b/runtime-acm/src/main/resources/application.yaml @@ -0,0 +1,66 @@ +spring: + security: + user: + name: runtimeUser + password: zb!XztG34 + http: + converters: + preferred-json-mapper: gson + datasource: + url: jdbc:mariadb://${mariadb.host:localhost}:${mariadb.port:3306}/clamp-acm + driverClassName: org.mariadb.jdbc.Driver + username: policy + password: P01icY + hikari: + connectionTimeout: 30000 + idleTimeout: 600000 + maxLifetime: 1800000 + maximumPoolSize: 10 + jpa: + hibernate: + ddl-auto: update + naming: + physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl + implicit-strategy: org.onap.policy.common.spring.utils.CustomImplicitNamingStrategy + properties: + hibernate: + dialect: org.hibernate.dialect.MariaDB103Dialect + format_sql: true + +security: + enable-csrf: false + +server: + port: 6969 + servlet: + context-path: /onap/policy/clamp/acm + error: + path: /error + +runtime: + participantParameters: + heartBeatMs: 20000 + maxStatusWaitMs: 100000 + updateParameters: + maxRetryCount: 4 + maxWaitMs: 20000 + topicParameterGroup: + topicSources: + - + topic: POLICY-ACRUNTIME-PARTICIPANT + servers: + - ${topicServer:localhost} + topicCommInfrastructure: dmaap + fetchTimeout: 15000 + topicSinks: + - + topic: POLICY-ACRUNTIME-PARTICIPANT + servers: + - ${topicServer:localhost} + topicCommInfrastructure: dmaap + +management: + endpoints: + web: + exposure: + include: health, metrics, prometheus diff --git a/runtime-acm/src/main/resources/version.txt b/runtime-acm/src/main/resources/version.txt new file mode 100644 index 000000000..46ff2c3a7 --- /dev/null +++ b/runtime-acm/src/main/resources/version.txt @@ -0,0 +1,4 @@ +ONAP Tosca defined automation composition +Version: ${project.version} +Built (UTC): ${maven.build.timestamp} +ONAP https://wiki.onap.org |