summaryrefslogtreecommitdiffstats
path: root/adapters/mso-adapter-utils/src/main/java/org/onap
diff options
context:
space:
mode:
authorBenjamin, Max (mb388a) <mb388a@us.att.com>2018-07-30 15:56:09 -0400
committerBenjamin, Max (mb388a) <mb388a@us.att.com>2018-07-31 11:09:25 -0400
commit5a6a6de6f1a26a1897e4917a0df613e25a24eb70 (patch)
tree59a968f27b4b603aacc9d5e7b51fb598aeec5321 /adapters/mso-adapter-utils/src/main/java/org/onap
parentb6dc38501f3b746426b42d9de4cc883d894149e8 (diff)
Containerization feature of SO
Change-Id: I95381232eeefcd247a66a5cec370a8ce1c288e18 Issue-ID: SO-670 Signed-off-by: Benjamin, Max (mb388a) <mb388a@us.att.com>
Diffstat (limited to 'adapters/mso-adapter-utils/src/main/java/org/onap')
-rw-r--r--adapters/mso-adapter-utils/src/main/java/org/onap/so/adapters/vdu/VduPlugin.java186
-rw-r--r--adapters/mso-adapter-utils/src/main/java/org/onap/so/cloud/Application.java38
-rw-r--r--adapters/mso-adapter-utils/src/main/java/org/onap/so/cloud/AuthenticationType.java25
-rw-r--r--adapters/mso-adapter-utils/src/main/java/org/onap/so/cloud/CloudConfig.java216
-rw-r--r--adapters/mso-adapter-utils/src/main/java/org/onap/so/cloud/CloudConfigIdentityMapper.java30
-rw-r--r--adapters/mso-adapter-utils/src/main/java/org/onap/so/cloud/CloudIdentity.java203
-rw-r--r--adapters/mso-adapter-utils/src/main/java/org/onap/so/cloud/CloudSite.java196
-rw-r--r--adapters/mso-adapter-utils/src/main/java/org/onap/so/cloud/CloudifyManager.java153
-rw-r--r--adapters/mso-adapter-utils/src/main/java/org/onap/so/cloud/ServerType.java25
-rw-r--r--adapters/mso-adapter-utils/src/main/java/org/onap/so/cloud/authentication/AuthenticationMethodFactory.java53
-rw-r--r--adapters/mso-adapter-utils/src/main/java/org/onap/so/cloud/authentication/models/RackspaceAuthentication.java102
-rw-r--r--adapters/mso-adapter-utils/src/main/java/org/onap/so/cloudify/beans/DeploymentInfo.java186
-rw-r--r--adapters/mso-adapter-utils/src/main/java/org/onap/so/cloudify/beans/DeploymentStatus.java31
-rw-r--r--adapters/mso-adapter-utils/src/main/java/org/onap/so/cloudify/exceptions/MsoBlueprintAlreadyExists.java33
-rw-r--r--adapters/mso-adapter-utils/src/main/java/org/onap/so/cloudify/exceptions/MsoCloudifyException.java86
-rw-r--r--adapters/mso-adapter-utils/src/main/java/org/onap/so/cloudify/exceptions/MsoCloudifyManagerNotFound.java33
-rw-r--r--adapters/mso-adapter-utils/src/main/java/org/onap/so/cloudify/exceptions/MsoCloudifyTimeout.java64
-rw-r--r--adapters/mso-adapter-utils/src/main/java/org/onap/so/cloudify/exceptions/MsoCloudifyWorkflowException.java54
-rw-r--r--adapters/mso-adapter-utils/src/main/java/org/onap/so/cloudify/exceptions/MsoDeploymentAlreadyExists.java33
-rw-r--r--adapters/mso-adapter-utils/src/main/java/org/onap/so/cloudify/utils/MsoCloudifyUtils.java1408
-rw-r--r--adapters/mso-adapter-utils/src/main/java/org/onap/so/config/beans/PoConfig.java53
-rw-r--r--adapters/mso-adapter-utils/src/main/java/org/onap/so/openstack/beans/HeatCacheEntry.java60
-rw-r--r--adapters/mso-adapter-utils/src/main/java/org/onap/so/openstack/beans/NeutronCacheEntry.java67
-rw-r--r--adapters/mso-adapter-utils/src/main/java/org/onap/so/openstack/beans/VnfRollback.java216
-rw-r--r--adapters/mso-adapter-utils/src/main/java/org/onap/so/openstack/utils/MsoCommonUtils.java303
-rw-r--r--adapters/mso-adapter-utils/src/main/java/org/onap/so/openstack/utils/MsoHeatEnvironmentEntry.java257
-rw-r--r--adapters/mso-adapter-utils/src/main/java/org/onap/so/openstack/utils/MsoHeatEnvironmentParameter.java77
-rw-r--r--adapters/mso-adapter-utils/src/main/java/org/onap/so/openstack/utils/MsoHeatEnvironmentResource.java96
-rw-r--r--adapters/mso-adapter-utils/src/main/java/org/onap/so/openstack/utils/MsoHeatUtils.java1790
-rw-r--r--adapters/mso-adapter-utils/src/main/java/org/onap/so/openstack/utils/MsoHeatUtilsWithUpdate.java438
-rw-r--r--adapters/mso-adapter-utils/src/main/java/org/onap/so/openstack/utils/MsoKeystoneUtils.java669
-rw-r--r--adapters/mso-adapter-utils/src/main/java/org/onap/so/openstack/utils/MsoNeutronUtils.java550
-rw-r--r--adapters/mso-adapter-utils/src/main/java/org/onap/so/openstack/utils/MsoTenantUtils.java58
-rw-r--r--adapters/mso-adapter-utils/src/main/java/org/onap/so/openstack/utils/MsoTenantUtilsFactory.java56
-rw-r--r--adapters/mso-adapter-utils/src/main/java/org/onap/so/openstack/utils/MsoYamlEditorWithEnvt.java163
35 files changed, 8008 insertions, 0 deletions
diff --git a/adapters/mso-adapter-utils/src/main/java/org/onap/so/adapters/vdu/VduPlugin.java b/adapters/mso-adapter-utils/src/main/java/org/onap/so/adapters/vdu/VduPlugin.java
new file mode 100644
index 0000000000..ff30c0ee70
--- /dev/null
+++ b/adapters/mso-adapter-utils/src/main/java/org/onap/so/adapters/vdu/VduPlugin.java
@@ -0,0 +1,186 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP - SO
+ * ================================================================================
+ * Copyright (C) 2018 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.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.so.adapters.vdu;
+
+/**
+ * This interface defines a common API for template-based cloud deployments.
+ * The methods here should be adaptable for Openstack (Heat), Cloudify (TOSCA),
+ * Aria (TOSCA), Multi-VIM (TBD), and others (e.g. Azure Resource Manager).
+ *
+ * The deployed instances are referred to here as Virtual Deployment Units (VDUs).
+ * The package of templates that define a give VDU is referred to as its blueprint.
+ *
+ * Template-based orchestrators all follow a similar template/blueprint model.
+ * - One main template that is the top level definition
+ * - Optional nested templates referenced/included by the main template
+ * - Optional files attached to the template package, typically containing
+ * configuration files, install scripts, orchestration scripts, etc.
+ *
+ * The main template also defines the required inputs for creating a new instance,
+ * and output values exposed by successfully deployed instances. Inputs and outputs
+ * may include simple or complex (JSON) data types.
+ *
+ * Each implementation of this interface is expected to understand the MSO CloudConfig
+ * to obtain the credentials for its sub-orchestrator and the targeted cloud.
+ * The sub-orchestrator may have different credentials from the cloud (e.g. an Aria
+ * instance in front of an Openstack cloud) or they may be the same (e.g. Heat)
+ */
+import java.util.Map;
+
+public interface VduPlugin {
+
+ /**
+ * The instantiateVdu interface deploys a new VDU instance from a vdu model package.
+ *
+ * For some VIMs, this may be a single command (e.g. Heat -> create stack) or may
+ * require a series of API calls (e.g. Cloudify -> upload blueprint, create deployment,
+ * execute install workflow). These details are hidden within the plug-in implementation.
+ * The instantiation should be fully completed before returning. On failures, this
+ * method is expected to back out the attempt, leaving the cloud in its previous state.
+ *
+ * It is expected that parameters have been validated and contain at minimum the
+ * required parameters for the given template with no extra parameters.
+ *
+ * The VDU name supplied by the caller will be globally unique, and identify the artifact
+ * in A&AI. Inventory is managed by the higher levels invoking this function.
+ *
+ * @param cloudInfo The target cloud + tenant identifiers for the VDU.
+ * @param instanceName A unique name for the VDU instance to update.
+ * @param inputs A map of key/value inputs. Values may be strings, numbers, or JSON objects.
+ * Will completely replace any inputs provided on the original instantiation.
+ * @param vduModel Object containing the collection of templates and files that comprise
+ * the blueprint for this VDU.
+ * @param rollbackOnFailure Flag to preserve or roll back the update on Failure. Should normally
+ * be True except in troubleshooting/debug cases. Might not be supported in all plug-ins.
+ *
+ * @return A VduInstance object
+ * @throws VduException Thrown if the sub-orchestrator API calls fail or if a timeout occurs.
+ * Various subclasses of VduException may be thrown.
+ */
+ public VduInstance instantiateVdu (
+ CloudInfo cloudInfo,
+ String instanceName,
+ Map<String,Object> inputs,
+ VduModelInfo vduModel,
+ boolean rollbackOnFailure)
+ throws VduException;
+
+ /**
+ * Query a deployed VDU instance. This call will return a VduInstance object, or null
+ * if the deployment does not exist.
+ *
+ * Some VIM orchestrators identify deployment instances by string UUIDs, and others
+ * by integers. In the latter case, the ID will be passed in as a numeric string.
+ *
+ * The returned VduInstance object contains the input and output parameter maps,
+ * as well as other properties of the deployment (name, status, last action, etc.).
+ *
+ * @param cloudInfo The target cloud + tenant identifiers for the VDU.
+ * @param vduInstanceId The ID of the deployment to query
+ *
+ * @return A VduInstance object
+ * @throws VduException Thrown if the sub-orchestrator API calls fail or if a timeout occurs.
+ * Various subclasses of VduException may be thrown.
+ */
+ public VduInstance queryVdu (
+ CloudInfo cloudInfo,
+ String vduInstanceId)
+ throws VduException;
+
+
+ /**
+ * Delete a VDU instance by ID. If the VIM sub-orchestrator supports pre-installation
+ * of blueprints/models, the blueprint itself may remain installed. This is recommended,
+ * since other VDU instances may be using it.
+ *
+ * Some VIM orchestrators identify deployment instances by string UUIDs, and others
+ * by integers. In the latter case, the ID will be passed in as a numeric string.
+ *
+ * For some VIMs, deletion may be a single command (e.g. Heat -> delete stack) or a
+ * series of API calls (e.g. Cloudify -> execute uninstall workflow, delete deployment).
+ * These details are hidden within the plug-in implementation. The deletion should be
+ * fully completed before returning.
+ *
+ * The successful return is a VduInstance object which contains the state of the VDU
+ * just prior to deletion, with a status of DELETED. If the deployment was not found,
+ * the VduInstance object should be empty (with a status of NOTFOUND).
+ * There is no rollback from a successful deletion.
+ *
+ * A deletion failure will result in an undefined deployment state - the components may
+ * or may not have been all or partially uninstalled, so the resulting deployment must
+ * be considered invalid.
+ *
+ * @param cloudInfo The target cloud + tenant identifiers for the VDU.
+ * @param instanceId The unique id of the deployment to delete.
+ * @param timeoutMinutes Timeout after which the delete action will be cancelled.
+ * Consider sending the entire model here, if it may be of use to the plug-in?
+ *
+ * @return A VduInstance object, representing its state just prior to deletion.
+ *
+ * @throws VduException Thrown if the API calls fail or if a timeout occurs.
+ * Various subclasses of VduException may be thrown.
+ */
+ public VduInstance deleteVdu (
+ CloudInfo cloudInfo,
+ String instanceId,
+ int timeoutMinutes)
+ throws VduException;
+
+
+ /**
+ * The updateVdu interface attempts to update a VDU in-place, using either new inputs or
+ * a new model definition (i.e. updated templates/blueprints). This depends on the
+ * capabilities of the targeted sub-orchestrator, as not all implementations are expected
+ * to support this ability. It is primary included initially only for Heat.
+ *
+ * It is expected that parameters have been validated and contain at minimum the required
+ * parameters for the given template with no extra parameters. The VDU instance name cannot
+ * be updated.
+ *
+ * The update should be fully completed before returning. The successful return is a
+ * VduInstance object containing the updated VDU state.
+ *
+ * An update failure will result in an undefined deployment state - the components may
+ * or may not have been all or partially modified, deleted, recreated, etc. So the resulting
+ * VDU must be considered invalid.
+ *
+ * @param cloudInfo The target cloud + tenant identifiers for the VDU.
+ * @param instanceId The unique ID for the VDU instance to update.
+ * @param inputs A map of key/value inputs. Values may be strings, numbers, or JSON objects.
+ * Will completely replace any inputs provided on the original instantiation.
+ * @param vduModel Object containing the collection of templates and files that comprise
+ * the blueprint for this VDU.
+ * @param rollbackOnFailure Flag to preserve or roll back the update on Failure. Should normally
+ * be True except in troubleshooting/debug cases. Might not be supported in all plug-ins.
+ *
+ * @return A VduInfo object
+ * @throws VduException Thrown if the sub-orchestrator API calls fail or if a timeout occurs.
+ * Various subclasses of VduException may be thrown.
+ */
+ public VduInstance updateVdu (
+ CloudInfo cloudInfo,
+ String instanceId,
+ Map<String,Object> inputs,
+ VduModelInfo vduModel,
+ boolean rollbackOnFailure)
+ throws VduException;
+
+} \ No newline at end of file
diff --git a/adapters/mso-adapter-utils/src/main/java/org/onap/so/cloud/Application.java b/adapters/mso-adapter-utils/src/main/java/org/onap/so/cloud/Application.java
new file mode 100644
index 0000000000..bc04b09588
--- /dev/null
+++ b/adapters/mso-adapter-utils/src/main/java/org/onap/so/cloud/Application.java
@@ -0,0 +1,38 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP - SO
+ * ================================================================================
+ * Copyright (C) 2017 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.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.so.cloud;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.autoconfigure.domain.EntityScan;
+import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
+
+@SpringBootApplication(scanBasePackages = { "org.onap"})
+@EnableJpaRepositories({"org.onap.so.db.catalog.data.repository", "org.onap.so.db.request.data.repository"})
+@EntityScan({"org.onap.so.db.catalog.beans", "org.onap.so.db.request.beans"})
+public class Application {
+
+ public static void main(String[] args) {
+ SpringApplication.run(Application.class, args);
+ System.getProperties().setProperty("mso.db", "MARIADB");
+ System.getProperties().setProperty("server.name", "Springboot");
+ }
+}
diff --git a/adapters/mso-adapter-utils/src/main/java/org/onap/so/cloud/AuthenticationType.java b/adapters/mso-adapter-utils/src/main/java/org/onap/so/cloud/AuthenticationType.java
new file mode 100644
index 0000000000..7cb2222525
--- /dev/null
+++ b/adapters/mso-adapter-utils/src/main/java/org/onap/so/cloud/AuthenticationType.java
@@ -0,0 +1,25 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP - SO
+ * ================================================================================
+ * Copyright (C) 2017 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.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.so.cloud;
+
+public enum AuthenticationType {
+ USERNAME_PASSWORD, RACKSPACE_APIKEY;
+}
diff --git a/adapters/mso-adapter-utils/src/main/java/org/onap/so/cloud/CloudConfig.java b/adapters/mso-adapter-utils/src/main/java/org/onap/so/cloud/CloudConfig.java
new file mode 100644
index 0000000000..ef5f8232e0
--- /dev/null
+++ b/adapters/mso-adapter-utils/src/main/java/org/onap/so/cloud/CloudConfig.java
@@ -0,0 +1,216 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP - SO
+ * ================================================================================
+ * Copyright (C) 2017 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.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.so.cloud;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Optional;
+
+import javax.annotation.PostConstruct;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonRootName;
+
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+import org.apache.commons.lang3.builder.HashCodeBuilder;
+import org.apache.commons.lang3.builder.EqualsBuilder;
+
+/**
+ * JavaBean JSON class for a CloudConfig. This bean maps a JSON-format cloud
+ * configuration file to Java. The CloudConfig contains information about
+ * Openstack cloud configurations. It includes:
+ * - CloudIdentity objects,representing DCP nodes (Openstack Identity Service)
+ * - CloudSite objects, representing LCP nodes (Openstack Compute & other services)
+ *
+ * Note that this is only used to access Cloud Configurations loaded from a JSON
+ * config file, so there are no explicit property setters.
+ *
+ * This class also contains methods to query cloud sites and/or identity
+ * services by ID.
+ *
+ */
+
+@Configuration
+@JsonRootName("cloud_config")
+@ConfigurationProperties(prefix="cloud_config")
+public class CloudConfig {
+
+ private static final String CLOUD_SITE_VERSION = "2.5";
+ private static final String DEFAULT_CLOUD_SITE_ID = "default";
+
+ @JsonProperty("identity_services")
+ private Map<String, CloudIdentity> identityServices = new HashMap<>();
+
+ @JsonProperty("cloud_sites")
+ private Map <String, CloudSite> cloudSites = new HashMap<>();
+
+ @JsonProperty("cloudify_managers")
+ private Map <String, CloudifyManager> cloudifyManagers = new HashMap<>();
+
+ @PostConstruct
+ private void init() {
+ for (Entry<String, CloudIdentity> entry : identityServices.entrySet()) {
+ entry.getValue().setId(entry.getKey());
+ }
+
+ for (Entry<String, CloudSite> entry : cloudSites.entrySet()) {
+ entry.getValue().setId(entry.getKey());
+ }
+
+ for (Entry<String, CloudifyManager> entry : cloudifyManagers.entrySet()) {
+ entry.getValue().setId(entry.getKey());
+ }
+ }
+
+ /**
+ * Get a map of all identity services that have been loaded.
+ */
+ public Map<String, CloudIdentity> getIdentityServices() {
+ return identityServices;
+ }
+
+ /**
+ * Get a map of all cloud sites that have been loaded.
+ */
+ public Map<String, CloudSite> getCloudSites() {
+ return cloudSites;
+ }
+
+ /**
+ * Get a Map of all CloudifyManagers that have been loaded.
+ * @return the Map
+ */
+ public Map<String,CloudifyManager> getCloudifyManagers() {
+ return cloudifyManagers;
+ }
+
+ /**
+ * Get a specific CloudSites, based on an ID. The ID is first checked
+ * against the regions, and if no match is found there, then against
+ * individual entries to try and find one with a CLLI that matches the ID
+ * and an AIC version of 2.5.
+ *
+ * @param id the ID to match
+ * @return an Optional of CloudSite object.
+ */
+ public synchronized Optional<CloudSite> getCloudSite(String id) {
+ if (id == null) {
+ return Optional.empty();
+ }
+ if (cloudSites.containsKey(id)) {
+ return Optional.ofNullable(cloudSites.get(id));
+ } else {
+ return getCloudSiteWithClli(id);
+ }
+ }
+
+ public String getCloudSiteId(CloudSite cloudSite) {
+ for(Entry<String, CloudSite> entry : this.getCloudSites().entrySet()){
+ if(entry.getValue().equals(cloudSite))
+ return entry.getKey();
+ }
+ return null;
+ }
+
+ /**
+ * Get a specific CloudSites, based on a CLLI and (optional) version, which
+ * will be matched against the aic_version field of the CloudSite.
+ *
+ * @param clli
+ * the CLLI to match
+ * @param version
+ * the version to match; may be null in which case any version
+ * matches
+ * @return a CloudSite, or null of no match found
+ */
+ private Optional<CloudSite> getCloudSiteWithClli(String clli) {
+ Optional <CloudSite> cloudSiteOptional = cloudSites.values().stream().filter(cs ->
+ cs.getClli() != null && clli.equals(cs.getClli()) && (CLOUD_SITE_VERSION.equals(cs.getAicVersion())))
+ .findAny();
+ if (cloudSiteOptional.isPresent()) {
+ return cloudSiteOptional;
+ } else {
+ return getDefaultCloudSite(clli);
+ }
+ }
+
+ private Optional<CloudSite> getDefaultCloudSite(String clli) {
+ Optional<CloudSite> cloudSiteOpt = cloudSites.values().stream()
+ .filter(cs -> cs.getId().equalsIgnoreCase(DEFAULT_CLOUD_SITE_ID)).findAny();
+ if (cloudSiteOpt.isPresent()) {
+ CloudSite defaultCloudSite = cloudSiteOpt.get();
+ CloudSite clone = new CloudSite(defaultCloudSite);
+ clone.setRegionId(clli);
+ clone.setId(clli);
+ return Optional.of(clone);
+ } else {
+ return Optional.empty();
+ }
+ }
+
+ /**
+ * Get a specific CloudIdentity, based on an ID.
+ *
+ * @param id
+ * the ID to match
+ * @return a CloudIdentity, or null of no match found
+ */
+ public CloudIdentity getIdentityService(String id) {
+ return identityServices.get(id);
+ }
+
+ /**
+ * Get a specific CloudifyManager, based on an ID.
+ * @param id the ID to match
+ * @return a CloudifyManager, or null of no match found
+ */
+ public CloudifyManager getCloudifyManager (String id) {
+ return cloudifyManagers.get(id);
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE)
+ .append("identityServices", getIdentityServices()).append("cloudSites", getCloudSites()).toString();
+ }
+
+ @Override
+ public boolean equals(final Object other) {
+ if (other == null) {
+ return false;
+ }
+ if (!getClass().equals(other.getClass())) {
+ return false;
+ }
+ CloudConfig castOther = (CloudConfig) other;
+ return new EqualsBuilder().append(getIdentityServices(), castOther.getIdentityServices())
+ .append(getCloudSites(), castOther.getCloudSites()).isEquals();
+ }
+
+ @Override
+ public int hashCode() {
+ return new HashCodeBuilder(1, 31).append(getIdentityServices()).append(getCloudSites()).toHashCode();
+ }
+}
diff --git a/adapters/mso-adapter-utils/src/main/java/org/onap/so/cloud/CloudConfigIdentityMapper.java b/adapters/mso-adapter-utils/src/main/java/org/onap/so/cloud/CloudConfigIdentityMapper.java
new file mode 100644
index 0000000000..f554aa46cd
--- /dev/null
+++ b/adapters/mso-adapter-utils/src/main/java/org/onap/so/cloud/CloudConfigIdentityMapper.java
@@ -0,0 +1,30 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP - SO
+ * ================================================================================
+ * Copyright (C) 2017 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.
+ * ============LICENSE_END=========================================================
+ */
+package org.onap.so.cloud;
+
+/**
+ * This interface provides the method signature for mapping registration.
+ * All mappings should be registered by the implementing class.
+ */
+@FunctionalInterface
+public interface CloudConfigIdentityMapper {
+
+ public void registerAllMappings();
+}
diff --git a/adapters/mso-adapter-utils/src/main/java/org/onap/so/cloud/CloudIdentity.java b/adapters/mso-adapter-utils/src/main/java/org/onap/so/cloud/CloudIdentity.java
new file mode 100644
index 0000000000..188a93025e
--- /dev/null
+++ b/adapters/mso-adapter-utils/src/main/java/org/onap/so/cloud/CloudIdentity.java
@@ -0,0 +1,203 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP - SO
+ * ================================================================================
+ * Copyright (C) 2017 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.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.so.cloud;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.openpojo.business.annotation.BusinessKey;
+import org.apache.commons.lang3.builder.HashCodeBuilder;
+
+import java.util.Comparator;
+
+import org.apache.commons.lang3.builder.EqualsBuilder;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+
+/**
+ * JavaBean JSON class for a CloudIdentity. This bean represents a cloud identity
+ * service instance (i.e. a DCP node) in the NVP/AIC cloud. It will be loaded via
+ * CloudConfig object, of which it is a component (a CloudConfig JSON configuration
+ * file may contain multiple CloudIdentity definitions).
+ *
+ * Note that this is only used to access Cloud Configurations loaded from a
+ * JSON config file, so there are no explicit setters.
+ *
+ */
+public class CloudIdentity {
+
+ @JsonProperty
+ @BusinessKey
+ private String id;
+ @JsonProperty("identity_url")
+ @BusinessKey
+ private String identityUrl;
+ @JsonProperty("mso_id")
+ @BusinessKey
+ private String msoId;
+ @JsonProperty("mso_pass")
+ @BusinessKey
+ private String msoPass;
+ @JsonProperty("admin_tenant")
+ @BusinessKey
+ private String adminTenant;
+ @JsonProperty("member_role")
+ @BusinessKey
+ private String memberRole;
+ @JsonProperty("tenant_metadata")
+ @BusinessKey
+ private Boolean tenantMetadata;
+ @JsonProperty("identity_server_type")
+ @BusinessKey
+ private ServerType identityServerType;
+ @JsonProperty("identity_authentication_type")
+ @BusinessKey
+ private AuthenticationType identityAuthenticationType;
+
+ public CloudIdentity() {}
+
+ public String getId () {
+ return id;
+ }
+
+ public void setId (String id) {
+ this.id = id;
+ }
+
+ public String getIdentityUrl() {
+ return this.identityUrl;
+ }
+ public void setIdentityUrl(String url) {
+ this.identityUrl = url;
+ }
+
+ public String getMsoId () {
+ return msoId;
+ }
+
+ public void setMsoId (String id) {
+ this.msoId = id;
+ }
+
+ public String getMsoPass () {
+ return msoPass;
+ }
+
+ public void setMsoPass (String pwd) {
+ this.msoPass = pwd;
+ }
+
+ public String getAdminTenant () {
+ return adminTenant;
+ }
+
+ public void setAdminTenant (String tenant) {
+ this.adminTenant = tenant;
+ }
+
+ public String getMemberRole () {
+ return memberRole;
+ }
+
+ public void setMemberRole (String role) {
+ this.memberRole = role;
+ }
+
+ public Boolean hasTenantMetadata () {
+ return tenantMetadata;
+ }
+
+ public void setTenantMetadata (Boolean meta) {
+ this.tenantMetadata = meta;
+ }
+
+ public ServerType getIdentityServerType() {
+ return this.identityServerType;
+ }
+ public void setIdentityServerType(ServerType ist) {
+ this.identityServerType = ist;
+ }
+ public String getIdentityServerTypeAsString() {
+ return this.identityServerType.toString();
+ }
+ /**
+ * @return the identityAuthenticationType
+ */
+ public AuthenticationType getIdentityAuthenticationType() {
+ return identityAuthenticationType;
+ }
+
+ /**
+ * @param identityAuthenticationType the identityAuthenticationType to set
+ */
+ public void setIdentityAuthenticationType(AuthenticationType identityAuthenticationType) {
+ this.identityAuthenticationType = identityAuthenticationType;
+ }
+
+ @Override
+ public CloudIdentity clone() {
+ CloudIdentity cloudIdentityCopy = new CloudIdentity();
+
+ cloudIdentityCopy.id = this.id;
+ cloudIdentityCopy.identityUrl = this.identityUrl;
+ cloudIdentityCopy.msoId = this.msoId;
+ cloudIdentityCopy.msoPass = this.msoPass;
+ cloudIdentityCopy.adminTenant = this.adminTenant;
+ cloudIdentityCopy.memberRole = this.memberRole;
+ cloudIdentityCopy.tenantMetadata = this.tenantMetadata;
+ cloudIdentityCopy.identityServerType = this.identityServerType;
+ cloudIdentityCopy.identityAuthenticationType = this.identityAuthenticationType;
+
+ return cloudIdentityCopy;
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE).append("id", getId())
+ .append("identityUrl", getIdentityUrl()).append("msoId", getMsoId())
+ .append("adminTenant", getAdminTenant()).append("memberRole", getMemberRole())
+ .append("tenantMetadata", hasTenantMetadata()).append("identityServerType", getIdentityServerType())
+ .append("identityAuthenticationType", getIdentityAuthenticationType()).toString();
+ }
+
+ @Override
+ public boolean equals(final Object other) {
+ if (other == null) {
+ return false;
+ }
+ if (!getClass().equals(other.getClass())) {
+ return false;
+ }
+ CloudIdentity castOther = (CloudIdentity) other;
+ return new EqualsBuilder().append(getId(), castOther.getId())
+ .append(getIdentityUrl(), castOther.getIdentityUrl()).append(getMsoId(), castOther.getMsoId())
+ .append(getMsoPass(), castOther.getMsoPass()).append(getAdminTenant(), castOther.getAdminTenant())
+ .append(getMemberRole(), castOther.getMemberRole())
+ .append(hasTenantMetadata(), castOther.hasTenantMetadata())
+ .append(getIdentityServerType(), castOther.getIdentityServerType())
+ .append(getIdentityAuthenticationType(), castOther.getIdentityAuthenticationType()).isEquals();
+ }
+
+ @Override
+ public int hashCode() {
+ return new HashCodeBuilder(1, 31).append(getId()).append(getIdentityUrl()).append(getMsoId())
+ .append(getMsoPass()).append(getAdminTenant()).append(getMemberRole()).append(hasTenantMetadata())
+ .append(getIdentityServerType()).append(getIdentityAuthenticationType()).toHashCode();
+ }
+} \ No newline at end of file
diff --git a/adapters/mso-adapter-utils/src/main/java/org/onap/so/cloud/CloudSite.java b/adapters/mso-adapter-utils/src/main/java/org/onap/so/cloud/CloudSite.java
new file mode 100644
index 0000000000..f38403d0cd
--- /dev/null
+++ b/adapters/mso-adapter-utils/src/main/java/org/onap/so/cloud/CloudSite.java
@@ -0,0 +1,196 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP - SO
+ * ================================================================================
+ * Copyright (C) 2017 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.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.so.cloud;
+
+
+import java.util.Comparator;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.openpojo.business.annotation.BusinessKey;
+import org.apache.commons.lang3.builder.HashCodeBuilder;
+import org.apache.commons.lang3.builder.EqualsBuilder;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+
+/**
+ * JavaBean JSON class for a CloudSite. This bean represents a cloud location
+ * (i.e. and LCP node) in the NVP/AIC cloud. It will be loaded via CloudConfig
+ * object, of which it is a component (a CloudConfig JSON configuration file
+ * will contain multiple CloudSite definitions).
+ *
+ * Note that this is only used to access Cloud Configurations loaded from a
+ * JSON config file, so there are no explicit setters.
+ *
+ */
+public class CloudSite {
+ @JsonProperty
+ @BusinessKey
+ private String id;
+ @JsonProperty("region_id")
+ @BusinessKey
+ private String regionId;
+ @JsonProperty("identity_service_id")
+ @BusinessKey
+ private String identityServiceId;
+ @JsonProperty("aic_version")
+ @BusinessKey
+ private String aicVersion;
+ @JsonProperty("clli")
+ @BusinessKey
+ private String clli;
+ @JsonProperty("cloudify_id")
+ @BusinessKey
+ private String cloudifyId;
+ @JsonProperty("platform")
+ @BusinessKey
+ private String platform;
+ @JsonProperty("orchestrator")
+ @BusinessKey
+ private String orchestrator;
+
+ // Derived property (set by CloudConfig loader based on identityServiceId)
+ private CloudIdentity identityService;
+ // Derived property (set by CloudConfig loader based on cloudifyId)
+ private CloudifyManager cloudifyManager;
+
+ public CloudSite() {
+
+ }
+
+ public CloudSite(CloudSite site) {
+ this.aicVersion = site.getAicVersion();
+ this.clli = site.getClli();
+ this.cloudifyId = this.getCloudifyId();
+ this.cloudifyManager = this.getCloudifyManager();
+ this.id = site.getId();
+ this.identityService = site.getIdentityService();
+ this.identityServiceId = site.getIdentityServiceId();
+ this.orchestrator = site.getOrchestrator();
+ this.platform = site.getPlatform();
+ this.regionId = this.getRegionId();
+ }
+ public String getId() {
+ return this.id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public String getRegionId() {
+ return regionId;
+ }
+
+ public void setRegionId(String regionId) {
+ this.regionId = regionId;
+ }
+
+ public String getIdentityServiceId() {
+ return identityServiceId;
+ }
+
+ public void setIdentityServiceId(String identityServiceId) {
+ this.identityServiceId = identityServiceId;
+ }
+ public String getAicVersion() {
+ return aicVersion;
+ }
+
+ public void setAicVersion(String aicVersion) {
+ this.aicVersion = aicVersion;
+ }
+
+ public String getClli() {
+ return clli;
+ }
+
+ public void setClli(String clli) {
+ this.clli = clli;
+ }
+
+ public String getCloudifyId() {
+ return cloudifyId;
+ }
+
+ public void setCloudifyId (String id) {
+ this.cloudifyId = id;
+ }
+
+ public String getPlatform() {
+ return platform;
+ }
+
+ public void setPlatform(String platform) {
+ this.platform = platform;
+ }
+
+ public String getOrchestrator() {
+ return orchestrator;
+ }
+
+ public void setOrchestrator(String orchestrator) {
+ this.orchestrator = orchestrator;
+ }
+
+ public CloudIdentity getIdentityService () {
+ return identityService;
+ }
+
+ public void setIdentityService (CloudIdentity identity) {
+ this.identityService = identity;
+ }
+
+ public CloudifyManager getCloudifyManager () {
+ return cloudifyManager;
+ }
+
+ public void setCloudifyManager (CloudifyManager cloudify) {
+ this.cloudifyManager = cloudify;
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE).append("regionId", getRegionId())
+ .append("identityServiceId", getIdentityServiceId()).append("aicVersion", getAicVersion())
+ .append("clli", getClli()).append("cloudifyId", getCloudifyId()).append("platform", getPlatform())
+ .append("orchestrator", getOrchestrator()).toString();
+ }
+
+ @Override
+ public boolean equals(final Object other) {
+ if (other == null) {
+ return false;
+ }
+ if (!getClass().equals(other.getClass())) {
+ return false;
+ }
+ CloudSite castOther = (CloudSite) other;
+ return new EqualsBuilder().append(getRegionId(), castOther.getRegionId())
+ .append(getIdentityServiceId(), castOther.getIdentityServiceId())
+ .append(getAicVersion(), castOther.getAicVersion()).append(getClli(), castOther.getClli()).isEquals();
+ }
+
+ @Override
+ public int hashCode() {
+ return new HashCodeBuilder(1, 31).append(getRegionId()).append(getIdentityServiceId()).append(getAicVersion())
+ .append(getClli()).toHashCode();
+ }
+} \ No newline at end of file
diff --git a/adapters/mso-adapter-utils/src/main/java/org/onap/so/cloud/CloudifyManager.java b/adapters/mso-adapter-utils/src/main/java/org/onap/so/cloud/CloudifyManager.java
new file mode 100644
index 0000000000..1bf3f136b0
--- /dev/null
+++ b/adapters/mso-adapter-utils/src/main/java/org/onap/so/cloud/CloudifyManager.java
@@ -0,0 +1,153 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP - SO
+ * ================================================================================
+ * Copyright (C) 2017 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.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.so.cloud;
+
+import java.security.GeneralSecurityException;
+import java.util.Comparator;
+
+import org.onap.so.logger.MessageEnum;
+import org.onap.so.logger.MsoLogger;
+import org.onap.so.utils.CryptoUtils;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.openpojo.business.annotation.BusinessKey;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+import org.apache.commons.lang3.builder.HashCodeBuilder;
+import org.apache.commons.lang3.builder.EqualsBuilder;
+
+/**
+ * JavaBean JSON class for a Cloudify Manager. This bean represents a Cloudify
+ * node through which TOSCA-based VNFs may be deployed. Each CloudSite in the
+ * CloudConfig may have a Cloudify Manager for deployments using TOSCA blueprints.
+ * Cloudify Managers may support multiple Cloud Sites, but each site will have
+ * at most one Cloudify Manager.
+ *
+ * This does not replace the ability to use the CloudSite directly via Openstack.
+ *
+ * Note that this is only used to access Cloud Configurations loaded from a
+ * JSON config file, so there are no explicit setters.
+ *
+ * @author JC1348
+ */
+public class CloudifyManager {
+
+ private static MsoLogger LOGGER = MsoLogger.getMsoLogger (MsoLogger.Catalog.RA, CloudifyManager.class);
+
+ @BusinessKey
+ @JsonProperty
+ private String id;
+
+ @BusinessKey
+ @JsonProperty ("cloudify_url")
+ private String cloudifyUrl;
+
+ @BusinessKey
+ @JsonProperty("username")
+ private String username;
+
+ @BusinessKey
+ @JsonProperty("password")
+ private String password;
+
+ @BusinessKey
+ @JsonProperty("version")
+ private String version;
+
+ public CloudifyManager() {}
+
+ public String getId() {
+ return id;
+ }
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public String getCloudifyUrl() {
+ return cloudifyUrl;
+ }
+
+ public void setCloudifyUrl(String cloudifyUrl) {
+ this.cloudifyUrl = cloudifyUrl;
+ }
+
+ public String getUsername() {
+ return username;
+ }
+
+ public void setUsername(String username) {
+ this.username = username;
+ }
+
+ public String getPassword() {
+ return password;
+ }
+
+ public void setPassword(String password) {
+ this.password = password;
+ }
+
+ public String getVersion() {
+ return version;
+ }
+
+ public void setVersion(String version) {
+ this.version = version;
+ }
+
+ @Override
+ public CloudifyManager clone() {
+ CloudifyManager cloudifyManagerCopy = new CloudifyManager();
+ cloudifyManagerCopy.id = this.id;
+ cloudifyManagerCopy.cloudifyUrl = this.cloudifyUrl;
+ cloudifyManagerCopy.username = this.username;
+ cloudifyManagerCopy.password = this.password;
+ cloudifyManagerCopy.version = this.version;
+ return cloudifyManagerCopy;
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE).append("id", getId())
+ .append("cloudifyUrl", getCloudifyUrl()).append("username", getUsername())
+ .append("password", getPassword()).append("version", getVersion()).toString();
+ }
+
+ @Override
+ public boolean equals(final Object other) {
+ if (other == null) {
+ return false;
+ }
+ if (!getClass().equals(other.getClass())) {
+ return false;
+ }
+ CloudifyManager castOther = (CloudifyManager) other;
+ return new EqualsBuilder().append(getId(), castOther.getId())
+ .append(getCloudifyUrl(), castOther.getCloudifyUrl()).append(getUsername(), castOther.getUsername())
+ .append(getPassword(), castOther.getPassword()).append(getVersion(), castOther.getVersion()).isEquals();
+ }
+
+ @Override
+ public int hashCode() {
+ return new HashCodeBuilder(1, 31).append(getId()).append(getCloudifyUrl()).append(getUsername())
+ .append(getPassword()).append(getVersion()).toHashCode();
+ }
+} \ No newline at end of file
diff --git a/adapters/mso-adapter-utils/src/main/java/org/onap/so/cloud/ServerType.java b/adapters/mso-adapter-utils/src/main/java/org/onap/so/cloud/ServerType.java
new file mode 100644
index 0000000000..ac59018c6b
--- /dev/null
+++ b/adapters/mso-adapter-utils/src/main/java/org/onap/so/cloud/ServerType.java
@@ -0,0 +1,25 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP - SO
+ * ================================================================================
+ * Copyright (C) 2017 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.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.so.cloud;
+
+public enum ServerType {
+ KEYSTONE, ORM;
+}
diff --git a/adapters/mso-adapter-utils/src/main/java/org/onap/so/cloud/authentication/AuthenticationMethodFactory.java b/adapters/mso-adapter-utils/src/main/java/org/onap/so/cloud/authentication/AuthenticationMethodFactory.java
new file mode 100644
index 0000000000..5c648eb5e3
--- /dev/null
+++ b/adapters/mso-adapter-utils/src/main/java/org/onap/so/cloud/authentication/AuthenticationMethodFactory.java
@@ -0,0 +1,53 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP - SO
+ * ================================================================================
+ * Copyright (C) 2017 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.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.so.cloud.authentication;
+
+import org.onap.so.cloud.AuthenticationType;
+import org.onap.so.cloud.CloudIdentity;
+import org.onap.so.cloud.authentication.models.RackspaceAuthentication;
+import org.onap.so.utils.CryptoUtils;
+import org.springframework.stereotype.Component;
+
+import com.woorea.openstack.keystone.model.Authentication;
+import com.woorea.openstack.keystone.model.authentication.UsernamePassword;
+
+/**
+ * This factory manages all the wrappers associated to authentication types.
+ *
+ */
+@Component
+public final class AuthenticationMethodFactory {
+
+ public final Authentication getAuthenticationFor(CloudIdentity cloudIdentity) {
+ if (cloudIdentity == null) {
+ throw new IllegalArgumentException("Cloud identity cannot be null");
+ }
+ if ((cloudIdentity.getIdentityAuthenticationType() == null)|| ("".equals(cloudIdentity.getIdentityAuthenticationType().toString()))) {
+ throw new IllegalArgumentException("Cloud identity authentication type cannot be null or empty, provided value is " + cloudIdentity.getIdentityAuthenticationType() + ".");
+ }
+ AuthenticationType authenticationType = cloudIdentity.getIdentityAuthenticationType();
+ if (AuthenticationType.RACKSPACE_APIKEY.equals(authenticationType)) {
+ return new RackspaceAuthentication (cloudIdentity.getMsoId (), CryptoUtils.decryptCloudConfigPassword(cloudIdentity.getMsoPass ()));
+ } else {
+ return new UsernamePassword (cloudIdentity.getMsoId (), CryptoUtils.decryptCloudConfigPassword(cloudIdentity.getMsoPass ()));
+ }
+ }
+}
diff --git a/adapters/mso-adapter-utils/src/main/java/org/onap/so/cloud/authentication/models/RackspaceAuthentication.java b/adapters/mso-adapter-utils/src/main/java/org/onap/so/cloud/authentication/models/RackspaceAuthentication.java
new file mode 100644
index 0000000000..009c9a4c6c
--- /dev/null
+++ b/adapters/mso-adapter-utils/src/main/java/org/onap/so/cloud/authentication/models/RackspaceAuthentication.java
@@ -0,0 +1,102 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP - SO
+ * ================================================================================
+ * Copyright (C) 2017 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.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.so.cloud.authentication.models;
+
+import java.io.Serializable;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonRootName;
+import com.woorea.openstack.keystone.model.Authentication;
+
+@JsonRootName("auth")
+public class RackspaceAuthentication extends Authentication {
+
+ /**
+ *
+ */
+ private static final long serialVersionUID = 5451283386875662918L;
+
+ @JsonIgnore
+ private String tenantId;
+
+ @JsonIgnore
+ private String tenantName;
+
+ public static final class Token implements Serializable{
+
+ /**
+ *
+ */
+ private static final long serialVersionUID = -4448875265818207908L;
+ private String username;
+ private String apiKey;
+
+ /**
+ * @return the username
+ */
+ public String getUsername() {
+ return username;
+ }
+ /**
+ * @param username the username to set
+ */
+ public void setUsername(String username) {
+ this.username = username;
+ }
+ /**
+ * @return the apiKey
+ */
+ public String getApiKey() {
+ return apiKey;
+ }
+ /**
+ * @param apiKey the apiKey to set
+ */
+ public void setApiKey(String apiKey) {
+ this.apiKey = apiKey;
+ }
+ }
+
+ @JsonProperty("RAX-KSKEY:apiKeyCredentials")
+ private Token token = new Token();
+
+ public RackspaceAuthentication (String username, String apiKey) {
+ this.token.username = username;
+ this.token.apiKey = apiKey;
+
+ }
+
+ /**
+ * @return the token
+ */
+ public Token getToken() {
+ return token;
+ }
+
+ /**
+ * @param token the token to set
+ */
+ public void setToken(Token token) {
+ this.token = token;
+ }
+
+}
diff --git a/adapters/mso-adapter-utils/src/main/java/org/onap/so/cloudify/beans/DeploymentInfo.java b/adapters/mso-adapter-utils/src/main/java/org/onap/so/cloudify/beans/DeploymentInfo.java
new file mode 100644
index 0000000000..c6e29d05d7
--- /dev/null
+++ b/adapters/mso-adapter-utils/src/main/java/org/onap/so/cloudify/beans/DeploymentInfo.java
@@ -0,0 +1,186 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP - SO
+ * ================================================================================
+ * Copyright (C) 2017 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.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.so.cloudify.beans;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.onap.so.cloudify.v3.model.Deployment;
+import org.onap.so.cloudify.v3.model.DeploymentOutputs;
+import org.onap.so.cloudify.v3.model.Execution;
+
+/*
+ * This Java bean class relays Heat stack status information to ActiveVOS processes.
+ *
+ * This bean is returned by all Heat-specific adapter operations (create, query, delete)
+ */
+
+public class DeploymentInfo {
+ // Set defaults for everything
+ private String id = "";
+ private DeploymentStatus status = DeploymentStatus.NOTFOUND;
+ private Map<String,Object> outputs = new HashMap<String,Object>();
+ private Map<String,Object> inputs = new HashMap<String,Object>();
+ private String lastAction;
+ private String actionStatus;
+ private String errorMessage;
+
+ public DeploymentInfo () {
+ }
+
+ public DeploymentInfo (String id, Map<String,Object> outputs) {
+ this.id = id;
+ if (outputs != null) this.outputs = outputs;
+ }
+
+ public DeploymentInfo (String id) {
+ this.id = id;
+ }
+
+ public DeploymentInfo (String id, DeploymentStatus status) {
+ this.id = id;
+ this.status = status;
+ }
+
+ public DeploymentInfo (Deployment deployment) {
+ this(deployment, null, null);
+ }
+
+ /**
+ * Construct a DeploymentInfo object from a deployment and the latest Execution action
+ * @param deployment
+ * @param execution
+ */
+ public DeploymentInfo (Deployment deployment, DeploymentOutputs outputs, Execution execution)
+ {
+ if (deployment == null) {
+ this.id = null;
+ return;
+ }
+
+ this.id = deployment.getId();
+
+ if (outputs != null)
+ this.outputs = outputs.getOutputs();
+
+ if (deployment.getInputs() != null)
+ this.inputs = deployment.getInputs();
+
+ if (execution != null) {
+ this.lastAction = execution.getWorkflowId();
+ this.actionStatus = execution.getStatus();
+ this.errorMessage = execution.getError();
+
+ // Compute the status based on the last workflow
+ if (lastAction.equals("install")) {
+ if (actionStatus.equals("terminated"))
+ this.status = DeploymentStatus.INSTALLED;
+ else if (actionStatus.equals("failed"))
+ this.status = DeploymentStatus.FAILED;
+ else if (actionStatus.equals("started") || actionStatus.equals("pending"))
+ this.status = DeploymentStatus.INSTALLING;
+ else
+ this.status = DeploymentStatus.UNKNOWN;
+ }
+ else if (lastAction.equals("uninstall")) {
+ if (actionStatus.equals("terminated"))
+ this.status = DeploymentStatus.CREATED;
+ else if (actionStatus.equals("failed"))
+ this.status = DeploymentStatus.FAILED;
+ else if (actionStatus.equals("started") || actionStatus.equals("pending"))
+ this.status = DeploymentStatus.UNINSTALLING;
+ else
+ this.status = DeploymentStatus.UNKNOWN;
+ }
+ else {
+ // Could have more cases in the future for different actions.
+ this.status = DeploymentStatus.UNKNOWN;
+ }
+ }
+ else {
+ this.status = DeploymentStatus.CREATED;
+ }
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId (String id) {
+ this.id = id;
+ }
+
+ public DeploymentStatus getStatus() {
+ return status;
+ }
+
+ public void setStatus (DeploymentStatus status) {
+ this.status = status;
+ }
+
+ public Map<String,Object> getOutputs () {
+ return outputs;
+ }
+
+ public void setOutputs (Map<String,Object> outputs) {
+ this.outputs = outputs;
+ }
+
+ public Map<String,Object> getInputs () {
+ return inputs;
+ }
+
+ public void setInputs (Map<String,Object> inputs) {
+ this.inputs = inputs;
+ }
+
+ public String getLastAction() {
+ return lastAction;
+ }
+
+ public String getActionStatus() {
+ return actionStatus;
+ }
+
+ public String getErrorMessage() {
+ return errorMessage;
+ }
+
+ public void saveExecutionStatus (Execution execution) {
+ this.lastAction = execution.getWorkflowId();
+ this.actionStatus = execution.getStatus();
+ this.errorMessage = execution.getError();
+ }
+
+ @Override
+ public String toString() {
+ return "DeploymentInfo {" +
+ "id='" + id + '\'' +
+ ", inputs='" + inputs + '\'' +
+ ", outputs='" + outputs + '\'' +
+ ", lastAction='" + lastAction + '\'' +
+ ", status='" + status + '\'' +
+ ", errorMessage='" + errorMessage + '\'' +
+ '}';
+ }
+
+}
+
diff --git a/adapters/mso-adapter-utils/src/main/java/org/onap/so/cloudify/beans/DeploymentStatus.java b/adapters/mso-adapter-utils/src/main/java/org/onap/so/cloudify/beans/DeploymentStatus.java
new file mode 100644
index 0000000000..5aa47e9d6b
--- /dev/null
+++ b/adapters/mso-adapter-utils/src/main/java/org/onap/so/cloudify/beans/DeploymentStatus.java
@@ -0,0 +1,31 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP - SO
+ * ================================================================================
+ * Copyright (C) 2017 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.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.so.cloudify.beans;
+
+
+/*
+ * Enum status values to capture the state of a deployment, based on last known workflow
+ * (assume only INSTALL and UNINSTALL at this point).
+ */
+public enum DeploymentStatus {
+ NOTFOUND, CREATED, INSTALLED, FAILED, INSTALLING, UNINSTALLING, UNKNOWN
+}
+
diff --git a/adapters/mso-adapter-utils/src/main/java/org/onap/so/cloudify/exceptions/MsoBlueprintAlreadyExists.java b/adapters/mso-adapter-utils/src/main/java/org/onap/so/cloudify/exceptions/MsoBlueprintAlreadyExists.java
new file mode 100644
index 0000000000..d5d5684b0f
--- /dev/null
+++ b/adapters/mso-adapter-utils/src/main/java/org/onap/so/cloudify/exceptions/MsoBlueprintAlreadyExists.java
@@ -0,0 +1,33 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP - SO
+ * ================================================================================
+ * Copyright (C) 2017 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.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.so.cloudify.exceptions;
+
+public class MsoBlueprintAlreadyExists extends MsoCloudifyException {
+
+ private static final long serialVersionUID = 1L;
+
+ // Constructor to create a new MsoCloudifyException instance
+ public MsoBlueprintAlreadyExists (String blueprintId, String cloud) {
+ // Set the detailed error as the Exception 'message'
+ super(409, "Conflict", "Blueprint " + blueprintId + " already exists in Cloudify Manager supporting cloud site + " + cloud);
+ }
+
+}
diff --git a/adapters/mso-adapter-utils/src/main/java/org/onap/so/cloudify/exceptions/MsoCloudifyException.java b/adapters/mso-adapter-utils/src/main/java/org/onap/so/cloudify/exceptions/MsoCloudifyException.java
new file mode 100644
index 0000000000..992df5fd6a
--- /dev/null
+++ b/adapters/mso-adapter-utils/src/main/java/org/onap/so/cloudify/exceptions/MsoCloudifyException.java
@@ -0,0 +1,86 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP - SO
+ * ================================================================================
+ * Copyright (C) 2017 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.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.so.cloudify.exceptions;
+
+import org.onap.so.openstack.exceptions.MsoException;
+import org.onap.so.openstack.exceptions.MsoExceptionCategory;
+
+/**
+ * OpenStack exception.
+ */
+public class MsoCloudifyException extends MsoException
+{
+
+ /**
+ * Serialization id.
+ */
+ private static final long serialVersionUID = 3313636124141766495L;
+
+ private int statusCode;
+ private String statusMessage;
+ private String errorDetail;
+ private boolean pendingWorkflow;
+
+ /**
+ * Constructor to create a new MsoOpenstackException instance
+ * @param code the error code
+ * @param message the error message
+ * @param detail error details
+ */
+ public MsoCloudifyException (int code, String message, String detail) {
+ // Set the detailed error as the Exception 'message'
+ super(detail);
+ super.category = MsoExceptionCategory.OPENSTACK;
+
+ this.statusCode = code;
+ this.statusMessage = message;
+ this.errorDetail = detail;
+ this.pendingWorkflow = false;
+ }
+
+ /**
+ * Constructor to propagate the caught exception (mostly for stack trace)
+ * @param code the error code
+ * @param message the error message
+ * @param detail error details
+ * @param e the cause
+ */
+ public MsoCloudifyException (int code, String message, String detail, Exception e) {
+ // Set the detailed error as the Exception 'message'
+ super(detail, e);
+ super.category = MsoExceptionCategory.OPENSTACK;
+
+ this.statusCode = code;
+ this.statusMessage = message;
+ this.errorDetail = detail;
+ this.pendingWorkflow = false;
+ }
+
+ public void setPendingWorkflow (boolean pendingWorkflow) {
+ this.pendingWorkflow = pendingWorkflow;
+ }
+
+ @Override
+ public String toString () {
+ String error = "" + statusCode + " " + statusMessage + ": " + errorDetail + (pendingWorkflow ? " [workflow pending]" : "");
+ return error;
+ }
+}
diff --git a/adapters/mso-adapter-utils/src/main/java/org/onap/so/cloudify/exceptions/MsoCloudifyManagerNotFound.java b/adapters/mso-adapter-utils/src/main/java/org/onap/so/cloudify/exceptions/MsoCloudifyManagerNotFound.java
new file mode 100644
index 0000000000..0c795478cd
--- /dev/null
+++ b/adapters/mso-adapter-utils/src/main/java/org/onap/so/cloudify/exceptions/MsoCloudifyManagerNotFound.java
@@ -0,0 +1,33 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP - SO
+ * ================================================================================
+ * Copyright (C) 2017 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.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.so.cloudify.exceptions;
+
+public class MsoCloudifyManagerNotFound extends MsoCloudifyException {
+
+ private static final long serialVersionUID = 1L;
+
+ // Constructor to create a new MsoCloudifyException instance
+ public MsoCloudifyManagerNotFound (String cloudSiteId) {
+ // Set the detailed error as the Exception 'message'
+ super(0, "Cloudify Manager Not Found", "No Cloudify Manager configured for cloud site " + cloudSiteId);
+ }
+
+}
diff --git a/adapters/mso-adapter-utils/src/main/java/org/onap/so/cloudify/exceptions/MsoCloudifyTimeout.java b/adapters/mso-adapter-utils/src/main/java/org/onap/so/cloudify/exceptions/MsoCloudifyTimeout.java
new file mode 100644
index 0000000000..7dcd69d0a4
--- /dev/null
+++ b/adapters/mso-adapter-utils/src/main/java/org/onap/so/cloudify/exceptions/MsoCloudifyTimeout.java
@@ -0,0 +1,64 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP - SO
+ * ================================================================================
+ * Copyright (C) 2017 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.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.so.cloudify.exceptions;
+
+import org.onap.so.cloudify.v3.model.Execution;
+import org.onap.so.openstack.exceptions.MsoException;
+import org.onap.so.openstack.exceptions.MsoExceptionCategory;
+
+/**
+ * MSO Exception when a Cloudify workflow execution times out waiting for completion.
+ * Exception includes the last known state of the workflow execution.
+ */
+public class MsoCloudifyTimeout extends MsoException
+{
+
+ /**
+ * Serialization id.
+ */
+ private static final long serialVersionUID = 3313636124141766495L;
+
+ private Execution execution;
+
+ /**
+ * Constructor to create a new MsoOpenstackException instance
+ * @param code the error code
+ * @param message the error message
+ * @param detail error details
+ */
+ public MsoCloudifyTimeout (Execution execution) {
+ // Set the detailed error as the Exception 'message'
+ super("Cloudify Workflow Timeout for workflow " + execution.getWorkflowId() + " on deployment " + execution.getDeploymentId());
+ super.category = MsoExceptionCategory.OPENSTACK;
+
+ this.execution = execution;
+ }
+
+ public Execution getExecution() {
+ return this.execution;
+ }
+
+ @Override
+ public String toString () {
+ String error = "Workflow timeout: workflow=" + execution.getWorkflowId() + ",deployment=" + execution.getDeploymentId();
+ return error;
+ }
+}
diff --git a/adapters/mso-adapter-utils/src/main/java/org/onap/so/cloudify/exceptions/MsoCloudifyWorkflowException.java b/adapters/mso-adapter-utils/src/main/java/org/onap/so/cloudify/exceptions/MsoCloudifyWorkflowException.java
new file mode 100644
index 0000000000..a84da50dc4
--- /dev/null
+++ b/adapters/mso-adapter-utils/src/main/java/org/onap/so/cloudify/exceptions/MsoCloudifyWorkflowException.java
@@ -0,0 +1,54 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP - SO
+ * ================================================================================
+ * Copyright (C) 2017 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.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.so.cloudify.exceptions;
+
+/**
+ * Reports an error with a Cloudify Workflow execution.
+ * @author JC1348
+ *
+ */
+public class MsoCloudifyWorkflowException extends MsoCloudifyException {
+
+ private static final long serialVersionUID = 1L;
+
+ private String workflowStatus;
+ private boolean workflowStillRunning = false;
+
+ // Constructor to create a new MsoCloudifyException instance
+ public MsoCloudifyWorkflowException (String message, String deploymentId, String workflowId, String workflowStatus)
+ {
+ super(0, "Workflow Exception", "Workflow " + workflowId + " failed on deployment " + deploymentId + ": " + message);
+ this.workflowStatus = workflowStatus;
+ if (workflowStatus.equals("pending") || workflowStatus.equals("started") ||
+ workflowStatus.equals("cancelling") || workflowStatus.equals("force_cancelling"))
+ {
+ workflowStillRunning = true;
+ }
+ }
+
+ public String getWorkflowStatus() {
+ return workflowStatus;
+ }
+
+ public boolean isWorkflowStillRunning () {
+ return workflowStillRunning;
+ }
+}
diff --git a/adapters/mso-adapter-utils/src/main/java/org/onap/so/cloudify/exceptions/MsoDeploymentAlreadyExists.java b/adapters/mso-adapter-utils/src/main/java/org/onap/so/cloudify/exceptions/MsoDeploymentAlreadyExists.java
new file mode 100644
index 0000000000..4f5685efb3
--- /dev/null
+++ b/adapters/mso-adapter-utils/src/main/java/org/onap/so/cloudify/exceptions/MsoDeploymentAlreadyExists.java
@@ -0,0 +1,33 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP - SO
+ * ================================================================================
+ * Copyright (C) 2017 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.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.so.cloudify.exceptions;
+
+public class MsoDeploymentAlreadyExists extends MsoCloudifyException {
+
+ private static final long serialVersionUID = 1L;
+
+ // Constructor to create a new MsoCloudifyException instance
+ public MsoDeploymentAlreadyExists (String deploymentId, String cloud) {
+ // Set the detailed error as the Exception 'message'
+ super(409, "Conflict", "Deployment " + deploymentId + " already exists in Cloudify Manager suppporting cloud " + cloud);
+ }
+
+}
diff --git a/adapters/mso-adapter-utils/src/main/java/org/onap/so/cloudify/utils/MsoCloudifyUtils.java b/adapters/mso-adapter-utils/src/main/java/org/onap/so/cloudify/utils/MsoCloudifyUtils.java
new file mode 100644
index 0000000000..aa8e37f12b
--- /dev/null
+++ b/adapters/mso-adapter-utils/src/main/java/org/onap/so/cloudify/utils/MsoCloudifyUtils.java
@@ -0,0 +1,1408 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP - SO
+ * ================================================================================
+ * Copyright (C) 2017 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.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.so.cloudify.utils;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+
+import org.onap.so.adapters.vdu.CloudInfo;
+import org.onap.so.adapters.vdu.PluginAction;
+import org.onap.so.adapters.vdu.VduArtifact;
+import org.onap.so.adapters.vdu.VduArtifact.ArtifactType;
+import org.onap.so.adapters.vdu.VduException;
+import org.onap.so.adapters.vdu.VduInstance;
+import org.onap.so.adapters.vdu.VduModelInfo;
+import org.onap.so.adapters.vdu.VduPlugin;
+import org.onap.so.adapters.vdu.VduStateType;
+import org.onap.so.adapters.vdu.VduStatus;
+import org.onap.so.cloud.CloudConfig;
+import org.onap.so.cloud.CloudSite;
+import org.onap.so.cloud.CloudifyManager;
+import org.onap.so.cloudify.base.client.CloudifyBaseException;
+import org.onap.so.cloudify.base.client.CloudifyClientTokenProvider;
+import org.onap.so.cloudify.base.client.CloudifyConnectException;
+import org.onap.so.cloudify.base.client.CloudifyRequest;
+import org.onap.so.cloudify.base.client.CloudifyResponseException;
+import org.onap.so.cloudify.beans.DeploymentInfo;
+import org.onap.so.cloudify.beans.DeploymentStatus;
+import org.onap.so.cloudify.exceptions.MsoCloudifyException;
+import org.onap.so.cloudify.exceptions.MsoCloudifyManagerNotFound;
+import org.onap.so.cloudify.exceptions.MsoDeploymentAlreadyExists;
+import org.onap.so.cloudify.v3.client.BlueprintsResource.GetBlueprint;
+import org.onap.so.cloudify.v3.client.BlueprintsResource.UploadBlueprint;
+import org.onap.so.cloudify.v3.client.Cloudify;
+import org.onap.so.cloudify.v3.client.DeploymentsResource.CreateDeployment;
+import org.onap.so.cloudify.v3.client.DeploymentsResource.DeleteDeployment;
+import org.onap.so.cloudify.v3.client.DeploymentsResource.GetDeployment;
+import org.onap.so.cloudify.v3.client.DeploymentsResource.GetDeploymentOutputs;
+import org.onap.so.cloudify.v3.client.ExecutionsResource.CancelExecution;
+import org.onap.so.cloudify.v3.client.ExecutionsResource.GetExecution;
+import org.onap.so.cloudify.v3.client.ExecutionsResource.ListExecutions;
+import org.onap.so.cloudify.v3.client.ExecutionsResource.StartExecution;
+import org.onap.so.cloudify.v3.model.AzureConfig;
+import org.onap.so.cloudify.v3.model.Blueprint;
+import org.onap.so.cloudify.v3.model.CancelExecutionParams;
+import org.onap.so.cloudify.v3.model.CloudifyError;
+import org.onap.so.cloudify.v3.model.CreateDeploymentParams;
+import org.onap.so.cloudify.v3.model.Deployment;
+import org.onap.so.cloudify.v3.model.DeploymentOutputs;
+import org.onap.so.cloudify.v3.model.Execution;
+import org.onap.so.cloudify.v3.model.Executions;
+import org.onap.so.cloudify.v3.model.OpenstackConfig;
+import org.onap.so.cloudify.v3.model.StartExecutionParams;
+import org.onap.so.config.beans.PoConfig;
+import org.onap.so.db.catalog.beans.HeatTemplateParam;
+import org.onap.so.logger.MessageEnum;
+import org.onap.so.logger.MsoAlarmLogger;
+import org.onap.so.logger.MsoLogger;
+import org.onap.so.openstack.exceptions.MsoAdapterException;
+import org.onap.so.openstack.exceptions.MsoCloudSiteNotFound;
+import org.onap.so.openstack.exceptions.MsoException;
+import org.onap.so.openstack.exceptions.MsoExceptionCategory;
+import org.onap.so.openstack.exceptions.MsoIOException;
+import org.onap.so.openstack.exceptions.MsoOpenstackException;
+import org.onap.so.openstack.utils.MsoCommonUtils;
+import org.onap.so.utils.CryptoUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.env.Environment;
+import org.springframework.stereotype.Component;
+
+import com.fasterxml.jackson.core.JsonParseException;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+@Component
+public class MsoCloudifyUtils extends MsoCommonUtils implements VduPlugin{
+
+ private static final String CLOUDIFY_ERROR = "CloudifyError";
+ private static final String CLOUDIFY = "Cloudify";
+ private static final String CREATE_DEPLOYMENT = "CreateDeployment";
+ private static final String DELETE_DEPLOYMENT = "DeleteDeployment";
+ private static final String TERMINATED = "terminated";
+ private static final String CANCELLED = "cancelled";
+
+ // Fetch cloud configuration each time (may be cached in CloudConfig class)
+ @Autowired
+ protected CloudConfig cloudConfig;
+
+ @Autowired
+ private Environment environment;
+
+ @Autowired
+ private PoConfig poConfig;
+
+ private static final MsoLogger LOGGER = MsoLogger.getMsoLogger (MsoLogger.Catalog.RA, MsoCloudifyUtils.class);
+
+ // Properties names and variables (with default values)
+ protected String createPollIntervalProp = "ecomp.mso.adapters.po.pollInterval";
+ private String deletePollIntervalProp = "ecomp.mso.adapters.po.pollInterval";
+
+ protected String createPollIntervalDefault = "15";
+ private String deletePollIntervalDefault = "15";
+
+ private static final ObjectMapper JSON_MAPPER = new ObjectMapper();
+
+ /**
+ * Create a new Deployment from a specified blueprint, and install it in the specified
+ * cloud location and tenant. The blueprint identifier and parameter map are passed in
+ * as arguments, along with the cloud access credentials. The blueprint should have been
+ * previously uploaded to Cloudify.
+ *
+ * It is expected that parameters have been validated and contain at minimum the required
+ * parameters for the given template with no extra (undefined) parameters..
+ *
+ * The deployment ID supplied by the caller must be unique in the scope of the Cloudify
+ * tenant (not the Openstack tenant). However, it should also be globally unique, as it
+ * will be the identifier for the resource going forward in Inventory. This latter is
+ * managed by the higher levels invoking this function.
+ *
+ * This function executes the "install" workflow on the newly created workflow. Cloudify
+ * will be polled for completion unless the client requests otherwise.
+ *
+ * An error will be thrown if the requested Deployment already exists in the specified
+ * Cloudify instance.
+ *
+ * @param cloudSiteId The cloud (may be a region) in which to create the stack.
+ * @param tenantId The Openstack ID of the tenant in which to create the Stack
+ * @param deploymentId The identifier (name) of the deployment to create
+ * @param blueprintId The blueprint from which to create the deployment.
+ * @param inputs A map of key/value inputs
+ * @param pollForCompletion Indicator that polling should be handled in Java vs. in the client
+ * @param timeoutMinutes Timeout after which the "install" will be cancelled
+ * @param environment An optional yaml-format string to specify environmental parameters
+ * @param backout Flag to delete deployment on install Failure - defaulted to True
+ * @return A DeploymentInfo object
+ * @throws MsoCloudifyException Thrown if the Cloudify API call returns an exception.
+ * @throws MsoIOException Thrown on Cloudify connection errors.
+ */
+
+ public DeploymentInfo createAndInstallDeployment (String cloudSiteId,
+ String tenantId,
+ String deploymentId,
+ String blueprintId,
+ Map <String, ? extends Object> inputs,
+ boolean pollForCompletion,
+ int timeoutMinutes,
+ boolean backout) throws MsoException
+ {
+ // Obtain the cloud site information where we will create the stack
+ Optional<CloudSite> cloudSite = cloudConfig.getCloudSite (cloudSiteId);
+ if (!cloudSite.isPresent()) {
+ throw new MsoCloudSiteNotFound (cloudSiteId);
+ }
+
+ Cloudify cloudify = getCloudifyClient (cloudSite.get());
+
+ LOGGER.debug ("Ready to Create Deployment (" + deploymentId + ") with input params: " + inputs);
+
+ // Build up the inputs, including:
+ // - from provided "environment" file
+ // - passed in by caller
+ // - special input for cloud-specific Credentials
+ Map<String,Object> expandedInputs = new HashMap<> (inputs);
+
+ String platform = cloudSite.get().getPlatform();
+ if (platform == null || platform.equals("") || platform.equalsIgnoreCase("OPENSTACK")) {
+ // Create the Cloudify OpenstackConfig with the credentials
+ OpenstackConfig openstackConfig = getOpenstackConfig (cloudSite.get(), tenantId);
+ expandedInputs.put("openstack_config", openstackConfig);
+ } else if (platform.equalsIgnoreCase("AZURE")) {
+ // Create Cloudify AzureConfig with the credentials
+ AzureConfig azureConfig = getAzureConfig (cloudSite.get(), tenantId);
+ expandedInputs.put("azure_config", azureConfig);
+ }
+
+ // Build up the parameters to create a new deployment
+ CreateDeploymentParams deploymentParams = new CreateDeploymentParams();
+ deploymentParams.setBlueprintId(blueprintId);
+ deploymentParams.setInputs(expandedInputs);
+
+ Deployment deployment = null;
+ try {
+ CreateDeployment createDeploymentRequest = cloudify.deployments().create(deploymentId, deploymentParams);
+ LOGGER.debug (createDeploymentRequest.toString());
+
+ deployment = executeAndRecordCloudifyRequest (createDeploymentRequest);
+ }
+ catch (CloudifyResponseException e) {
+ // Since this came on the 'Create Deployment' command, nothing was changed
+ // in the cloud. Return the error as an exception.
+ if (e.getStatus () == 409) {
+ // Deployment already exists. Return a specific error for this case
+ MsoException me = new MsoDeploymentAlreadyExists (deploymentId, cloudSiteId);
+ me.addContext (CREATE_DEPLOYMENT);
+ throw me;
+ } else {
+ // Convert the CloudifyResponseException to an MsoException
+ LOGGER.debug("ERROR STATUS = " + e.getStatus() + ",\n" + e.getMessage() + "\n" + e.getLocalizedMessage());
+ MsoException me = cloudifyExceptionToMsoException (e, CREATE_DEPLOYMENT);
+ me.setCategory (MsoExceptionCategory.OPENSTACK);
+ throw me;
+ }
+ } catch (CloudifyConnectException e) {
+ // Error connecting to Cloudify instance. Convert to an MsoException
+ throw cloudifyExceptionToMsoException (e, CREATE_DEPLOYMENT);
+ } catch (RuntimeException e) {
+ // Catch-all
+ throw runtimeExceptionToMsoException (e, CREATE_DEPLOYMENT);
+ }
+
+ /*
+ * It can take some time for Cloudify to be ready to execute a workflow
+ * on the deployment. Sleep 30 seconds based on observation of behavior
+ * in a Cloudify VM instance (delay due to "create_deployment_environment").
+ */
+ sleep(30000);
+
+ /*
+ * Next execute the "install" workflow.
+ * Note - this assumes there are no additional parameters required for the workflow.
+ */
+ int createPollInterval = Integer.parseInt(this.environment.getProperty(createPollIntervalProp, createPollIntervalDefault));
+ int pollTimeout = (timeoutMinutes * 60) + createPollInterval;
+
+ Execution installWorkflow = null;
+
+ try {
+ installWorkflow = executeWorkflow (cloudify, deploymentId, "install", null, pollForCompletion, pollTimeout, createPollInterval);
+
+ if (installWorkflow.getStatus().equals(TERMINATED)) {
+ // Success!
+ // Create and return a DeploymentInfo structure. Include the Runtime outputs
+ DeploymentOutputs outputs = getDeploymentOutputs (cloudify, deploymentId);
+ return new DeploymentInfo (deployment, outputs, installWorkflow);
+ }
+ else {
+ // The workflow completed with errors. Must try to back it out.
+ if (!backout)
+ {
+ LOGGER.warn(MessageEnum.RA_CREATE_STACK_ERR, "Deployment installation failed, backout deletion suppressed", "", "", MsoLogger.ErrorCode.BusinessProcesssError, "Exception in Deployment Installation, backout suppressed");
+ }
+ else {
+ // Poll on delete if we rollback - use same values for now
+ int deletePollInterval = createPollInterval;
+ int deletePollTimeout = pollTimeout;
+
+ try {
+ // Run the uninstall to undo the install
+ Execution uninstallWorkflow = executeWorkflow (cloudify, deploymentId, "uninstall", null, pollForCompletion, deletePollTimeout, deletePollInterval);
+
+ if (uninstallWorkflow.getStatus().equals(TERMINATED))
+ {
+ // The uninstall completed. Delete the deployment itself
+ DeleteDeployment deleteRequest = cloudify.deployments().deleteByName(deploymentId);
+ executeAndRecordCloudifyRequest (deleteRequest);
+ }
+ else {
+ // Didn't uninstall successfully. Log this error
+ LOGGER.error (MessageEnum.RA_CREATE_STACK_ERR, "Create Deployment: Cloudify error rolling back deployment install: " + installWorkflow.getError(), "", "", MsoLogger.ErrorCode.BusinessProcesssError, "Create Stack: Cloudify error rolling back deployment installation");
+ }
+ }
+ catch (Exception e) {
+ // Catch-all for backout errors trying to uninstall/delete
+ // Log this error, and return the original exception
+ LOGGER.error (MessageEnum.RA_CREATE_STACK_ERR, "Create Stack: Nested exception rolling back deployment install: " + e, "", "", MsoLogger.ErrorCode.BusinessProcesssError, "Create Stack: Nested exception rolling back deployment installation");
+ }
+ }
+
+ MsoCloudifyException me = new MsoCloudifyException (0, "Workflow Execution Failed", installWorkflow.getError());
+ me.addContext (CREATE_DEPLOYMENT);
+ alarmLogger.sendAlarm(CLOUDIFY_ERROR, MsoAlarmLogger.CRITICAL, me.getContextMessage());
+ throw me;
+ }
+ }
+ catch (MsoException me) {
+ // Install failed. Unless requested otherwise, back out the deployment
+
+ if (!backout)
+ {
+ LOGGER.warn(MessageEnum.RA_CREATE_STACK_ERR, "Deployment installation failed, backout deletion suppressed", "", "", MsoLogger.ErrorCode.BusinessProcesssError, "Exception in Deployment Installation, backout suppressed");
+ }
+ else {
+ // Poll on delete if we rollback - use same values for now
+ int deletePollInterval = createPollInterval;
+ int deletePollTimeout = pollTimeout;
+
+ try {
+ // Run the uninstall to undo the install.
+ // Always try to run it, as it should be idempotent
+ executeWorkflow (cloudify, deploymentId, "uninstall", null, pollForCompletion, deletePollTimeout, deletePollInterval);
+
+ // Delete the deployment itself
+ DeleteDeployment deleteRequest = cloudify.deployments().deleteByName(deploymentId);
+ executeAndRecordCloudifyRequest (deleteRequest);
+ }
+ catch (Exception e) {
+ // Catch-all for backout errors trying to uninstall/delete
+ // Log this error, and return the original exception
+ LOGGER.error (MessageEnum.RA_CREATE_STACK_ERR, "Create Stack: Nested exception rolling back deployment install: " + e, "", "", MsoLogger.ErrorCode.BusinessProcesssError, "Create Stack: Nested exception rolling back deployment installation");
+
+ }
+ }
+
+ // Propagate the original exception from Stack Query.
+ me.addContext (CREATE_DEPLOYMENT);
+ alarmLogger.sendAlarm(CLOUDIFY_ERROR, MsoAlarmLogger.CRITICAL, me.getContextMessage());
+ throw me;
+ }
+ }
+
+
+ /*
+ * Get the runtime Outputs of a deployment.
+ * Return the Map of tag/value outputs.
+ */
+ private DeploymentOutputs getDeploymentOutputs (Cloudify cloudify, String deploymentId)
+ throws MsoException
+ {
+ // Build and send the Cloudify request
+ DeploymentOutputs deploymentOutputs = null;
+ try {
+ GetDeploymentOutputs queryDeploymentOutputs = cloudify.deployments().outputsById(deploymentId);
+ LOGGER.debug (queryDeploymentOutputs.toString());
+
+ deploymentOutputs = executeAndRecordCloudifyRequest(queryDeploymentOutputs);
+ }
+ catch (CloudifyConnectException ce) {
+ // Couldn't connect to Cloudify
+ LOGGER.error (MessageEnum.RA_CREATE_STACK_ERR, "QueryDeploymentOutputs: Cloudify connection failure: " + ce, "", "", MsoLogger.ErrorCode.BusinessProcesssError, "QueryDeploymentOutputs: Cloudify connection failure");
+ throw new MsoIOException (ce.getMessage(), ce);
+ }
+ catch (CloudifyResponseException re) {
+ if (re.getStatus () == 404) {
+ // No Outputs
+ return null;
+ }
+ throw new MsoCloudifyException (re.getStatus(), re.getMessage(), re.getLocalizedMessage(), re);
+ }
+ catch (Exception e) {
+ // Catch-all
+ throw new MsoAdapterException (e.getMessage(), e);
+ }
+
+ return deploymentOutputs;
+ }
+
+ /*
+ * Execute a workflow on a deployment. Handle polling for completion with timeout.
+ * Return the final Execution object with status.
+ * Throw an exception on Errors.
+ * Question - how does the client know whether rollback needs to be done?
+ */
+ private Execution executeWorkflow (Cloudify cloudify, String deploymentId, String workflowId, Map<String,Object> workflowParams, boolean pollForCompletion, int timeout, int pollInterval)
+ throws MsoCloudifyException
+ {
+ LOGGER.debug("Executing '" + workflowId + "' workflow on deployment '" + deploymentId + "'");
+
+ StartExecutionParams executeParams = new StartExecutionParams();
+ executeParams.setWorkflowId(workflowId);
+ executeParams.setDeploymentId(deploymentId);
+ executeParams.setParameters(workflowParams);
+
+ Execution execution = null;
+ String executionId = null;
+ String command = "start";
+ Exception savedException = null;
+
+ try {
+ StartExecution executionRequest = cloudify.executions().start(executeParams);
+ LOGGER.debug (executionRequest.toString());
+ execution = executeAndRecordCloudifyRequest (executionRequest);
+ executionId = execution.getId();
+
+ if (!pollForCompletion) {
+ // Client did not request polling, so just return the Execution object
+ return execution;
+ }
+
+ // Enter polling loop
+ boolean timedOut = false;
+ int pollTimeout = timeout;
+
+ String status = execution.getStatus();
+
+ // Create a reusable cloudify query request
+ GetExecution queryExecution = cloudify.executions().byId(executionId);
+ command = "query";
+
+ while (!timedOut && !(status.equals(TERMINATED) || status.equals("failed") || status.equals(CANCELLED)))
+ {
+ // workflow is still running; check for timeout
+ if (pollTimeout <= 0) {
+ LOGGER.debug ("workflow " + execution.getWorkflowId() + " timed out on deployment " + execution.getDeploymentId());
+ timedOut = true;
+ continue;
+ }
+
+ sleep(pollInterval * 1000L);
+
+ pollTimeout -= pollInterval;
+ LOGGER.debug("pollTimeout remaining: " + pollTimeout);
+
+ execution = queryExecution.execute();
+ status = execution.getStatus();
+ }
+
+ // Broke the loop. Check again for a terminal state
+ if (status.equals(TERMINATED)){
+ // Success!
+ LOGGER.debug ("Workflow '" + workflowId + "' completed successfully on deployment '" + deploymentId + "'");
+ return execution;
+ }
+ else if (status.equals("failed")){
+ // Workflow failed. Log it and return the execution object (don't throw exception here)
+ LOGGER.error (MessageEnum.RA_CREATE_STACK_ERR, "Cloudify workflow failure: " + execution.getError(), "", "", MsoLogger.ErrorCode.BusinessProcesssError, "Execute Workflow: Failed: " + execution.getError());
+ return execution;
+ }
+ else if (status.equals(CANCELLED)){
+ // Workflow was cancelled, leaving the deployment in an indeterminate state. Log it and return the execution object (don't throw exception here)
+ LOGGER.error (MessageEnum.RA_CREATE_STACK_ERR, "Cloudify workflow cancelled. Deployment is in an indeterminate state", "", "", MsoLogger.ErrorCode.BusinessProcesssError, "Execute Workflow cancelled: " + workflowId);
+ return execution;
+ }
+ else {
+ // Can only get here after a timeout
+ LOGGER.error (MessageEnum.RA_CREATE_STACK_ERR, "Cloudify workflow timeout", "", "", MsoLogger.ErrorCode.BusinessProcesssError, "Execute Workflow: Timed Out");
+ }
+ }
+ catch (CloudifyConnectException ce) {
+ LOGGER.error (MessageEnum.RA_CREATE_STACK_ERR, "Execute Workflow (" + command + "): Cloudify connection failure: " + ce, "", "", MsoLogger.ErrorCode.BusinessProcesssError, "Execute Workflow (" + command + "): Cloudify connection failure");
+ savedException = ce;
+ }
+ catch (CloudifyResponseException re) {
+ LOGGER.error (MessageEnum.RA_CREATE_STACK_ERR, "Execute Workflow (" + command + "): Cloudify response error: " + re, "", "", MsoLogger.ErrorCode.BusinessProcesssError, "Execute Workflow (" + command + "): Cloudify error" + re.getMessage());
+ savedException = re;
+ }
+ catch (RuntimeException e) {
+ // Catch-all
+ LOGGER.error (MessageEnum.RA_CREATE_STACK_ERR, "Execute Workflow (" + command + "): Unexpected error: " + e, "", "", MsoLogger.ErrorCode.BusinessProcesssError, "Execute Workflow (" + command + "): Internal error" + e.getMessage());
+ savedException = e;
+ }
+
+ // Get to this point ONLY on an error or timeout
+ // The cloudify execution is still running (we've not received a terminal status),
+ // so try to Cancel it.
+ CancelExecutionParams cancelParams = new CancelExecutionParams();
+ cancelParams.setAction("cancel");
+ // TODO: Use force_cancel?
+
+ Execution cancelExecution = null;
+
+ try {
+ CancelExecution cancelRequest = cloudify.executions().cancel(executionId, cancelParams);
+ LOGGER.debug (cancelRequest.toString());
+ cancelExecution = cancelRequest.execute();
+
+ // Enter polling loop
+ boolean timedOut = false;
+ int cancelTimeout = timeout; // TODO: For now, just use same timeout
+
+ String status = cancelExecution.getStatus();
+
+ // Poll for completion. Create a reusable cloudify query request
+ GetExecution queryExecution = cloudify.executions().byId(executionId);
+
+ while (!timedOut && !status.equals(CANCELLED))
+ {
+ // workflow is still running; check for timeout
+ if (cancelTimeout <= 0) {
+ LOGGER.debug ("Cancel timeout for workflow " + workflowId + " on deployment " + deploymentId);
+ timedOut = true;
+ continue;
+ }
+
+ sleep(pollInterval * 1000L);
+
+ cancelTimeout -= pollInterval;
+ LOGGER.debug("pollTimeout remaining: " + cancelTimeout);
+
+ execution = queryExecution.execute();
+ status = execution.getStatus();
+ }
+
+ // Broke the loop. Check again for a terminal state
+ if (status.equals(CANCELLED)){
+ // Finished cancelling. Return the original exception
+ LOGGER.debug ("Cancel workflow " + workflowId + " completed on deployment " + deploymentId);
+ throw new MsoCloudifyException (-1, "", "", savedException);
+ }
+ else {
+ // Can only get here after a timeout
+ LOGGER.debug ("Cancel workflow " + workflowId + " timeout out on deployment " + deploymentId);
+ MsoCloudifyException exception = new MsoCloudifyException (-1, "", "", savedException);
+ exception.setPendingWorkflow(true);
+ throw exception;
+ }
+ }
+ catch (Exception e) {
+ // Catch-all. Log the message and throw the original exception
+// LOGGER.error (MessageEnum.RA_CREATE_STACK_ERR, "Execute Workflow (" + command + "): Unexpected error: " + e, "", "", MsoLogger.ErrorCode.BusinessProcesssError, "Execute Workflow (" + command + "): Internal error" + e.getMessage());
+ LOGGER.debug ("Cancel workflow " + workflowId + " failed for deployment " + deploymentId + ": " + e.getMessage());
+ MsoCloudifyException exception = new MsoCloudifyException (-1, "", "", savedException);
+ exception.setPendingWorkflow(true);
+ throw exception;
+ }
+ }
+
+
+
+ /**
+ * Query for a Cloudify Deployment (by Name). This call will always return a
+ * DeploymentInfo object. If the deployment does not exist, an "empty" DeploymentInfo will be
+ * returned - containing only the deployment ID and a special status of NOTFOUND.
+ *
+ * @param tenantId The Openstack ID of the tenant in which to query
+ * @param cloudSiteId The cloud identifier (may be a region) in which to query
+ * @param stackName The name of the stack to query (may be simple or canonical)
+ * @return A StackInfo object
+ * @throws MsoOpenstackException Thrown if the Openstack API call returns an exception.
+ */
+ public DeploymentInfo queryDeployment (String cloudSiteId, String tenantId, String deploymentId)
+ throws MsoException
+ {
+ LOGGER.debug ("Query Cloudify Deployment: " + deploymentId + " in tenant " + tenantId);
+
+ // Obtain the cloud site information where we will create the stack
+ Optional<CloudSite> cloudSite = cloudConfig.getCloudSite (cloudSiteId);
+ if (!cloudSite.isPresent()) {
+ throw new MsoCloudSiteNotFound (cloudSiteId);
+ }
+
+ Cloudify cloudify = getCloudifyClient (cloudSite.get());
+
+ // Build and send the Cloudify request
+ Deployment deployment = null;
+ DeploymentOutputs outputs = null;
+ try {
+ GetDeployment queryDeployment = cloudify.deployments().byId(deploymentId);
+ LOGGER.debug (queryDeployment.toString());
+
+// deployment = queryDeployment.execute();
+ deployment = executeAndRecordCloudifyRequest(queryDeployment);
+
+ outputs = getDeploymentOutputs (cloudify, deploymentId);
+
+ // Next look for the latest execution
+ ListExecutions listExecutions = cloudify.executions().listFiltered ("deployment_id=" + deploymentId, "-created_at");
+ Executions executions = listExecutions.execute();
+
+ // If no executions, does this give NOT_FOUND or empty set?
+ if (executions.getItems().isEmpty()) {
+ return new DeploymentInfo (deployment);
+ }
+ else {
+ return new DeploymentInfo (deployment, outputs, executions.getItems().get(0));
+ }
+ }
+ catch (CloudifyConnectException ce) {
+ // Couldn't connect to Cloudify
+ LOGGER.error (MessageEnum.RA_CREATE_STACK_ERR, "QueryDeployment: Cloudify connection failure: " + ce, "", "", MsoLogger.ErrorCode.BusinessProcesssError, "QueryDeployment: Cloudify connection failure");
+ throw new MsoIOException (ce.getMessage(), ce);
+ }
+ catch (CloudifyResponseException re) {
+ if (re.getStatus () == 404) {
+ // Got a NOT FOUND error. React differently based on deployment vs. execution
+ if (deployment != null) {
+ // Got NOT_FOUND on the executions. Assume this is a valid "empty" set
+ return new DeploymentInfo (deployment, outputs, null);
+ } else {
+ // Deployment not found. Default status of a DeploymentInfo object is NOTFOUND
+ return new DeploymentInfo (deploymentId);
+ }
+ }
+ throw new MsoCloudifyException (re.getStatus(), re.getMessage(), re.getLocalizedMessage(), re);
+ }
+ catch (Exception e) {
+ // Catch-all
+ throw new MsoAdapterException (e.getMessage(), e);
+ }
+ }
+
+
+ /**
+ * Delete a Cloudify deployment (by ID). If the deployment is not found, it will be
+ * considered a successful deletion. The return value is a DeploymentInfo object which
+ * contains the last deployment status.
+ *
+ * There is no rollback from a successful deletion. A deletion failure will
+ * also result in an undefined deployment state - the components may or may not have been
+ * all or partially deleted, so the resulting deployment must be considered invalid.
+ *
+ * @param tenantId The Openstack ID of the tenant in which to perform the delete
+ * @param cloudSiteId The cloud identifier (may be a region) from which to delete the stack.
+ * @param stackName The name/id of the stack to delete. May be simple or canonical
+ * @param pollForCompletion Indicator that polling should be handled in Java vs. in the client
+ * @return A StackInfo object
+ * @throws MsoOpenstackException Thrown if the Openstack API call returns an exception.
+ * @throws MsoCloudSiteNotFound
+ */
+ public DeploymentInfo uninstallAndDeleteDeployment (String cloudSiteId,
+ String tenantId,
+ String deploymentId,
+ int timeoutMinutes) throws MsoException
+ {
+ // Obtain the cloud site information where we will create the stack
+ Optional<CloudSite> cloudSite = cloudConfig.getCloudSite (cloudSiteId);
+ if (!cloudSite.isPresent()) {
+ throw new MsoCloudSiteNotFound (cloudSiteId);
+ }
+
+ Cloudify cloudify = getCloudifyClient (cloudSite.get());
+
+ LOGGER.debug ("Ready to Uninstall/Delete Deployment (" + deploymentId + ")");
+
+ // Query first to save the trouble if deployment not found
+ Deployment deployment = null;
+ try {
+ GetDeployment queryDeploymentRequest = cloudify.deployments().byId(deploymentId);
+ LOGGER.debug (queryDeploymentRequest.toString());
+
+ deployment = executeAndRecordCloudifyRequest (queryDeploymentRequest);
+ }
+ catch (CloudifyResponseException e) {
+ // Since this came on the 'Create Deployment' command, nothing was changed
+ // in the cloud. Return the error as an exception.
+ if (e.getStatus () == 404) {
+ // Deployment doesn't exist. Return a "NOTFOUND" DeploymentInfo object
+ // TODO: Should return NULL?
+ LOGGER.debug("Deployment requested for deletion does not exist: " + deploymentId);
+ return new DeploymentInfo (deploymentId, DeploymentStatus.NOTFOUND);
+ } else {
+ // Convert the CloudifyResponseException to an MsoOpenstackException
+ LOGGER.debug("ERROR STATUS = " + e.getStatus() + ",\n" + e.getMessage() + "\n" + e.getLocalizedMessage());
+ MsoException me = cloudifyExceptionToMsoException (e, DELETE_DEPLOYMENT);
+ me.setCategory (MsoExceptionCategory.INTERNAL);
+ throw me;
+ }
+ } catch (CloudifyConnectException e) {
+ // Error connecting to Cloudify instance. Convert to an MsoException
+ throw cloudifyExceptionToMsoException (e, DELETE_DEPLOYMENT);
+ } catch (RuntimeException e) {
+ // Catch-all
+ throw runtimeExceptionToMsoException (e, DELETE_DEPLOYMENT);
+ }
+
+ /*
+ * Query the outputs before deleting so they can be returned as well
+ */
+ DeploymentOutputs outputs = getDeploymentOutputs (cloudify, deploymentId);
+
+ /*
+ * Next execute the "uninstall" workflow.
+ * Note - this assumes there are no additional parameters required for the workflow.
+ */
+ // TODO: No deletePollInterval that I'm aware of. Use the create interval
+ int deletePollInterval = Integer.parseInt(this.environment.getProperty (deletePollIntervalProp, deletePollIntervalDefault));
+ int pollTimeout = (timeoutMinutes * 60) + deletePollInterval;
+
+ Execution uninstallWorkflow = null;
+
+ try {
+ uninstallWorkflow = executeWorkflow (cloudify, deploymentId, "uninstall", null, true, pollTimeout, deletePollInterval);
+
+ if (uninstallWorkflow.getStatus().equals(TERMINATED)) {
+ // Successful uninstall.
+ LOGGER.debug("Uninstall successful for deployment " + deploymentId);
+ }
+ else {
+ // The uninstall workflow completed with an error. Must fail the request, but will
+ // leave the deployment in an indeterminate state, as cloud resources may still exist.
+ MsoCloudifyException me = new MsoCloudifyException (0, "Uninstall Workflow Failed", uninstallWorkflow.getError());
+ me.addContext (DELETE_DEPLOYMENT);
+ alarmLogger.sendAlarm(CLOUDIFY_ERROR, MsoAlarmLogger.CRITICAL, me.getContextMessage());
+ throw me;
+ }
+ }
+ catch (MsoException me) {
+ // Uninstall workflow has failed.
+ // Must fail the deletion... may leave the deployment in an inconclusive state
+ me.addContext (DELETE_DEPLOYMENT);
+ alarmLogger.sendAlarm(CLOUDIFY_ERROR, MsoAlarmLogger.CRITICAL, me.getContextMessage());
+ throw me;
+ }
+
+ // At this point, the deployment has been successfully uninstalled.
+ // Next step is to delete the deployment itself
+ try {
+ DeleteDeployment deleteRequest = cloudify.deployments().deleteByName(deploymentId);
+ LOGGER.debug(deleteRequest.toString());
+
+ // The delete request returns the deleted deployment
+ deployment = deleteRequest.execute();
+
+ }
+ catch (CloudifyConnectException ce) {
+ // Failed to delete. Must fail the request, but will leave the (uninstalled)
+ // deployment in Cloudify DB.
+ MsoCloudifyException me = new MsoCloudifyException (0, "Deployment Delete Failed", ce.getMessage(), ce);
+ me.addContext (DELETE_DEPLOYMENT);
+ alarmLogger.sendAlarm(CLOUDIFY_ERROR, MsoAlarmLogger.CRITICAL, me.getContextMessage());
+ throw me;
+ }
+ catch (CloudifyResponseException re) {
+ // Failed to delete. Must fail the request, but will leave the (uninstalled)
+ // deployment in the Cloudify DB.
+ MsoCloudifyException me = new MsoCloudifyException (re.getStatus(), re.getMessage(), re.getMessage(), re);
+ me.addContext (DELETE_DEPLOYMENT);
+ alarmLogger.sendAlarm(CLOUDIFY_ERROR, MsoAlarmLogger.CRITICAL, me.getContextMessage());
+ throw me;
+ }
+ catch (Exception e) {
+ // Catch-all
+ MsoAdapterException ae = new MsoAdapterException (e.getMessage(), e);
+ ae.addContext (DELETE_DEPLOYMENT);
+ alarmLogger.sendAlarm(CLOUDIFY_ERROR, MsoAlarmLogger.CRITICAL, ae.getContextMessage());
+ throw ae;
+ }
+
+ // Return the deleted deployment info (with runtime outputs) along with the completed uninstall workflow status
+ return new DeploymentInfo (deployment, outputs, uninstallWorkflow);
+ }
+
+
+ /**
+ * Check if a blueprint is available for use at a targeted cloud site.
+ * This requires checking the Cloudify Manager which is servicing that
+ * cloud site to see if the specified blueprint has been loaded.
+ *
+ * @param cloudSiteId The cloud site where the blueprint is needed
+ * @param blueprintId The ID for the blueprint in Cloudify
+ */
+ public boolean isBlueprintLoaded (String cloudSiteId, String blueprintId)
+ throws MsoException
+ {
+ // Obtain the cloud site information where we will load the blueprint
+ Optional<CloudSite> cloudSite = cloudConfig.getCloudSite (cloudSiteId);
+ if (!cloudSite.isPresent()) {
+ throw new MsoCloudSiteNotFound (cloudSiteId);
+ }
+
+ Cloudify cloudify = getCloudifyClient (cloudSite.get());
+
+ GetBlueprint getRequest = cloudify.blueprints().getMetadataById(blueprintId);
+ try {
+ Blueprint bp = getRequest.execute();
+ LOGGER.debug("Blueprint exists: " + bp.getId());
+ return true;
+ }
+ catch (CloudifyResponseException ce) {
+ if (ce.getStatus() == 404) {
+ return false;
+ } else {
+ throw ce;
+ }
+ } catch (Exception e) {
+ throw e;
+ }
+ }
+
+ /**
+ * Upload a blueprint to the Cloudify Manager that is servicing a Cloud Site.
+ * The blueprint currently must be structured as a single directory with all
+ * of the required files. One of those files is designated the "main file"
+ * for the blueprint. Files are provided as byte arrays, though expect only
+ * text files will be distributed from ASDC and stored by MSO.
+ *
+ * Cloudify requires a single root directory in its blueprint zip files.
+ * The requested blueprint ID will also be used as the directory.
+ * All of the files will be added to this directory in the zip file.
+ */
+ public void uploadBlueprint (String cloudSiteId,
+ String blueprintId,
+ String mainFileName,
+ Map<String,byte[]> blueprintFiles,
+ boolean failIfExists)
+ throws MsoException
+ {
+ // Obtain the cloud site information where we will load the blueprint
+ Optional<CloudSite> cloudSite = cloudConfig.getCloudSite (cloudSiteId);
+ if (!cloudSite.isPresent()) {
+ throw new MsoCloudSiteNotFound (cloudSiteId);
+ }
+
+ Cloudify cloudify = getCloudifyClient (cloudSite.get());
+
+ boolean blueprintUploaded = uploadBlueprint (cloudify, blueprintId, mainFileName, blueprintFiles);
+
+ if (!blueprintUploaded && failIfExists) {
+ throw new MsoAdapterException ("Blueprint already exists");
+ }
+ }
+
+ /*
+ * Common method to load a blueprint. May be called from
+ */
+ protected boolean uploadBlueprint (Cloudify cloudify, String blueprintId, String mainFileName, Map<String,byte[]> blueprintFiles)
+ throws MsoException
+ {
+ // Check if it already exists. If so, return false.
+ GetBlueprint getRequest = cloudify.blueprints().getMetadataById(blueprintId);
+ try {
+ Blueprint bp = getRequest.execute();
+ LOGGER.debug("Blueprint " + bp.getId() + " already exists.");
+ return false;
+ }
+ catch (CloudifyResponseException ce) {
+ if (ce.getStatus() == 404) {
+ // This is the expected result.
+ LOGGER.debug("Verified that Blueprint doesn't exist yet");
+ } else {
+ throw ce;
+ }
+ } catch (Exception e) {
+ throw e;
+ }
+
+ // Create a blueprint ZIP file in memory
+ ByteArrayOutputStream zipBuffer = new ByteArrayOutputStream();
+ ZipOutputStream zipOut = new ZipOutputStream(zipBuffer);
+
+ try {
+ // Put the root directory
+ String rootDir = blueprintId + ((blueprintId.endsWith("/") ? "" : "/"));
+ zipOut.putNextEntry(new ZipEntry (rootDir));
+ zipOut.closeEntry();
+
+ for (String fileName : blueprintFiles.keySet()) {
+ ZipEntry ze = new ZipEntry (rootDir + fileName);
+ zipOut.putNextEntry (ze);
+ zipOut.write (blueprintFiles.get(fileName));
+ zipOut.closeEntry();
+ }
+ zipOut.close();
+ }
+ catch (IOException e) {
+ // Since we're writing to a byte array, this should never happen
+ }
+ LOGGER.debug ("Blueprint zip file size: " + zipBuffer.size());
+
+ // Ready to upload the blueprint zip
+
+ try (InputStream blueprintStream = new ByteArrayInputStream (zipBuffer.toByteArray())) {
+ UploadBlueprint uploadRequest = cloudify.blueprints().uploadFromStream(blueprintId, mainFileName, blueprintStream);
+ Blueprint blueprint = uploadRequest.execute();
+ System.out.println("Successfully uploaded blueprint " + blueprint.getId());
+ }
+ catch (CloudifyResponseException | CloudifyConnectException e) {
+ throw cloudifyExceptionToMsoException (e, "UPLOAD_BLUEPRINT");
+ }
+ catch (RuntimeException e) {
+ // Catch-all
+ throw runtimeExceptionToMsoException (e, "UPLOAD_BLUEPRINT");
+ } catch (IOException e) {
+ // for try-with-resources
+ throw ioExceptionToMsoException(e, "UPLOAD_BLUEPRINT");
+ }
+
+ return true;
+ }
+
+
+
+ // ---------------------------------------------------------------
+ // PRIVATE FUNCTIONS FOR USE WITHIN THIS CLASS
+
+ /**
+ * Get a Cloudify client for the specified cloud site.
+ * Everything that is required can be found in the Cloud Config.
+ *
+ * @param cloudSite
+ * @return a Cloudify object
+ */
+ public Cloudify getCloudifyClient (CloudSite cloudSite) throws MsoException
+ {
+ CloudifyManager cloudifyConfig = cloudConfig.getCloudifyManager(cloudSite.getCloudifyId());
+ if (cloudifyConfig == null) {
+ throw new MsoCloudifyManagerNotFound (cloudConfig.getCloudSiteId(cloudSite));
+ }
+
+ // Get a Cloudify client
+ // Set a Token Provider to fetch tokens from Cloudify itself.
+ String cloudifyUrl = cloudifyConfig.getCloudifyUrl();
+ Cloudify cloudify = new Cloudify (cloudifyUrl);
+ cloudify.setTokenProvider(new CloudifyClientTokenProvider(cloudifyUrl, cloudifyConfig.getUsername(), CryptoUtils.decryptCloudConfigPassword(cloudifyConfig.getPassword())));
+
+ return cloudify;
+ }
+
+
+ /*
+ * Query for a Cloudify Deployment. This function is needed in several places, so
+ * a common method is useful. This method takes an authenticated CloudifyClient
+ * (which internally identifies the cloud & tenant to search), and returns
+ * a Deployment object if found, Null if not found, or an MsoCloudifyException
+ * if the Cloudify API call fails.
+ *
+ * @param cloudifyClient an authenticated Cloudify client
+ *
+ * @param deploymentId the deployment to query
+ *
+ * @return a Deployment object or null if the requested deployment doesn't exist.
+ *
+ * @throws MsoCloudifyException Thrown if the Cloudify API call returns an exception
+ */
+ protected Deployment queryDeployment (Cloudify cloudify, String deploymentId) throws MsoException {
+ if (deploymentId == null) {
+ return null;
+ }
+ try {
+ GetDeployment request = cloudify.deployments().byId (deploymentId);
+ return executeAndRecordCloudifyRequest (request);
+ } catch (CloudifyResponseException e) {
+ if (e.getStatus () == 404) {
+ LOGGER.debug ("queryDeployment - not found: " + deploymentId);
+ return null;
+ } else {
+ // Convert the CloudifyResponseException to an MsoCloudifyException
+ throw cloudifyExceptionToMsoException (e, "QueryDeployment");
+ }
+ } catch (CloudifyConnectException e) {
+ // Connection to Openstack failed
+ throw cloudifyExceptionToMsoException (e, "QueryDeployment");
+ }
+ }
+
+
+ public void copyStringOutputsToInputs(Map<String, String> inputs,
+ Map<String, Object> otherStackOutputs, boolean overWrite) {
+ if (inputs == null || otherStackOutputs == null)
+ return;
+
+ for (Map.Entry<String, Object> entry : otherStackOutputs.entrySet()) {
+ String key = entry.getKey();
+ Object value = entry.getValue();
+
+ if (value instanceof JsonNode) {
+ // This is a bit of mess - but I think it's the least impacting
+ // let's convert it BACK to a string - then it will get converted back later
+ try {
+ inputs.put(key, this.convertNode((JsonNode) value));
+ } catch (Exception e) {
+ LOGGER.debug("WARNING: unable to convert JsonNode output value for "+ key);
+ //effect here is this value will not have been copied to the inputs - and therefore will error out downstream
+ }
+ } else if (value instanceof java.util.LinkedHashMap) {
+ LOGGER.debug("LinkedHashMap - this is showing up as a LinkedHashMap instead of JsonNode");
+ try {
+ inputs.put(key, JSON_MAPPER.writeValueAsString(value));
+ } catch (Exception e) {
+ LOGGER.debug("WARNING: unable to convert LinkedHashMap output value for "+ key);
+ }
+ } else {
+ // just try to cast it - could be an integer or some such
+ try {
+ inputs.put(key, (String) value);
+ } catch (Exception e) {
+ LOGGER.debug("WARNING: unable to convert output value for "+ key);
+ //effect here is this value will not have been copied to the inputs - and therefore will error out downstream
+ }
+ }
+ }
+ return;
+ }
+
+ /*
+ * Normalize an input value to an Object, based on the target parameter type.
+ * If the type is not recognized, it will just be returned unchanged (as a string).
+ */
+ public Object convertInputValue (String inputValue, HeatTemplateParam templateParam)
+ {
+ String type = templateParam.getParamType();
+ LOGGER.debug("Parameter: " + templateParam.getParamName() + " is of type " + type);
+
+ if (type.equalsIgnoreCase("number")) {
+ try {
+ return Integer.valueOf(inputValue);
+ }
+ catch (Exception e) {
+ LOGGER.debug("Unable to convert " + inputValue + " to an integer!");
+ return null;
+ }
+ } else if (type.equalsIgnoreCase("json")) {
+ try {
+ return new ObjectMapper().readTree(inputValue);
+ }
+ catch (Exception e) {
+ LOGGER.debug("Unable to convert " + inputValue + " to a JsonNode!");
+ return null;
+ }
+ } else if (type.equalsIgnoreCase("boolean")) {
+ return new Boolean(inputValue);
+ }
+
+ // Nothing else matched. Return the original string
+ return inputValue;
+ }
+
+
+ private String convertNode(final JsonNode node) {
+ try {
+ final Object obj = JSON_MAPPER.treeToValue(node, Object.class);
+ return JSON_MAPPER.writeValueAsString(obj);
+ } catch (JsonParseException jpe) {
+ LOGGER.debug("Error converting json to string " + jpe.getMessage());
+ } catch (Exception e) {
+ LOGGER.debug("Error converting json to string " + e.getMessage());
+ }
+ return "[Error converting json to string]";
+ }
+
+
+ /*
+ * Method to execute a Cloudify command and track its execution time.
+ * For the metrics log, a category of "Cloudify" is used along with a
+ * sub-category that identifies the specific call (using the real
+ * cloudify-client classname of the CloudifyRequest<T> parameter).
+ */
+
+
+ protected <T> T executeAndRecordCloudifyRequest (CloudifyRequest <T> request) {
+
+ String requestType;
+ if (request.getClass ().getEnclosingClass () != null) {
+ requestType = request.getClass ().getEnclosingClass ().getSimpleName () + "."
+ + request.getClass ().getSimpleName ();
+ } else {
+ requestType = request.getClass ().getSimpleName ();
+ }
+
+ int retryDelay = poConfig.getRetryDelay();
+ int retryCount = poConfig.getRetryCount();
+ String retryCodes = poConfig.getRetryCodes();
+
+ // Run the actual command. All exceptions will be propagated
+ while (true)
+ {
+ try {
+ return request.execute ();
+ }
+ catch (CloudifyResponseException e) {
+ boolean retry = false;
+ if (retryCodes != null ) {
+ int code = e.getStatus();
+ LOGGER.debug ("Config values RetryDelay:" + retryDelay + " RetryCount:" + retryCount + " RetryCodes:" + retryCodes + " ResponseCode:" + code);
+ for (String rCode : retryCodes.split (",")) {
+ try {
+ if (retryCount > 0 && code == Integer.parseInt (rCode))
+ {
+ retryCount--;
+ retry = true;
+ LOGGER.debug ("CloudifyResponseException ResponseCode:" + code + " request:" + requestType + " Retry indicated. Attempts remaining:" + retryCount);
+ break;
+ }
+ } catch (NumberFormatException e1) {
+ LOGGER.error (MessageEnum.RA_CONFIG_EXC, "No retries. Exception in parsing retry code in config:" + rCode, "", "", MsoLogger.ErrorCode.SchemaError, "Exception in parsing retry code in config");
+ throw e;
+ }
+ }
+ }
+ if (retry)
+ {
+ sleep(retryDelay * 1000L);
+ }
+ else
+ throw e; // exceeded retryCount or code is not retryable
+ }
+ catch (CloudifyConnectException e) {
+ // Connection to Cloudify failed
+ if (retryCount > 0)
+ {
+ retryCount--;
+ LOGGER.debug (" request:" + requestType + " Retry indicated. Attempts remaining:" + retryCount);
+ sleep(retryDelay * 1000L);
+ }
+ else
+ throw e;
+
+ }
+ }
+ }
+ /*
+ * Convert an Exception on a Cloudify call to an MsoCloudifyException.
+ * This method supports CloudifyResponseException and CloudifyConnectException.
+ */
+ protected MsoException cloudifyExceptionToMsoException (CloudifyBaseException e, String context) {
+ MsoException me = null;
+
+ if (e instanceof CloudifyResponseException) {
+ CloudifyResponseException re = (CloudifyResponseException) e;
+
+ try {
+ // Failed Cloudify calls return an error entity body.
+ CloudifyError error = re.getResponse ().getErrorEntity (CloudifyError.class);
+ LOGGER.error (MessageEnum.RA_CONNECTION_EXCEPTION, CLOUDIFY, "Cloudify Error on " + context + ": " + error.getErrorCode(), CLOUDIFY, "", MsoLogger.ErrorCode.DataError, "Exception - Cloudify Error on " + context);
+ String fullError = error.getErrorCode() + ": " + error.getMessage();
+ LOGGER.debug(fullError);
+ me = new MsoCloudifyException (re.getStatus(),
+ re.getMessage(),
+ fullError);
+ } catch (Exception e2) {
+ // Couldn't parse the body as a "CloudifyError". Report the original HTTP error.
+ LOGGER.error (MessageEnum.RA_CONNECTION_EXCEPTION, CLOUDIFY, "HTTP Error on " + context + ": " + re.getStatus() + "," + e.getMessage(), CLOUDIFY, "", MsoLogger.ErrorCode.DataError, "Exception - HTTP Error on " + context, e2);
+ me = new MsoCloudifyException (re.getStatus (), re.getMessage (), "");
+ }
+
+ // Add the context of the error
+ me.addContext (context);
+
+ // Generate an alarm for 5XX and higher errors.
+ if (re.getStatus () >= 500) {
+ alarmLogger.sendAlarm (CLOUDIFY_ERROR, MsoAlarmLogger.CRITICAL, me.getContextMessage ());
+ }
+ } else if (e instanceof CloudifyConnectException) {
+ CloudifyConnectException ce = (CloudifyConnectException) e;
+
+ me = new MsoIOException (ce.getMessage ());
+ me.addContext (context);
+
+ // Generate an alarm for all connection errors.
+ alarmLogger.sendAlarm ("CloudifyIOError", MsoAlarmLogger.CRITICAL, me.getContextMessage ());
+ LOGGER.error(MessageEnum.RA_CONNECTION_EXCEPTION, CLOUDIFY, "Cloudify connection error on " + context + ": " + e, CLOUDIFY, "", MsoLogger.ErrorCode.DataError, "Cloudify connection error on " + context);
+ }
+
+ return me;
+ }
+
+
+
+ /*******************************************************************************
+ *
+ * Methods (and associated utilities) to implement the VduPlugin interface
+ *
+ *******************************************************************************/
+
+ /**
+ * VduPlugin interface for instantiate function.
+ *
+ * This one is a bit more complex, in that it will first upload the blueprint if needed,
+ * then create the Cloudify deployment and execute the install workflow.
+ *
+ * This implementation also merges any parameters defined in the ENV file with the other
+ * other input parameters for any undefined parameters).
+ * The basic MsoCloudifyUtils separates blueprint management from deploument actions,
+ * but the VduPlugin does not declare blueprint management operations.
+ */
+ @Override
+ public VduInstance instantiateVdu (
+ CloudInfo cloudInfo,
+ String instanceName,
+ Map<String,Object> inputs,
+ VduModelInfo vduModel,
+ boolean rollbackOnFailure)
+ throws VduException
+ {
+ String cloudSiteId = cloudInfo.getCloudSiteId();
+ String tenantId = cloudInfo.getTenantId();
+
+ // Translate the VDU ModelInformation structure to that which is needed for
+ // creating and uploading a blueprint. Use the model customization UUID as
+ // the blueprint identifier.
+
+ String blueprintId = vduModel.getModelCustomizationUUID();
+
+ try {
+
+ if (! isBlueprintLoaded (cloudSiteId, blueprintId)) {
+ LOGGER.debug ("Blueprint " + blueprintId + " is not loaded. Will upload it now.");
+
+ // Prepare the blueprint inputs. Need the set of blueprint templates and files,
+ // plus the main blueprint name.
+ Map<String,byte[]> blueprintFiles = new HashMap<>();
+ String mainTemplate = "";
+
+ // Add all of the blueprint artifacts from the VDU model
+ List<VduArtifact> vduArtifacts = vduModel.getArtifacts();
+ for (VduArtifact vduArtifact: vduArtifacts)
+ {
+ // Add all artifacts to the blueprint, with one exception.
+ // ENVIRONMENT files will be processed later as additional parameters.
+
+ ArtifactType artifactType = vduArtifact.getType();
+ if (artifactType != ArtifactType.ENVIRONMENT) {
+ blueprintFiles.put(vduArtifact.getName(), vduArtifact.getContent());
+
+ if (artifactType == ArtifactType.MAIN_TEMPLATE) {
+ mainTemplate = vduArtifact.getName();
+ }
+ }
+ }
+
+ // Upload the blueprint package
+ uploadBlueprint(cloudSiteId, blueprintId, mainTemplate, blueprintFiles, false);
+ }
+ }
+ catch (Exception e) {
+ throw new VduException ("CloudifyUtils (instantiateVDU): blueprint Exception", e);
+ }
+
+
+ // Next, create and install a new deployment based on the blueprint.
+ // For Cloudify, the deploymentId is specified by the client. Just use the instance name
+ // as the ID.
+
+ try {
+ // Query the Cloudify Deployment object and populate a VduInstance
+ DeploymentInfo deployment = createAndInstallDeployment (cloudSiteId,
+ tenantId,
+ instanceName,
+ blueprintId,
+ inputs,
+ true, // (poll for completion)
+ vduModel.getTimeoutMinutes(),
+ rollbackOnFailure);
+
+ return deploymentInfoToVduInstance(deployment);
+ }
+ catch (Exception e) {
+ throw new VduException ("CloudifyUtils (instantiateVDU): Create-and-install-deployment Exception", e);
+ }
+ }
+
+
+ /**
+ * VduPlugin interface for query function.
+ */
+ @Override
+ public VduInstance queryVdu (CloudInfo cloudInfo, String instanceId)
+ throws VduException
+ {
+ String cloudSiteId = cloudInfo.getCloudSiteId();
+ String tenantId = cloudInfo.getTenantId();
+
+ try {
+ // Query the Cloudify Deployment object and populate a VduInstance
+ DeploymentInfo deployment = queryDeployment (cloudSiteId, tenantId, instanceId);
+
+ return deploymentInfoToVduInstance(deployment);
+ }
+ catch (Exception e) {
+ throw new VduException ("Query VDU Exception", e);
+ }
+ }
+
+
+ /**
+ * VduPlugin interface for delete function.
+ */
+ @Override
+ public VduInstance deleteVdu (CloudInfo cloudInfo, String instanceId, int timeoutMinutes)
+ throws VduException
+ {
+ String cloudSiteId = cloudInfo.getCloudSiteId();
+ String tenantId = cloudInfo.getTenantId();
+
+ try {
+ // Uninstall and delete the Cloudify Deployment
+ DeploymentInfo deployment = uninstallAndDeleteDeployment (cloudSiteId, tenantId, instanceId, timeoutMinutes);
+
+ // Populate a VduInstance based on the deleted Cloudify Deployment object
+ return deploymentInfoToVduInstance(deployment);
+ }
+ catch (Exception e) {
+ throw new VduException ("Delete VDU Exception", e);
+ }
+ }
+
+
+ /**
+ * VduPlugin interface for update function.
+ *
+ * Update is currently not supported in the MsoCloudifyUtils implementation.
+ * Just return a VduException.
+ *
+ */
+ @Override
+ public VduInstance updateVdu (
+ CloudInfo cloudInfo,
+ String instanceId,
+ Map<String,Object> inputs,
+ VduModelInfo vduModel,
+ boolean rollbackOnFailure)
+ throws VduException
+ {
+ throw new VduException ("CloudifyUtils: updateVDU interface not supported");
+ }
+
+
+ /*
+ * Convert the local DeploymentInfo object (Cloudify-specific) to a generic VduInstance object
+ */
+ protected VduInstance deploymentInfoToVduInstance (DeploymentInfo deployment)
+ {
+ VduInstance vduInstance = new VduInstance();
+
+ // only one ID in Cloudify, use for both VDU name and ID
+ vduInstance.setVduInstanceId(deployment.getId());
+ vduInstance.setVduInstanceName(deployment.getId());
+
+ // Copy inputs and outputs
+ vduInstance.setInputs(deployment.getInputs());
+ vduInstance.setOutputs(deployment.getOutputs());
+
+ // Translate the status elements
+ vduInstance.setStatus(deploymentStatusToVduStatus (deployment));
+
+ return vduInstance;
+ }
+
+ protected VduStatus deploymentStatusToVduStatus (DeploymentInfo deployment)
+ {
+ VduStatus vduStatus = new VduStatus();
+
+ // Determine the status based on last action & status
+ // DeploymentInfo object should be enhanced to report a better status internally.
+ DeploymentStatus status = deployment.getStatus();
+
+ if (status == null) {
+ vduStatus.setState(VduStateType.UNKNOWN);
+ }
+ else if (status == DeploymentStatus.NOTFOUND) {
+ vduStatus.setState(VduStateType.NOTFOUND);
+ }
+ else if (status == DeploymentStatus.INSTALLED) {
+ vduStatus.setState(VduStateType.INSTANTIATED);
+ }
+ else if (status == DeploymentStatus.CREATED) {
+ // Deployment exists but is not installed. This shouldn't really happen,
+ // since create + install or uninstall + delete are always done together.
+ // But account for it anyway, assuming the operation is still in progress.
+ String lastAction = deployment.getLastAction();
+ if (lastAction == null)
+ vduStatus.setState(VduStateType.INSTANTIATING);
+ else
+ vduStatus.setState(VduStateType.DELETING);
+ }
+ else if (status == DeploymentStatus.FAILED) {
+ vduStatus.setState(VduStateType.FAILED);
+ } else {
+ vduStatus.setState(VduStateType.UNKNOWN);
+ }
+
+ vduStatus.setErrorMessage(deployment.getErrorMessage());
+ vduStatus.setLastAction(new PluginAction(deployment.getLastAction(), deployment.getActionStatus(), deployment.getErrorMessage()));
+
+ return vduStatus;
+ }
+
+ /*
+ * Return an OpenstackConfig object as expected by Cloudify Openstack Plug-in.
+ * Base the values on the CloudSite definition.
+ */
+ protected OpenstackConfig getOpenstackConfig (CloudSite cloudSite, String tenantId) {
+ OpenstackConfig openstackConfig = new OpenstackConfig();
+ openstackConfig.setRegion (cloudSite.getRegionId());
+ openstackConfig.setAuthUrl (cloudConfig.getIdentityService(cloudSite.getIdentityServiceId()).getIdentityUrl());
+ openstackConfig.setUsername (cloudConfig.getIdentityService(cloudSite.getIdentityServiceId()).getMsoId());
+ openstackConfig.setPassword (CryptoUtils.decryptCloudConfigPassword(cloudConfig.getIdentityService(cloudSite.getIdentityServiceId()).getMsoPass()));
+ openstackConfig.setTenantName (tenantId);
+ return openstackConfig;
+ }
+
+ /*
+ * Return an Azure object as expected by Cloudify Azure Plug-in.
+ * Base the values on the CloudSite definition.
+ */
+ protected AzureConfig getAzureConfig (CloudSite cloudSite, String tenantId) {
+ AzureConfig azureConfig = new AzureConfig();
+ // TODO: Use adminTenant for now, instead of adding another element
+ azureConfig.setSubscriptionId (cloudSite.getIdentityService().getAdminTenant());
+ azureConfig.setTenantId (tenantId);
+ azureConfig.setClientId (cloudSite.getIdentityService().getMsoId());
+ azureConfig.setClientSecret (cloudSite.getIdentityService().getMsoPass());
+ return azureConfig;
+ }
+
+ private void sleep(long time) {
+ try {
+ Thread.sleep(time);
+ } catch (InterruptedException e) {
+ LOGGER.debug("Thread interrupted while sleeping!", e);
+ Thread.currentThread().interrupt();
+ }
+ }
+}
diff --git a/adapters/mso-adapter-utils/src/main/java/org/onap/so/config/beans/PoConfig.java b/adapters/mso-adapter-utils/src/main/java/org/onap/so/config/beans/PoConfig.java
new file mode 100644
index 0000000000..3098a5410a
--- /dev/null
+++ b/adapters/mso-adapter-utils/src/main/java/org/onap/so/config/beans/PoConfig.java
@@ -0,0 +1,53 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP - SO
+ * ================================================================================
+ * Copyright (C) 2017 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.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.so.config.beans;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+@ConfigurationProperties(prefix="adapters.po")
+public class PoConfig {
+
+ private String retryCodes;
+ private int retryDelay;
+ private int retryCount;
+
+ public String getRetryCodes() {
+ return retryCodes;
+ }
+ public void setRetryCodes(String retryCodes) {
+ this.retryCodes = retryCodes;
+ }
+ public int getRetryDelay() {
+ return retryDelay;
+ }
+ public void setRetryDelay(int retryDelay) {
+ this.retryDelay = retryDelay;
+ }
+ public int getRetryCount() {
+ return retryCount;
+ }
+ public void setRetryCount(int retryCount) {
+ this.retryCount = retryCount;
+ }
+
+}
diff --git a/adapters/mso-adapter-utils/src/main/java/org/onap/so/openstack/beans/HeatCacheEntry.java b/adapters/mso-adapter-utils/src/main/java/org/onap/so/openstack/beans/HeatCacheEntry.java
new file mode 100644
index 0000000000..5eaca976d0
--- /dev/null
+++ b/adapters/mso-adapter-utils/src/main/java/org/onap/so/openstack/beans/HeatCacheEntry.java
@@ -0,0 +1,60 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP - SO
+ * ================================================================================
+ * Copyright (C) 2017 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.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.so.openstack.beans;
+
+import java.io.Serializable;
+import java.util.Calendar;
+
+import com.woorea.openstack.heat.Heat;
+
+/*
+ * An entry in the Heat Client Cache. It saves the Heat client object
+ * along with the token expiration. After this interval, this cache
+ * item will no longer be used.
+ */
+public class HeatCacheEntry implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ private String heatUrl;
+ private String token;
+ private Calendar expires;
+
+ public HeatCacheEntry (String heatUrl, String token, Calendar expires) {
+ this.heatUrl = heatUrl;
+ this.token = token;
+ this.expires = expires;
+ }
+
+ public Heat getHeatClient () {
+ Heat heatClient = new Heat (heatUrl);
+ heatClient.token (token);
+ return heatClient;
+ }
+
+ public boolean isExpired () {
+ if (expires == null) {
+ return true;
+ }
+
+ return System.currentTimeMillis() > expires.getTimeInMillis();
+ }
+}
diff --git a/adapters/mso-adapter-utils/src/main/java/org/onap/so/openstack/beans/NeutronCacheEntry.java b/adapters/mso-adapter-utils/src/main/java/org/onap/so/openstack/beans/NeutronCacheEntry.java
new file mode 100644
index 0000000000..d89fd1a73f
--- /dev/null
+++ b/adapters/mso-adapter-utils/src/main/java/org/onap/so/openstack/beans/NeutronCacheEntry.java
@@ -0,0 +1,67 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP - SO
+ * ================================================================================
+ * Copyright (C) 2017 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.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.so.openstack.beans;
+
+import java.io.Serializable;
+import java.util.Calendar;
+
+/*
+ * An entry in the Neutron Client Cache. It saves the Neutron client object
+ * along with the token expiration. After this interval, this cache
+ * item will no longer be used.
+ */
+public class NeutronCacheEntry implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ private String neutronUrl;
+ private String token;
+ private Calendar expires;
+
+ public NeutronCacheEntry (String neutronUrl, String token, Calendar expires) {
+ this.neutronUrl = neutronUrl;
+ this.token = token;
+ this.expires = expires;
+ }
+
+ public String getNeutronUrl() {
+ return neutronUrl;
+ }
+
+ public void setNeutronUrl(String neutronUrl) {
+ this.neutronUrl = neutronUrl;
+ }
+
+ public String getToken() {
+ return token;
+ }
+
+ public void setToken(String token) {
+ this.token = token;
+ }
+
+ public boolean isExpired() {
+ if (expires == null) {
+ return true;
+ }
+
+ return System.currentTimeMillis() > expires.getTimeInMillis();
+ }
+}
diff --git a/adapters/mso-adapter-utils/src/main/java/org/onap/so/openstack/beans/VnfRollback.java b/adapters/mso-adapter-utils/src/main/java/org/onap/so/openstack/beans/VnfRollback.java
new file mode 100644
index 0000000000..bb8aa92281
--- /dev/null
+++ b/adapters/mso-adapter-utils/src/main/java/org/onap/so/openstack/beans/VnfRollback.java
@@ -0,0 +1,216 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP - SO
+ * ================================================================================
+ * Copyright (C) 2017 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.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.so.openstack.beans;
+
+import org.onap.so.entity.MsoRequest;
+import org.springframework.stereotype.Component;
+/**
+ * Javabean representing the rollback criteria following a "Create VNF"
+ * operation. This structure can be passed back to the "Rollback VNF"
+ * operation to undo the effects of the create.
+ *
+ *
+ */
+@Component
+public class VnfRollback {
+ private String vnfId;
+ private String tenantId;
+ private String cloudSiteId;
+ private boolean tenantCreated = false;
+ private boolean vnfCreated = false;
+ private MsoRequest msoRequest;
+ private String volumeGroupName;
+ private String volumeGroupId;
+ private String requestType;
+ private String volumeGroupHeatStackId;
+ private String baseGroupHeatStackId;
+ private boolean isBase = false;
+ private String vfModuleStackId;
+ private String modelCustomizationUuid; //NOTE: this is the vfModule's modelCustomizationUuid
+ private String mode = "HEAT";
+
+ public VnfRollback() {}
+
+ /**
+ * For backwards compatibility... orchestration mode defaults to HEAT
+ *
+ * @param vnfId
+ * @param tenantId
+ * @param cloudSiteId
+ * @param tenantCreated
+ * @param vnfCreated
+ * @param msoRequest
+ * @param volumeGroupName
+ * @param volumeGroupId
+ * @param requestType
+ * @param modelCustomizationUuid
+ */
+ public VnfRollback(String vnfId, String tenantId, String cloudSiteId,
+ boolean tenantCreated, boolean vnfCreated,
+ MsoRequest msoRequest,
+ String volumeGroupName, String volumeGroupId, String requestType, String modelCustomizationUuid) {
+ super();
+ this.vnfId = vnfId;
+ this.tenantId = tenantId;
+ this.cloudSiteId = cloudSiteId;
+ this.tenantCreated = tenantCreated;
+ this.vnfCreated = vnfCreated;
+ this.msoRequest = msoRequest;
+ this.volumeGroupName = volumeGroupName;
+ this.volumeGroupId = volumeGroupId;
+ this.requestType = requestType;
+ this.modelCustomizationUuid = modelCustomizationUuid;
+ }
+
+ /**
+ * For backwards compatibility... orchestration mode defaults to HEAT
+ *
+ * @param vnfId
+ * @param tenantId
+ * @param cloudSiteId
+ * @param tenantCreated
+ * @param vnfCreated
+ * @param msoRequest
+ * @param volumeGroupName
+ * @param volumeGroupId
+ * @param requestType
+ * @param modelCustomizationUuid
+ */
+ public VnfRollback(String vnfId, String tenantId, String cloudSiteId,
+ boolean tenantCreated, boolean vnfCreated,
+ MsoRequest msoRequest, String volumeGroupName, String volumeGroupId,
+ String requestType, String modelCustomizationUuid, String orchestrationMode) {
+ super();
+ this.vnfId = vnfId;
+ this.tenantId = tenantId;
+ this.cloudSiteId = cloudSiteId;
+ this.tenantCreated = tenantCreated;
+ this.vnfCreated = vnfCreated;
+ this.msoRequest = msoRequest;
+ this.volumeGroupName = volumeGroupName;
+ this.volumeGroupId = volumeGroupId;
+ this.requestType = requestType;
+ this.modelCustomizationUuid = modelCustomizationUuid;
+ this.mode = orchestrationMode;
+ }
+
+ public String getVnfId() {
+ return vnfId;
+ }
+ public void setVnfId(String vnfId) {
+ this.vnfId = vnfId;
+ }
+ public String getTenantId() {
+ return tenantId;
+ }
+
+ public void setTenantId(String tenantId) {
+ this.tenantId = tenantId;
+ }
+ public String getCloudSiteId() {
+ return cloudSiteId;
+ }
+ public void setCloudSiteId(String cloudId) {
+ this.cloudSiteId = cloudId;
+ }
+ public boolean getTenantCreated() {
+ return tenantCreated;
+ }
+ public void setTenantCreated(boolean tenantCreated) {
+ this.tenantCreated = tenantCreated;
+ }
+ public boolean getVnfCreated() {
+ return vnfCreated;
+ }
+ public void setVnfCreated(boolean vnfCreated) {
+ this.vnfCreated = vnfCreated;
+ }
+ public MsoRequest getMsoRequest() {
+ return msoRequest;
+ }
+ public void setMsoRequest (MsoRequest msoRequest) {
+ this.msoRequest = msoRequest;
+ }
+ public String getVolumeGroupName() {
+ return this.volumeGroupName;
+ }
+ public void setVolumeGroupName(String volumeGroupName) {
+ this.volumeGroupName = volumeGroupName;
+ }
+ public String getVolumeGroupId() {
+ return this.volumeGroupId;
+ }
+ public void setVolumeGroupId(String volumeGroupId) {
+ this.volumeGroupId = volumeGroupId;
+ }
+ public String getRequestType() {
+ return this.requestType;
+ }
+ public void setRequestType(String requestType) {
+ this.requestType = requestType;
+ }
+ public String getVolumeGroupHeatStackId() {
+ return this.volumeGroupHeatStackId;
+ }
+ public void setVolumeGroupHeatStackId(String volumeGroupHeatStackId) {
+ this.volumeGroupHeatStackId = volumeGroupHeatStackId;
+ }
+
+ public String getBaseGroupHeatStackId() {
+ return this.baseGroupHeatStackId;
+ }
+ public void setBaseGroupHeatStackId(String baseGroupHeatStackId) {
+ this.baseGroupHeatStackId = baseGroupHeatStackId;
+ }
+
+ public boolean isBase() {
+ return this.isBase;
+ }
+ public void setIsBase(boolean isBase) {
+ this.isBase = isBase;
+ }
+ public String getVfModuleStackId() {
+ return this.vfModuleStackId;
+ }
+ public void setVfModuleStackId(String vfModuleStackId) {
+ this.vfModuleStackId = vfModuleStackId;
+ }
+ public String getModelCustomizationUuid() {
+ return this.modelCustomizationUuid;
+ }
+ public void setModelCustomizationUuid(String modelCustomizationUuid) {
+ this.modelCustomizationUuid = modelCustomizationUuid;
+ }
+ public String getMode() {
+ return this.mode;
+ }
+ public void setMode(String mode) {
+ this.mode = mode;
+ }
+ @Override
+ public String toString() {
+ return "VnfRollback: cloud=" + cloudSiteId + ", tenant=" + tenantId +
+ ", vnf=" + vnfId + ", tenantCreated=" + tenantCreated +
+ ", vnfCreated=" + vnfCreated + ", requestType = " + requestType
+ + ", modelCustomizationUuid=" + this.modelCustomizationUuid
+ + ", mode=" + mode;
+ }
+}
diff --git a/adapters/mso-adapter-utils/src/main/java/org/onap/so/openstack/utils/MsoCommonUtils.java b/adapters/mso-adapter-utils/src/main/java/org/onap/so/openstack/utils/MsoCommonUtils.java
new file mode 100644
index 0000000000..98793601d0
--- /dev/null
+++ b/adapters/mso-adapter-utils/src/main/java/org/onap/so/openstack/utils/MsoCommonUtils.java
@@ -0,0 +1,303 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP - SO
+ * ================================================================================
+ * Copyright (C) 2017 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.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.so.openstack.utils;
+
+
+import java.io.IOException;
+
+import org.onap.so.config.beans.PoConfig;
+import org.onap.so.logger.MessageEnum;
+import org.onap.so.logger.MsoAlarmLogger;
+import org.onap.so.logger.MsoLogger;
+import org.onap.so.openstack.exceptions.MsoAdapterException;
+import org.onap.so.openstack.exceptions.MsoException;
+import org.onap.so.openstack.exceptions.MsoExceptionCategory;
+import org.onap.so.openstack.exceptions.MsoIOException;
+import org.onap.so.openstack.exceptions.MsoOpenstackException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import com.woorea.openstack.base.client.OpenStackBaseException;
+import com.woorea.openstack.base.client.OpenStackConnectException;
+import com.woorea.openstack.base.client.OpenStackRequest;
+import com.woorea.openstack.base.client.OpenStackResponseException;
+import com.woorea.openstack.heat.model.Explanation;
+import com.woorea.openstack.keystone.model.Error;
+import com.woorea.openstack.quantum.model.NeutronError;
+
+@Component("CommonUtils")
+public class MsoCommonUtils {
+
+ private static MsoLogger logger = MsoLogger.getMsoLogger(MsoLogger.Catalog.RA, MsoCommonUtils.class);
+ protected static MsoAlarmLogger alarmLogger = new MsoAlarmLogger();
+
+ @Autowired
+ private PoConfig poConfig;
+ /*
+ * Method to execute an Openstack command and track its execution time.
+ * For the metrics log, a category of "Openstack" is used along with a
+ * sub-category that identifies the specific call (using the real
+ * openstack-java-sdk classname of the OpenStackRequest<T> parameter).
+ */
+
+ protected <T> T executeAndRecordOpenstackRequest (OpenStackRequest <T> request) {
+
+ int limit;
+
+ long start = System.currentTimeMillis ();
+ String requestType;
+ if (request.getClass ().getEnclosingClass () != null) {
+ requestType = request.getClass ().getEnclosingClass ().getSimpleName () + "."
+ + request.getClass ().getSimpleName ();
+ } else {
+ requestType = request.getClass ().getSimpleName ();
+ }
+
+ int retryDelay = poConfig.getRetryDelay();
+ int retryCount = poConfig.getRetryCount();
+ String retryCodes = poConfig.getRetryCodes();
+
+ // Run the actual command. All exceptions will be propagated
+ while (true)
+ {
+ try {
+ return request.execute ();
+ }
+ catch (OpenStackResponseException e) {
+ boolean retry = false;
+ if (retryCodes != null ) {
+ int code = e.getStatus();
+ logger.debug ("Config values RetryDelay:" + retryDelay + " RetryCount:" + retryCount + " RetryCodes:" + retryCodes + " ResponseCode:" + code);
+ for (String rCode : retryCodes.split (",")) {
+ try {
+ if (retryCount > 0 && code == Integer.parseInt (rCode))
+ {
+ retryCount--;
+ retry = true;
+ logger.debug ("OpenStackResponseException ResponseCode:" + code + " request:" + requestType + " Retry indicated. Attempts remaining:" + retryCount);
+ break;
+ }
+ } catch (NumberFormatException e1) {
+ logger.error (MessageEnum.RA_CONFIG_EXC, "No retries. Exception in parsing retry code in config:" + rCode, "", "", MsoLogger.ErrorCode.SchemaError, "Exception in parsing retry code in config");
+ throw e;
+ }
+ }
+ }
+ if (retry)
+ {
+ try {
+ Thread.sleep (retryDelay * 1000L);
+ } catch (InterruptedException e1) {
+ logger.debug ("Thread interrupted while sleeping", e1);
+ Thread.currentThread().interrupt();
+ }
+ }
+ else
+ throw e; // exceeded retryCount or code is not retryable
+ }
+ catch (OpenStackConnectException e) {
+ // Connection to Openstack failed
+ if (retryCount > 0)
+ {
+ retryCount--;
+ logger.debug (" request:" + requestType + " Retry indicated. Attempts remaining:" + retryCount);
+ try {
+ Thread.sleep (retryDelay * 1000L);
+ } catch (InterruptedException e1) {
+ logger.debug ("Thread interrupted while sleeping", e1);
+ Thread.currentThread().interrupt();
+ }
+ }
+ else
+ throw e;
+
+ }
+ }
+ }
+
+ /*
+ * Convert an Openstack Exception on a Keystone call to an MsoException.
+ * This method supports both OpenstackResponseException and OpenStackConnectException.
+ */
+ protected MsoException keystoneErrorToMsoException (OpenStackBaseException e, String context) {
+ MsoException me = null;
+
+ if (e instanceof OpenStackResponseException) {
+ OpenStackResponseException re = (OpenStackResponseException) e;
+
+ try {
+ // Failed Keystone calls return an Error entity body.
+ Error error = re.getResponse ().getErrorEntity (Error.class);
+ logger.error (MessageEnum.RA_CONNECTION_EXCEPTION, "Openstack Keystone Error on " + context + ": " + error, "Openstack", "", MsoLogger.ErrorCode.DataError, "Openstack Keystone Error on " + context);
+ me = new MsoOpenstackException (error.getCode (), error.getTitle (), error.getMessage ());
+ } catch (Exception e2) {
+ // Can't parse the body as an "Error". Report the HTTP error
+ logger.error (MessageEnum.RA_CONNECTION_EXCEPTION, "HTTP Error on " + context + ": " + re.getStatus() + "," + re.getMessage(), "Openstack", "", MsoLogger.ErrorCode.DataError, "HTTP Error on " + context, e2);
+ me = new MsoOpenstackException (re.getStatus (), re.getMessage (), "");
+ }
+
+ // Add the context of the error
+ me.addContext (context);
+
+ // Generate an alarm for 5XX and higher errors.
+ if (re.getStatus () >= 500) {
+ alarmLogger.sendAlarm ("KeystoneError", MsoAlarmLogger.CRITICAL, me.getContextMessage ());
+ }
+ } else if (e instanceof OpenStackConnectException) {
+ OpenStackConnectException ce = (OpenStackConnectException) e;
+
+ me = new MsoIOException (ce.getMessage ());
+ me.addContext (context);
+
+ // Generate an alarm for all connection errors.
+ logger.error(MessageEnum.RA_GENERAL_EXCEPTION_ARG, "Openstack Keystone connection error on " + context + ": " + e, "Openstack", "", MsoLogger.ErrorCode.DataError, "Openstack Keystone connection error on " + context);
+ alarmLogger.sendAlarm ("KeystoneIOError", MsoAlarmLogger.CRITICAL, me.getContextMessage ());
+ }
+
+ return me;
+ }
+
+ /*
+ * Convert an Openstack Exception on a Heat call to an MsoOpenstackException.
+ * This method supports both OpenstackResponseException and OpenStackConnectException.
+ */
+ protected MsoException heatExceptionToMsoException (OpenStackBaseException e, String context) {
+ MsoException me = null;
+
+ if (e instanceof OpenStackResponseException) {
+ OpenStackResponseException re = (OpenStackResponseException) e;
+
+ try {
+ // Failed Heat calls return an Explanation entity body.
+ Explanation explanation = re.getResponse ().getErrorEntity (Explanation.class);
+ logger.error (MessageEnum.RA_CONNECTION_EXCEPTION, "OpenStack", "Openstack Error on " + context + ": " + explanation.toString(), "Openstack", "", MsoLogger.ErrorCode.DataError, "Exception - Openstack Error on " + context);
+ String fullError = explanation.getExplanation() + ", error.type=" + explanation.getError().getType() + ", error.message=" + explanation.getError().getMessage();
+ logger.debug(fullError);
+ me = new MsoOpenstackException (explanation.getCode (),
+ explanation.getTitle (),
+ //explanation.getExplanation ());
+ fullError);
+ } catch (Exception e2) {
+ // Couldn't parse the body as an "Explanation". Report the original HTTP error.
+ logger.error (MessageEnum.RA_CONNECTION_EXCEPTION, "OpenStack", "HTTP Error on " + context + ": " + re.getStatus() + "," + e.getMessage(), "Openstack", "", MsoLogger.ErrorCode.DataError, "Exception - HTTP Error on " + context, e2);
+ me = new MsoOpenstackException (re.getStatus (), re.getMessage (), "");
+ }
+
+ // Add the context of the error
+ me.addContext (context);
+
+ // Generate an alarm for 5XX and higher errors.
+ if (re.getStatus () >= 500) {
+ alarmLogger.sendAlarm ("HeatError", MsoAlarmLogger.CRITICAL, me.getContextMessage ());
+ }
+ } else if (e instanceof OpenStackConnectException) {
+ OpenStackConnectException ce = (OpenStackConnectException) e;
+
+ me = new MsoIOException (ce.getMessage ());
+ me.addContext (context);
+
+ // Generate an alarm for all connection errors.
+ alarmLogger.sendAlarm ("HeatIOError", MsoAlarmLogger.CRITICAL, me.getContextMessage ());
+ logger.error(MessageEnum.RA_CONNECTION_EXCEPTION, "OpenStack", "Openstack Heat connection error on " + context + ": " + e, "Openstack", "", MsoLogger.ErrorCode.DataError, "Openstack Heat connection error on " + context);
+ }
+
+ return me;
+ }
+
+ /*
+ * Convert an Openstack Exception on a Neutron call to an MsoOpenstackException.
+ * This method supports both OpenstackResponseException and OpenStackConnectException.
+ */
+ protected MsoException neutronExceptionToMsoException (OpenStackBaseException e, String context) {
+ MsoException me = null;
+
+ if (e instanceof OpenStackResponseException) {
+ OpenStackResponseException re = (OpenStackResponseException) e;
+
+ try {
+ // Failed Neutron calls return an NeutronError entity body
+ NeutronError error = re.getResponse ().getErrorEntity (NeutronError.class);
+ logger.error (MessageEnum.RA_CONNECTION_EXCEPTION, "OpenStack", "Openstack Neutron Error on " + context + ": " + error, "Openstack", "", MsoLogger.ErrorCode.DataError, "Openstack Neutron Error on " + context);
+ me = new MsoOpenstackException (re.getStatus (), error.getType (), error.getMessage ());
+ } catch (Exception e2) {
+ // Couldn't parse body as a NeutronError. Report the HTTP error.
+ logger.error (MessageEnum.RA_CONNECTION_EXCEPTION, "OpenStack", "HTTP Error on " + context + ": " + re.getStatus() + "," + e.getMessage(), "Openstack", "", MsoLogger.ErrorCode.DataError, "Openstack HTTP Error on " + context, e2);
+ me = new MsoOpenstackException (re.getStatus (), re.getMessage (), null);
+ }
+
+ // Add the context of the error
+ me.addContext (context);
+
+ // Generate an alarm for 5XX and higher errors.
+ if (re.getStatus () >= 500) {
+ alarmLogger.sendAlarm ("NeutronError", MsoAlarmLogger.CRITICAL, me.getContextMessage ());
+ }
+ } else if (e instanceof OpenStackConnectException) {
+ OpenStackConnectException ce = (OpenStackConnectException) e;
+
+ me = new MsoIOException (ce.getMessage ());
+ me.addContext (context);
+
+ // Generate an alarm for all connection errors.
+ alarmLogger.sendAlarm ("NeutronIOError", MsoAlarmLogger.CRITICAL, me.getContextMessage ());
+ logger.error(MessageEnum.RA_CONNECTION_EXCEPTION, "OpenStack", "Openstack Neutron Connection error on "+ context + ": " + e, "OpenStack", "", MsoLogger.ErrorCode.DataError, "Openstack Neutron Connection error on "+ context);
+ }
+
+ return me;
+ }
+
+ /*
+ * Convert a Java Runtime Exception to an MsoException.
+ * All Runtime exceptions will be translated into an MsoAdapterException,
+ * which captures internal errors.
+ * Alarms will be generated on all such exceptions.
+ */
+ protected MsoException runtimeExceptionToMsoException (RuntimeException e, String context) {
+ MsoAdapterException me = new MsoAdapterException (e.getMessage (), e);
+ me.addContext (context);
+ me.setCategory (MsoExceptionCategory.INTERNAL);
+
+ // Always generate an alarm for internal exceptions
+ logger.error(MessageEnum.RA_GENERAL_EXCEPTION_ARG, "An exception occured on "+ context + ": " + e, "OpenStack", "", MsoLogger.ErrorCode.DataError, "An exception occured on "+ context);
+ alarmLogger.sendAlarm ("AdapterInternalError", MsoAlarmLogger.CRITICAL, me.getContextMessage ());
+
+ return me;
+ }
+
+ protected MsoException ioExceptionToMsoException(IOException e, String context) {
+ MsoAdapterException me = new MsoAdapterException (e.getMessage (), e);
+ me.addContext (context);
+ me.setCategory (MsoExceptionCategory.INTERNAL);
+
+ // Always generate an alarm for internal exceptions
+ logger.error(MessageEnum.RA_GENERAL_EXCEPTION_ARG, "An exception occured on "+ context + ": " + e, "OpenStack", "", MsoLogger.ErrorCode.DataError, "An exception occured on "+ context);
+ alarmLogger.sendAlarm ("AdapterInternalError", MsoAlarmLogger.CRITICAL, me.getContextMessage ());
+
+ return me;
+ }
+
+ public boolean isNullOrEmpty (String s) {
+ return s == null || s.isEmpty();
+ }
+
+
+
+}
diff --git a/adapters/mso-adapter-utils/src/main/java/org/onap/so/openstack/utils/MsoHeatEnvironmentEntry.java b/adapters/mso-adapter-utils/src/main/java/org/onap/so/openstack/utils/MsoHeatEnvironmentEntry.java
new file mode 100644
index 0000000000..c95e62dad0
--- /dev/null
+++ b/adapters/mso-adapter-utils/src/main/java/org/onap/so/openstack/utils/MsoHeatEnvironmentEntry.java
@@ -0,0 +1,257 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP - SO
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * Copyright (C) 2017 Huawei Technologies Co., Ltd. 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.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.so.openstack.utils;
+
+
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.onap.so.db.catalog.beans.HeatTemplateParam;
+import org.onap.so.logger.MsoLogger;
+
+public class MsoHeatEnvironmentEntry {
+
+ private static final MsoLogger LOGGER = MsoLogger.getMsoLogger (MsoLogger.Catalog.RA, MsoHeatEnvironmentEntry.class);
+
+ private Set<MsoHeatEnvironmentParameter> parameters = null;
+ private Set<MsoHeatEnvironmentResource> resources = null;
+ private StringBuilder rawEntry = null;
+ private boolean valid = true;
+ private String errorString = null;
+ private StringBuilder resourceRegistryEntryRaw = null;
+
+ public MsoHeatEnvironmentEntry() {
+ super();
+ }
+
+ public MsoHeatEnvironmentEntry(StringBuilder sb) {
+ this();
+ this.rawEntry = sb;
+ this.processRawEntry();
+ }
+
+ private void processRawEntry() {
+ try {
+ if (this.rawEntry == null || "".equals(this.rawEntry.toString()))
+ return;
+ byte[] b = this.rawEntry.toString().getBytes();
+ MsoYamlEditorWithEnvt yaml = new MsoYamlEditorWithEnvt(b);
+ this.parameters = yaml.getParameterListFromEnvt();
+ //this.resources = yaml.getResourceListFromEnvt();
+ StringBuilder sb = this.getResourceRegistryRawEntry();
+ if (sb == null) {
+ this.resourceRegistryEntryRaw = new StringBuilder("");
+ } else {
+ this.resourceRegistryEntryRaw = sb;
+ }
+ } catch (Exception e) {
+ LOGGER.debug("Exception:", e);
+ this.valid = false;
+ this.errorString = e.getMessage();
+ //e.printStackTrace();
+ }
+ }
+
+ public boolean isValid() {
+ return this.valid;
+ }
+ public String getErrorString() {
+ return this.errorString;
+ }
+
+ public Set<MsoHeatEnvironmentParameter> getParameters() {
+ return this.parameters;
+ }
+ public Set<MsoHeatEnvironmentResource> getResources() {
+ return this.resources;
+ }
+ public void setParameters(Set<MsoHeatEnvironmentParameter> paramSet) {
+ if (paramSet == null) {
+ this.parameters = null;
+ } else {
+ this.parameters = paramSet;
+ }
+ }
+ public void setResources(Set<MsoHeatEnvironmentResource> resourceSet) {
+ if (resourceSet == null) {
+ this.resources = null;
+ } else {
+ this.resources = resourceSet;
+ }
+ }
+
+ public void addParameter(MsoHeatEnvironmentParameter hep) {
+ if (this.parameters == null) {
+ this.parameters = new HashSet<>();
+ }
+ this.parameters.add(hep);
+ }
+ public void addResource(MsoHeatEnvironmentResource her) {
+ if (this.resources == null) {
+ this.resources = new HashSet<>();
+ }
+ this.resources.add(her);
+ }
+
+ public int getNumberOfParameters() {
+ return this.parameters.size();
+ }
+ public int getNumberOfResources() {
+ return this.resources.size();
+ }
+
+ public boolean hasResources() {
+ if (this.resources != null && this.resources.size() > 0) {
+ return true;
+ }
+ return false;
+ }
+ public boolean hasParameters() {
+ if (this.parameters != null && this.parameters.size() > 0) {
+ return true;
+ }
+ return false;
+ }
+
+ public boolean containsParameter(String paramName) {
+ boolean contains = false;
+ if (this.parameters == null || this.parameters.size() < 1) {
+ return false;
+ }
+ if (this.parameters.contains(new MsoHeatEnvironmentParameter(paramName))) {
+ contains = true;
+ }
+ return contains;
+ }
+
+ public boolean containsParameter(String paramName, String paramAlias) {
+ if (this.containsParameter(paramName)) {
+ return true;
+ }
+ if (this.containsParameter(paramAlias)) {
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return "MsoHeatEnvironmentEntry{" + "parameters=" + parameters +
+ ", resourceRegistryEntryRaw='" + resourceRegistryEntryRaw + '\'' +
+ '}';
+ }
+
+ public StringBuilder toFullStringExcludeNonParams(Set<HeatTemplateParam> params) {
+ // Basically give back the envt - but exclude the params that aren't in the HeatTemplate
+
+ StringBuilder sb = new StringBuilder();
+ ArrayList<String> paramNameList = new ArrayList<String>(params.size());
+ for (HeatTemplateParam htp : params) {
+ paramNameList.add(htp.getParamName());
+ }
+
+ if (this.hasParameters()) {
+ sb.append("parameters:\n");
+ for (MsoHeatEnvironmentParameter hep : this.parameters) {
+ String paramName = hep.getName();
+ if (paramNameList.contains(paramName)) {
+ // This parameter *is* in the Heat Template - so include it:
+ sb.append(" " + hep.getName() + ": " + hep.getValue() + "\n");
+ // New - 1607 - if any of the params mapped badly - JUST RETURN THE ORIGINAL ENVT!
+ if (hep.getValue().startsWith("_BAD")) {
+ return this.rawEntry;
+ }
+ }
+ }
+ sb.append("\n");
+ }
+// if (this.hasResources()) {
+// sb.append("resource_registry:\n");
+// for (MsoHeatEnvironmentResource her : this.resources) {
+// sb.append(" \"" + her.getName() + "\": " + her.getValue() + "\n");
+// }
+// }
+ sb.append("\n");
+ sb.append(this.resourceRegistryEntryRaw);
+ return sb;
+ }
+
+ public StringBuilder toFullString() {
+ StringBuilder sb = new StringBuilder();
+
+ if (this.hasParameters()) {
+ sb.append("parameters:\n");
+ for (MsoHeatEnvironmentParameter hep : this.parameters) {
+ sb.append(" " + hep.getName() + ": " + hep.getValue() + "\n");
+ }
+ sb.append("\n");
+ }
+// if (this.hasResources()) {
+// sb.append("resource_registry:\n");
+// for (MsoHeatEnvironmentResource her : this.resources) {
+// sb.append(" \"" + her.getName() + "\": " + her.getValue() + "\n");
+// }
+// }
+ sb.append("\n");
+ sb.append(this.resourceRegistryEntryRaw);
+ return sb;
+ }
+
+ public StringBuilder getRawEntry() {
+ return this.rawEntry;
+ }
+
+ private StringBuilder getResourceRegistryRawEntry() {
+
+ if (this.rawEntry == null) {
+ return null;
+ }
+
+ StringBuilder sb = new StringBuilder();
+ int indexOf = this.rawEntry.indexOf("resource_registry:");
+ if (indexOf < 0) { // no resource_registry:
+ return null;
+ }
+ sb.append(this.rawEntry.substring(indexOf));
+ return sb;
+ }
+
+ public void setHPAParameters(StringBuilder hpasb) {
+ try {
+ MsoYamlEditorWithEnvt yaml = new MsoYamlEditorWithEnvt(hpasb.toString().getBytes());
+ Set<MsoHeatEnvironmentParameter> hpaParams = yaml.getParameterListFromEnvt();
+ for (MsoHeatEnvironmentParameter hpaparam : hpaParams) {
+ for (MsoHeatEnvironmentParameter param : this.parameters) {
+ if (param.getName() == hpaparam.getName()) {
+ param.setValue(hpaparam.getValue());
+ }
+ }
+ }
+ } catch (Exception e) {
+ LOGGER.debug("Exception:", e);
+ this.errorString = e.getMessage();
+ //e.printStackTrace();
+ }
+ }
+}
diff --git a/adapters/mso-adapter-utils/src/main/java/org/onap/so/openstack/utils/MsoHeatEnvironmentParameter.java b/adapters/mso-adapter-utils/src/main/java/org/onap/so/openstack/utils/MsoHeatEnvironmentParameter.java
new file mode 100644
index 0000000000..7e4c9d00c4
--- /dev/null
+++ b/adapters/mso-adapter-utils/src/main/java/org/onap/so/openstack/utils/MsoHeatEnvironmentParameter.java
@@ -0,0 +1,77 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP - SO
+ * ================================================================================
+ * Copyright (C) 2017 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.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.so.openstack.utils;
+
+import java.util.Objects;
+
+public class MsoHeatEnvironmentParameter {
+
+ private String name;
+ private String value;
+
+ public MsoHeatEnvironmentParameter(String name, String value) {
+ super();
+ this.name = name;
+ this.value = value;
+ }
+ public MsoHeatEnvironmentParameter(String name) {
+ // Allow to initialize with a null value
+ this(name, null);
+ }
+ public MsoHeatEnvironmentParameter() {
+ this(null, null);
+ }
+
+ public String getName() {
+ return this.name;
+ }
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getValue() {
+ return this.value;
+ }
+ public void setValue(String value) {
+ this.value = value;
+ }
+ public String toString() {
+ return this.name + ": " + this.value;
+ }
+
+ public boolean equals(Object o) {
+ if (!(o instanceof MsoHeatEnvironmentParameter)) {
+ return false;
+ }
+ if (this == o) {
+ return true;
+ }
+ MsoHeatEnvironmentParameter hep = (MsoHeatEnvironmentParameter) o;
+ // If the name of the parameter is the same, then they're equal
+ return hep.getName().equals(this.getName());
+ }
+
+ public int hashCode() {
+ return Objects.hashCode(this.name);
+ }
+
+
+}
diff --git a/adapters/mso-adapter-utils/src/main/java/org/onap/so/openstack/utils/MsoHeatEnvironmentResource.java b/adapters/mso-adapter-utils/src/main/java/org/onap/so/openstack/utils/MsoHeatEnvironmentResource.java
new file mode 100644
index 0000000000..c174b58f95
--- /dev/null
+++ b/adapters/mso-adapter-utils/src/main/java/org/onap/so/openstack/utils/MsoHeatEnvironmentResource.java
@@ -0,0 +1,96 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP - SO
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * Copyright (C) 2017 Huawei Technologies Co., Ltd. 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.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.so.openstack.utils;
+
+import org.onap.so.logger.MsoLogger;
+
+public class MsoHeatEnvironmentResource {
+
+ private static final MsoLogger LOGGER = MsoLogger.getMsoLogger (MsoLogger.Catalog.RA, MsoHeatEnvironmentResource.class);
+
+ private String name;
+ private String value;
+
+ public MsoHeatEnvironmentResource(String name, String value) {
+ super();
+ this.name = name;
+ this.value = value;
+ }
+ public MsoHeatEnvironmentResource(String name) {
+ // Allow to initialize with a null value
+ this(name, null);
+ }
+ public MsoHeatEnvironmentResource() {
+ this(null, null);
+ }
+
+ public String getName() {
+ return this.name;
+ }
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getValue() {
+ return this.value;
+ }
+ public void setValue(String value) {
+ this.value = value;
+ }
+
+ @Override
+ public String toString() {
+ return "\"" +
+ this.name +
+ "\": " +
+ this.value;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof MsoHeatEnvironmentResource)) {
+ return false;
+ }
+ if (this == o) {
+ return true;
+ }
+ MsoHeatEnvironmentResource her = (MsoHeatEnvironmentResource) o;
+ // If the name of the parameter is the same, then they're equal
+ if (her.getName().equals(this.getName())) {
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 0;
+ try {
+ result = this.name.hashCode();
+ } catch (Exception e) {
+ LOGGER.debug("Exception:", e);
+ }
+ return result;
+ }
+
+
+}
diff --git a/adapters/mso-adapter-utils/src/main/java/org/onap/so/openstack/utils/MsoHeatUtils.java b/adapters/mso-adapter-utils/src/main/java/org/onap/so/openstack/utils/MsoHeatUtils.java
new file mode 100644
index 0000000000..e5ece20cb7
--- /dev/null
+++ b/adapters/mso-adapter-utils/src/main/java/org/onap/so/openstack/utils/MsoHeatUtils.java
@@ -0,0 +1,1790 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP - SO
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * Copyright (C) 2017 Huawei Technologies Co., Ltd. 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.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.so.openstack.utils;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+import org.onap.so.adapters.vdu.CloudInfo;
+import org.onap.so.adapters.vdu.PluginAction;
+import org.onap.so.adapters.vdu.VduArtifact;
+import org.onap.so.adapters.vdu.VduArtifact.ArtifactType;
+import org.onap.so.adapters.vdu.VduException;
+import org.onap.so.adapters.vdu.VduInstance;
+import org.onap.so.adapters.vdu.VduModelInfo;
+import org.onap.so.adapters.vdu.VduPlugin;
+import org.onap.so.adapters.vdu.VduStateType;
+import org.onap.so.adapters.vdu.VduStatus;
+import org.onap.so.cloud.CloudConfig;
+import org.onap.so.cloud.CloudIdentity;
+import org.onap.so.cloud.CloudSite;
+import org.onap.so.cloud.authentication.AuthenticationMethodFactory;
+import org.onap.so.db.catalog.beans.HeatTemplate;
+import org.onap.so.db.catalog.beans.HeatTemplateParam;
+import org.onap.so.logger.MessageEnum;
+import org.onap.so.logger.MsoAlarmLogger;
+import org.onap.so.logger.MsoLogger;
+import org.onap.so.openstack.beans.HeatCacheEntry;
+import org.onap.so.openstack.beans.HeatStatus;
+import org.onap.so.openstack.beans.StackInfo;
+import org.onap.so.openstack.exceptions.MsoAdapterException;
+import org.onap.so.openstack.exceptions.MsoCloudSiteNotFound;
+import org.onap.so.openstack.exceptions.MsoException;
+import org.onap.so.openstack.exceptions.MsoIOException;
+import org.onap.so.openstack.exceptions.MsoOpenstackException;
+import org.onap.so.openstack.exceptions.MsoStackAlreadyExists;
+import org.onap.so.openstack.exceptions.MsoTenantNotFound;
+import org.onap.so.openstack.mappers.StackInfoMapper;
+import org.onap.so.utils.CryptoUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Primary;
+import org.springframework.core.env.Environment;
+import org.springframework.stereotype.Component;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.woorea.openstack.base.client.OpenStackConnectException;
+import com.woorea.openstack.base.client.OpenStackRequest;
+import com.woorea.openstack.base.client.OpenStackResponseException;
+import com.woorea.openstack.heat.Heat;
+import com.woorea.openstack.heat.model.CreateStackParam;
+import com.woorea.openstack.heat.model.Stack;
+import com.woorea.openstack.heat.model.Stack.Output;
+import com.woorea.openstack.heat.model.Stacks;
+import com.woorea.openstack.keystone.Keystone;
+import com.woorea.openstack.keystone.model.Access;
+import com.woorea.openstack.keystone.model.Authentication;
+import com.woorea.openstack.keystone.utils.KeystoneUtils;
+
+@Primary
+@Component
+public class MsoHeatUtils extends MsoCommonUtils implements VduPlugin{
+
+ private static final String TOKEN_AUTH = "TokenAuth";
+
+ private static final String QUERY_ALL_STACKS = "QueryAllStacks";
+
+ private static final String DELETE_STACK = "DeleteStack";
+
+ private static final String HEAT_ERROR = "HeatError";
+
+ private static final String CREATE_STACK = "CreateStack";
+
+ // Cache Heat Clients statically. Since there is just one MSO user, there is no
+ // benefit to re-authentication on every request (or across different flows). The
+ // token will be used until it expires.
+ //
+ // The cache key is "tenantId:cloudId"
+ private static Map <String, HeatCacheEntry> heatClientCache = new HashMap <> ();
+
+ // Fetch cloud configuration each time (may be cached in CloudConfig class)
+ @Autowired
+ protected CloudConfig cloudConfig;
+
+ @Autowired
+ private Environment environment;
+
+ @Autowired
+ private AuthenticationMethodFactory authenticationMethodFactory;
+
+ @Autowired
+ private MsoTenantUtilsFactory tenantUtilsFactory;
+
+ private static final MsoLogger LOGGER = MsoLogger.getMsoLogger (MsoLogger.Catalog.RA, MsoHeatUtils.class);
+
+ // Properties names and variables (with default values)
+ protected String createPollIntervalProp = "ecomp.mso.adapters.po.pollInterval";
+ private String deletePollIntervalProp = "ecomp.mso.adapters.po.pollInterval";
+ private String deletePollTimeoutProp = "ecomp.mso.adapters.po.pollTimeout";
+
+ protected static final String createPollIntervalDefault = "15";
+ private static final String deletePollIntervalDefault = "15";
+
+ private static final ObjectMapper JSON_MAPPER = new ObjectMapper();
+
+ /**
+ * keep this old method signature here to maintain backwards compatibility. keep others as well.
+ * this method does not include environment, files, or heatFiles
+ */
+ public StackInfo createStack (String cloudSiteId,
+ String tenantId,
+ String stackName,
+ String heatTemplate,
+ Map <String, ?> stackInputs,
+ boolean pollForCompletion,
+ int timeoutMinutes) throws MsoException {
+ // Just call the new method with the environment & files variable set to null
+ return this.createStack (cloudSiteId,
+ tenantId,
+ stackName,
+ heatTemplate,
+ stackInputs,
+ pollForCompletion,
+ timeoutMinutes,
+ null,
+ null,
+ null,
+ true);
+ }
+
+ // This method has environment, but not files or heatFiles
+ public StackInfo createStack (String cloudSiteId,
+ String tenantId,
+ String stackName,
+ String heatTemplate,
+ Map <String, ?> stackInputs,
+ boolean pollForCompletion,
+ int timeoutMinutes,
+ String environment) throws MsoException {
+ // Just call the new method with the files/heatFiles variables set to null
+ return this.createStack (cloudSiteId,
+ tenantId,
+ stackName,
+ heatTemplate,
+ stackInputs,
+ pollForCompletion,
+ timeoutMinutes,
+ environment,
+ null,
+ null,
+ true);
+ }
+
+ // This method has environment and files, but not heatFiles.
+ public StackInfo createStack (String cloudSiteId,
+ String tenantId,
+ String stackName,
+ String heatTemplate,
+ Map <String, ?> stackInputs,
+ boolean pollForCompletion,
+ int timeoutMinutes,
+ String environment,
+ Map <String, Object> files) throws MsoException {
+ return this.createStack (cloudSiteId,
+ tenantId,
+ stackName,
+ heatTemplate,
+ stackInputs,
+ pollForCompletion,
+ timeoutMinutes,
+ environment,
+ files,
+ null,
+ true);
+ }
+
+ // This method has environment, files, heatfiles
+ public StackInfo createStack (String cloudSiteId,
+ String tenantId,
+ String stackName,
+ String heatTemplate,
+ Map <String, ?> stackInputs,
+ boolean pollForCompletion,
+ int timeoutMinutes,
+ String environment,
+ Map <String, Object> files,
+ Map <String, Object> heatFiles) throws MsoException {
+ return this.createStack (cloudSiteId,
+ tenantId,
+ stackName,
+ heatTemplate,
+ stackInputs,
+ pollForCompletion,
+ timeoutMinutes,
+ environment,
+ files,
+ heatFiles,
+ true);
+ }
+
+ /**
+ * Create a new Stack in the specified cloud location and tenant. The Heat template
+ * and parameter map are passed in as arguments, along with the cloud access credentials.
+ * It is expected that parameters have been validated and contain at minimum the required
+ * parameters for the given template with no extra (undefined) parameters..
+ *
+ * The Stack name supplied by the caller must be unique in the scope of this tenant.
+ * However, it should also be globally unique, as it will be the identifier for the
+ * resource going forward in Inventory. This latter is managed by the higher levels
+ * invoking this function.
+ *
+ * The caller may choose to let this function poll Openstack for completion of the
+ * stack creation, or may handle polling itself via separate calls to query the status.
+ * In either case, a StackInfo object will be returned containing the current status.
+ * When polling is enabled, a status of CREATED is expected. When not polling, a
+ * status of BUILDING is expected.
+ *
+ * An error will be thrown if the requested Stack already exists in the specified
+ * Tenant and Cloud.
+ *
+ * For 1510 - add "environment", "files" (nested templates), and "heatFiles" (get_files) as
+ * parameters for createStack. If environment is non-null, it will be added to the stack.
+ * The nested templates and get_file entries both end up being added to the "files" on the
+ * stack. We must combine them before we add them to the stack if they're both non-null.
+ *
+ * @param cloudSiteId The cloud (may be a region) in which to create the stack.
+ * @param tenantId The Openstack ID of the tenant in which to create the Stack
+ * @param stackName The name of the stack to create
+ * @param heatTemplate The Heat template
+ * @param stackInputs A map of key/value inputs
+ * @param pollForCompletion Indicator that polling should be handled in Java vs. in the client
+ * @param environment An optional yaml-format string to specify environmental parameters
+ * @param files a Map<String, Object> that lists the child template IDs (file is the string, object is an int of
+ * Template id)
+ * @param heatFiles a Map<String, Object> that lists the get_file entries (fileName, fileBody)
+ * @param backout Donot delete stack on create Failure - defaulted to True
+ * @return A StackInfo object
+ * @throws MsoOpenstackException Thrown if the Openstack API call returns an exception.
+ */
+
+ @SuppressWarnings("unchecked")
+ public StackInfo createStack (String cloudSiteId,
+ String tenantId,
+ String stackName,
+ String heatTemplate,
+ Map <String, ?> stackInputs,
+ boolean pollForCompletion,
+ int timeoutMinutes,
+ String environment,
+ Map <String, Object> files,
+ Map <String, Object> heatFiles,
+ boolean backout) throws MsoException {
+ // Create local variables checking to see if we have an environment, nested, get_files
+ // Could later add some checks to see if it's valid.
+ boolean haveEnvtVariable = true;
+ if (environment == null || "".equalsIgnoreCase (environment.trim ())) {
+ haveEnvtVariable = false;
+ LOGGER.debug ("createStack called with no environment variable");
+ } else {
+ LOGGER.debug ("createStack called with an environment variable: " + environment);
+ }
+
+ boolean haveFiles = true;
+ if (files == null || files.isEmpty ()) {
+ haveFiles = false;
+ LOGGER.debug ("createStack called with no files / child template ids");
+ } else {
+ LOGGER.debug ("createStack called with " + files.size () + " files / child template ids");
+ }
+
+ boolean haveHeatFiles = true;
+ if (heatFiles == null || heatFiles.isEmpty ()) {
+ haveHeatFiles = false;
+ LOGGER.debug ("createStack called with no heatFiles");
+ } else {
+ LOGGER.debug ("createStack called with " + heatFiles.size () + " heatFiles");
+ }
+
+ // Obtain the cloud site information where we will create the stack
+ CloudSite cloudSite = cloudConfig.getCloudSite(cloudSiteId).orElseThrow(
+ () -> new MsoCloudSiteNotFound(cloudSiteId));
+ LOGGER.debug("Found: " + cloudSite.toString());
+ // Get a Heat client. They are cached between calls (keyed by tenantId:cloudId)
+ // This could throw MsoTenantNotFound or MsoOpenstackException (both propagated)
+ Heat heatClient = getHeatClient (cloudSite, tenantId);
+ if (heatClient != null) {
+ LOGGER.debug("Found: " + heatClient.toString());
+ }
+
+ LOGGER.debug ("Ready to Create Stack (" + heatTemplate + ") with input params: " + stackInputs);
+
+ //force entire stackInput object to generic Map<String, Object> for openstack compatibility
+ ObjectMapper mapper = new ObjectMapper();
+ Map<String, Object> normalized = new HashMap<>();
+ try {
+ normalized = mapper.readValue(mapper.writeValueAsString(stackInputs), new TypeReference<HashMap<String,Object>>() {});
+ } catch (IOException e1) {
+ LOGGER.debug("could not map json", e1);
+ }
+
+ // Build up the stack to create
+ // Disable auto-rollback, because error reason is lost. Always rollback in the code.
+ CreateStackParam stack = new CreateStackParam ();
+ stack.setStackName (stackName);
+ stack.setTimeoutMinutes (timeoutMinutes);
+ stack.setParameters (normalized);
+ stack.setTemplate (heatTemplate);
+ stack.setDisableRollback (true);
+ // TJM New for PO Adapter - add envt variable
+ if (haveEnvtVariable) {
+ LOGGER.debug ("Found an environment variable - value: " + environment);
+ stack.setEnvironment (environment);
+ }
+ // Now handle nested templates or get_files - have to combine if we have both
+ // as they're both treated as "files:" on the stack.
+ if (haveFiles && haveHeatFiles) {
+ // Let's do this here - not in the bean
+ LOGGER.debug ("Found files AND heatFiles - combine and add!");
+ Map <String, Object> combinedFiles = new HashMap <> ();
+ for (Entry<String, Object> entry : files.entrySet()) {
+ combinedFiles.put(entry.getKey(), entry.getValue());
+ }
+ for (Entry<String, Object> entry : heatFiles.entrySet()) {
+ combinedFiles.put(entry.getKey(), entry.getValue());
+ }
+ stack.setFiles (combinedFiles);
+ } else {
+ // Handle if we only have one or neither:
+ if (haveFiles) {
+ LOGGER.debug ("Found files - adding to stack");
+ stack.setFiles (files);
+ }
+ if (haveHeatFiles) {
+ LOGGER.debug ("Found heatFiles - adding to stack");
+ // the setFiles was modified to handle adding the entries
+ stack.setFiles (heatFiles);
+ }
+ }
+
+ // 1802 - attempt to add better formatted printout of request to openstack
+ try {
+ Map<String, Object> inputs = new HashMap<>();
+ for (Entry<String, ?> entry : stackInputs.entrySet()) {
+ if (entry.getValue() != null) {
+ inputs.put(entry.getKey(), entry.getValue());
+ }
+ }
+ LOGGER.debug(this.printStackRequest(tenantId, heatFiles, files, environment, inputs, stackName, heatTemplate, timeoutMinutes, backout, cloudSiteId));
+ } catch (Exception e) {
+ // that's okay - this is a nice-to-have
+ LOGGER.debug("(had an issue printing nicely formatted request to debuglog) " + e.getMessage());
+ }
+
+ Stack heatStack = null;
+ try {
+ // Execute the actual Openstack command to create the Heat stack
+ OpenStackRequest <Stack> request = heatClient.getStacks ().create (stack);
+ // Begin X-Auth-User
+ // Obtain an MSO token for the tenant
+ CloudIdentity cloudIdentity = cloudConfig.getIdentityService(cloudSite.getIdentityServiceId());
+ // cloudIdentity.getMsoId(), cloudIdentity.getMsoPass()
+ //req
+ request.header ("X-Auth-User", cloudIdentity.getMsoId ());
+ request.header ("X-Auth-Key", CryptoUtils.decryptCloudConfigPassword(cloudIdentity.getMsoPass ()));
+ LOGGER.debug ("headers added, about to executeAndRecordOpenstackRequest");
+ //LOGGER.debug(this.requestToStringBuilder(stack).toString());
+ // END - try to fix X-Auth-User
+ heatStack = executeAndRecordOpenstackRequest (request);
+ } catch (OpenStackResponseException e) {
+ // Since this came on the 'Create Stack' command, nothing was changed
+ // in the cloud. Return the error as an exception.
+ if (e.getStatus () == 409) {
+ // Stack already exists. Return a specific error for this case
+ MsoStackAlreadyExists me = new MsoStackAlreadyExists (stackName, tenantId, cloudSiteId);
+ me.addContext (CREATE_STACK);
+ throw me;
+ } else {
+ // Convert the OpenStackResponseException to an MsoOpenstackException
+ LOGGER.debug("ERROR STATUS = " + e.getStatus() + ",\n" + e.getMessage() + "\n" + e.getLocalizedMessage());
+ throw heatExceptionToMsoException (e, CREATE_STACK);
+ }
+ } catch (OpenStackConnectException e) {
+ // Error connecting to Openstack instance. Convert to an MsoException
+ throw heatExceptionToMsoException (e, CREATE_STACK);
+ } catch (RuntimeException e) {
+ // Catch-all
+ throw runtimeExceptionToMsoException (e, CREATE_STACK);
+ }
+
+ // Subsequent access by the canonical name "<stack name>/<stack-id>".
+ // Otherwise, simple query by name returns a 302 redirect.
+ // NOTE: This is specific to the v1 Orchestration API.
+ String canonicalName = stackName + "/" + heatStack.getId ();
+
+ // If client has requested a final response, poll for stack completion
+ if (pollForCompletion) {
+ // Set a time limit on overall polling.
+ // Use the resource (template) timeout for Openstack (expressed in minutes)
+ // and add one poll interval to give Openstack a chance to fail on its own.s
+
+ int createPollInterval = Integer.parseInt(this.environment.getProperty(createPollIntervalProp, createPollIntervalDefault));
+ int pollTimeout = (timeoutMinutes * 60) + createPollInterval;
+ // New 1610 - poll on delete if we rollback - use same values for now
+ int deletePollInterval = createPollInterval;
+ int deletePollTimeout = pollTimeout;
+ boolean createTimedOut = false;
+ StringBuilder stackErrorStatusReason = new StringBuilder("");
+ LOGGER.debug("createPollInterval=" + createPollInterval + ", pollTimeout=" + pollTimeout);
+
+ while (true) {
+ try {
+ heatStack = queryHeatStack (heatClient, canonicalName);
+ LOGGER.debug (heatStack.getStackStatus () + " (" + canonicalName + ")");
+ try {
+ LOGGER.debug("Current stack " + this.getOutputsAsStringBuilder(heatStack).toString());
+ } catch (Exception e) {
+ LOGGER.debug("an error occurred trying to print out the current outputs of the stack", e);
+ }
+
+ if ("CREATE_IN_PROGRESS".equals (heatStack.getStackStatus ())) {
+ // Stack creation is still running.
+ // Sleep and try again unless timeout has been reached
+ if (pollTimeout <= 0) {
+ // Note that this should not occur, since there is a timeout specified
+ // in the Openstack call.
+ LOGGER.error (MessageEnum.RA_CREATE_STACK_TIMEOUT, cloudSiteId, tenantId, stackName, heatStack.getStackStatus (), "", "", MsoLogger.ErrorCode.AvailabilityError, "Create stack timeout");
+ createTimedOut = true;
+ break;
+ }
+
+ sleep(createPollInterval * 1000L);
+
+ pollTimeout -= createPollInterval;
+ LOGGER.debug("pollTimeout remaining: " + pollTimeout);
+ } else {
+ //save off the status & reason msg before we attempt delete
+ stackErrorStatusReason.append("Stack error (" + heatStack.getStackStatus() + "): " + heatStack.getStackStatusReason());
+ break;
+ }
+ } catch (MsoException me) {
+ // Cannot query the stack status. Something is wrong.
+ // Try to roll back the stack
+ if (!backout)
+ {
+ LOGGER.warn(MessageEnum.RA_CREATE_STACK_ERR, "Create Stack errored, stack deletion suppressed", "", "", MsoLogger.ErrorCode.BusinessProcesssError, "Exception in Create Stack, stack deletion suppressed");
+ }
+ else
+ {
+ try {
+ LOGGER.debug("Create Stack error - unable to query for stack status - attempting to delete stack: " + canonicalName + " - This will likely fail and/or we won't be able to query to see if delete worked");
+ OpenStackRequest <Void> request = heatClient.getStacks ().deleteByName (canonicalName);
+ executeAndRecordOpenstackRequest (request);
+ // this may be a waste of time - if we just got an exception trying to query the stack - we'll just
+ // get another one, n'est-ce pas?
+ boolean deleted = false;
+ while (!deleted) {
+ try {
+ heatStack = queryHeatStack(heatClient, canonicalName);
+ if (heatStack != null) {
+ LOGGER.debug(heatStack.getStackStatus());
+ if ("DELETE_IN_PROGRESS".equals(heatStack.getStackStatus())) {
+ if (deletePollTimeout <= 0) {
+ LOGGER.error (MessageEnum.RA_CREATE_STACK_TIMEOUT, cloudSiteId, tenantId, stackName,
+ heatStack.getStackStatus (), "", "", MsoLogger.ErrorCode.AvailabilityError,
+ "Rollback: DELETE stack timeout");
+ break;
+ } else {
+ sleep(deletePollInterval * 1000L);
+ deletePollTimeout -= deletePollInterval;
+ }
+ } else if ("DELETE_COMPLETE".equals(heatStack.getStackStatus())){
+ LOGGER.debug("DELETE_COMPLETE for " + canonicalName);
+ deleted = true;
+ continue;
+ } else {
+ //got a status other than DELETE_IN_PROGRESS or DELETE_COMPLETE - so break and evaluate
+ break;
+ }
+ } else {
+ // assume if we can't find it - it's deleted
+ LOGGER.debug("heatStack returned null - assume the stack " + canonicalName + " has been deleted");
+ deleted = true;
+ continue;
+ }
+
+ } catch (Exception e3) {
+ // Just log this one. We will report the original exception.
+ LOGGER.error (MessageEnum.RA_CREATE_STACK_ERR, "Create Stack: Nested exception rolling back stack: " + e3, "", "", MsoLogger.ErrorCode.BusinessProcesssError, "Create Stack: Nested exception rolling back stack on error on query");
+
+ }
+ }
+ } catch (Exception e2) {
+ // Just log this one. We will report the original exception.
+ LOGGER.error (MessageEnum.RA_CREATE_STACK_ERR, "Create Stack: Nested exception rolling back stack: " + e2, "", "", MsoLogger.ErrorCode.BusinessProcesssError, "Create Stack: Nested exception rolling back stack");
+ }
+ }
+
+ // Propagate the original exception from Stack Query.
+ me.addContext (CREATE_STACK);
+ throw me;
+ }
+ }
+
+ if (!"CREATE_COMPLETE".equals (heatStack.getStackStatus ())) {
+ LOGGER.error (MessageEnum.RA_CREATE_STACK_ERR, "Create Stack error: Polling complete with non-success status: "
+ + heatStack.getStackStatus () + ", " + heatStack.getStackStatusReason (), "", "", MsoLogger.ErrorCode.BusinessProcesssError, "Create Stack error");
+
+ // Rollback the stack creation, since it is in an indeterminate state.
+ if (!backout)
+ {
+ LOGGER.warn(MessageEnum.RA_CREATE_STACK_ERR, "Create Stack errored, stack deletion suppressed", "", "", MsoLogger.ErrorCode.BusinessProcesssError, "Create Stack error, stack deletion suppressed");
+ }
+ else
+ {
+ try {
+ LOGGER.debug("Create Stack errored - attempting to DELETE stack: " + canonicalName);
+ LOGGER.debug("deletePollInterval=" + deletePollInterval + ", deletePollTimeout=" + deletePollTimeout);
+ OpenStackRequest <Void> request = heatClient.getStacks ().deleteByName (canonicalName);
+ executeAndRecordOpenstackRequest (request);
+ boolean deleted = false;
+ while (!deleted) {
+ try {
+ heatStack = queryHeatStack(heatClient, canonicalName);
+ if (heatStack != null) {
+ LOGGER.debug(heatStack.getStackStatus() + " (" + canonicalName + ")");
+ if ("DELETE_IN_PROGRESS".equals(heatStack.getStackStatus())) {
+ if (deletePollTimeout <= 0) {
+ LOGGER.error (MessageEnum.RA_CREATE_STACK_TIMEOUT, cloudSiteId, tenantId, stackName,
+ heatStack.getStackStatus (), "", "", MsoLogger.ErrorCode.AvailabilityError,
+ "Rollback: DELETE stack timeout");
+ break;
+ } else {
+ sleep(deletePollInterval * 1000L);
+ deletePollTimeout -= deletePollInterval;
+ LOGGER.debug("deletePollTimeout remaining: " + deletePollTimeout);
+ }
+ } else if ("DELETE_COMPLETE".equals(heatStack.getStackStatus())){
+ LOGGER.debug("DELETE_COMPLETE for " + canonicalName);
+ deleted = true;
+ continue;
+ } else if ("DELETE_FAILED".equals(heatStack.getStackStatus())) {
+ // Warn about this (?) - but still throw the original exception
+ LOGGER.warn(MessageEnum.RA_CREATE_STACK_ERR, "Create Stack errored, stack deletion FAILED", "", "", MsoLogger.ErrorCode.BusinessProcesssError, "Create Stack error, stack deletion FAILED");
+ LOGGER.debug("Stack deletion FAILED on a rollback of a create - " + canonicalName + ", status=" + heatStack.getStackStatus() + ", reason=" + heatStack.getStackStatusReason());
+ break;
+ } else {
+ //got a status other than DELETE_IN_PROGRESS or DELETE_COMPLETE - so break and evaluate
+ break;
+ }
+ } else {
+ // assume if we can't find it - it's deleted
+ LOGGER.debug("heatStack returned null - assume the stack " + canonicalName + " has been deleted");
+ deleted = true;
+ continue;
+ }
+
+ } catch (MsoException me2) {
+ // We got an exception on the delete - don't throw this exception - throw the original - just log.
+ LOGGER.debug("Exception thrown trying to delete " + canonicalName + " on a create->rollback: " + me2.getContextMessage(), me2);
+ LOGGER.warn(MessageEnum.RA_CREATE_STACK_ERR, "Create Stack errored, then stack deletion FAILED - exception thrown", "", "", MsoLogger.ErrorCode.BusinessProcesssError, me2.getContextMessage());
+ }
+
+ } // end while !deleted
+ StringBuilder errorContextMessage;
+ if (createTimedOut) {
+ errorContextMessage = new StringBuilder("Stack Creation Timeout");
+ } else {
+ errorContextMessage = stackErrorStatusReason;
+ }
+ if (deleted) {
+ errorContextMessage.append(" - stack successfully deleted");
+ } else {
+ errorContextMessage.append(" - encountered an error trying to delete the stack");
+ }
+// MsoOpenstackException me = new MsoOpenstackException(0, "", stackErrorStatusReason.toString());
+ // me.addContext(CREATE_STACK);
+ // alarmLogger.sendAlarm(HEAT_ERROR, MsoAlarmLogger.CRITICAL, me.getContextMessage());
+ // throw me;
+ } catch (Exception e2) {
+ // shouldn't happen - but handle
+ LOGGER.error (MessageEnum.RA_CREATE_STACK_ERR, "Create Stack: Nested exception rolling back stack: " + e2, "", "", MsoLogger.ErrorCode.BusinessProcesssError, "Exception in Create Stack: rolling back stack");
+ }
+ }
+ MsoOpenstackException me = new MsoOpenstackException(0, "", stackErrorStatusReason.toString());
+ me.addContext(CREATE_STACK);
+ alarmLogger.sendAlarm(HEAT_ERROR, MsoAlarmLogger.CRITICAL, me.getContextMessage());
+ throw me;
+ }
+
+ } else {
+ // Get initial status, since it will have been null after the create.
+ heatStack = queryHeatStack (heatClient, canonicalName);
+ LOGGER.debug (heatStack.getStackStatus ());
+ }
+
+ return new StackInfoMapper(heatStack).map();
+ }
+
+ /**
+ * Query for a single stack (by Name) in a tenant. This call will always return a
+ * StackInfo object. If the stack does not exist, an "empty" StackInfo will be
+ * returned - containing only the stack name and a status of NOTFOUND.
+ *
+ * @param tenantId The Openstack ID of the tenant in which to query
+ * @param cloudSiteId The cloud identifier (may be a region) in which to query
+ * @param stackName The name of the stack to query (may be simple or canonical)
+ * @return A StackInfo object
+ * @throws MsoOpenstackException Thrown if the Openstack API call returns an exception.
+ */
+ public StackInfo queryStack (String cloudSiteId, String tenantId, String stackName) throws MsoException {
+ LOGGER.debug ("Query HEAT stack: " + stackName + " in tenant " + tenantId);
+
+ // Obtain the cloud site information where we will create the stack
+ CloudSite cloudSite = cloudConfig.getCloudSite(cloudSiteId).orElseThrow(
+ () -> new MsoCloudSiteNotFound(cloudSiteId));
+ LOGGER.debug("Found: " + cloudSite.toString());
+
+ // Get a Heat client. They are cached between calls (keyed by tenantId:cloudId)
+ Heat heatClient = null;
+ try {
+ heatClient = getHeatClient (cloudSite, tenantId);
+ if (heatClient != null) {
+ LOGGER.debug("Found: " + heatClient.toString());
+ }
+ } catch (MsoTenantNotFound e) {
+ // Tenant doesn't exist, so stack doesn't either
+ LOGGER.debug ("Tenant with id " + tenantId + "not found.", e);
+ return new StackInfo (stackName, HeatStatus.NOTFOUND);
+ } catch (MsoException me) {
+ // Got an Openstack error. Propagate it
+ LOGGER.error (MessageEnum.RA_CONNECTION_EXCEPTION, "OpenStack", "Openstack Exception on Token request: " + me, "Openstack", "", MsoLogger.ErrorCode.AvailabilityError, "Connection Exception");
+ me.addContext ("QueryStack");
+ throw me;
+ }
+
+ // Query the Stack.
+ // An MsoException will propagate transparently to the caller.
+ Stack heatStack = queryHeatStack (heatClient, stackName);
+
+ if (heatStack == null) {
+ // Stack does not exist. Return a StackInfo with status NOTFOUND
+ return new StackInfo (stackName, HeatStatus.NOTFOUND);
+ }
+
+ return new StackInfoMapper(heatStack).map();
+ }
+
+ /**
+ * Delete a stack (by Name/ID) in a tenant. If the stack is not found, it will be
+ * considered a successful deletion. The return value is a StackInfo object which
+ * contains the current stack status.
+ *
+ * The client may choose to let the adapter poll Openstack for completion of the
+ * stack deletion, or may handle polling itself via separate query calls. In either
+ * case, a StackInfo object will be returned. When polling is enabled, a final
+ * status of NOTFOUND is expected. When not polling, a status of DELETING is expected.
+ *
+ * There is no rollback from a successful stack deletion. A deletion failure will
+ * also result in an undefined stack state - the components may or may not have been
+ * all or partially deleted, so the resulting stack must be considered invalid.
+ *
+ * @param tenantId The Openstack ID of the tenant in which to perform the delete
+ * @param cloudSiteId The cloud identifier (may be a region) from which to delete the stack.
+ * @param stackName The name/id of the stack to delete. May be simple or canonical
+ * @param pollForCompletion Indicator that polling should be handled in Java vs. in the client
+ * @return A StackInfo object
+ * @throws MsoOpenstackException Thrown if the Openstack API call returns an exception.
+ * @throws MsoCloudSiteNotFound
+ */
+ public StackInfo deleteStack (String tenantId,
+ String cloudSiteId,
+ String stackName,
+ boolean pollForCompletion) throws MsoException {
+ // Obtain the cloud site information where we will create the stack
+ CloudSite cloudSite = cloudConfig.getCloudSite(cloudSiteId).orElseThrow(
+ () -> new MsoCloudSiteNotFound(cloudSiteId));
+ LOGGER.debug("Found: " + cloudSite.toString());
+
+ // Get a Heat client. They are cached between calls (keyed by tenantId:cloudId)
+ Heat heatClient = null;
+ try {
+ heatClient = getHeatClient (cloudSite, tenantId);
+ if (heatClient != null) {
+ LOGGER.debug("Found: " + heatClient.toString());
+ }
+ } catch (MsoTenantNotFound e) {
+ // Tenant doesn't exist, so stack doesn't either
+ LOGGER.debug ("Tenant with id " + tenantId + "not found.", e);
+ return new StackInfo (stackName, HeatStatus.NOTFOUND);
+ } catch (MsoException me) {
+ // Got an Openstack error. Propagate it
+ LOGGER.error (MessageEnum.RA_CONNECTION_EXCEPTION, "Openstack", "Openstack Exception on Token request: " + me, "Openstack", "", MsoLogger.ErrorCode.AvailabilityError, "Connection Exception");
+ me.addContext (DELETE_STACK);
+ throw me;
+ }
+
+ // OK if stack not found, perform a query first
+ Stack heatStack = queryHeatStack (heatClient, stackName);
+ if (heatStack == null || "DELETE_COMPLETE".equals (heatStack.getStackStatus ())) {
+ // Not found. Return a StackInfo with status NOTFOUND
+ return new StackInfo (stackName, HeatStatus.NOTFOUND);
+ }
+
+ // Delete the stack.
+
+ // Use canonical name "<stack name>/<stack-id>" to delete.
+ // Otherwise, deletion by name returns a 302 redirect.
+ // NOTE: This is specific to the v1 Orchestration API.
+ String canonicalName = heatStack.getStackName () + "/" + heatStack.getId ();
+
+ try {
+ OpenStackRequest <Void> request = null;
+ if(null != heatClient) {
+ request = heatClient.getStacks ().deleteByName (canonicalName);
+ }
+ else {
+ LOGGER.debug ("Heat Client is NULL" );
+ }
+
+ executeAndRecordOpenstackRequest (request);
+ } catch (OpenStackResponseException e) {
+ if (e.getStatus () == 404) {
+ // Not found. We are OK with this. Return a StackInfo with status NOTFOUND
+ return new StackInfo (stackName, HeatStatus.NOTFOUND);
+ } else {
+ // Convert the OpenStackResponseException to an MsoOpenstackException
+ throw heatExceptionToMsoException (e, DELETE_STACK);
+ }
+ } catch (OpenStackConnectException e) {
+ // Error connecting to Openstack instance. Convert to an MsoException
+ throw heatExceptionToMsoException (e, DELETE_STACK);
+ } catch (RuntimeException e) {
+ // Catch-all
+ throw runtimeExceptionToMsoException (e, DELETE_STACK);
+ }
+
+ // Requery the stack for current status.
+ // It will probably still exist with "DELETE_IN_PROGRESS" status.
+ heatStack = queryHeatStack (heatClient, canonicalName);
+
+ if (pollForCompletion) {
+ // Set a timeout on polling
+
+ int pollInterval = Integer.parseInt(this.environment.getProperty(deletePollIntervalProp, "" + deletePollIntervalDefault));
+ int pollTimeout = Integer.parseInt(this.environment.getProperty(deletePollTimeoutProp, "" + deletePollIntervalDefault));
+
+ // When querying by canonical name, Openstack returns DELETE_COMPLETE status
+ // instead of "404" (which would result from query by stack name).
+ while (heatStack != null && !"DELETE_COMPLETE".equals (heatStack.getStackStatus ())) {
+ LOGGER.debug ("Stack status: " + heatStack.getStackStatus ());
+
+ if ("DELETE_FAILED".equals (heatStack.getStackStatus ())) {
+ // Throw a 'special case' of MsoOpenstackException to report the Heat status
+ String error = "Stack delete error (" + heatStack.getStackStatus ()
+ + "): "
+ + heatStack.getStackStatusReason ();
+ MsoOpenstackException me = new MsoOpenstackException (0, "", error);
+ me.addContext (DELETE_STACK);
+
+ // Alarm this condition, stack deletion failed
+ alarmLogger.sendAlarm (HEAT_ERROR, MsoAlarmLogger.CRITICAL, me.getContextMessage ());
+
+ throw me;
+ }
+
+ if (pollTimeout <= 0) {
+ LOGGER.error (MessageEnum.RA_DELETE_STACK_TIMEOUT, cloudSiteId, tenantId, stackName, heatStack.getStackStatus (), "", "", MsoLogger.ErrorCode.AvailabilityError, "Delete Stack Timeout");
+
+ // Throw a 'special case' of MsoOpenstackException to report the Heat status
+ MsoOpenstackException me = new MsoOpenstackException (0, "", "Stack Deletion Timeout");
+ me.addContext (DELETE_STACK);
+
+ // Alarm this condition, stack deletion failed
+ alarmLogger.sendAlarm (HEAT_ERROR, MsoAlarmLogger.CRITICAL, me.getContextMessage ());
+
+ throw me;
+ }
+
+ sleep(pollInterval * 1000L);
+
+ pollTimeout -= pollInterval;
+ LOGGER.debug("pollTimeout remaining: " + pollTimeout);
+
+ heatStack = queryHeatStack (heatClient, canonicalName);
+ }
+
+ // The stack is gone when this point is reached
+ return new StackInfo (stackName, HeatStatus.NOTFOUND);
+ }
+
+ // Return the current status (if not polling, the delete may still be in progress)
+ StackInfo stackInfo = new StackInfoMapper(heatStack).map();
+ stackInfo.setName (stackName);
+
+ return stackInfo;
+ }
+
+ /**
+ * Query for all stacks in a tenant site. This call will return a List of StackInfo
+ * objects, one for each deployed stack.
+ *
+ * Note that this is limited to a single site. To ensure that a tenant is truly
+ * empty would require looping across all tenant endpoints.
+ *
+ * @param tenantId The Openstack ID of the tenant to query
+ * @param cloudSiteId The cloud identifier (may be a region) in which to query.
+ * @return A List of StackInfo objects
+ * @throws MsoOpenstackException Thrown if the Openstack API call returns an exception.
+ * @throws MsoCloudSiteNotFound
+ */
+ public List <StackInfo> queryAllStacks (String tenantId, String cloudSiteId) throws MsoException {
+ // Obtain the cloud site information where we will create the stack
+ CloudSite cloudSite = cloudConfig.getCloudSite(cloudSiteId).orElseThrow(
+ () -> new MsoCloudSiteNotFound(cloudSiteId));
+ // Get a Heat client. They are cached between calls (keyed by tenantId:cloudId)
+ Heat heatClient = getHeatClient (cloudSite, tenantId);
+
+ try {
+ OpenStackRequest <Stacks> request = heatClient.getStacks ().list ();
+ Stacks stacks = executeAndRecordOpenstackRequest (request);
+
+ List <StackInfo> stackList = new ArrayList <> ();
+
+ // Not sure if returns an empty list or null if no stacks exist
+ if (stacks != null) {
+ for (Stack stack : stacks) {
+ stackList.add (new StackInfoMapper(stack).map());
+ }
+ }
+
+ return stackList;
+ } catch (OpenStackResponseException e) {
+ if (e.getStatus () == 404) {
+ // Not sure if this can happen, but return an empty list
+ LOGGER.debug ("queryAllStacks - stack not found: ");
+ return new ArrayList <> ();
+ } else {
+ // Convert the OpenStackResponseException to an MsoOpenstackException
+ throw heatExceptionToMsoException (e, QUERY_ALL_STACKS);
+ }
+ } catch (OpenStackConnectException e) {
+ // Error connecting to Openstack instance. Convert to an MsoException
+ throw heatExceptionToMsoException (e, QUERY_ALL_STACKS);
+ } catch (RuntimeException e) {
+ // Catch-all
+ throw runtimeExceptionToMsoException (e, QUERY_ALL_STACKS);
+ }
+ }
+
+ /**
+ * Validate parameters to be passed to Heat template. This method performs
+ * three functions:
+ * 1. Apply default values to parameters which have them defined
+ * 2. Report any required parameters that are missing. This will generate an
+ * exception in the caller, since stack create/update operations would fail.
+ * 3. Report and remove any extraneous parameters. This will allow clients to
+ * pass supersets of parameters and not get errors.
+ *
+ * These functions depend on the HeatTemplate definition from the MSO Catalog DB,
+ * along with the input parameter Map. The output is an updated parameter map.
+ * If the parameters are invalid for the template, an IllegalArgumentException
+ * is thrown.
+ */
+ public Map <String, Object> validateStackParams (Map <String, Object> inputParams,
+ HeatTemplate heatTemplate) {
+ // Check that required parameters have been supplied for this template type
+ StringBuilder missingParams = null;
+ List <String> paramList = new ArrayList <> ();
+
+ // TODO: Enhance DB to support defaults for Heat Template parameters
+
+ for (HeatTemplateParam parm : heatTemplate.getParameters ()) {
+ if (parm.isRequired () && !inputParams.containsKey (parm.getParamName ())) {
+ if (missingParams == null) {
+ missingParams = new StringBuilder(parm.getParamName());
+ } else {
+ missingParams.append("," + parm.getParamName());
+ }
+ }
+ paramList.add (parm.getParamName ());
+ }
+ if (missingParams != null) {
+ // Problem - missing one or more required parameters
+ String error = "Missing Required inputs for HEAT Template: " + missingParams;
+ LOGGER.error (MessageEnum.RA_MISSING_PARAM, missingParams + " for HEAT Template", "", "", MsoLogger.ErrorCode.SchemaError, "Missing Required inputs for HEAT Template: " + missingParams);
+ throw new IllegalArgumentException (error);
+ }
+
+ // Remove any extraneous parameters (don't throw an error)
+ Map <String, Object> updatedParams = new HashMap <> ();
+ List <String> extraParams = new ArrayList <> ();
+
+ for (Entry<String, Object> entry : inputParams.entrySet()) {
+ if (!paramList.contains(entry.getKey())) {
+ // This is not a valid parameter for this template
+ extraParams.add(entry.getKey());
+ } else {
+ updatedParams.put(entry.getKey(), entry.getValue());
+ }
+ }
+
+ if (!extraParams.isEmpty ()) {
+ LOGGER.warn (MessageEnum.RA_GENERAL_WARNING, "Heat Stack (" + heatTemplate.getTemplateName ()
+ + ") extra input params received: "
+ + extraParams, "", "", MsoLogger.ErrorCode.DataError, "Heat Stack (" + heatTemplate.getTemplateName () + ") extra input params received: "+ extraParams);
+ }
+
+ return updatedParams;
+ }
+
+ // ---------------------------------------------------------------
+ // PRIVATE FUNCTIONS FOR USE WITHIN THIS CLASS
+
+ /**
+ * Get a Heat client for the Openstack Identity service.
+ * This requires a 'member'-level userId + password, which will be retrieved from
+ * properties based on the specified cloud Id. The tenant in which to operate
+ * must also be provided.
+ * <p>
+ * On successful authentication, the Heat object will be cached for the
+ * tenantID + cloudId so that it can be reused without reauthenticating with
+ * Openstack every time.
+ *
+ * @return an authenticated Heat object
+ */
+ public Heat getHeatClient (CloudSite cloudSite, String tenantId) throws MsoException {
+ String cloudId = cloudConfig.getCloudSiteId(cloudSite);
+
+ // Check first in the cache of previously authorized clients
+ String cacheKey = cloudId + ":" + tenantId;
+ if (heatClientCache.containsKey (cacheKey)) {
+ if (!heatClientCache.get (cacheKey).isExpired ()) {
+ LOGGER.debug ("Using Cached HEAT Client for " + cacheKey);
+ return heatClientCache.get (cacheKey).getHeatClient ();
+ } else {
+ // Token is expired. Remove it from cache.
+ heatClientCache.remove (cacheKey);
+ LOGGER.debug ("Expired Cached HEAT Client for " + cacheKey);
+ }
+ }
+
+ // Obtain an MSO token for the tenant
+ CloudIdentity cloudIdentity = cloudConfig.getIdentityService(cloudSite.getIdentityServiceId());
+ LOGGER.debug("Found: " + cloudIdentity.toString());
+ MsoTenantUtils tenantUtils = tenantUtilsFactory.getTenantUtilsByServerType(cloudIdentity.getIdentityServerType());
+ String keystoneUrl = tenantUtils.getKeystoneUrl(cloudId, cloudIdentity);
+ LOGGER.debug("keystoneUrl=" + keystoneUrl);
+ Keystone keystoneTenantClient = new Keystone (keystoneUrl);
+ Access access = null;
+ try {
+ Authentication credentials = authenticationMethodFactory.getAuthenticationFor(cloudIdentity);
+
+ OpenStackRequest <Access> request = keystoneTenantClient.tokens ()
+ .authenticate (credentials).withTenantId (tenantId);
+
+ access = executeAndRecordOpenstackRequest (request);
+ } catch (OpenStackResponseException e) {
+ if (e.getStatus () == 401) {
+ // Authentication error.
+ String error = "Authentication Failure: tenant=" + tenantId + ",cloud=" + cloudIdentity.getId ();
+ alarmLogger.sendAlarm ("MsoAuthenticationError", MsoAlarmLogger.CRITICAL, error);
+ throw new MsoAdapterException (error);
+ } else {
+ throw keystoneErrorToMsoException (e, TOKEN_AUTH);
+ }
+ } catch (OpenStackConnectException e) {
+ // Connection to Openstack failed
+ MsoIOException me = new MsoIOException (e.getMessage (), e);
+ me.addContext (TOKEN_AUTH);
+ throw me;
+ } catch (RuntimeException e) {
+ // Catch-all
+ throw runtimeExceptionToMsoException (e, TOKEN_AUTH);
+ }
+
+ // For DCP/LCP, the region should be the cloudId.
+ String region = cloudSite.getRegionId ();
+ String heatUrl = null;
+ try {
+ // Isolate trying to printout the region IDs
+ try {
+ LOGGER.debug("access=" + access.toString());
+ for (Access.Service service : access.getServiceCatalog()) {
+ List<Access.Service.Endpoint> endpoints = service.getEndpoints();
+ for (Access.Service.Endpoint endpoint : endpoints) {
+ LOGGER.debug("AIC returned region=" + endpoint.getRegion());
+ }
+ }
+ } catch (Exception e) {
+ LOGGER.debug("Encountered an error trying to printout Access object returned from AIC. " + e.getMessage());
+ }
+ heatUrl = KeystoneUtils.findEndpointURL (access.getServiceCatalog (), "orchestration", region, "public");
+ LOGGER.debug("heatUrl=" + heatUrl + ", region=" + region);
+ } catch (RuntimeException e) {
+ // This comes back for not found (probably an incorrect region ID)
+ String error = "AIC did not match an orchestration service for: region=" + region + ",cloud=" + cloudIdentity.getIdentityUrl();
+ alarmLogger.sendAlarm ("MsoConfigurationError", MsoAlarmLogger.CRITICAL, error);
+ throw new MsoAdapterException (error, e);
+ }
+
+ Heat heatClient = new Heat (heatUrl);
+ heatClient.token (access.getToken ().getId ());
+
+ heatClientCache.put (cacheKey,
+ new HeatCacheEntry (heatUrl,
+ access.getToken ().getId (),
+ access.getToken ().getExpires ()));
+ LOGGER.debug ("Caching HEAT Client for " + cacheKey);
+
+ return heatClient;
+ }
+
+ /**
+ * Forcibly expire a HEAT client from the cache. This call is for use by
+ * the KeystoneClient in case where a tenant is deleted. In that case,
+ * all cached credentials must be purged so that fresh authentication is
+ * done if a similarly named tenant is re-created.
+ * <p>
+ * Note: This is probably only applicable to dev/test environments where
+ * the same Tenant Name is repeatedly used for creation/deletion.
+ * <p>
+ *
+ */
+ public void expireHeatClient (String tenantId, String cloudId) {
+ String cacheKey = cloudId + ":" + tenantId;
+ if (heatClientCache.containsKey (cacheKey)) {
+ heatClientCache.remove (cacheKey);
+ LOGGER.debug ("Deleted Cached HEAT Client for " + cacheKey);
+ }
+ }
+
+ /*
+ * Query for a Heat Stack. This function is needed in several places, so
+ * a common method is useful. This method takes an authenticated Heat Client
+ * (which internally identifies the cloud & tenant to search), and returns
+ * a Stack object if found, Null if not found, or an MsoOpenstackException
+ * if the Openstack API call fails.
+ *
+ * The stack name may be a simple name or a canonical name ("{name}/{id}").
+ * When simple name is used, Openstack always returns a 302 redirect which
+ * results in a 2nd request (to the canonical name). Note that query by
+ * canonical name for a deleted stack returns a Stack object with status
+ * "DELETE_COMPLETE" while query by simple name for a deleted stack returns
+ * HTTP 404.
+ *
+ * @param heatClient an authenticated Heat client
+ *
+ * @param stackName the stack name to query
+ *
+ * @return a Stack object that describes the current stack or null if the
+ * requested stack doesn't exist.
+ *
+ * @throws MsoOpenstackException Thrown if the Openstack API call returns an exception
+ */
+ protected Stack queryHeatStack (Heat heatClient, String stackName) throws MsoException {
+ if (stackName == null) {
+ return null;
+ }
+ try {
+ OpenStackRequest <Stack> request = heatClient.getStacks ().byName (stackName);
+ return executeAndRecordOpenstackRequest (request);
+ } catch (OpenStackResponseException e) {
+ if (e.getStatus () == 404) {
+ LOGGER.debug ("queryHeatStack - stack not found: " + stackName);
+ return null;
+ } else {
+ // Convert the OpenStackResponseException to an MsoOpenstackException
+ throw heatExceptionToMsoException (e, "QueryStack");
+ }
+ } catch (OpenStackConnectException e) {
+ // Connection to Openstack failed
+ throw heatExceptionToMsoException (e, "QueryAllStack");
+ }
+ }
+
+
+ public Map<String, Object> queryStackForOutputs(String cloudSiteId,
+ String tenantId, String stackName) throws MsoException {
+ LOGGER.debug("MsoHeatUtils.queryStackForOutputs)");
+ StackInfo heatStack = this.queryStack(cloudSiteId, tenantId, stackName);
+ if (heatStack == null || heatStack.getStatus() == HeatStatus.NOTFOUND) {
+ return null;
+ }
+ return heatStack.getOutputs();
+ }
+
+ public void copyStringOutputsToInputs(Map<String, String> inputs,
+ Map<String, Object> otherStackOutputs, boolean overWrite) {
+ if (inputs == null || otherStackOutputs == null)
+ return;
+ for (String key : otherStackOutputs.keySet()) {
+ if (!inputs.containsKey(key)) {
+ Object obj = otherStackOutputs.get(key);
+ if (obj instanceof String) {
+ inputs.put(key, (String) otherStackOutputs.get(key));
+ } else if (obj instanceof JsonNode ){
+ // This is a bit of mess - but I think it's the least impacting
+ // let's convert it BACK to a string - then it will get converted back later
+ try {
+ String str = this.convertNode((JsonNode) obj);
+ inputs.put(key, str);
+ } catch (Exception e) {
+ LOGGER.debug("DANGER WILL ROBINSON: unable to convert value for JsonNode "+ key, e);
+ //effect here is this value will not have been copied to the inputs - and therefore will error out downstream
+ }
+ } else if (obj instanceof java.util.LinkedHashMap) {
+ LOGGER.debug("LinkedHashMap - this is showing up as a LinkedHashMap instead of JsonNode");
+ try {
+ String str = JSON_MAPPER.writeValueAsString(obj);
+ inputs.put(key, str);
+ } catch (Exception e) {
+ LOGGER.debug("DANGER WILL ROBINSON: unable to convert value for LinkedHashMap "+ key, e);
+ }
+ } else if (obj instanceof Integer) {
+ try {
+ String str = "" + obj;
+ inputs.put(key, str);
+ } catch (Exception e) {
+ LOGGER.debug("DANGER WILL ROBINSON: unable to convert value for Integer "+ key, e);
+ }
+ } else {
+ try {
+ String str = obj.toString();
+ inputs.put(key, str);
+ } catch (Exception e) {
+ LOGGER.debug("DANGER WILL ROBINSON: unable to convert value for Other "+ key +" (" + e.getMessage() + ")", e);
+ //effect here is this value will not have been copied to the inputs - and therefore will error out downstream
+ }
+ }
+ }
+ }
+ return;
+ }
+ public StringBuilder requestToStringBuilder(CreateStackParam stack) {
+ StringBuilder sb = new StringBuilder();
+ sb.append("Stack:\n");
+ sb.append("\tStackName: " + stack.getStackName());
+ sb.append("\tTemplateUrl: " + stack.getTemplateUrl());
+ sb.append("\tTemplate: " + stack.getTemplate());
+ sb.append("\tEnvironment: " + stack.getEnvironment());
+ sb.append("\tTimeout: " + stack.getTimeoutMinutes());
+ sb.append("\tParameters:\n");
+ Map<String, Object> params = stack.getParameters();
+ if (params == null || params.size() < 1) {
+ sb.append("\nNONE");
+ } else {
+ for (String key : params.keySet()) {
+ if (params.get(key) instanceof String) {
+ sb.append("\n").append(key).append("=").append((String) params.get(key));
+ } else if (params.get(key) instanceof JsonNode) {
+ String jsonStringOut = this.convertNode((JsonNode)params.get(key));
+ sb.append("\n").append(key).append("=").append(jsonStringOut);
+ } else if (params.get(key) instanceof Integer) {
+ String integerOut = "" + params.get(key);
+ sb.append("\n").append(key).append("=").append(integerOut);
+
+ } else {
+ try {
+ String str = params.get(key).toString();
+ sb.append("\n").append(key).append("=").append(str);
+ } catch (Exception e) {
+ LOGGER.debug("Exception :",e);
+ }
+ }
+ }
+ }
+ return sb;
+ }
+
+ private String convertNode(final JsonNode node) {
+ try {
+ final Object obj = JSON_MAPPER.treeToValue(node, Object.class);
+ final String json = JSON_MAPPER.writeValueAsString(obj);
+ return json;
+ } catch (Exception e) {
+ LOGGER.debug("Error converting json to string " + e.getMessage(), e);
+ }
+ return "[Error converting json to string]";
+ }
+
+
+ private StringBuilder getOutputsAsStringBuilder(Stack heatStack) {
+ // This should only be used as a utility to print out the stack outputs
+ // to the log
+ StringBuilder sb = new StringBuilder("");
+ if (heatStack == null) {
+ sb.append("(heatStack is null)");
+ return sb;
+ }
+ List<Output> outputList = heatStack.getOutputs();
+ if (outputList == null || outputList.isEmpty()) {
+ sb.append("(outputs is empty)");
+ return sb;
+ }
+ Map<String, Object> outputs = new HashMap<>();
+ for (Output outputItem : outputList) {
+ outputs.put(outputItem.getOutputKey(), outputItem.getOutputValue());
+ }
+ int counter = 0;
+ sb.append("OUTPUTS:\n");
+ for (String key : outputs.keySet()) {
+ sb.append("outputs[").append(counter++).append("]: ").append(key).append("=");
+ Object obj = outputs.get(key);
+ if (obj instanceof String) {
+ sb.append((String) obj).append(" (a string)");
+ } else if (obj instanceof JsonNode) {
+ sb.append(this.convertNode((JsonNode) obj)).append(" (a JsonNode)");
+ } else if (obj instanceof java.util.LinkedHashMap) {
+ try {
+ String str = JSON_MAPPER.writeValueAsString(obj);
+ sb.append(str).append(" (a java.util.LinkedHashMap)");
+ } catch (Exception e) {
+ LOGGER.debug("Exception :",e);
+ sb.append("(a LinkedHashMap value that would not convert nicely)");
+ }
+ } else if (obj instanceof Integer) {
+ String str = "";
+ try {
+ str = obj.toString() + " (an Integer)\n";
+ } catch (Exception e) {
+ LOGGER.debug("Exception :",e);
+ str = "(an Integer unable to call .toString() on)";
+ }
+ sb.append(str);
+ } else if (obj instanceof ArrayList) {
+ String str = "";
+ try {
+ str = obj.toString() + " (an ArrayList)";
+ } catch (Exception e) {
+ LOGGER.debug("Exception :",e);
+ str = "(an ArrayList unable to call .toString() on?)";
+ }
+ sb.append(str);
+ } else if (obj instanceof Boolean) {
+ String str = "";
+ try {
+ str = obj.toString() + " (a Boolean)";
+ } catch (Exception e) {
+ LOGGER.debug("Exception :",e);
+ str = "(an Boolean unable to call .toString() on?)";
+ }
+ sb.append(str);
+ }
+ else {
+ String str = "";
+ try {
+ str = obj.toString() + " (unknown Object type)";
+ } catch (Exception e) {
+ LOGGER.debug("Exception :",e);
+ str = "(a value unable to call .toString() on?)";
+ }
+ sb.append(str);
+ }
+ sb.append("\n");
+ }
+ sb.append("[END]");
+ return sb;
+ }
+
+
+ public void copyBaseOutputsToInputs(Map<String, Object> inputs,
+ Map<String, Object> otherStackOutputs, List<String> paramNames, Map<String, String> aliases) {
+ if (inputs == null || otherStackOutputs == null)
+ return;
+ for (String key : otherStackOutputs.keySet()) {
+ if (paramNames != null) {
+ if (!paramNames.contains(key) && !aliases.containsKey(key)) {
+ LOGGER.debug("\tParameter " + key + " is NOT defined to be in the template - do not copy to inputs");
+ continue;
+ }
+ if (aliases.containsKey(key)) {
+ LOGGER.debug("Found an alias! Will move " + key + " to " + aliases.get(key));
+ Object obj = otherStackOutputs.get(key);
+ key = aliases.get(key);
+ otherStackOutputs.put(key, obj);
+ }
+ }
+ if (!inputs.containsKey(key)) {
+ Object obj = otherStackOutputs.get(key);
+ LOGGER.debug("\t**Adding " + key + " to inputs (.toString()=" + obj.toString());
+ if (obj instanceof String) {
+ LOGGER.debug("\t\t**A String");
+ inputs.put(key, obj);
+ } else if (obj instanceof Integer) {
+ LOGGER.debug("\t\t**An Integer");
+ inputs.put(key, obj);
+ } else if (obj instanceof JsonNode) {
+ LOGGER.debug("\t\t**A JsonNode");
+ inputs.put(key, obj);
+ } else if (obj instanceof Boolean) {
+ LOGGER.debug("\t\t**A Boolean");
+ inputs.put(key, obj);
+ } else if (obj instanceof java.util.LinkedHashMap) {
+ LOGGER.debug("\t\t**A java.util.LinkedHashMap **");
+ inputs.put(key, obj);
+ } else if (obj instanceof java.util.ArrayList) {
+ LOGGER.debug("\t\t**An ArrayList");
+ inputs.put(key, obj);
+ } else {
+ LOGGER.debug("\t\t**UNKNOWN OBJECT TYPE");
+ inputs.put(key, obj);
+ }
+ } else {
+ LOGGER.debug("key=" + key + " is already in the inputs - will not overwrite");
+ }
+ }
+ return;
+ }
+
+ public List<String> convertCdlToArrayList(String cdl) {
+ String cdl2 = cdl.trim();
+ String cdl3;
+ if (cdl2.startsWith("[") && cdl2.endsWith("]")) {
+ cdl3 = cdl2.substring(1, cdl2.lastIndexOf("]"));
+ } else {
+ cdl3 = cdl2;
+ }
+ return new ArrayList<>(Arrays.asList(cdl3.split(",")));
+ }
+
+ /**
+ * New with 1707 - this method will convert all the String *values* of the inputs
+ * to their "actual" object type (based on the param type: in the db - which comes from the template):
+ * (heat variable type) -> java Object type
+ * string -> String
+ * number -> Integer
+ * json -> JsonNode XXX Removed with MSO-1475 / 1802
+ * comma_delimited_list -> ArrayList
+ * boolean -> Boolean
+ * if any of the conversions should fail, we will default to adding it to the inputs
+ * as a string - see if Openstack can handle it.
+ * Also, will remove any params that are extra.
+ * Any aliases will be converted to their appropriate name (anyone use this feature?)
+ * @param inputs - the Map<String, String> of the inputs received on the request
+ * @param template the HeatTemplate object - this is so we can also verify if the param is valid for this template
+ * @return HashMap<String, Object> of the inputs, cleaned and converted
+ */
+ public Map<String, Object> convertInputMap(Map<String, String> inputs, HeatTemplate template) {
+ HashMap<String, Object> newInputs = new HashMap<>();
+ HashMap<String, HeatTemplateParam> params = new HashMap<>();
+ HashMap<String, HeatTemplateParam> paramAliases = new HashMap<>();
+
+ if (inputs == null) {
+ LOGGER.debug("convertInputMap - inputs is null - nothing to do here");
+ return new HashMap<>();
+ }
+
+ LOGGER.debug("convertInputMap in MsoHeatUtils called, with " + inputs.size() + " inputs, and template " + template.getArtifactUuid());
+ try {
+ LOGGER.debug(template.toString());
+ Set<HeatTemplateParam> paramSet = template.getParameters();
+ LOGGER.debug("paramSet has " + paramSet.size() + " entries");
+ } catch (Exception e) {
+ LOGGER.debug("Exception occurred in convertInputMap:" + e.getMessage(), e);
+ }
+
+ for (HeatTemplateParam htp : template.getParameters()) {
+ LOGGER.debug("Adding " + htp.getParamName());
+ params.put(htp.getParamName(), htp);
+ if (htp.getParamAlias() != null && !"".equals(htp.getParamAlias())) {
+ LOGGER.debug("\tFound ALIAS " + htp.getParamName() + "->" + htp.getParamAlias());
+ paramAliases.put(htp.getParamAlias(), htp);
+ }
+ }
+ LOGGER.debug("Now iterate through the inputs...");
+ for (String key : inputs.keySet()) {
+ LOGGER.debug("key=" + key);
+ boolean alias = false;
+ String realName = null;
+ if (!params.containsKey(key)) {
+ LOGGER.debug(key + " is not a parameter in the template! - check for an alias");
+ // add check here for an alias
+ if (!paramAliases.containsKey(key)) {
+ LOGGER.debug("The parameter " + key + " is in the inputs, but it's not a parameter for this template - omit");
+ continue;
+ } else {
+ alias = true;
+ realName = paramAliases.get(key).getParamName();
+ LOGGER.debug("FOUND AN ALIAS! Will use " + realName + " in lieu of give key/alias " + key);
+ }
+ }
+ String type = params.get(key).getParamType();
+ if (type == null || "".equals(type)) {
+ LOGGER.debug("**PARAM_TYPE is null/empty for " + key + ", will default to string");
+ type = "string";
+ }
+ LOGGER.debug("Parameter: " + key + " is of type " + type);
+ if ("string".equalsIgnoreCase(type)) {
+ // Easiest!
+ String str = inputs.get(key);
+ if (alias)
+ newInputs.put(realName, str);
+ else
+ newInputs.put(key, str);
+ } else if ("number".equalsIgnoreCase(type)) {
+ String integerString = inputs.get(key);
+ Integer anInteger = null;
+ try {
+ anInteger = Integer.parseInt(integerString);
+ } catch (Exception e) {
+ LOGGER.debug("Unable to convert " + integerString + " to an integer!!", e);
+ anInteger = null;
+ }
+ if (anInteger != null) {
+ if (alias)
+ newInputs.put(realName, anInteger);
+ else
+ newInputs.put(key, anInteger);
+ }
+ else {
+ if (alias)
+ newInputs.put(realName, integerString);
+ else
+ newInputs.put(key, integerString);
+ }
+ } else if ("json".equalsIgnoreCase(type)) {
+ // MSO-1475 - Leave this as a string now
+ String jsonString = inputs.get(key);
+ LOGGER.debug("Skipping conversion to jsonNode...");
+ if (alias)
+ newInputs.put(realName, jsonString);
+ else
+ newInputs.put(key, jsonString);
+ //}
+ } else if ("comma_delimited_list".equalsIgnoreCase(type)) {
+ String commaSeparated = inputs.get(key);
+ try {
+ List<String> anArrayList = this.convertCdlToArrayList(commaSeparated);
+ if (alias)
+ newInputs.put(realName, anArrayList);
+ else
+ newInputs.put(key, anArrayList);
+ } catch (Exception e) {
+ LOGGER.debug("Unable to convert " + commaSeparated + " to an ArrayList!!", e);
+ if (alias)
+ newInputs.put(realName, commaSeparated);
+ else
+ newInputs.put(key, commaSeparated);
+ }
+ } else if ("boolean".equalsIgnoreCase(type)) {
+ String booleanString = inputs.get(key);
+ Boolean aBool = Boolean.valueOf(booleanString);
+ if (alias)
+ newInputs.put(realName, aBool);
+ else
+ newInputs.put(key, aBool);
+ } else {
+ // it's null or something undefined - just add it back as a String
+ String str = inputs.get(key);
+ if (alias)
+ newInputs.put(realName, str);
+ else
+ newInputs.put(key, str);
+ }
+ }
+ return newInputs;
+ }
+
+ /*
+ * This helpful method added for Valet
+ */
+ public String getCloudSiteKeystoneUrl(String cloudSiteId) throws MsoCloudSiteNotFound {
+ String keystone_url = null;
+ try {
+ CloudSite cloudSite = cloudConfig.getCloudSite(cloudSiteId).orElseThrow(() -> new MsoCloudSiteNotFound(cloudSiteId));
+ CloudIdentity cloudIdentity = cloudConfig.getIdentityService(cloudSite.getIdentityServiceId());
+ keystone_url = cloudIdentity.getIdentityUrl();
+ } catch (Exception e) {
+ throw new MsoCloudSiteNotFound(cloudSiteId);
+ }
+ if (keystone_url == null || keystone_url.isEmpty()) {
+ throw new MsoCloudSiteNotFound(cloudSiteId);
+ }
+ return keystone_url;
+ }
+
+ /*
+ * Create a string suitable for being dumped to a debug log that creates a
+ * pseudo-JSON request dumping what's being sent to Openstack API in the create or update request
+ */
+
+ private String printStackRequest(String tenantId,
+ Map<String, Object> heatFiles,
+ Map<String, Object> nestedTemplates,
+ String environment,
+ Map<String, Object> inputs,
+ String vfModuleName,
+ String template,
+ int timeoutMinutes,
+ boolean backout,
+ String cloudSiteId) {
+ StringBuilder sb = new StringBuilder();
+ sb.append("CREATE STACK REQUEST (formatted for readability)\n");
+ sb.append("tenant=" + tenantId + ", cloud=" + cloudSiteId);
+ sb.append("{\n");
+ sb.append(" \"stack_name\": \"" + vfModuleName + "\",\n");
+ sb.append(" \"disable_rollback\": " + backout + ",\n");
+ sb.append(" \"timeout_mins\": " + timeoutMinutes + ",\n");
+ sb.append(" \"template\": {\n");
+ sb.append(template);
+ sb.append(" },\n");
+ sb.append(" \"environment\": {\n");
+ if (environment == null)
+ sb.append("<none>");
+ else
+ sb.append(environment);
+ sb.append(" },\n");
+ sb.append(" \"files\": {\n");
+ int filesCounter = 0;
+ if (heatFiles != null) {
+ for (String key : heatFiles.keySet()) {
+ filesCounter++;
+ if (filesCounter > 1) {
+ sb.append(",\n");
+ }
+ sb.append(" \"" + key + "\": {\n");
+ sb.append(heatFiles.get(key).toString() + "\n }");
+ }
+ }
+ if (nestedTemplates != null) {
+ for (String key : nestedTemplates.keySet()) {
+ filesCounter++;
+ if (filesCounter > 1) {
+ sb.append(",\n");
+ }
+ sb.append(" \"" + key + "\": {\n");
+ sb.append(nestedTemplates.get(key).toString() + "\n }");
+ }
+ }
+ sb.append("\n },\n");
+ sb.append(" \"parameters\": {\n");
+ int paramCounter = 0;
+ for (String name : inputs.keySet()) {
+ paramCounter++;
+ if (paramCounter > 1) {
+ sb.append(",\n");
+ }
+ Object o = inputs.get(name);
+ if (o instanceof java.lang.String) {
+ sb.append(" \"" + name + "\": \"" + inputs.get(name).toString() + "\"");
+ } else if (o instanceof Integer) {
+ sb.append(" \"" + name + "\": " + inputs.get(name).toString() );
+ } else if (o instanceof ArrayList) {
+ sb.append(" \"" + name + "\": " + inputs.get(name).toString() );
+ } else if (o instanceof Boolean) {
+ sb.append(" \"" + name + "\": " + inputs.get(name).toString() );
+ } else {
+ sb.append(" \"" + name + "\": " + "\"(there was an issue trying to dump this value...)\"" );
+ }
+ }
+ sb.append("\n }\n}\n");
+
+ return sb.toString();
+ }
+
+ /*******************************************************************************
+ *
+ * Methods (and associated utilities) to implement the VduPlugin interface
+ *
+ *******************************************************************************/
+
+ /**
+ * VduPlugin interface for instantiate function.
+ *
+ * Translate the VduPlugin parameters to the corresponding 'createStack' parameters,
+ * and then invoke the existing function.
+ */
+ @Override
+ public VduInstance instantiateVdu (
+ CloudInfo cloudInfo,
+ String instanceName,
+ Map<String,Object> inputs,
+ VduModelInfo vduModel,
+ boolean rollbackOnFailure)
+ throws VduException
+ {
+ String cloudSiteId = cloudInfo.getCloudSiteId();
+ String tenantId = cloudInfo.getTenantId();
+
+ // Translate the VDU ModelInformation structure to that which is needed for
+ // creating the Heat stack. Loop through the artifacts, looking specifically
+ // for MAIN_TEMPLATE and ENVIRONMENT. Any other artifact will
+ // be attached as a FILE.
+ String heatTemplate = null;
+ Map<String,Object> nestedTemplates = new HashMap<>();
+ Map<String,Object> files = new HashMap<>();
+ String heatEnvironment = null;
+
+ for (VduArtifact vduArtifact: vduModel.getArtifacts()) {
+ if (vduArtifact.getType() == ArtifactType.MAIN_TEMPLATE) {
+ heatTemplate = new String(vduArtifact.getContent());
+ }
+ else if (vduArtifact.getType() == ArtifactType.NESTED_TEMPLATE) {
+ nestedTemplates.put(vduArtifact.getName(), new String(vduArtifact.getContent()));
+ }
+ else if (vduArtifact.getType() == ArtifactType.ENVIRONMENT) {
+ heatEnvironment = new String(vduArtifact.getContent());
+ }
+ }
+
+ try {
+ StackInfo stackInfo = createStack (cloudSiteId,
+ tenantId,
+ instanceName,
+ heatTemplate,
+ inputs,
+ true, // poll for completion
+ vduModel.getTimeoutMinutes(),
+ heatEnvironment,
+ nestedTemplates,
+ files,
+ rollbackOnFailure);
+
+ // Populate a vduInstance from the StackInfo
+ return stackInfoToVduInstance(stackInfo);
+ }
+ catch (Exception e) {
+ throw new VduException ("MsoHeatUtils (instantiateVDU): createStack Exception", e);
+ }
+ }
+
+
+ /**
+ * VduPlugin interface for query function.
+ */
+ @Override
+ public VduInstance queryVdu (CloudInfo cloudInfo, String instanceId)
+ throws VduException
+ {
+ String cloudSiteId = cloudInfo.getCloudSiteId();
+ String tenantId = cloudInfo.getTenantId();
+
+ try {
+ // Query the Cloudify Deployment object and populate a VduInstance
+ StackInfo stackInfo = queryStack (cloudSiteId, tenantId, instanceId);
+
+ return stackInfoToVduInstance(stackInfo);
+ }
+ catch (Exception e) {
+ throw new VduException ("MsoHeatUtile (queryVdu): queryStack Exception ", e);
+ }
+ }
+
+
+ /**
+ * VduPlugin interface for delete function.
+ */
+ @Override
+ public VduInstance deleteVdu (CloudInfo cloudInfo, String instanceId, int timeoutMinutes)
+ throws VduException
+ {
+ String cloudSiteId = cloudInfo.getCloudSiteId();
+ String tenantId = cloudInfo.getTenantId();
+
+ try {
+ // Delete the Heat stack
+ StackInfo stackInfo = deleteStack (tenantId, cloudSiteId, instanceId, true);
+
+ // Populate a VduInstance based on the deleted Cloudify Deployment object
+ VduInstance vduInstance = stackInfoToVduInstance(stackInfo);
+
+ // Override return state to DELETED (HeatUtils sets to NOTFOUND)
+ vduInstance.getStatus().setState(VduStateType.DELETED);
+
+ return vduInstance;
+ }
+ catch (Exception e) {
+ throw new VduException ("Delete VDU Exception", e);
+ }
+ }
+
+
+ /**
+ * VduPlugin interface for update function.
+ *
+ * Update is currently not supported in the MsoHeatUtils implementation of VduPlugin.
+ * Just return a VduException.
+ *
+ */
+ @Override
+ public VduInstance updateVdu (
+ CloudInfo cloudInfo,
+ String instanceId,
+ Map<String,Object> inputs,
+ VduModelInfo vduModel,
+ boolean rollbackOnFailure)
+ throws VduException
+ {
+ throw new VduException ("MsoHeatUtils: updateVdu interface not supported");
+ }
+
+
+ /*
+ * Convert the local DeploymentInfo object (Cloudify-specific) to a generic VduInstance object
+ */
+ private VduInstance stackInfoToVduInstance (StackInfo stackInfo)
+ {
+ VduInstance vduInstance = new VduInstance();
+
+ // The full canonical name as the instance UUID
+ vduInstance.setVduInstanceId(stackInfo.getCanonicalName());
+ vduInstance.setVduInstanceName(stackInfo.getName());
+
+ // Copy inputs and outputs
+ vduInstance.setInputs(stackInfo.getParameters());
+ vduInstance.setOutputs(stackInfo.getOutputs());
+
+ // Translate the status elements
+ vduInstance.setStatus(stackStatusToVduStatus (stackInfo));
+
+ return vduInstance;
+ }
+
+ private VduStatus stackStatusToVduStatus (StackInfo stackInfo)
+ {
+ VduStatus vduStatus = new VduStatus();
+
+ // Map the status fields to more generic VduStatus.
+ // There are lots of HeatStatus values, so this is a bit long...
+ HeatStatus heatStatus = stackInfo.getStatus();
+ String statusMessage = stackInfo.getStatusMessage();
+
+ if (heatStatus == HeatStatus.INIT || heatStatus == HeatStatus.BUILDING) {
+ vduStatus.setState(VduStateType.INSTANTIATING);
+ vduStatus.setLastAction((new PluginAction ("create", "in_progress", statusMessage)));
+ }
+ else if (heatStatus == HeatStatus.NOTFOUND) {
+ vduStatus.setState(VduStateType.NOTFOUND);
+ }
+ else if (heatStatus == HeatStatus.CREATED) {
+ vduStatus.setState(VduStateType.INSTANTIATED);
+ vduStatus.setLastAction((new PluginAction ("create", "complete", statusMessage)));
+ }
+ else if (heatStatus == HeatStatus.UPDATED) {
+ vduStatus.setState(VduStateType.INSTANTIATED);
+ vduStatus.setLastAction((new PluginAction ("update", "complete", statusMessage)));
+ }
+ else if (heatStatus == HeatStatus.UPDATING) {
+ vduStatus.setState(VduStateType.UPDATING);
+ vduStatus.setLastAction((new PluginAction ("update", "in_progress", statusMessage)));
+ }
+ else if (heatStatus == HeatStatus.DELETING) {
+ vduStatus.setState(VduStateType.DELETING);
+ vduStatus.setLastAction((new PluginAction ("delete", "in_progress", statusMessage)));
+ }
+ else if (heatStatus == HeatStatus.FAILED) {
+ vduStatus.setState(VduStateType.FAILED);
+ vduStatus.setErrorMessage(stackInfo.getStatusMessage());
+ } else {
+ vduStatus.setState(VduStateType.UNKNOWN);
+ }
+
+ return vduStatus;
+ }
+
+ private void sleep(long time) {
+ try {
+ Thread.sleep(time);
+ } catch (InterruptedException e) {
+ LOGGER.debug ("Thread interrupted while sleeping", e);
+ Thread.currentThread().interrupt();
+ }
+ }
+
+}
diff --git a/adapters/mso-adapter-utils/src/main/java/org/onap/so/openstack/utils/MsoHeatUtilsWithUpdate.java b/adapters/mso-adapter-utils/src/main/java/org/onap/so/openstack/utils/MsoHeatUtilsWithUpdate.java
new file mode 100644
index 0000000000..0b3f9dfe17
--- /dev/null
+++ b/adapters/mso-adapter-utils/src/main/java/org/onap/so/openstack/utils/MsoHeatUtilsWithUpdate.java
@@ -0,0 +1,438 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP - SO
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * Copyright (C) 2017 Huawei Technologies Co., Ltd. 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.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.so.openstack.utils;
+
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.onap.so.cloud.CloudConfig;
+import org.onap.so.cloud.CloudSite;
+import org.onap.so.logger.MessageEnum;
+import org.onap.so.logger.MsoLogger;
+import org.onap.so.openstack.beans.StackInfo;
+import org.onap.so.openstack.exceptions.MsoCloudSiteNotFound;
+import org.onap.so.openstack.exceptions.MsoException;
+import org.onap.so.openstack.exceptions.MsoOpenstackException;
+import org.onap.so.openstack.exceptions.MsoStackNotFound;
+import org.onap.so.openstack.mappers.StackInfoMapper;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.env.Environment;
+import org.springframework.stereotype.Component;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.woorea.openstack.base.client.OpenStackBaseException;
+import com.woorea.openstack.base.client.OpenStackRequest;
+import com.woorea.openstack.heat.Heat;
+import com.woorea.openstack.heat.model.Stack;
+import com.woorea.openstack.heat.model.Stack.Output;
+import com.woorea.openstack.heat.model.UpdateStackParam;
+
+@Component
+public class MsoHeatUtilsWithUpdate extends MsoHeatUtils {
+
+ private static final String UPDATE_STACK = "UpdateStack";
+ private static final MsoLogger LOGGER = MsoLogger.getMsoLogger (MsoLogger.Catalog.RA, MsoHeatUtilsWithUpdate.class);
+
+ private static final ObjectMapper JSON_MAPPER = new ObjectMapper();
+
+ @Autowired
+ private Environment environment;
+ /*
+ * Keep these methods around for backward compatibility
+ */
+
+ public StackInfo updateStack (String cloudSiteId,
+ String tenantId,
+ String stackName,
+ String heatTemplate,
+ Map <String, Object> stackInputs,
+ boolean pollForCompletion,
+ int timeoutMinutes) throws MsoException {
+ // Keeping this method to allow compatibility with no environment or files variable sent. In this case,
+ // simply return the new method with the environment variable set to null.
+ return this.updateStack (cloudSiteId,
+ tenantId,
+ stackName,
+ heatTemplate,
+ stackInputs,
+ pollForCompletion,
+ timeoutMinutes,
+ null,
+ null,
+ null);
+ }
+
+ public StackInfo updateStack (String cloudSiteId,
+ String tenantId,
+ String stackName,
+ String heatTemplate,
+ Map <String, Object> stackInputs,
+ boolean pollForCompletion,
+ int timeoutMinutes,
+ String environment) throws MsoException {
+ // Keeping this method to allow compatibility with no environment variable sent. In this case,
+ // simply return the new method with the files variable set to null.
+ return this.updateStack (cloudSiteId,
+ tenantId,
+ stackName,
+ heatTemplate,
+ stackInputs,
+ pollForCompletion,
+ timeoutMinutes,
+ environment,
+ null,
+ null);
+ }
+
+ public StackInfo updateStack (String cloudSiteId,
+ String tenantId,
+ String stackName,
+ String heatTemplate,
+ Map <String, Object> stackInputs,
+ boolean pollForCompletion,
+ int timeoutMinutes,
+ String environment,
+ Map <String, Object> files) throws MsoException {
+ return this.updateStack (cloudSiteId,
+ tenantId,
+ stackName,
+ heatTemplate,
+ stackInputs,
+ pollForCompletion,
+ timeoutMinutes,
+ environment,
+ files,
+ null);
+ }
+
+ /**
+ * Update a Stack in the specified cloud location and tenant. The Heat template
+ * and parameter map are passed in as arguments, along with the cloud access credentials.
+ * It is expected that parameters have been validated and contain at minimum the required
+ * parameters for the given template with no extra (undefined) parameters..
+ *
+ * The Stack name supplied by the caller must be unique in the scope of this tenant.
+ * However, it should also be globally unique, as it will be the identifier for the
+ * resource going forward in Inventory. This latter is managed by the higher levels
+ * invoking this function.
+ *
+ * The caller may choose to let this function poll Openstack for completion of the
+ * stack creation, or may handle polling itself via separate calls to query the status.
+ * In either case, a StackInfo object will be returned containing the current status.
+ * When polling is enabled, a status of CREATED is expected. When not polling, a
+ * status of BUILDING is expected.
+ *
+ * An error will be thrown if the requested Stack already exists in the specified
+ * Tenant and Cloud.
+ *
+ * @param tenantId The Openstack ID of the tenant in which to create the Stack
+ * @param cloudSiteId The cloud identifier (may be a region) in which to create the tenant.
+ * @param stackName The name of the stack to update
+ * @param heatTemplate The Heat template
+ * @param stackInputs A map of key/value inputs
+ * @param pollForCompletion Indicator that polling should be handled in Java vs. in the client
+ * @param environment An optional yaml-format string to specify environmental parameters
+ * @param files a Map<String, Object> for listing child template IDs
+ * @param heatFiles a Map<String, Object> for listing get_file entries (fileName, fileBody)
+ * @return A StackInfo object
+ * @throws MsoException Thrown if the Openstack API call returns an exception.
+ */
+
+ public StackInfo updateStack (String cloudSiteId,
+ String tenantId,
+ String stackName,
+ String heatTemplate,
+ Map <String, Object> stackInputs,
+ boolean pollForCompletion,
+ int timeoutMinutes,
+ String environment,
+ Map <String, Object> files,
+ Map <String, Object> heatFiles) throws MsoException {
+ boolean heatEnvtVariable = true;
+ if (environment == null || "".equalsIgnoreCase (environment.trim ())) {
+ heatEnvtVariable = false;
+ }
+ boolean haveFiles = true;
+ if (files == null || files.isEmpty ()) {
+ haveFiles = false;
+ }
+ boolean haveHeatFiles = true;
+ if (heatFiles == null || heatFiles.isEmpty ()) {
+ haveHeatFiles = false;
+ }
+
+ // Obtain the cloud site information where we will create the stack
+ CloudSite cloudSite = cloudConfig.getCloudSite(cloudSiteId).orElseThrow(
+ () -> new MsoCloudSiteNotFound(cloudSiteId));
+ // Get a Heat client. They are cached between calls (keyed by tenantId:cloudId)
+ // This could throw MsoTenantNotFound or MsoOpenstackException (both propagated)
+ Heat heatClient = getHeatClient (cloudSite, tenantId);
+
+ // Perform a query first to get the current status
+ Stack heatStack = queryHeatStack (heatClient, stackName);
+ if (heatStack == null || "DELETE_COMPLETE".equals (heatStack.getStackStatus ())) {
+ // Not found. Return a StackInfo with status NOTFOUND
+ throw new MsoStackNotFound (stackName, tenantId, cloudSiteId);
+ }
+
+ // Use canonical name "<stack name>/<stack-id>" to update the stack.
+ // Otherwise, update by name returns a 302 redirect.
+ // NOTE: This is specific to the v1 Orchestration API.
+ String canonicalName = heatStack.getStackName () + "/" + heatStack.getId ();
+
+ LOGGER.debug ("Ready to Update Stack (" + canonicalName + ") with input params: " + stackInputs);
+ //force entire stackInput object to generic Map<String, Object> for openstack compatibility
+ ObjectMapper mapper = new ObjectMapper();
+ Map<String, Object> normalized = new HashMap<>();
+ try {
+ normalized = mapper.readValue(mapper.writeValueAsString(stackInputs), new TypeReference<HashMap<String,Object>>() {});
+ } catch (IOException e1) {
+ LOGGER.debug("could not map json", e1);
+ }
+ // Build up the stack update parameters
+ // Disable auto-rollback, because error reason is lost. Always rollback in the code.
+ UpdateStackParam stack = new UpdateStackParam ();
+ stack.setTimeoutMinutes (timeoutMinutes);
+ stack.setParameters (normalized);
+ stack.setTemplate (heatTemplate);
+ stack.setDisableRollback (true);
+ // TJM add envt to stack
+ if (heatEnvtVariable) {
+ stack.setEnvironment (environment);
+ }
+
+ // Handle nested templates & get_files here. if we have both - must combine
+ // and then add to stack (both are part of "files:" being added to stack)
+ if (haveFiles && haveHeatFiles) {
+ // Let's do this here - not in the bean
+ LOGGER.debug ("Found files AND heatFiles - combine and add!");
+ Map <String, Object> combinedFiles = new HashMap<>();
+ for (String keyString : files.keySet ()) {
+ combinedFiles.put (keyString, files.get (keyString));
+ }
+ for (String keyString : heatFiles.keySet ()) {
+ combinedFiles.put (keyString, heatFiles.get (keyString));
+ }
+ stack.setFiles (combinedFiles);
+ } else {
+ // Handle case where we have one or neither
+ if (haveFiles) {
+ stack.setFiles (files);
+ }
+ if (haveHeatFiles) {
+ // setFiles method modified to handle adding a map.
+ stack.setFiles (heatFiles);
+ }
+ }
+
+ try {
+ // Execute the actual Openstack command to update the Heat stack
+ OpenStackRequest <Void> request = heatClient.getStacks ().update (canonicalName, stack);
+ executeAndRecordOpenstackRequest (request);
+ } catch (OpenStackBaseException e) {
+ // Since this came on the 'Update Stack' command, nothing was changed
+ // in the cloud. Rethrow the error as an MSO exception.
+ throw heatExceptionToMsoException (e, UPDATE_STACK);
+ } catch (RuntimeException e) {
+ // Catch-all
+ throw runtimeExceptionToMsoException (e, UPDATE_STACK);
+ }
+
+ // If client has requested a final response, poll for stack completion
+ Stack updateStack = null;
+ if (pollForCompletion) {
+ // Set a time limit on overall polling.
+ // Use the resource (template) timeout for Openstack (expressed in minutes)
+ // and add one poll interval to give Openstack a chance to fail on its own.
+ int createPollInterval = Integer.parseInt(this.environment.getProperty(createPollIntervalProp, createPollIntervalDefault));
+ int pollTimeout = (timeoutMinutes * 60) + createPollInterval;
+
+ boolean loopAgain = true;
+ while (loopAgain) {
+ try {
+ updateStack = queryHeatStack (heatClient, canonicalName);
+ LOGGER.debug (updateStack.getStackStatus () + " (" + canonicalName + ")");
+ try {
+ LOGGER.debug("Current stack " + this.getOutputsAsStringBuilderWithUpdate(heatStack).toString());
+ } catch (Exception e) {
+ LOGGER.debug("an error occurred trying to print out the current outputs of the stack", e);
+ }
+
+
+ if ("UPDATE_IN_PROGRESS".equals (updateStack.getStackStatus ())) {
+ // Stack update is still running.
+ // Sleep and try again unless timeout has been reached
+ if (pollTimeout <= 0) {
+ // Note that this should not occur, since there is a timeout specified
+ // in the Openstack call.
+ LOGGER.error (MessageEnum.RA_UPDATE_STACK_TIMEOUT, cloudSiteId, tenantId, stackName, updateStack.getStackStatus(), "", "", MsoLogger.ErrorCode.AvailabilityError, "Update stack timeout");
+ loopAgain = false;
+ } else {
+ try {
+ Thread.sleep (createPollInterval * 1000L);
+ } catch (InterruptedException e) {
+ // If we are interrupted, we should stop ASAP.
+ loopAgain = false;
+ // Set again the interrupted flag
+ Thread.currentThread().interrupt();
+ }
+ }
+ pollTimeout -= createPollInterval;
+ LOGGER.debug("pollTimeout remaining: " + pollTimeout);
+ } else {
+ loopAgain = false;
+ }
+ } catch (MsoException e) {
+ // Cannot query the stack. Something is wrong.
+
+ // TODO: No way to roll back the stack at this point. What to do?
+ e.addContext (UPDATE_STACK);
+ throw e;
+ }
+ }
+
+ if (!"UPDATE_COMPLETE".equals (updateStack.getStackStatus ())) {
+ LOGGER.error (MessageEnum.RA_UPDATE_STACK_ERR, updateStack.getStackStatus(), updateStack.getStackStatusReason(), "", "", MsoLogger.ErrorCode.DataError, "Update Stack error");
+
+ // TODO: No way to roll back the stack at this point. What to do?
+ // Throw a 'special case' of MsoOpenstackException to report the Heat status
+ MsoOpenstackException me = null;
+ if ("UPDATE_IN_PROGRESS".equals (updateStack.getStackStatus ())) {
+ me = new MsoOpenstackException (0, "", "Stack Update Timeout");
+ } else {
+ String error = "Stack error (" + updateStack.getStackStatus ()
+ + "): "
+ + updateStack.getStackStatusReason ();
+ me = new MsoOpenstackException (0, "", error);
+ }
+ me.addContext (UPDATE_STACK);
+ throw me;
+ }
+
+ } else {
+ // Return the current status.
+ updateStack = queryHeatStack (heatClient, canonicalName);
+ if (updateStack != null) {
+ LOGGER.debug ("UpdateStack, status = " + updateStack.getStackStatus ());
+ } else {
+ LOGGER.debug ("UpdateStack, stack not found");
+ }
+ }
+ return new StackInfoMapper(updateStack).map();
+ }
+
+ private StringBuilder getOutputsAsStringBuilderWithUpdate(Stack heatStack) {
+ // This should only be used as a utility to print out the stack outputs
+ // to the log
+ StringBuilder sb = new StringBuilder("");
+ if (heatStack == null) {
+ sb.append("(heatStack is null)");
+ return sb;
+ }
+ List<Output> outputList = heatStack.getOutputs();
+ if (outputList == null || outputList.isEmpty()) {
+ sb.append("(outputs is empty)");
+ return sb;
+ }
+ Map<String, Object> outputs = new HashMap<>();
+ for (Output outputItem : outputList) {
+ outputs.put(outputItem.getOutputKey(), outputItem.getOutputValue());
+ }
+ int counter = 0;
+ sb.append("OUTPUTS:\n");
+ for (String key : outputs.keySet()) {
+ sb.append("outputs[").append(counter++).append("]: ").append(key).append("=");
+ Object obj = outputs.get(key);
+ if (obj instanceof String) {
+ sb.append((String) obj).append(" (a string)");
+ } else if (obj instanceof JsonNode) {
+ sb.append(this.convertNodeWithUpdate((JsonNode) obj)).append(" (a JsonNode)");
+ } else if (obj instanceof java.util.LinkedHashMap) {
+ try {
+ String str = JSON_MAPPER.writeValueAsString(obj);
+ sb.append(str).append(" (a java.util.LinkedHashMap)");
+ } catch (Exception e) {
+ LOGGER.debug("Exception :", e);
+ sb.append("(a LinkedHashMap value that would not convert nicely)");
+ }
+ } else if (obj instanceof Integer) {
+ String str = "";
+ try {
+ str = obj.toString() + " (an Integer)\n";
+ } catch (Exception e) {
+ LOGGER.debug("Exception :", e);
+ str = "(an Integer unable to call .toString() on)";
+ }
+ sb.append(str);
+ } else if (obj instanceof ArrayList) {
+ String str = "";
+ try {
+ str = obj.toString() + " (an ArrayList)";
+ } catch (Exception e) {
+ LOGGER.debug("Exception :", e);
+ str = "(an ArrayList unable to call .toString() on?)";
+ }
+ sb.append(str);
+ } else if (obj instanceof Boolean) {
+ String str = "";
+ try {
+ str = obj.toString() + " (a Boolean)";
+ } catch (Exception e) {
+ LOGGER.debug("Exception :", e);
+ str = "(an Boolean unable to call .toString() on?)";
+ }
+ sb.append(str);
+ }
+ else {
+ String str = "";
+ try {
+ str = obj.toString() + " (unknown Object type)";
+ } catch (Exception e) {
+ LOGGER.debug("Exception :", e);
+ str = "(a value unable to call .toString() on?)";
+ }
+ sb.append(str);
+ }
+ sb.append("\n");
+ }
+ sb.append("[END]");
+ return sb;
+ }
+
+ private String convertNodeWithUpdate(final JsonNode node) {
+ try {
+ final Object obj = JSON_MAPPER.treeToValue(node, Object.class);
+ final String json = JSON_MAPPER.writeValueAsString(obj);
+ return json;
+ } catch (Exception e) {
+ LOGGER.debug("Error converting json to string " + e.getMessage(), e);
+ }
+ return "[Error converting json to string]";
+ }
+
+}
diff --git a/adapters/mso-adapter-utils/src/main/java/org/onap/so/openstack/utils/MsoKeystoneUtils.java b/adapters/mso-adapter-utils/src/main/java/org/onap/so/openstack/utils/MsoKeystoneUtils.java
new file mode 100644
index 0000000000..d3ec74db8d
--- /dev/null
+++ b/adapters/mso-adapter-utils/src/main/java/org/onap/so/openstack/utils/MsoKeystoneUtils.java
@@ -0,0 +1,669 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP - SO
+ * ================================================================================
+ * Copyright (C) 2017 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.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.so.openstack.utils;
+
+
+import java.io.Serializable;
+import java.util.Calendar;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+
+import org.onap.so.cloud.CloudIdentity;
+import org.onap.so.cloud.CloudSite;
+import org.onap.so.cloud.authentication.AuthenticationMethodFactory;
+import org.onap.so.logger.MessageEnum;
+import org.onap.so.logger.MsoAlarmLogger;
+import org.onap.so.logger.MsoLogger;
+import org.onap.so.openstack.beans.MsoTenant;
+import org.onap.so.openstack.exceptions.MsoAdapterException;
+import org.onap.so.openstack.exceptions.MsoCloudSiteNotFound;
+import org.onap.so.openstack.exceptions.MsoException;
+import org.onap.so.openstack.exceptions.MsoOpenstackException;
+import org.onap.so.openstack.exceptions.MsoTenantAlreadyExists;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import com.woorea.openstack.base.client.OpenStackBaseException;
+import com.woorea.openstack.base.client.OpenStackConnectException;
+import com.woorea.openstack.base.client.OpenStackRequest;
+import com.woorea.openstack.base.client.OpenStackResponseException;
+import com.woorea.openstack.keystone.Keystone;
+import com.woorea.openstack.keystone.model.Access;
+import com.woorea.openstack.keystone.model.Authentication;
+import com.woorea.openstack.keystone.model.Metadata;
+import com.woorea.openstack.keystone.model.Role;
+import com.woorea.openstack.keystone.model.Roles;
+import com.woorea.openstack.keystone.model.Tenant;
+import com.woorea.openstack.keystone.model.User;
+import com.woorea.openstack.keystone.utils.KeystoneUtils;
+
+@Component
+public class MsoKeystoneUtils extends MsoTenantUtils {
+
+ // Cache the Keystone Clients statically. Since there is just one MSO user, there is no
+ // benefit to re-authentication on every request (or across different flows). The
+ // token will be used until it expires.
+ //
+ // The cache key is "cloudId"
+ private static Map <String, KeystoneCacheEntry> adminClientCache = new HashMap<>();
+
+ private static MsoLogger LOGGER = MsoLogger.getMsoLogger (MsoLogger.Catalog.RA, MsoKeystoneUtils.class);
+
+ @Autowired
+ private AuthenticationMethodFactory authenticationMethodFactory;
+
+ @Autowired
+ private MsoHeatUtils msoHeatUtils;
+
+ @Autowired
+ private MsoNeutronUtils msoNeutronUtils;
+
+ @Autowired
+ private MsoTenantUtilsFactory tenantUtilsFactory;
+ /**
+ * Create a tenant with the specified name in the given cloud. If the tenant already exists,
+ * an Exception will be thrown. The MSO User will also be added to the "member" list of
+ * the new tenant to perform subsequent Nova/Heat commands in the tenant. If the MSO User
+ * association fails, the entire transaction will be rolled back.
+ * <p>
+ * For the AIC Cloud (DCP/LCP): it is not clear that cloudId is needed, as all admin
+ * requests go to the centralized identity service in DCP. However, if some artifact
+ * must exist in each local LCP instance as well, then it will be needed to access the
+ * correct region.
+ * <p>
+ *
+ * @param tenantName The tenant name to create
+ * @param cloudId The cloud identifier (may be a region) in which to create the tenant.
+ * @return the tenant ID of the newly created tenant
+ * @throws MsoTenantAlreadyExists Thrown if the requested tenant already exists
+ * @throws MsoOpenstackException Thrown if the Openstack API call returns an exception
+ */
+ public String createTenant (String tenantName,
+ String cloudSiteId,
+ Map <String, String> metadata,
+ boolean backout) throws MsoException {
+ // Obtain the cloud site information where we will create the tenant
+ Optional<CloudSite> cloudSiteOpt = cloudConfig.getCloudSite(cloudSiteId);
+ if (!cloudSiteOpt.isPresent()) {
+ LOGGER.error(MessageEnum.RA_CREATE_TENANT_ERR, "MSOCloudSite not found", "", "", MsoLogger.ErrorCode.DataError, "MSOCloudSite not found");
+ throw new MsoCloudSiteNotFound (cloudSiteId);
+ }
+ Keystone keystoneAdminClient = getKeystoneAdminClient(cloudSiteOpt.get());
+ Tenant tenant = null;
+ try {
+ // Check if the tenant already exists
+ tenant = findTenantByName (keystoneAdminClient, tenantName);
+
+ if (tenant != null) {
+ // Tenant already exists. Throw an exception
+ LOGGER.error(MessageEnum.RA_TENANT_ALREADY_EXIST, tenantName, cloudSiteId, "", "", MsoLogger.ErrorCode.DataError, "Tenant already exists");
+ throw new MsoTenantAlreadyExists (tenantName, cloudSiteId);
+ }
+
+ // Does not exist, create a new one
+ tenant = new Tenant ();
+ tenant.setName (tenantName);
+ tenant.setDescription ("SDN Tenant (via MSO)");
+ tenant.setEnabled (true);
+
+ OpenStackRequest <Tenant> request = keystoneAdminClient.tenants ().create (tenant);
+ tenant = executeAndRecordOpenstackRequest (request);
+ } catch (OpenStackBaseException e) {
+ // Convert Keystone OpenStackResponseException to MsoOpenstackException
+ throw keystoneErrorToMsoException (e, "CreateTenant");
+ } catch (RuntimeException e) {
+ // Catch-all
+ throw runtimeExceptionToMsoException (e, "CreateTenant");
+ }
+
+ // Add MSO User to the tenant as a member and
+ // apply tenant metadata if supported by the cloud site
+ try {
+ CloudIdentity cloudIdentity = cloudConfig.getIdentityService(cloudSiteOpt.get().getIdentityServiceId());
+
+ User msoUser = findUserByNameOrId (keystoneAdminClient, cloudIdentity.getMsoId ());
+ Role memberRole = findRoleByNameOrId (keystoneAdminClient, cloudIdentity.getMemberRole ());
+
+ if(msoUser != null && memberRole != null) {
+ OpenStackRequest <Void> request = keystoneAdminClient.tenants ().addUser (tenant.getId (),
+ msoUser.getId (),
+ memberRole.getId ());
+ executeAndRecordOpenstackRequest (request);
+ }
+
+ if (cloudIdentity.hasTenantMetadata () && metadata != null && !metadata.isEmpty ()) {
+ Metadata tenantMetadata = new Metadata ();
+ tenantMetadata.setMetadata (metadata);
+
+ OpenStackRequest <Metadata> metaRequest = keystoneAdminClient.tenants ()
+ .createOrUpdateMetadata (tenant.getId (),
+ tenantMetadata);
+ executeAndRecordOpenstackRequest (metaRequest);
+ }
+ } catch (Exception e) {
+ // Failed to attach MSO User to the new tenant. Can't operate without access,
+ // so roll back the tenant.
+ if (!backout)
+ {
+ LOGGER.warn(MessageEnum.RA_CREATE_TENANT_ERR, "Create Tenant errored, Tenant deletion suppressed", "Openstack", "", MsoLogger.ErrorCode.DataError, "Create Tenant error, Tenant deletion suppressed");
+ }
+ else
+ {
+ try {
+ OpenStackRequest <Void> request = keystoneAdminClient.tenants ().delete (tenant.getId ());
+ executeAndRecordOpenstackRequest (request);
+ } catch (Exception e2) {
+ // Just log this one. We will report the original exception.
+ LOGGER.error (MessageEnum.RA_CREATE_TENANT_ERR, "Nested exception rolling back tenant", "Openstack", "", MsoLogger.ErrorCode.DataError, "Create Tenant error, Nested exception rolling back tenant", e2);
+ }
+ }
+
+
+ // Propagate the original exception on user/role/tenant mapping
+ if (e instanceof OpenStackBaseException) {
+ // Convert Keystone Exception to MsoOpenstackException
+ throw keystoneErrorToMsoException ((OpenStackBaseException) e, "CreateTenantUser");
+ } else {
+ MsoAdapterException me = new MsoAdapterException (e.getMessage (), e);
+ me.addContext ("CreateTenantUser");
+ throw me;
+ }
+ }
+ return tenant.getId ();
+ }
+
+ /**
+ * Query for a tenant by ID in the given cloud. If the tenant exists,
+ * return an MsoTenant object. If not, return null.
+ * <p>
+ * For the AIC Cloud (DCP/LCP): it is not clear that cloudId is needed, as all admin
+ * requests go to the centralized identity service in DCP. However, if some artifact
+ * must exist in each local LCP instance as well, then it will be needed to access the
+ * correct region.
+ * <p>
+ *
+ * @param tenantId The Openstack ID of the tenant to query
+ * @param cloudSiteId The cloud identifier (may be a region) in which to query the tenant.
+ * @return the tenant properties of the queried tenant, or null if not found
+ * @throws MsoOpenstackException Thrown if the Openstack API call returns an exception
+ */
+ public MsoTenant queryTenant (String tenantId, String cloudSiteId) throws MsoException {
+ // Obtain the cloud site information where we will query the tenant
+ CloudSite cloudSite = cloudConfig.getCloudSite(cloudSiteId).orElseThrow(
+ () -> new MsoCloudSiteNotFound(cloudSiteId));
+
+ Keystone keystoneAdminClient = getKeystoneAdminClient (cloudSite);
+
+ // Check if the tenant exists and return its Tenant Id
+ try {
+ Tenant tenant = findTenantById (keystoneAdminClient, tenantId);
+ if (tenant == null) {
+ return null;
+ }
+
+ Map <String, String> metadata = new HashMap <String, String> ();
+ if (cloudConfig.getIdentityService(cloudSite.getIdentityServiceId()).hasTenantMetadata ()) {
+ OpenStackRequest <Metadata> request = keystoneAdminClient.tenants ().showMetadata (tenant.getId ());
+ Metadata tenantMetadata = executeAndRecordOpenstackRequest (request);
+ if (tenantMetadata != null) {
+ metadata = tenantMetadata.getMetadata ();
+ }
+ }
+ return new MsoTenant (tenant.getId (), tenant.getName (), metadata);
+ } catch (OpenStackBaseException e) {
+ // Convert Keystone OpenStackResponseException to MsoOpenstackException
+ throw keystoneErrorToMsoException (e, "QueryTenant");
+ } catch (RuntimeException e) {
+ // Catch-all
+ throw runtimeExceptionToMsoException (e, "QueryTenant");
+ }
+ }
+
+ /**
+ * Query for a tenant with the specified name in the given cloud. If the tenant exists,
+ * return an MsoTenant object. If not, return null. This query is useful if the client
+ * knows it has the tenant name, skipping an initial lookup by ID that would always fail.
+ * <p>
+ * For the AIC Cloud (DCP/LCP): it is not clear that cloudId is needed, as all admin
+ * requests go to the centralized identity service in DCP. However, if some artifact
+ * must exist in each local LCP instance as well, then it will be needed to access the
+ * correct region.
+ * <p>
+ *
+ * @param tenantName The name of the tenant to query
+ * @param cloudSiteId The cloud identifier (may be a region) in which to query the tenant.
+ * @return the tenant properties of the queried tenant, or null if not found
+ * @throws MsoOpenstackException Thrown if the Openstack API call returns an exception
+ */
+ public MsoTenant queryTenantByName (String tenantName, String cloudSiteId) throws MsoException {
+ // Obtain the cloud site information where we will query the tenant
+ CloudSite cloudSite = cloudConfig.getCloudSite(cloudSiteId).orElseThrow(
+ () -> new MsoCloudSiteNotFound(cloudSiteId));
+ Keystone keystoneAdminClient = getKeystoneAdminClient (cloudSite);
+
+ try {
+ Tenant tenant = findTenantByName (keystoneAdminClient, tenantName);
+ if (tenant == null) {
+ return null;
+ }
+
+ Map <String, String> metadata = new HashMap <String, String> ();
+ if (cloudConfig.getIdentityService(cloudSite.getIdentityServiceId()).hasTenantMetadata ()) {
+ OpenStackRequest <Metadata> request = keystoneAdminClient.tenants ().showMetadata (tenant.getId ());
+ Metadata tenantMetadata = executeAndRecordOpenstackRequest (request);
+ if (tenantMetadata != null) {
+ metadata = tenantMetadata.getMetadata ();
+ }
+ }
+ return new MsoTenant (tenant.getId (), tenant.getName (), metadata);
+ } catch (OpenStackBaseException e) {
+ // Convert Keystone OpenStackResponseException to MsoOpenstackException
+ throw keystoneErrorToMsoException (e, "QueryTenantName");
+ } catch (RuntimeException e) {
+ // Catch-all
+ throw runtimeExceptionToMsoException (e, "QueryTenantName");
+ }
+ }
+
+ /**
+ * Delete the specified Tenant (by ID) in the given cloud. This method returns true or
+ * false, depending on whether the tenant existed and was successfully deleted, or if
+ * the tenant already did not exist. Both cases are treated as success (no Exceptions).
+ * <p>
+ * Note for the AIC Cloud (DCP/LCP): all admin requests go to the centralized identity
+ * service in DCP. So deleting a tenant from one cloudSiteId will remove it from all
+ * sites managed by that identity service.
+ * <p>
+ *
+ * @param tenantId The Openstack ID of the tenant to delete
+ * @param cloudSiteId The cloud identifier from which to delete the tenant.
+ * @return true if the tenant was deleted, false if the tenant did not exist.
+ * @throws MsoOpenstackException If the Openstack API call returns an exception.
+ */
+ public boolean deleteTenant (String tenantId, String cloudSiteId) throws MsoException {
+ // Obtain the cloud site information where we will query the tenant
+ CloudSite cloudSite = cloudConfig.getCloudSite(cloudSiteId).orElseThrow(
+ () -> new MsoCloudSiteNotFound(cloudSiteId));
+ Keystone keystoneAdminClient = getKeystoneAdminClient (cloudSite);
+
+ try {
+ // Check that the tenant exists. Also, need the ID to delete
+ Tenant tenant = findTenantById (keystoneAdminClient, tenantId);
+ if (tenant == null) {
+ LOGGER.error(MessageEnum.RA_TENANT_NOT_FOUND, tenantId, cloudSiteId, "", "", MsoLogger.ErrorCode.DataError, "Tenant not found");
+ return false;
+ }
+
+ OpenStackRequest <Void> request = keystoneAdminClient.tenants ().delete (tenant.getId ());
+ executeAndRecordOpenstackRequest (request);
+ LOGGER.debug ("Deleted Tenant " + tenant.getId () + " (" + tenant.getName () + ")");
+
+ // Clear any cached clients. Not really needed, ID will not be reused.
+ msoHeatUtils.expireHeatClient (tenant.getId (), cloudSiteId);
+ msoNeutronUtils.expireNeutronClient (tenant.getId (), cloudSiteId);
+ } catch (OpenStackBaseException e) {
+ // Convert Keystone OpenStackResponseException to MsoOpenstackException
+ throw keystoneErrorToMsoException (e, "Delete Tenant");
+ } catch (RuntimeException e) {
+ // Catch-all
+ throw runtimeExceptionToMsoException (e, "DeleteTenant");
+ }
+
+ return true;
+ }
+
+ /**
+ * Delete the specified Tenant (by Name) in the given cloud. This method returns true or
+ * false, depending on whether the tenant existed and was successfully deleted, or if
+ * the tenant already did not exist. Both cases are treated as success (no Exceptions).
+ * <p>
+ * Note for the AIC Cloud (DCP/LCP): all admin requests go to the centralized identity
+ * service in DCP. So deleting a tenant from one cloudSiteId will remove it from all
+ * sites managed by that identity service.
+ * <p>
+ *
+ * @param tenantName The name of the tenant to delete
+ * @param cloudSiteId The cloud identifier from which to delete the tenant.
+ * @return true if the tenant was deleted, false if the tenant did not exist.
+ * @throws MsoOpenstackException If the Openstack API call returns an exception.
+ */
+ public boolean deleteTenantByName (String tenantName, String cloudSiteId) throws MsoException {
+ // Obtain the cloud site information where we will query the tenant
+ Optional<CloudSite> cloudSite = cloudConfig.getCloudSite (cloudSiteId);
+ if (!cloudSite.isPresent()) {
+ throw new MsoCloudSiteNotFound (cloudSiteId);
+ }
+ Keystone keystoneAdminClient = getKeystoneAdminClient (cloudSite.get());
+
+ try {
+ // Need the Tenant ID to delete (can't directly delete by name)
+ Tenant tenant = findTenantByName (keystoneAdminClient, tenantName);
+ if (tenant == null) {
+ // OK if tenant already doesn't exist.
+ LOGGER.error(MessageEnum.RA_TENANT_NOT_FOUND, tenantName, cloudSiteId, "", "", MsoLogger.ErrorCode.DataError, "Tenant not found");
+ return false;
+ }
+
+ // Execute the Delete. It has no return value.
+ OpenStackRequest <Void> request = keystoneAdminClient.tenants ().delete (tenant.getId ());
+ executeAndRecordOpenstackRequest (request);
+
+ LOGGER.debug ("Deleted Tenant " + tenant.getId () + " (" + tenant.getName () + ")");
+
+ // Clear any cached clients. Not really needed, ID will not be reused.
+ msoHeatUtils.expireHeatClient (tenant.getId (), cloudSiteId);
+ msoNeutronUtils.expireNeutronClient (tenant.getId (), cloudSiteId);
+ } catch (OpenStackBaseException e) {
+ // Note: It doesn't seem to matter if tenant doesn't exist, no exception is thrown.
+ // Convert Keystone OpenStackResponseException to MsoOpenstackException
+ throw keystoneErrorToMsoException (e, "DeleteTenant");
+ } catch (RuntimeException e) {
+ // Catch-all
+ throw runtimeExceptionToMsoException (e, "DeleteTenant");
+ }
+
+ return true;
+ }
+
+ // -------------------------------------------------------------------
+ // PRIVATE UTILITY FUNCTIONS FOR USE WITHIN THIS CLASS
+
+ /*
+ * Get a Keystone Admin client for the Openstack Identity service.
+ * This requires an 'admin'-level userId + password along with an 'admin' tenant
+ * in the target cloud. These values will be retrieved from properties based
+ * on the specified cloud ID.
+ * <p>
+ * On successful authentication, the Keystone object will be cached for the cloudId
+ * so that it can be reused without going back to Openstack every time.
+ *
+ * @param cloudId
+ *
+ * @return an authenticated Keystone object
+ */
+ public Keystone getKeystoneAdminClient (CloudSite cloudSite) throws MsoException {
+ CloudIdentity cloudIdentity = cloudConfig.getIdentityService(cloudSite.getIdentityServiceId());
+
+ String cloudId = cloudIdentity.getId ();
+ String adminTenantName = cloudIdentity.getAdminTenant ();
+ String region = cloudSite.getRegionId ();
+
+ // Check first in the cache of previously authorized clients
+ KeystoneCacheEntry entry = adminClientCache.get (cloudId);
+ if (entry != null) {
+ if (!entry.isExpired ()) {
+ return entry.getKeystoneClient ();
+ } else {
+ // Token is expired. Remove it from cache.
+ adminClientCache.remove (cloudId);
+ }
+ }
+ MsoTenantUtils tenantUtils = tenantUtilsFactory.getTenantUtilsByServerType(cloudIdentity.getIdentityServerType());
+ final String keystoneUrl = tenantUtils.getKeystoneUrl(region, cloudIdentity);
+ Keystone keystone = new Keystone(keystoneUrl);
+
+ // Must authenticate against the 'admin' tenant to get the services endpoints
+ Access access = null;
+ String token = null;
+ try {
+ Authentication credentials = authenticationMethodFactory.getAuthenticationFor(cloudIdentity);
+ OpenStackRequest <Access> request = keystone.tokens ()
+ .authenticate (credentials)
+ .withTenantName (adminTenantName);
+ access = executeAndRecordOpenstackRequest (request);
+ token = access.getToken ().getId ();
+ } catch (OpenStackResponseException e) {
+ if (e.getStatus () == 401) {
+ // Authentication error. Can't access admin tenant - something is mis-configured
+ String error = "MSO Authentication Failed for " + cloudIdentity.getId ();
+ alarmLogger.sendAlarm ("MsoAuthenticationError", MsoAlarmLogger.CRITICAL, error);
+ throw new MsoAdapterException (error);
+ } else {
+ throw keystoneErrorToMsoException (e, "TokenAuth");
+ }
+ } catch (OpenStackConnectException e) {
+ // Connection to Openstack failed
+ throw keystoneErrorToMsoException (e, "TokenAuth");
+ }
+
+ // Get the Identity service URL. Throws runtime exception if not found per region.
+ String adminUrl = null;
+ try {
+ // TODO: FOR TESTING!!!!
+ adminUrl = KeystoneUtils.findEndpointURL (access.getServiceCatalog (), "identity", region, "public");
+ adminUrl = adminUrl.replaceFirst("5000", "35357");
+ } catch (RuntimeException e) {
+ String error = "Identity service not found: region=" + region + ",cloud=" + cloudIdentity.getId ();
+ alarmLogger.sendAlarm ("MsoConfigurationError", MsoAlarmLogger.CRITICAL, error);
+ LOGGER.error(MessageEnum.IDENTITY_SERVICE_NOT_FOUND, region, cloudIdentity.getId(), "Openstack", "", MsoLogger.ErrorCode.DataError, "Exception in findEndpointURL");
+ throw new MsoAdapterException (error, e);
+ }
+
+ // A new Keystone object is required for the new URL. Use the auth token from above.
+ // Note: this doesn't go back to Openstack, it's just a local object.
+ keystone = new Keystone (adminUrl);
+ keystone.token (token);
+
+ // Cache to avoid re-authentication for every call.
+ KeystoneCacheEntry cacheEntry = new KeystoneCacheEntry (adminUrl, token, access.getToken ().getExpires ());
+ adminClientCache.put (cloudId, cacheEntry);
+
+ return keystone;
+ }
+
+ /*
+ * Find a tenant (or query its existance) by its Name or Id. Check first against the
+ * ID. If that fails, then try by name.
+ *
+ * @param adminClient an authenticated Keystone object
+ *
+ * @param tenantName the tenant name or ID to query
+ *
+ * @return a Tenant object or null if not found
+ */
+ public Tenant findTenantByNameOrId (Keystone adminClient, String tenantNameOrId) {
+ if (tenantNameOrId == null) {
+ return null;
+ }
+
+ Tenant tenant = findTenantById (adminClient, tenantNameOrId);
+ if (tenant == null) {
+ tenant = findTenantByName (adminClient, tenantNameOrId);
+ }
+
+ return tenant;
+ }
+
+ /*
+ * Find a tenant (or query its existance) by its Id.
+ *
+ * @param adminClient an authenticated Keystone object
+ *
+ * @param tenantName the tenant ID to query
+ *
+ * @return a Tenant object or null if not found
+ */
+ private Tenant findTenantById (Keystone adminClient, String tenantId) {
+ if (tenantId == null) {
+ return null;
+ }
+
+ try {
+ OpenStackRequest <Tenant> request = adminClient.tenants ().show (tenantId);
+ return executeAndRecordOpenstackRequest (request);
+ } catch (OpenStackResponseException e) {
+ if (e.getStatus () == 404) {
+ return null;
+ } else {
+ LOGGER.error(MessageEnum.RA_CONNECTION_EXCEPTION, "Openstack Error, GET Tenant by Id (" + tenantId + "): " + e, "Openstack", "", MsoLogger.ErrorCode.DataError, "Exception in Openstack GET tenant by Id");
+ throw e;
+ }
+ }
+ }
+
+ /*
+ * Find a tenant (or query its existance) by its Name. This method avoids an
+ * initial lookup by ID when it's known that we have the tenant Name.
+ *
+ * @param adminClient an authenticated Keystone object
+ *
+ * @param tenantName the tenant name to query
+ *
+ * @return a Tenant object or null if not found
+ */
+ public Tenant findTenantByName (Keystone adminClient, String tenantName) {
+ if (tenantName == null) {
+ return null;
+ }
+
+ try {
+ OpenStackRequest <Tenant> request = adminClient.tenants ().show ("").queryParam ("name", tenantName);
+ return executeAndRecordOpenstackRequest (request);
+ } catch (OpenStackResponseException e) {
+ if (e.getStatus () == 404) {
+ return null;
+ } else {
+ LOGGER.error (MessageEnum.RA_CONNECTION_EXCEPTION, "Openstack Error, GET Tenant By Name (" + tenantName + "): " + e, "Openstack", "", MsoLogger.ErrorCode.DataError, "Exception in Openstack GET Tenant By Name");
+ throw e;
+ }
+ }
+ }
+
+ /*
+ * Look up an Openstack User by Name or Openstack ID. Check the ID first, and if that
+ * fails, try the Name.
+ *
+ * @param adminClient an authenticated Keystone object
+ *
+ * @param userName the user name or ID to query
+ *
+ * @return a User object or null if not found
+ */
+ private User findUserByNameOrId (Keystone adminClient, String userNameOrId) {
+ if (userNameOrId == null) {
+ return null;
+ }
+
+ try {
+ OpenStackRequest <User> request = adminClient.users ().show (userNameOrId);
+ return executeAndRecordOpenstackRequest (request);
+ } catch (OpenStackResponseException e) {
+ if (e.getStatus () == 404) {
+ // Not found by ID. Search for name
+ return findUserByName (adminClient, userNameOrId);
+ } else {
+ LOGGER.error (MessageEnum.RA_CONNECTION_EXCEPTION, "Openstack Error, GET User (" + userNameOrId + "): " + e, "Openstack", "", MsoLogger.ErrorCode.DataError, "Exception in Openstack GET User");
+ throw e;
+ }
+ }
+ }
+
+ /*
+ * Look up an Openstack User by Name. This avoids initial Openstack query by ID
+ * if we know we have the User Name.
+ *
+ * @param adminClient an authenticated Keystone object
+ *
+ * @param userName the user name to query
+ *
+ * @return a User object or null if not found
+ */
+ public User findUserByName (Keystone adminClient, String userName) {
+ if (userName == null) {
+ return null;
+ }
+
+ try {
+ OpenStackRequest <User> request = adminClient.users ().show ("").queryParam ("name", userName);
+ return executeAndRecordOpenstackRequest (request);
+ } catch (OpenStackResponseException e) {
+ if (e.getStatus () == 404) {
+ return null;
+ } else {
+ LOGGER.error (MessageEnum.RA_CONNECTION_EXCEPTION, "Openstack Error, GET User By Name (" + userName + "): " + e, "Openstack", "", MsoLogger.ErrorCode.DataError, "Exception in Openstack GET User By Name");
+ throw e;
+ }
+ }
+ }
+
+ /*
+ * Look up an Openstack Role by Name or Id. There is no direct query for Roles, so
+ * need to retrieve a full list from Openstack and look for a match. By default,
+ * Openstack should have a "_member_" role for normal VM-level privileges and an
+ * "admin" role for expanded privileges (e.g. administer tenants, users, and roles).
+ * <p>
+ *
+ * @param adminClient an authenticated Keystone object
+ *
+ * @param roleNameOrId the Role name or ID to look up
+ *
+ * @return a Role object
+ */
+ private Role findRoleByNameOrId (Keystone adminClient, String roleNameOrId) {
+ if (roleNameOrId == null) {
+ return null;
+ }
+
+ // Search by name or ID. Must search in list
+ OpenStackRequest <Roles> request = adminClient.roles ().list ();
+ Roles roles = executeAndRecordOpenstackRequest (request);
+
+ for (Role role : roles) {
+ if (roleNameOrId.equals (role.getName ()) || roleNameOrId.equals (role.getId ())) {
+ return role;
+ }
+ }
+
+ return null;
+ }
+
+ private static class KeystoneCacheEntry implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ private String keystoneUrl;
+ private String token;
+ private Calendar expires;
+
+ public KeystoneCacheEntry (String url, String token, Calendar expires) {
+ this.keystoneUrl = url;
+ this.token = token;
+ this.expires = expires;
+ }
+
+ public Keystone getKeystoneClient () {
+ Keystone keystone = new Keystone (keystoneUrl);
+ keystone.token (token);
+ return keystone;
+ }
+
+ public boolean isExpired () {
+ // adding arbitrary guard timer of 5 minutes
+ return expires == null || System.currentTimeMillis() > (expires.getTimeInMillis() - 300000);
+ }
+ }
+
+ @Override
+ public String getKeystoneUrl(String regionId, CloudIdentity cloudIdentity) throws MsoException {
+ return cloudIdentity.getIdentityUrl();
+ }
+}
diff --git a/adapters/mso-adapter-utils/src/main/java/org/onap/so/openstack/utils/MsoNeutronUtils.java b/adapters/mso-adapter-utils/src/main/java/org/onap/so/openstack/utils/MsoNeutronUtils.java
new file mode 100644
index 0000000000..adeb008ad5
--- /dev/null
+++ b/adapters/mso-adapter-utils/src/main/java/org/onap/so/openstack/utils/MsoNeutronUtils.java
@@ -0,0 +1,550 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP - SO
+ * ================================================================================
+ * Copyright (C) 2017 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.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.so.openstack.utils;
+
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.onap.so.cloud.CloudConfig;
+import org.onap.so.cloud.CloudIdentity;
+import org.onap.so.cloud.CloudSite;
+import org.onap.so.cloud.authentication.AuthenticationMethodFactory;
+import org.onap.so.logger.MessageEnum;
+import org.onap.so.logger.MsoAlarmLogger;
+import org.onap.so.logger.MsoLogger;
+import org.onap.so.openstack.beans.NetworkInfo;
+import org.onap.so.openstack.beans.NeutronCacheEntry;
+import org.onap.so.openstack.exceptions.MsoAdapterException;
+import org.onap.so.openstack.exceptions.MsoCloudSiteNotFound;
+import org.onap.so.openstack.exceptions.MsoException;
+import org.onap.so.openstack.exceptions.MsoIOException;
+import org.onap.so.openstack.exceptions.MsoNetworkAlreadyExists;
+import org.onap.so.openstack.exceptions.MsoNetworkNotFound;
+import org.onap.so.openstack.exceptions.MsoOpenstackException;
+import org.onap.so.openstack.exceptions.MsoTenantNotFound;
+import org.onap.so.openstack.mappers.NetworkInfoMapper;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import com.woorea.openstack.base.client.OpenStackBaseException;
+import com.woorea.openstack.base.client.OpenStackConnectException;
+import com.woorea.openstack.base.client.OpenStackRequest;
+import com.woorea.openstack.base.client.OpenStackResponseException;
+import com.woorea.openstack.keystone.Keystone;
+import com.woorea.openstack.keystone.model.Access;
+import com.woorea.openstack.keystone.model.Authentication;
+import com.woorea.openstack.keystone.utils.KeystoneUtils;
+import com.woorea.openstack.quantum.Quantum;
+import com.woorea.openstack.quantum.model.Network;
+import com.woorea.openstack.quantum.model.Networks;
+import com.woorea.openstack.quantum.model.Segment;
+
+@Component
+public class MsoNeutronUtils extends MsoCommonUtils
+{
+ // Cache Neutron Clients statically. Since there is just one MSO user, there is no
+ // benefit to re-authentication on every request (or across different flows). The
+ // token will be used until it expires.
+ //
+ // The cache key is "tenantId:cloudId"
+ private static Map<String,NeutronCacheEntry> neutronClientCache = new HashMap<>();
+
+ // Fetch cloud configuration each time (may be cached in CloudConfig class)
+ @Autowired
+ private CloudConfig cloudConfig;
+
+ @Autowired
+ private AuthenticationMethodFactory authenticationMethodFactory;
+
+ @Autowired
+ private MsoTenantUtilsFactory tenantUtilsFactory;
+
+ private static MsoLogger LOGGER = MsoLogger.getMsoLogger (MsoLogger.Catalog.RA, MsoNeutronUtils.class);
+
+ public enum NetworkType {
+ BASIC, PROVIDER, MULTI_PROVIDER
+ };
+
+ /**
+ * Create a network with the specified parameters in the given cloud/tenant.
+ *
+ * If a network already exists with the same name, an exception will be thrown. Note that
+ * this is an MSO-imposed restriction. Openstack does not require uniqueness on network names.
+ * <p>
+ * @param cloudSiteId The cloud identifier (may be a region) in which to create the network.
+ * @param tenantId The tenant in which to create the network
+ * @param type The type of network to create (Basic, Provider, Multi-Provider)
+ * @param networkName The network name to create
+ * @param provider The provider network name (for Provider or Multi-Provider networks)
+ * @param vlans A list of VLAN segments for the network (for Provider or Multi-Provider networks)
+ * @return a NetworkInfo object which describes the newly created network
+ * @throws MsoNetworkAlreadyExists Thrown if a network with the same name already exists
+ * @throws MsoOpenstackException Thrown if the Openstack API call returns an exception
+ * @throws MsoCloudSiteNotFound Thrown if the cloudSite is invalid or unknown
+ */
+ public NetworkInfo createNetwork (String cloudSiteId, String tenantId, NetworkType type, String networkName, String provider, List<Integer> vlans)
+ throws MsoException
+ {
+ // Obtain the cloud site information where we will create the stack
+ CloudSite cloudSite = cloudConfig.getCloudSite(cloudSiteId).orElseThrow(
+ () -> new MsoCloudSiteNotFound(cloudSiteId));
+
+ Quantum neutronClient = getNeutronClient (cloudSite, tenantId);
+
+ // Check if a network already exists with this name
+ // Openstack will allow duplicate name, so require explicit check
+ Network network = findNetworkByName (neutronClient, networkName);
+
+ if (network != null) {
+ // Network already exists. Throw an exception
+ LOGGER.error(MessageEnum.RA_NETWORK_ALREADY_EXIST, networkName, cloudSiteId, tenantId, "Openstack", "", MsoLogger.ErrorCode.DataError, "Network already exists");
+ throw new MsoNetworkAlreadyExists (networkName, tenantId, cloudSiteId);
+ }
+
+ // Does not exist, create a new one
+ network = new Network();
+ network.setName(networkName);
+ network.setAdminStateUp(true);
+
+ if (type == NetworkType.PROVIDER) {
+ if (provider != null && vlans != null && vlans.size() > 0) {
+ network.setProviderPhysicalNetwork (provider);
+ network.setProviderNetworkType("vlan");
+ network.setProviderSegmentationId (vlans.get(0));
+ }
+ } else if (type == NetworkType.MULTI_PROVIDER) {
+ if (provider != null && vlans != null && vlans.size() > 0) {
+ List<Segment> segments = new ArrayList<>(vlans.size());
+ for (int vlan : vlans) {
+ Segment segment = new Segment();
+ segment.setProviderPhysicalNetwork (provider);
+ segment.setProviderNetworkType("vlan");
+ segment.setProviderSegmentationId (vlan);
+
+ segments.add(segment);
+ }
+ network.setSegments(segments);
+ }
+ }
+
+ try {
+ OpenStackRequest<Network> request = neutronClient.networks().create(network);
+ Network newNetwork = executeAndRecordOpenstackRequest(request);
+ return new NetworkInfoMapper(newNetwork).map();
+ }
+ catch (OpenStackBaseException e) {
+ // Convert Neutron exception to an MsoOpenstackException
+ MsoException me = neutronExceptionToMsoException (e, "CreateNetwork");
+ throw me;
+ }
+ catch (RuntimeException e) {
+ // Catch-all
+ MsoException me = runtimeExceptionToMsoException(e, "CreateNetwork");
+ throw me;
+ }
+ }
+
+
+ /**
+ * Query for a network with the specified name or ID in the given cloud. If the network exists,
+ * return an NetworkInfo object. If not, return null.
+ * <p>
+ * Whenever possible, the network ID should be used as it is much more efficient. Query by
+ * name requires retrieval of all networks for the tenant and search for matching name.
+ * <p>
+ * @param networkNameOrId The network to query
+ * @param tenantId The Openstack tenant to look in for the network
+ * @param cloudSiteId The cloud identifier (may be a region) in which to query the network.
+ * @return a NetworkInfo object describing the queried network, or null if not found
+ * @throws MsoOpenstackException Thrown if the Openstack API call returns an exception
+ * @throws MsoCloudSiteNotFound
+ */
+ public NetworkInfo queryNetwork(String networkNameOrId, String tenantId, String cloudSiteId) throws MsoException
+ {
+ LOGGER.debug("In queryNetwork");
+
+ // Obtain the cloud site information
+ CloudSite cloudSite = cloudConfig.getCloudSite(cloudSiteId).orElseThrow(
+ () -> new MsoCloudSiteNotFound(cloudSiteId));
+
+ Quantum neutronClient = getNeutronClient (cloudSite, tenantId);
+
+ // Check if the network exists and return its info
+ try {
+ Network network = findNetworkByNameOrId (neutronClient, networkNameOrId);
+ if (network == null) {
+ LOGGER.debug ("Query Network: " + networkNameOrId + " not found in tenant " + tenantId);
+ return null;
+ }
+ return new NetworkInfoMapper(network).map();
+ }
+ catch (OpenStackBaseException e) {
+ // Convert Neutron exception to an MsoOpenstackException
+ MsoException me = neutronExceptionToMsoException (e, "QueryNetwork");
+ throw me;
+ }
+ catch (RuntimeException e) {
+ // Catch-all
+ MsoException me = runtimeExceptionToMsoException(e, "QueryNetwork");
+ throw me;
+ }
+ }
+
+ /**
+ * Delete the specified Network (by ID) in the given cloud.
+ * If the network does not exist, success is returned.
+ * <p>
+ * @param networkId Openstack ID of the network to delete
+ * @param tenantId The Openstack tenant.
+ * @param cloudSiteId The cloud identifier (may be a region) from which to delete the network.
+ * @return true if the network was deleted, false if the network did not exist
+ * @throws MsoOpenstackException If the Openstack API call returns an exception, this local
+ * exception will be thrown.
+ * @throws MsoCloudSiteNotFound
+ */
+ public boolean deleteNetwork(String networkId, String tenantId, String cloudSiteId) throws MsoException
+ {
+ // Obtain the cloud site information where we will create the stack
+ CloudSite cloudSite = cloudConfig.getCloudSite(cloudSiteId).orElseThrow(
+ () -> new MsoCloudSiteNotFound(cloudSiteId));
+ Quantum neutronClient = getNeutronClient (cloudSite, tenantId);
+
+ try {
+ // Check that the network exists.
+ Network network = findNetworkById (neutronClient, networkId);
+ if (network == null) {
+ LOGGER.info(MessageEnum.RA_DELETE_NETWORK_EXC, networkId, cloudSiteId, tenantId, "Openstack", "");
+ return false;
+ }
+
+ OpenStackRequest<Void> request = neutronClient.networks().delete(network.getId());
+ executeAndRecordOpenstackRequest(request);
+
+ LOGGER.debug ("Deleted Network " + network.getId() + " (" + network.getName() + ")");
+ }
+ catch (OpenStackBaseException e) {
+ // Convert Neutron exception to an MsoOpenstackException
+ MsoException me = neutronExceptionToMsoException (e, "Delete Network");
+ throw me;
+ }
+ catch (RuntimeException e) {
+ // Catch-all
+ MsoException me = runtimeExceptionToMsoException(e, "DeleteNetwork");
+ throw me;
+ }
+
+ return true;
+ }
+
+
+ /**
+ * Update a network with the specified parameters in the given cloud/tenant.
+ *
+ * Specifically, this call is intended to update the VLAN segments on a
+ * multi-provider network. The provider segments will be replaced with the
+ * supplied list of VLANs.
+ * <p>
+ * Note that updating the 'segments' array is not normally supported by Neutron.
+ * This method relies on a Platform Orchestration extension (using SDN controller
+ * to manage the virtual networking).
+ *
+ * @param cloudSiteId The cloud site ID (may be a region) in which to update the network.
+ * @param tenantId Openstack ID of the tenant in which to update the network
+ * @param networkId The unique Openstack ID of the network to be updated
+ * @param type The network type (Basic, Provider, Multi-Provider)
+ * @param provider The provider network name. This should not change.
+ * @param vlans The list of VLAN segments to replace
+ * @return a NetworkInfo object which describes the updated network
+ * @throws MsoNetworkNotFound Thrown if the requested network does not exist
+ * @throws MsoOpenstackException Thrown if the Openstack API call returns an exception
+ * @throws MsoCloudSiteNotFound
+ */
+ public NetworkInfo updateNetwork (String cloudSiteId, String tenantId, String networkId, NetworkType type, String provider, List<Integer> vlans)
+ throws MsoException
+ {
+ // Obtain the cloud site information where we will create the stack
+ CloudSite cloudSite = cloudConfig.getCloudSite(cloudSiteId).orElseThrow(
+ () -> new MsoCloudSiteNotFound(cloudSiteId));
+ Quantum neutronClient = getNeutronClient (cloudSite, tenantId);
+
+ // Check that the network exists
+ Network network = findNetworkById (neutronClient, networkId);
+
+ if (network == null) {
+ // Network not found. Throw an exception
+ LOGGER.error(MessageEnum.RA_NETWORK_NOT_FOUND, networkId, cloudSiteId, tenantId, "Openstack", "", MsoLogger.ErrorCode.DataError, "Network not found");
+ throw new MsoNetworkNotFound (networkId, tenantId, cloudSiteId);
+ }
+
+ // Overwrite the properties to be updated
+ if (type == NetworkType.PROVIDER) {
+ if (provider != null && vlans != null && vlans.size() > 0) {
+ network.setProviderPhysicalNetwork (provider);
+ network.setProviderNetworkType("vlan");
+ network.setProviderSegmentationId (vlans.get(0));
+ }
+ } else if (type == NetworkType.MULTI_PROVIDER) {
+ if (provider != null && vlans != null && vlans.size() > 0) {
+ List<Segment> segments = new ArrayList<>(vlans.size());
+ for (int vlan : vlans) {
+ Segment segment = new Segment();
+ segment.setProviderPhysicalNetwork (provider);
+ segment.setProviderNetworkType("vlan");
+ segment.setProviderSegmentationId (vlan);
+
+ segments.add(segment);
+ }
+ network.setSegments(segments);
+ }
+ }
+
+ try {
+ OpenStackRequest<Network> request = neutronClient.networks().update(network);
+ Network newNetwork = executeAndRecordOpenstackRequest(request);
+ return new NetworkInfoMapper(newNetwork).map();
+ }
+ catch (OpenStackBaseException e) {
+ // Convert Neutron exception to an MsoOpenstackException
+ MsoException me = neutronExceptionToMsoException (e, "UpdateNetwork");
+ throw me;
+ }
+ catch (RuntimeException e) {
+ // Catch-all
+ MsoException me = runtimeExceptionToMsoException(e, "UpdateNetwork");
+ throw me;
+ }
+ }
+
+
+ // -------------------------------------------------------------------
+ // PRIVATE UTILITY FUNCTIONS FOR USE WITHIN THIS CLASS
+
+ /**
+ * Get a Neutron (Quantum) client for the Openstack Network service.
+ * This requires a 'member'-level userId + password, which will be retrieved from
+ * properties based on the specified cloud Id. The tenant in which to operate
+ * must also be provided.
+ * <p>
+ * On successful authentication, the Quantum object will be cached for the
+ * tenantID + cloudId so that it can be reused without reauthenticating with
+ * Openstack every time.
+ *
+ * @param cloudSite - a cloud site definition
+ * @param tenantId - Openstack tenant ID
+ * @return an authenticated Quantum object
+ */
+ private Quantum getNeutronClient(CloudSite cloudSite, String tenantId) throws MsoException
+ {
+ String cloudId = cloudSite.getId();
+
+ // Check first in the cache of previously authorized clients
+ String cacheKey = cloudId + ":" + tenantId;
+ if (neutronClientCache.containsKey(cacheKey)) {
+ if (! neutronClientCache.get(cacheKey).isExpired()) {
+ LOGGER.debug ("Using Cached HEAT Client for " + cacheKey);
+ NeutronCacheEntry cacheEntry = neutronClientCache.get(cacheKey);
+ Quantum neutronClient = new Quantum(cacheEntry.getNeutronUrl());
+ neutronClient.token(cacheEntry.getToken());
+ return neutronClient;
+ }
+ else {
+ // Token is expired. Remove it from cache.
+ neutronClientCache.remove(cacheKey);
+ LOGGER.debug ("Expired Cached Neutron Client for " + cacheKey);
+ }
+ }
+
+ // Obtain an MSO token for the tenant from the identity service
+ CloudIdentity cloudIdentity = cloudConfig.getIdentityService(cloudSite.getIdentityServiceId());
+ MsoTenantUtils tenantUtils = tenantUtilsFactory.getTenantUtilsByServerType(cloudIdentity.getIdentityServerType());
+ final String keystoneUrl = tenantUtils.getKeystoneUrl(cloudId, cloudIdentity);
+ Keystone keystoneTenantClient = new Keystone(keystoneUrl);
+ Access access = null;
+ try {
+ Authentication credentials = authenticationMethodFactory.getAuthenticationFor(cloudIdentity);
+ OpenStackRequest<Access> request = keystoneTenantClient.tokens().authenticate(credentials).withTenantId(tenantId);
+ access = executeAndRecordOpenstackRequest(request);
+ }
+ catch (OpenStackResponseException e) {
+ if (e.getStatus() == 401) {
+ // Authentication error.
+ String error = "Authentication Failure: tenant=" + tenantId + ",cloud=" + cloudIdentity.getId();
+ alarmLogger .sendAlarm("MsoAuthenticationError", MsoAlarmLogger.CRITICAL, error);
+ throw new MsoAdapterException(error);
+ }
+ else {
+ MsoException me = keystoneErrorToMsoException(e, "TokenAuth");
+ throw me;
+ }
+ }
+ catch (OpenStackConnectException e) {
+ // Connection to Openstack failed
+ MsoIOException me = new MsoIOException (e.getMessage(), e);
+ me.addContext("TokenAuth");
+ throw me;
+ }
+ catch (RuntimeException e) {
+ // Catch-all
+ MsoException me = runtimeExceptionToMsoException(e, "TokenAuth");
+ throw me;
+ }
+
+ String region = cloudSite.getRegionId();
+ String neutronUrl = null;
+ try {
+ neutronUrl = KeystoneUtils.findEndpointURL(access.getServiceCatalog(), "network", region, "public");
+ if (! neutronUrl.endsWith("/")) {
+ neutronUrl += "/v2.0/";
+ }
+ } catch (RuntimeException e) {
+ // This comes back for not found (probably an incorrect region ID)
+ String error = "Network service not found: region=" + region + ",cloud=" + cloudIdentity.getId();
+ alarmLogger.sendAlarm("MsoConfigurationError", MsoAlarmLogger.CRITICAL, error);
+ throw new MsoAdapterException (error, e);
+ }
+
+ Quantum neutronClient = new Quantum(neutronUrl);
+ neutronClient.token(access.getToken().getId());
+
+ neutronClientCache.put(cacheKey, new NeutronCacheEntry(neutronUrl, access.getToken().getId(), access.getToken().getExpires()));
+ LOGGER.debug ("Caching Neutron Client for " + cacheKey);
+
+ return neutronClient;
+ }
+
+ /**
+ * Forcibly expire a Neutron client from the cache. This call is for use by
+ * the KeystoneClient in case where a tenant is deleted. In that case,
+ * all cached credentials must be purged so that fresh authentication is
+ * done on subsequent calls.
+ * <p>
+ * @param tenantName
+ * @param cloudId
+ */
+ public void expireNeutronClient (String tenantId, String cloudId) {
+ String cacheKey = cloudId + ":" + tenantId;
+ if (neutronClientCache.containsKey(cacheKey)) {
+ neutronClientCache.remove(cacheKey);
+ LOGGER.debug ("Deleted Cached Neutron Client for " + cacheKey);
+ }
+ }
+
+
+ /*
+ * Find a tenant (or query its existence) by its Name or Id. Check first against the
+ * ID. If that fails, then try by name.
+ *
+ * @param adminClient an authenticated Keystone object
+ * @param tenantName the tenant name or ID to query
+ * @return a Tenant object or null if not found
+ */
+ public Network findNetworkByNameOrId (Quantum neutronClient, String networkNameOrId)
+ {
+ if (networkNameOrId == null) {
+ return null;
+ }
+
+ Network network = findNetworkById(neutronClient, networkNameOrId);
+
+ if (network == null) {
+ network = findNetworkByName(neutronClient, networkNameOrId);
+ }
+
+ return network;
+ }
+
+ /*
+ * Find a network (or query its existence) by its Id.
+ *
+ * @param neutronClient an authenticated Quantum object
+ * @param networkId the network ID to query
+ * @return a Network object or null if not found
+ */
+ private Network findNetworkById (Quantum neutronClient, String networkId)
+ {
+ if (networkId == null) {
+ return null;
+ }
+
+ try {
+ OpenStackRequest<Network> request = neutronClient.networks().show(networkId);
+ Network network = executeAndRecordOpenstackRequest(request);
+ return network;
+ }
+ catch (OpenStackResponseException e) {
+ if (e.getStatus() == 404) {
+ return null;
+ } else {
+ LOGGER.error (MessageEnum.RA_CONNECTION_EXCEPTION, "OpenStack", "Openstack Error, GET Network By ID (" + networkId + "): " + e, "Openstack", "", MsoLogger.ErrorCode.DataError, "Exception in Openstack");
+ throw e;
+ }
+ }
+ }
+
+ /*
+ * Find a network (or query its existence) by its Name. This method avoids an
+ * initial lookup by ID when it's known that we have the network Name.
+ *
+ * Neutron does not support 'name=*' query parameter for Network query (show).
+ * The only way to query by name is to retrieve all networks and look for the
+ * match. While inefficient, this capability will be provided as it is needed
+ * by MSO, but should be avoided in favor of ID whenever possible.
+ *
+ * TODO:
+ * Network names are not required to be unique, though MSO will attempt to enforce
+ * uniqueness. This call probably needs to return an error (instead of returning
+ * the first match).
+ *
+ * @param neutronClient an authenticated Quantum object
+ * @param networkName the network name to query
+ * @return a Network object or null if not found
+ */
+ public Network findNetworkByName (Quantum neutronClient, String networkName)
+ {
+ if (networkName == null) {
+ return null;
+ }
+
+ try {
+ OpenStackRequest<Networks> request = neutronClient.networks().list();
+ Networks networks = executeAndRecordOpenstackRequest(request);
+ for (Network network : networks.getList()) {
+ if (network.getName().equals(networkName)) {
+ LOGGER.debug ("Found match on network name: " + networkName);
+ return network;
+ }
+ }
+ LOGGER.debug ("findNetworkByName - no match found for " + networkName);
+ return null;
+ }
+ catch (OpenStackResponseException e) {
+ if (e.getStatus() == 404) {
+ return null;
+ } else {
+ LOGGER.error (MessageEnum.RA_CONNECTION_EXCEPTION, "OpenStack", "Openstack Error, GET Network By Name (" + networkName + "): " + e, "OpenStack", "", MsoLogger.ErrorCode.DataError, "Exception in OpenStack");
+ throw e;
+ }
+ }
+ }
+}
diff --git a/adapters/mso-adapter-utils/src/main/java/org/onap/so/openstack/utils/MsoTenantUtils.java b/adapters/mso-adapter-utils/src/main/java/org/onap/so/openstack/utils/MsoTenantUtils.java
new file mode 100644
index 0000000000..28911bc45c
--- /dev/null
+++ b/adapters/mso-adapter-utils/src/main/java/org/onap/so/openstack/utils/MsoTenantUtils.java
@@ -0,0 +1,58 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP - SO
+ * ================================================================================
+ * Copyright (C) 2017 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.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.so.openstack.utils;
+
+
+import java.util.Map;
+
+import org.onap.so.cloud.CloudConfig;
+import org.onap.so.cloud.CloudIdentity;
+import org.onap.so.logger.MsoLogger;
+import org.onap.so.openstack.beans.MsoTenant;
+import org.onap.so.openstack.exceptions.MsoCloudSiteNotFound;
+import org.onap.so.openstack.exceptions.MsoException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+@Component
+public abstract class MsoTenantUtils extends MsoCommonUtils {
+
+ protected static MsoLogger LOGGER = MsoLogger.getMsoLogger (MsoLogger.Catalog.RA, MsoTenantUtils.class);
+
+ @Autowired
+ protected CloudConfig cloudConfig;
+
+ public abstract String createTenant (String tenantName, String cloudSiteId, Map <String, String> metadata, boolean backout)
+ throws MsoException;
+
+ public abstract MsoTenant queryTenant (String tenantId, String cloudSiteId)
+ throws MsoException, MsoCloudSiteNotFound;
+
+ public abstract MsoTenant queryTenantByName (String tenantName, String cloudSiteId)
+ throws MsoException, MsoCloudSiteNotFound;
+
+ public abstract boolean deleteTenant (String tenantId, String cloudSiteId)
+ throws MsoException;
+
+ public abstract String getKeystoneUrl (String regionId, CloudIdentity cloudIdentity)
+ throws MsoException;
+
+}
diff --git a/adapters/mso-adapter-utils/src/main/java/org/onap/so/openstack/utils/MsoTenantUtilsFactory.java b/adapters/mso-adapter-utils/src/main/java/org/onap/so/openstack/utils/MsoTenantUtilsFactory.java
new file mode 100644
index 0000000000..68d0ef2fad
--- /dev/null
+++ b/adapters/mso-adapter-utils/src/main/java/org/onap/so/openstack/utils/MsoTenantUtilsFactory.java
@@ -0,0 +1,56 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP - SO
+ * ================================================================================
+ * Copyright (C) 2017 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.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.so.openstack.utils;
+
+import org.onap.so.cloud.CloudConfig;
+import org.onap.so.cloud.CloudSite;
+import org.onap.so.cloud.ServerType;
+import org.onap.so.logger.MsoLogger;
+import org.onap.so.openstack.exceptions.MsoCloudSiteNotFound;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+@Component
+public class MsoTenantUtilsFactory {
+
+ protected static MsoLogger LOGGER = MsoLogger.getMsoLogger(MsoLogger.Catalog.RA, MsoTenantUtilsFactory.class);
+ @Autowired
+ protected CloudConfig cloudConfig;
+ @Autowired
+ protected MsoKeystoneUtils keystoneUtils;
+
+ // based on Cloud IdentityServerType returns ORM or KEYSTONE Utils
+ public MsoTenantUtils getTenantUtils(String cloudSiteId) throws MsoCloudSiteNotFound {
+ CloudSite cloudSite = cloudConfig.getCloudSite(cloudSiteId).orElseThrow(
+ () -> new MsoCloudSiteNotFound(cloudSiteId));
+
+ return getTenantUtilsByServerType(cloudConfig.getIdentityService(cloudSite.getIdentityServiceId()).getIdentityServerType());
+ }
+
+ public MsoTenantUtils getTenantUtilsByServerType(ServerType serverType) {
+
+ MsoTenantUtils tenantU = null;
+ if (ServerType.KEYSTONE.equals(serverType)) {
+ tenantU = keystoneUtils;
+ }
+ return tenantU;
+ }
+}
diff --git a/adapters/mso-adapter-utils/src/main/java/org/onap/so/openstack/utils/MsoYamlEditorWithEnvt.java b/adapters/mso-adapter-utils/src/main/java/org/onap/so/openstack/utils/MsoYamlEditorWithEnvt.java
new file mode 100644
index 0000000000..649eb6b07c
--- /dev/null
+++ b/adapters/mso-adapter-utils/src/main/java/org/onap/so/openstack/utils/MsoYamlEditorWithEnvt.java
@@ -0,0 +1,163 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP - SO
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * Copyright (C) 2017 Huawei Technologies Co., Ltd. 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.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.so.openstack.utils;
+
+
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+import org.onap.so.db.catalog.beans.HeatTemplateParam;
+import org.onap.so.logger.MsoLogger;
+import org.yaml.snakeyaml.Yaml;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+public class MsoYamlEditorWithEnvt {
+
+ private static final MsoLogger LOGGER = MsoLogger.getMsoLogger (MsoLogger.Catalog.RA, MsoYamlEditorWithEnvt.class);
+
+ private Map <String, Object> yml;
+ private Yaml yaml = new Yaml ();
+ private static final ObjectMapper JSON_MAPPER = new ObjectMapper();
+
+ public MsoYamlEditorWithEnvt() {
+ super();
+ }
+ public MsoYamlEditorWithEnvt(byte[] b) {
+ init(b);
+ }
+
+ @SuppressWarnings("unchecked")
+ private synchronized void init (byte[] body) {
+ InputStream input = new ByteArrayInputStream (body);
+ yml = (Map <String, Object>) yaml.load (input);
+ }
+
+ @SuppressWarnings("unchecked")
+ public synchronized Set <MsoHeatEnvironmentParameter> getParameterListFromEnvt() {
+ // In an environment entry, the parameters section can only contain the name:value -
+ // not other attributes.
+ Set <MsoHeatEnvironmentParameter> paramSet = new HashSet<>();
+ Map<String, Object> resourceMap = null;
+ try {
+ resourceMap = (Map<String,Object>) yml.get("parameters");
+ } catch (Exception e) {
+ LOGGER.debug("Exception:", e);
+ return paramSet;
+ }
+ if (resourceMap == null) {
+ return paramSet;
+ }
+
+ for (Entry<String, Object> stringObjectEntry : resourceMap.entrySet()) {
+ MsoHeatEnvironmentParameter hep = new MsoHeatEnvironmentParameter();
+ Entry<String, Object> pair = stringObjectEntry;
+ String value;
+ Object obj = pair.getValue();
+ if (obj instanceof String) {
+ value = yaml.dump(obj);
+ // but this adds an extra '\n' at the end - which won't hurt - but we don't need it
+ value = value.substring(0, value.length() - 1);
+ } else if (obj instanceof LinkedHashMap) {
+ //Handle that it's json
+ try {
+ value = JSON_MAPPER.writeValueAsString(obj);
+ } catch (Exception e) {
+ LOGGER.debug("Exception:", e);
+ value = "_BAD_JSON_MAPPING";
+ }
+ } else {
+ //this handles integers/longs/floats/etc.
+ value = String.valueOf(obj);
+ }
+ hep.setName((String) pair.getKey());
+ hep.setValue(value);
+ paramSet.add(hep);
+ }
+ return paramSet;
+ }
+ public synchronized Set <MsoHeatEnvironmentResource> getResourceListFromEnvt() {
+ try {
+ Set<MsoHeatEnvironmentResource> resourceList = new HashSet<>();
+ @SuppressWarnings("unchecked")
+ Map<String, Object> resourceMap = (Map<String,Object>) yml.get("resource_registry");
+
+ for (Entry<String, Object> stringObjectEntry : resourceMap.entrySet()) {
+ MsoHeatEnvironmentResource her = new MsoHeatEnvironmentResource();
+ Entry<String, Object> pair = stringObjectEntry;
+ her.setName((String) pair.getKey());
+ her.setValue((String) pair.getValue());
+ resourceList.add(her);
+ }
+ return resourceList;
+ } catch (Exception e) {
+ LOGGER.debug("Exception:", e);
+ }
+ return null;
+ }
+ public synchronized Set <HeatTemplateParam> getParameterList () {
+ Set <HeatTemplateParam> paramSet = new HashSet <> ();
+ @SuppressWarnings("unchecked")
+ Map <String, Object> resourceMap = (Map <String, Object>) yml.get ("parameters");
+
+ for (Entry<String, Object> stringObjectEntry : resourceMap.entrySet()) {
+ HeatTemplateParam param = new HeatTemplateParam();
+ Entry<String, Object> pair = stringObjectEntry;
+ @SuppressWarnings("unchecked")
+ Map<String, String> resourceEntry = (Map<String, String>) pair.getValue();
+ String value = null;
+ try {
+ value = resourceEntry.get("default");
+ } catch (ClassCastException cce) {
+ LOGGER.debug("Exception:", cce);
+ // This exception only - the value is an integer. For what we're doing
+ // here - we don't care - so set value to something - and it will
+ // get marked as not being required - which is correct.
+ //System.out.println("cce exception!");
+ value = "300";
+ // okay
+ }
+ param.setParamName((String) pair.getKey());
+ if (value != null) {
+ param.setRequired(false);
+ } else {
+ param.setRequired(true);
+ }
+ value = resourceEntry.get("type");
+ param.setParamType(value);
+
+ paramSet.add(param);
+
+ }
+ return paramSet;
+
+ }
+
+
+}