Synchronize upstream version of 0.9
[parser.git] / tosca2heat / heat-translator / translator / hot / syntax / hot_resource.py
index 7b83906..ff2111a 100644 (file)
@@ -25,6 +25,14 @@ SECTIONS = (TYPE, PROPERTIES, MEDADATA, DEPENDS_ON, UPDATE_POLICY,
             DELETION_POLICY) = \
            ('type', 'properties', 'metadata',
             'depends_on', 'update_policy', 'deletion_policy')
+
+policy_type = ['tosca.policies.Placement',
+               'tosca.policies.Scaling',
+               'tosca.policies.Scaling.Cluster',
+               'tosca.policies.Placement.Colocate',
+               'tosca.policies.Placement.Antilocate',
+               'tosca.policies.Monitoring']
+
 log = logging.getLogger('heat-translator')
 
 
@@ -33,7 +41,7 @@ class HotResource(object):
 
     def __init__(self, nodetemplate, name=None, type=None, properties=None,
                  metadata=None, depends_on=None,
-                 update_policy=None, deletion_policy=None):
+                 update_policy=None, deletion_policy=None, csar_dir=None):
         log.debug(_('Translating TOSCA node type to HOT resource type.'))
         self.nodetemplate = nodetemplate
         if name:
@@ -42,11 +50,19 @@ class HotResource(object):
             self.name = nodetemplate.name
         self.type = type
         self.properties = properties or {}
+
+        self.csar_dir = csar_dir
         # special case for HOT softwareconfig
+        cwd = os.getcwd()
         if type == 'OS::Heat::SoftwareConfig':
             config = self.properties.get('config')
-            if config:
-                implementation_artifact = config.get('get_file')
+            if isinstance(config, dict):
+                if self.csar_dir:
+                    os.chdir(self.csar_dir)
+                    implementation_artifact = os.path.abspath(config.get(
+                        'get_file'))
+                else:
+                    implementation_artifact = config.get('get_file')
                 if implementation_artifact:
                     filename, file_extension = os.path.splitext(
                         implementation_artifact)
@@ -63,7 +79,7 @@ class HotResource(object):
 
             if self.properties.get('group') is None:
                 self.properties['group'] = 'script'
-
+        os.chdir(cwd)
         self.metadata = metadata
 
         # The difference between depends_on and depends_on_nodes is
@@ -126,62 +142,75 @@ class HotResource(object):
         hosting_server = None
         if self.nodetemplate.requirements is not None:
             hosting_server = self._get_hosting_server()
+
+        sw_deployment_resouce = HOTSoftwareDeploymentResources(hosting_server)
+        server_key = sw_deployment_resouce.server_key
+        servers = sw_deployment_resouce.servers
+        sw_deploy_res = sw_deployment_resouce.software_deployment
+
+        # hosting_server is None if requirements is None
+        hosting_on_server = hosting_server if hosting_server else None
+        base_type = HotResource.get_base_type_str(
+            self.nodetemplate.type_definition)
+        # if we are on a compute node the host is self
+        if hosting_on_server is None and base_type == 'tosca.nodes.Compute':
+            hosting_on_server = self.name
+            servers = {'get_resource': self.name}
+
+        cwd = os.getcwd()
         for operation in operations.values():
             if operation.name in operations_deploy_sequence:
                 config_name = node_name + '_' + operation.name + '_config'
                 deploy_name = node_name + '_' + operation.name + '_deploy'
+                if self.csar_dir:
+                    os.chdir(self.csar_dir)
+                    get_file = os.path.abspath(operation.implementation)
+                else:
+                    get_file = operation.implementation
                 hot_resources.append(
                     HotResource(self.nodetemplate,
                                 config_name,
                                 'OS::Heat::SoftwareConfig',
                                 {'config':
-                                    {'get_file': operation.implementation}}))
-
-                # hosting_server is None if requirements is None
-                hosting_on_server = (hosting_server.name if
-                                     hosting_server else None)
-                base_type = HotResource.get_base_type(
-                    self.nodetemplate.type_definition).type
-                # handle interfaces directly defined on a compute
-                if hosting_on_server is None \
-                    and base_type == 'tosca.nodes.Compute':
-                    hosting_on_server = self.name
-
+                                    {'get_file': get_file}},
+                                csar_dir=self.csar_dir))
                 if operation.name == reserve_current and \
                     base_type != 'tosca.nodes.Compute':
                     deploy_resource = self
                     self.name = deploy_name
-                    self.type = 'OS::Heat::SoftwareDeployment'
+                    self.type = sw_deploy_res
                     self.properties = {'config': {'get_resource': config_name},
-                                       'server': {'get_resource':
-                                                  hosting_on_server},
+                                       server_key: servers,
                                        'signal_transport': 'HEAT_SIGNAL'}
                     deploy_lookup[operation] = self
                 else:
                     sd_config = {'config': {'get_resource': config_name},
-                                 'server': {'get_resource':
-                                            hosting_on_server},
+                                 server_key: servers,
                                  'signal_transport': 'HEAT_SIGNAL'}
                     deploy_resource = \
                         HotResource(self.nodetemplate,
                                     deploy_name,
-                                    'OS::Heat::SoftwareDeployment',
-                                    sd_config)
+                                    sw_deploy_res,
+                                    sd_config, csar_dir=self.csar_dir)
                     hot_resources.append(deploy_resource)
                     deploy_lookup[operation] = deploy_resource
                 lifecycle_inputs = self._get_lifecycle_inputs(operation)
                 if lifecycle_inputs:
                     deploy_resource.properties['input_values'] = \
                         lifecycle_inputs
+        os.chdir(cwd)
 
         # Add dependencies for the set of HOT resources in the sequence defined
         # in operations_deploy_sequence
         # TODO(anyone): find some better way to encode this implicit sequence
         group = {}
+        op_index_min = None
         op_index_max = -1
         for op, hot in deploy_lookup.items():
             # position to determine potential preceding nodes
             op_index = operations_deploy_sequence.index(op.name)
+            if op_index_min is None or op_index < op_index_min:
+                op_index_min = op_index
             if op_index > op_index_max:
                 op_index_max = op_index
             for preceding_op_name in \
@@ -205,8 +234,60 @@ class HotResource(object):
         for hot in hot_resources:
             hot.group_dependencies.update(group)
 
+        roles_deploy_resource = self._handle_ansiblegalaxy_roles(
+            hot_resources, node_name, servers)
+
+        # add a dependency to this ansible roles deploy to
+        # the first "classic" deploy generated for this node
+        if roles_deploy_resource and op_index_min:
+            first_deploy = deploy_lookup.get(operations.get(
+                operations_deploy_sequence[op_index_min]))
+            first_deploy.depends_on.append(roles_deploy_resource)
+            first_deploy.depends_on_nodes.append(roles_deploy_resource)
+
         return hot_resources, deploy_lookup, last_deploy
 
+    def _handle_ansiblegalaxy_roles(self, hot_resources, initial_node_name,
+                                    hosting_on_server):
+        artifacts = self.get_all_artifacts(self.nodetemplate)
+        install_roles_script = ''
+
+        sw_deployment_resouce = \
+            HOTSoftwareDeploymentResources(hosting_on_server)
+        server_key = sw_deployment_resouce.server_key
+        sw_deploy_res = sw_deployment_resouce.software_deployment
+        for artifact_name, artifact in artifacts.items():
+            artifact_type = artifact.get('type', '').lower()
+            if artifact_type == 'tosca.artifacts.ansiblegalaxy.role':
+                role = artifact.get('file', None)
+                if role:
+                    install_roles_script += 'ansible-galaxy install ' + role \
+                                            + '\n'
+
+        if install_roles_script:
+            # remove trailing \n
+            install_roles_script = install_roles_script[:-1]
+            # add shebang and | to use literal scalar type (for multiline)
+            install_roles_script = '|\n#!/bin/bash\n' + install_roles_script
+
+            config_name = initial_node_name + '_install_roles_config'
+            deploy_name = initial_node_name + '_install_roles_deploy'
+            hot_resources.append(
+                HotResource(self.nodetemplate, config_name,
+                            'OS::Heat::SoftwareConfig',
+                            {'config': install_roles_script},
+                            csar_dir=self.csar_dir))
+            sd_config = {'config': {'get_resource': config_name},
+                         server_key: hosting_on_server,
+                         'signal_transport': 'HEAT_SIGNAL'}
+            deploy_resource = \
+                HotResource(self.nodetemplate, deploy_name,
+                            sw_deploy_res,
+                            sd_config, csar_dir=self.csar_dir)
+            hot_resources.append(deploy_resource)
+
+            return deploy_resource
+
     def handle_connectsto(self, tosca_source, tosca_target, hot_source,
                           hot_target, config_location, operation):
         # The ConnectsTo relationship causes a configuration operation in
@@ -220,17 +301,22 @@ class HotResource(object):
         elif config_location == 'source':
             hosting_server = self._get_hosting_server()
             hot_depends = hot_source
+        sw_deployment_resouce = HOTSoftwareDeploymentResources(hosting_server)
+        server_key = sw_deployment_resouce.server_key
+        servers = sw_deployment_resouce.servers
+        sw_deploy_res = sw_deployment_resouce.software_deployment
+
         deploy_name = tosca_source.name + '_' + tosca_target.name + \
             '_connect_deploy'
         sd_config = {'config': {'get_resource': self.name},
-                     'server': {'get_resource': hosting_server.name},
+                     server_key: servers,
                      'signal_transport': 'HEAT_SIGNAL'}
         deploy_resource = \
             HotResource(self.nodetemplate,
                         deploy_name,
-                        'OS::Heat::SoftwareDeployment',
+                        sw_deploy_res,
                         sd_config,
-                        depends_on=[hot_depends])
+                        depends_on=[hot_depends], csar_dir=self.csar_dir)
         connect_inputs = self._get_connect_inputs(config_location, operation)
         if connect_inputs:
             deploy_resource.properties['input_values'] = connect_inputs
@@ -244,17 +330,31 @@ class HotResource(object):
         # handle hosting server for the OS:HEAT::SoftwareDeployment
         # from the TOSCA nodetemplate, traverse the relationship chain
         # down to the server
-        if self.type == 'OS::Heat::SoftwareDeployment':
+        sw_deploy_group = \
+            HOTSoftwareDeploymentResources.HOT_SW_DEPLOYMENT_GROUP_RESOURCE
+        sw_deploy = HOTSoftwareDeploymentResources.HOT_SW_DEPLOYMENT_RESOURCE
+
+        if self.properties.get('servers') and \
+                self.properties.get('server'):
+            del self.properties['server']
+        if self.type == sw_deploy_group or self.type == sw_deploy:
             # skip if already have hosting
             # If type is NodeTemplate, look up corresponding HotResrouce
-            host_server = self.properties.get('server')
-            if host_server is None or not host_server['get_resource']:
+            host_server = self.properties.get('servers') \
+                or self.properties.get('server')
+            if host_server is None:
                 raise Exception(_("Internal Error: expecting host "
                                   "in software deployment"))
-            elif isinstance(host_server['get_resource'], NodeTemplate):
+
+            elif isinstance(host_server.get('get_resource'), NodeTemplate):
                 self.properties['server']['get_resource'] = \
                     host_server['get_resource'].name
 
+            elif isinstance(host_server, dict) and \
+                not host_server.get('get_resource'):
+                self.properties['servers'] = \
+                    host_server
+
     def top_of_chain(self):
         dependent = self.group_dependencies.get(self)
         if dependent is None:
@@ -262,6 +362,19 @@ class HotResource(object):
         else:
             return dependent.top_of_chain()
 
+    # this function allows to provides substacks as external files
+    # those files will be dumped along the output file.
+    #
+    # return a dict of filename-content
+    def extract_substack_templates(self, base_filename, hot_template_version):
+        return {}
+
+    # this function asks the resource to embed substacks
+    # into the main template, if any.
+    # this is used when the final output is stdout
+    def embed_substack_templates(self, hot_template_version):
+        pass
+
     def get_dict_output(self):
         resource_sections = OrderedDict()
         resource_sections[TYPE] = self.type
@@ -291,7 +404,7 @@ class HotResource(object):
             inputs = operation.value.get('inputs')
             deploy_inputs = {}
             if inputs:
-                for name, value in six.iteritems(inputs):
+                for name, value in inputs.items():
                     deploy_inputs[name] = value
             return deploy_inputs
 
@@ -302,17 +415,19 @@ class HotResource(object):
             inputs = operation.get('pre_configure_source').get('inputs')
         deploy_inputs = {}
         if inputs:
-            for name, value in six.iteritems(inputs):
+            for name, value in inputs.items():
                 deploy_inputs[name] = value
         return deploy_inputs
 
     def _get_hosting_server(self, node_template=None):
         # find the server that hosts this software by checking the
         # requirements and following the hosting chain
+        hosting_servers = []
+        host_exists = False
         this_node_template = self.nodetemplate \
             if node_template is None else node_template
         for requirement in this_node_template.requirements:
-            for requirement_name, assignment in six.iteritems(requirement):
+            for requirement_name, assignment in requirement.items():
                 for check_node in this_node_template.related_nodes:
                     # check if the capability is Container
                     if isinstance(assignment, dict):
@@ -322,17 +437,20 @@ class HotResource(object):
                     if node_name and node_name == check_node.name:
                         if self._is_container_type(requirement_name,
                                                    check_node):
-                            return check_node
-                        elif check_node.related_nodes:
+                            hosting_servers.append(check_node.name)
+                            host_exists = True
+                        elif check_node.related_nodes and not host_exists:
                             return self._get_hosting_server(check_node)
+        if hosting_servers:
+            return hosting_servers
         return None
 
     def _is_container_type(self, requirement_name, node):
         # capability is a list of dict
         # For now just check if it's type tosca.nodes.Compute
         # TODO(anyone): match up requirement and capability
-        base_type = HotResource.get_base_type(node.type_definition)
-        if base_type.type == 'tosca.nodes.Compute':
+        base_type = HotResource.get_base_type_str(node.type_definition)
+        if base_type == 'tosca.nodes.Compute':
             return True
         else:
             return False
@@ -352,6 +470,23 @@ class HotResource(object):
                 tosca_props[prop.name] = prop.value
         return tosca_props
 
+    @staticmethod
+    def get_all_artifacts(nodetemplate):
+        # workaround bug in the parser
+        base_type = HotResource.get_base_type_str(nodetemplate.type_definition)
+        if base_type in policy_type:
+            artifacts = {}
+        else:
+            artifacts = nodetemplate.type_definition.get_value('artifacts',
+                                                               parent=True)
+        if not artifacts:
+            artifacts = {}
+        tpl_artifacts = nodetemplate.entity_tpl.get('artifacts')
+        if tpl_artifacts:
+            artifacts.update(tpl_artifacts)
+
+        return artifacts
+
     @staticmethod
     def get_all_operations(node):
         operations = {}
@@ -406,5 +541,48 @@ class HotResource(object):
                 return node_type
             else:
                 return HotResource.get_base_type(node_type.parent_type)
-        else:
+        return node_type.type
+
+    @staticmethod
+    def get_base_type_str(node_type):
+        if isinstance(node_type, six.string_types):
             return node_type
+        if node_type.parent_type is not None:
+            parent_type_str = None
+            if isinstance(node_type.parent_type, six.string_types):
+                parent_type_str = node_type.parent_type
+            else:
+                parent_type_str = node_type.parent_type.type
+
+            if parent_type_str and parent_type_str.endswith('.Root'):
+                return node_type.type
+            else:
+                return HotResource.get_base_type_str(node_type.parent_type)
+
+        return node_type.type
+
+
+class HOTSoftwareDeploymentResources(object):
+    """Provides HOT Software Deployment resources
+
+    SoftwareDeployment or SoftwareDeploymentGroup Resource
+    """
+
+    HOT_SW_DEPLOYMENT_RESOURCE = 'OS::Heat::SoftwareDeployment'
+    HOT_SW_DEPLOYMENT_GROUP_RESOURCE = 'OS::Heat::SoftwareDeploymentGroup'
+
+    def __init__(self, hosting_server=None):
+        self.software_deployment = self.HOT_SW_DEPLOYMENT_RESOURCE
+        self.software_deployment_group = self.HOT_SW_DEPLOYMENT_GROUP_RESOURCE
+        self.server_key = 'server'
+        self.hosting_server = hosting_server
+        self.servers = {}
+        if hosting_server is not None:
+            if len(self.hosting_server) == 1:
+                if isinstance(hosting_server, list):
+                    self.servers['get_resource'] = self.hosting_server[0]
+            else:
+                for server in self.hosting_server:
+                    self.servers[server] = {'get_resource': server}
+                self.software_deployment = self.software_deployment_group
+                self.server_key = 'servers'