diff --git a/translator/hot/syntax/hot_resource.py b/translator/hot/syntax/hot_resource.py index d0443088..19bcd7a1 100644 --- a/translator/hot/syntax/hot_resource.py +++ b/translator/hot/syntax/hot_resource.py @@ -141,14 +141,22 @@ class HotResource(object): # hosting_server is None if requirements is None hosting_on_server = (hosting_server.name if hosting_server else None) - if operation.name == reserve_current: + 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 + + if operation.name == reserve_current and \ + base_type != 'tosca.nodes.Compute': deploy_resource = self self.name = deploy_name self.type = 'OS::Heat::SoftwareDeployment' self.properties = {'config': {'get_resource': config_name}, 'server': {'get_resource': hosting_on_server}} - deploy_lookup[operation.name] = self + deploy_lookup[operation] = self else: sd_config = {'config': {'get_resource': config_name}, 'server': {'get_resource': @@ -159,7 +167,7 @@ class HotResource(object): 'OS::Heat::SoftwareDeployment', sd_config) hot_resources.append(deploy_resource) - deploy_lookup[operation.name] = deploy_resource + deploy_lookup[operation] = deploy_resource lifecycle_inputs = self._get_lifecycle_inputs(operation) if lifecycle_inputs: deploy_resource.properties['input_values'] = \ @@ -169,24 +177,34 @@ class HotResource(object): # in operations_deploy_sequence # TODO(anyone): find some better way to encode this implicit sequence group = {} + op_index_max = -1 for op, hot in deploy_lookup.items(): # position to determine potential preceding nodes - op_index = operations_deploy_sequence.index(op) - for preceding_op in \ + op_index = operations_deploy_sequence.index(op.name) + if op_index > op_index_max: + op_index_max = op_index + for preceding_op_name in \ reversed(operations_deploy_sequence[:op_index]): - preceding_hot = deploy_lookup.get(preceding_op) + preceding_hot = deploy_lookup.get( + operations.get(preceding_op_name)) if preceding_hot: hot.depends_on.append(preceding_hot) hot.depends_on_nodes.append(preceding_hot) group[preceding_hot] = hot break + if op_index_max >= 0: + last_deploy = deploy_lookup.get(operations.get( + operations_deploy_sequence[op_index_max])) + else: + last_deploy = None + # save this dependency chain in the set of HOT resources self.group_dependencies.update(group) for hot in hot_resources: hot.group_dependencies.update(group) - return hot_resources + return hot_resources, deploy_lookup, last_deploy def handle_connectsto(self, tosca_source, tosca_target, hot_source, hot_target, config_location, operation): diff --git a/translator/hot/tosca/tosca_block_storage_attachment.py b/translator/hot/tosca/tosca_block_storage_attachment.py index 71b98225..2242c2d9 100644 --- a/translator/hot/tosca/tosca_block_storage_attachment.py +++ b/translator/hot/tosca/tosca_block_storage_attachment.py @@ -50,4 +50,4 @@ class ToscaBlockStorageAttachment(HotResource): self.properties.pop('device') def handle_life_cycle(self): - pass + return None, None, None diff --git a/translator/hot/tosca/tosca_compute.py b/translator/hot/tosca/tosca_compute.py index 08fbfc67..b8ad83ca 100755 --- a/translator/hot/tosca/tosca_compute.py +++ b/translator/hot/tosca/tosca_compute.py @@ -84,6 +84,14 @@ class ToscaCompute(HotResource): ('architecture', 'distribution', 'type', 'version') toscatype = 'tosca.nodes.Compute' + ALLOWED_NOVA_SERVER_PROPS = \ + ('admin_pass', 'availability_zone', 'block_device_mapping', + 'block_device_mapping_v2', 'config_drive', 'diskConfig', 'flavor', + 'flavor_update_policy', 'image', 'image_update_policy', 'key_name', + 'metadata', 'name', 'networks', 'personality', 'reservation_id', + 'scheduler_hints', 'security_groups', 'software_config_transport', + 'user_data', 'user_data_format', 'user_data_update_policy') + def __init__(self, nodetemplate): super(ToscaCompute, self).__init__(nodetemplate, type='OS::Nova::Server') @@ -98,7 +106,8 @@ class ToscaCompute(HotResource): self.properties['user_data_format'] = 'SOFTWARE_CONFIG' tosca_props = self.get_tosca_props() for key, value in tosca_props.items(): - self.properties[key] = value + if key in self.ALLOWED_NOVA_SERVER_PROPS: + self.properties[key] = value # To be reorganized later based on new development in Glance and Graffiti def translate_compute_flavor_and_image(self, diff --git a/translator/hot/translate_node_templates.py b/translator/hot/translate_node_templates.py index 14cf6337..3b11fd91 100644 --- a/translator/hot/translate_node_templates.py +++ b/translator/hot/translate_node_templates.py @@ -146,6 +146,9 @@ class TranslateNodeTemplates(object): log.debug(_('Mapping between TOSCA nodetemplate and HOT resource.')) self.hot_lookup = {} self.policies = self.tosca.topology_template.policies + # stores the last deploy of generated behavior for a resource + # useful to satisfy underlying dependencies between interfaces + self.last_deploy_map = {} def translate(self): return self._translate_nodetemplates() @@ -219,9 +222,14 @@ class TranslateNodeTemplates(object): # into multiple HOT resources and may change their name lifecycle_resources = [] for resource in self.hot_resources: - expanded = resource.handle_life_cycle() - if expanded: - lifecycle_resources += expanded + expanded_resources, deploy_lookup, last_deploy = resource.\ + handle_life_cycle() + if expanded_resources: + lifecycle_resources += expanded_resources + if deploy_lookup: + self.hot_lookup.update(deploy_lookup) + if last_deploy: + self.last_deploy_map[resource] = last_deploy self.hot_resources += lifecycle_resources # Handle configuration from ConnectsTo relationship in the TOSCA node: @@ -253,7 +261,9 @@ class TranslateNodeTemplates(object): # if the source of dependency is a server and the # relationship type is 'tosca.relationships.HostedOn', # add dependency as properties.server - if node_depend.type == 'tosca.nodes.Compute' and \ + base_type = HotResource.get_base_type( + node_depend.type_definition) + if base_type.type == 'tosca.nodes.Compute' and \ node.related[node_depend].type == \ node.type_definition.HOSTEDON: self.hot_lookup[node].properties['server'] = \ @@ -266,6 +276,13 @@ class TranslateNodeTemplates(object): self.hot_lookup[node].depends_on_nodes.append( self.hot_lookup[node_depend].top_of_chain()) + last_deploy = self.last_deploy_map.get( + self.hot_lookup[node_depend]) + if last_deploy and \ + last_deploy not in self.hot_lookup[node].depends_on: + self.hot_lookup[node].depends_on.append(last_deploy) + self.hot_lookup[node].depends_on_nodes.append(last_deploy) + # handle hosting relationship for resource in self.hot_resources: resource.handle_hosting() @@ -297,6 +314,17 @@ class TranslateNodeTemplates(object): for name, value in six.iteritems(inputs): inputs[name] = self._translate_input(value, resource) + # remove resources without type defined + # for example a SoftwareComponent without interfaces + # would fall in this case + to_remove = [] + for resource in self.hot_resources: + if resource.type is None: + to_remove.append(resource) + + for resource in to_remove: + self.hot_resources.remove(resource) + return self.hot_resources def _translate_input(self, input_value, resource): diff --git a/translator/tests/data/hot_output/hot_interface_on_compute.yaml b/translator/tests/data/hot_output/hot_interface_on_compute.yaml new file mode 100644 index 00000000..0399c062 --- /dev/null +++ b/translator/tests/data/hot_output/hot_interface_on_compute.yaml @@ -0,0 +1,44 @@ +heat_template_version: 2013-05-23 + +description: > + TOSCA template to test Compute node with interface + +parameters: {} +resources: + softwarecomponent_depending_on_customcompute_install_create_deploy: + type: OS::Heat::SoftwareDeployment + properties: + config: + get_resource: softwarecomponent_depending_on_customcompute_install_create_config + server: + get_resource: server + depends_on: + - server_create_deploy + server: + type: OS::Nova::Server + properties: + flavor: m1.small + image: ubuntu-12.04-software-config-os-init + user_data_format: SOFTWARE_CONFIG + softwarecomponent_depending_on_customcompute_install_create_config: + type: OS::Heat::SoftwareConfig + properties: + config: + get_file: post_install.sh + group: script + server_create_config: + type: OS::Heat::SoftwareConfig + properties: + config: + get_file: install.sh + group: script + server_create_deploy: + type: OS::Heat::SoftwareDeployment + properties: + config: + get_resource: server_create_config + input_values: + install_path: /opt + server: + get_resource: server +outputs: {} \ No newline at end of file diff --git a/translator/tests/data/test_tosca_interface_on_compute.yaml b/translator/tests/data/test_tosca_interface_on_compute.yaml new file mode 100644 index 00000000..e033c3c0 --- /dev/null +++ b/translator/tests/data/test_tosca_interface_on_compute.yaml @@ -0,0 +1,48 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +description: TOSCA template to test Compute node with interface + +node_types: + tosca.nodes.CustomCompute: + derived_from: tosca.nodes.Compute + properties: + install_path: + type: string + default: /opt + interfaces: + Standard: + create: + implementation: install.sh + inputs: + install_path: { get_property: [ SELF, install_path ] } + +topology_template: + node_templates: + + softwarecomponent_without_behavior: + type: tosca.nodes.SoftwareComponent + requirements: + - host: server + + softwarecomponent_depending_on_customcompute_install: + type: tosca.nodes.SoftwareComponent + interfaces: + Standard: + create: + implementation: post_install.sh + requirements: + - host: server + + server: + type: tosca.nodes.CustomCompute + capabilities: + host: + properties: + num_cpus: 1 + mem_size: 1 GB + os: + properties: + type: Linux + distribution: Ubuntu + version: 12.04 + architecture: x86_64 diff --git a/translator/tests/test_tosca_hot_translation.py b/translator/tests/test_tosca_hot_translation.py index 6dca898b..3723b99d 100644 --- a/translator/tests/test_tosca_hot_translation.py +++ b/translator/tests/test_tosca_hot_translation.py @@ -471,3 +471,9 @@ class ToscaHotTranslationTest(TestCase): hot_file = '../tests/data/hot_output/hot_script_types.yaml' params = {} self._test_successful_translation(tosca_file, hot_file, params) + + def test_hot_interface_on_compute(self): + tosca_file = '../tests/data/test_tosca_interface_on_compute.yaml' + hot_file = '../tests/data/hot_output/hot_interface_on_compute.yaml' + params = {} + self._test_successful_translation(tosca_file, hot_file, params)