From 5b1bf8c71b21d9645decbcc88285c2f215d3cb40 Mon Sep 17 00:00:00 2001 From: Simeon Monov Date: Mon, 16 Mar 2015 14:04:26 +0100 Subject: [PATCH] Add TOSCA networking features Add implementation for TOSCA networking features. Imeplement following tosca nodes and relationships: - tosca.nodes.network.Network - tosca.nodes.network.Port - tosca.relationships.network.LinksTo - tosca.relationships.network.BindsTo Partially-implements: blueprint tosca-networking Change-Id: Ic97094cd661cd8d118ed092a981f6a7e3c25f857 --- translator/hot/syntax/hot_resource.py | 21 ++ translator/hot/syntax/hot_template.py | 3 +- translator/hot/tosca/tosca_compute.py | 12 +- translator/hot/tosca/tosca_network_network.py | 115 +++++++++ translator/hot/tosca/tosca_network_port.py | 113 +++++++++ translator/hot/translate_node_templates.py | 37 ++- .../tosca_one_server_one_network.yaml | 43 ++++ .../tosca_one_server_three_networks.yaml | 68 ++++++ .../tosca_server_on_existing_network.yaml | 27 +++ .../tosca_two_servers_one_network.yaml | 72 ++++++ .../data/tosca_one_server_one_network.yaml | 41 ++++ .../data/tosca_one_server_three_networks.yaml | 62 +++++ .../tosca_server_on_existing_network.yaml | 36 +++ .../data/tosca_two_servers_one_network.yaml | 72 ++++++ translator/tests/test_network.py | 228 ++++++++++++++++++ 15 files changed, 944 insertions(+), 6 deletions(-) create mode 100644 translator/hot/tosca/tosca_network_network.py create mode 100644 translator/hot/tosca/tosca_network_port.py create mode 100644 translator/tests/data/hot_ouput/tosca_one_server_one_network.yaml create mode 100644 translator/tests/data/hot_ouput/tosca_one_server_three_networks.yaml create mode 100644 translator/tests/data/hot_ouput/tosca_server_on_existing_network.yaml create mode 100644 translator/tests/data/hot_ouput/tosca_two_servers_one_network.yaml create mode 100644 translator/tests/data/tosca_one_server_one_network.yaml create mode 100644 translator/tests/data/tosca_one_server_three_networks.yaml create mode 100644 translator/tests/data/tosca_server_on_existing_network.yaml create mode 100644 translator/tests/data/tosca_two_servers_one_network.yaml create mode 100644 translator/tests/test_network.py diff --git a/translator/hot/syntax/hot_resource.py b/translator/hot/syntax/hot_resource.py index 8092023..1e0f1c9 100644 --- a/translator/hot/syntax/hot_resource.py +++ b/translator/hot/syntax/hot_resource.py @@ -12,9 +12,12 @@ # under the License. import six + +from translator.toscalib.functions import GetInput from translator.toscalib.nodetemplate import NodeTemplate from translator.toscalib.utils.gettextutils import _ + SECTIONS = (TYPE, PROPERTIES, MEDADATA, DEPENDS_ON, UPDATE_POLICY, DELETION_POLICY) = \ ('type', 'properties', 'metadata', @@ -40,11 +43,16 @@ class HotResource(object): self.metadata = metadata if depends_on: self.depends_on = depends_on + self.depends_on_nodes = depends_on else: self.depends_on = [] + self.depends_on_nodes = [] self.update_policy = update_policy self.deletion_policy = deletion_policy self.group_dependencies = {} + # if hide_resource is set to true, then this resource will not be + # generated in the output yaml. + self.hide_resource = False def handle_properties(self): # the property can hold a value or the intrinsic function get_input @@ -127,6 +135,7 @@ class HotResource(object): preceding_hot = deploy_lookup.get(preceding_op) if preceding_hot: hot.depends_on.append(preceding_hot) + hot.depends_on_nodes.append(preceding_hot) group[preceding_hot] = hot break @@ -137,6 +146,9 @@ class HotResource(object): return hot_resources + def handle_expansion(self): + pass + def handle_hosting(self): # handle hosting server for the OS:HEAT::SoftwareDeployment # from the TOSCA nodetemplate, traverse the relationship chain @@ -217,3 +229,12 @@ class HotResource(object): # if translation is needed for the particular attribute raise Exception(_("No translation in TOSCA type {0} for attribute " "{1}").format(self.nodetemplate.type, attribute)) + + def _get_tosca_props(self, properties): + tosca_props = {} + for prop in self.nodetemplate.properties: + if isinstance(prop.value, GetInput): + tosca_props[prop.name] = {'get_param': prop.value.input_name} + else: + tosca_props[prop.name] = prop.value + return tosca_props diff --git a/translator/hot/syntax/hot_template.py b/translator/hot/syntax/hot_template.py index fc8f00a..898312e 100644 --- a/translator/hot/syntax/hot_template.py +++ b/translator/hot/syntax/hot_template.py @@ -52,7 +52,8 @@ class HotTemplate(object): # Resources all_resources = {} for resource in self.resources: - all_resources.update(resource.get_dict_output()) + if not resource.hide_resource: + all_resources.update(resource.get_dict_output()) dict_output.update({self.RESOURCES: all_resources}) # Outputs diff --git a/translator/hot/tosca/tosca_compute.py b/translator/hot/tosca/tosca_compute.py index 83a8e4a..88af171 100755 --- a/translator/hot/tosca/tosca_compute.py +++ b/translator/hot/tosca/tosca_compute.py @@ -47,7 +47,11 @@ IMAGES = {'ubuntu-software-config-os-init': {'architecture': 'x86_64', 'cirros-0.3.1-x86_64-uec': {'architecture': 'x86_64', 'type': 'Linux', 'distribution': 'CirrOS', - 'version': '0.3.1'}} + 'version': '0.3.1'}, + 'cirros-0.3.2-x86_64-uec': {'architecture': 'x86_64', + 'type': 'Linux', + 'distribution': 'CirrOS', + 'version': '0.3.2'}} class ToscaCompute(HotResource): @@ -58,12 +62,14 @@ class ToscaCompute(HotResource): def __init__(self, nodetemplate): super(ToscaCompute, self).__init__(nodetemplate, type='OS::Nova::Server') + # List with associated hot port resources with this server + self.assoc_port_resources = [] pass def handle_properties(self): - self.properties = self.translate_compute_flavor_and_image( + self.properties.update(self.translate_compute_flavor_and_image( self.nodetemplate.properties, - self.nodetemplate.get_capability('os')) + self.nodetemplate.get_capability('os'))) self.properties['user_data_format'] = 'SOFTWARE_CONFIG' # TODO(anyone): handle user key # hardcoded here for testing diff --git a/translator/hot/tosca/tosca_network_network.py b/translator/hot/tosca/tosca_network_network.py new file mode 100644 index 0000000..635f912 --- /dev/null +++ b/translator/hot/tosca/tosca_network_network.py @@ -0,0 +1,115 @@ +# +# 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. + +from translator.hot.syntax.hot_resource import HotResource +from translator.toscalib.common.exception import InvalidPropertyValueError + + +class ToscaNetwork(HotResource): + '''Translate TOSCA node type tosca.nodes.network.Network.''' + + toscatype = 'tosca.nodes.network.Network' + SUBNET_SUFFIX = '_subnet' + NETWORK_PROPS = ['network_name', 'network_id', 'segmentation_id'] + SUBNET_PROPS = ['ip_version', 'cidr', 'start_ip', 'end_ip', 'gateway_ip'] + + existing_resource_id = None + + def __init__(self, nodetemplate): + super(ToscaNetwork, self).__init__(nodetemplate, + type='OS::Neutron::Net') + pass + + def handle_properties(self): + tosca_props = self._get_tosca_props(self.nodetemplate.properties) + + net_props = {} + for key, value in tosca_props.items(): + if key in self.NETWORK_PROPS: + if key == 'network_name': + # If CIDR is specified network_name should + # be used as the name for the new network. + if 'cidr' in tosca_props.keys(): + net_props['name'] = value + # If CIDR is not specified network_name will be used + # to lookup existing network. If network_id is specified + # together with network_name then network_id should be + # used to lookup the network instead + elif 'network_id' not in tosca_props.keys(): + self.hide_resource = True + self.existing_resource_id = value + break + elif key == 'network_id': + self.hide_resource = True + self.existing_resource_id = value + break + elif key == 'segmentation_id': + net_props['segmentation_id'] = \ + tosca_props['segmentation_id'] + # Hardcode to vxlan for now until we add the network type + # and physical network to the spec. + net_props['value_specs'] = {'provider:segmentation_id': + value, 'provider:network_type': + 'vxlan'} + self.properties = net_props + + def handle_expansion(self): + # If the network resource should not be output (they are hidden), + # there is no need to generate subnet resource + if self.hide_resource: + return + + tosca_props = self._get_tosca_props(self.nodetemplate.properties) + + subnet_props = {} + + ip_pool_start = None + ip_pool_end = None + + for key, value in tosca_props.items(): + if key in self.SUBNET_PROPS: + if key == 'start_ip': + ip_pool_start = value + elif key == 'end_ip': + ip_pool_end = value + elif key == 'dhcp_enabled': + subnet_props['enable_dhcp'] = value + else: + subnet_props[key] = value + + if 'network_id' in tosca_props: + subnet_props['network'] = tosca_props['network_id'] + else: + subnet_props['network'] = '{ get_resource: %s }' % (self.name) + + # Handle allocation pools + # Do this only if both start_ip and end_ip are provided + # If one of them is missing throw an exception. + if ip_pool_start and ip_pool_end: + allocation_pool = {} + allocation_pool['start'] = ip_pool_start + allocation_pool['end'] = ip_pool_end + allocation_pools = [allocation_pool] + subnet_props['allocation_pools'] = allocation_pools + elif ip_pool_start: + raise InvalidPropertyValueError(what='start_ip') + elif ip_pool_end: + raise InvalidPropertyValueError(what='end_ip') + + subnet_resource_name = self.name + self.SUBNET_SUFFIX + + hot_resources = [HotResource(self.nodetemplate, + type='OS::Neutron::Subnet', + name=subnet_resource_name, + properties=subnet_props)] + return hot_resources diff --git a/translator/hot/tosca/tosca_network_port.py b/translator/hot/tosca/tosca_network_port.py new file mode 100644 index 0000000..fdb4061 --- /dev/null +++ b/translator/hot/tosca/tosca_network_port.py @@ -0,0 +1,113 @@ +# +# 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. + +from translator.hot.syntax.hot_resource import HotResource + + +class ToscaNetworkPort(HotResource): + '''Translate TOSCA node type tosca.nodes.network.Port.''' + + toscatype = 'tosca.nodes.network.Port' + + def __init__(self, nodetemplate): + super(ToscaNetworkPort, self).__init__(nodetemplate, + type='OS::Neutron::Port') + # Default order + self.order = 0 + pass + + def _generate_networks_for_compute(self, port_resources): + '''Generate compute networks property list from the port resources.''' + networks = [] + for resource in port_resources: + networks.append({'port': '{ get_resource: %s }' % (resource.name)}) + return networks + + def _insert_sorted_resource(self, resources, resource): + '''Insert a resource in the list of resources and keep the order.''' + lo = 0 + hi = len(resources) + while lo < hi: + mid = (lo + hi) // 2 + if resource.order < resources[mid].order: + hi = mid + else: + lo = mid + 1 + resources.insert(lo, resource) + + def handle_properties(self): + tosca_props = self._get_tosca_props(self.nodetemplate.properties) + + port_props = {} + for key, value in tosca_props.items(): + if key == 'ip_address': + fixed_ip = [] + fixed_ip['ip_address'] = value + fixed_ip['subnet'] = '' + port_props['fixed_ips'] = [fixed_ip] + elif key == 'order': + self.order = value + #TODO(sdmonov). Need to implement the properties below + elif key == 'is_default': + pass + elif key == 'ip_range_start': + pass + elif key == 'ip_range_end': + pass + else: + port_props[key] = value + + # Get the nodetype relationships + relationships = {relation.type: node for relation, node in + self.nodetemplate.relationships.items()} + + # Check for LinksTo relations. If found add a network property with + # the network name into the port + links_to = None + if 'tosca.relationships.network.LinksTo' in relationships: + links_to = relationships['tosca.relationships.network.LinksTo'] + + network_resource = None + for hot_resource in self.depends_on_nodes: + if links_to.name == hot_resource.name: + network_resource = hot_resource + break + + if network_resource.existing_resource_id: + port_props['network'] =\ + str(network_resource.existing_resource_id) + self.depends_on = None + else: + port_props['network'] = '{ get_resource: %s }'\ + % (links_to.name) + + # Check for BindsTo relationship. If found add network to the networks + # property of the corresponding compute resource + binds_to = None + if 'tosca.relationships.network.BindsTo' in relationships: + binds_to = relationships['tosca.relationships.network.BindsTo'] + compute_resource = None + for hot_resource in self.depends_on_nodes: + if binds_to.name == hot_resource.name: + compute_resource = hot_resource + break + if compute_resource: + port_resources = compute_resource.assoc_port_resources + self._insert_sorted_resource(port_resources, self) + #TODO(sdmonov). Using generate networks every time we add a + # network is not the fastest way to do the things. We should + # do this only once at the end. + networks = self._generate_networks_for_compute(port_resources) + compute_resource.properties['networks'] = networks + + self.properties = port_props diff --git a/translator/hot/translate_node_templates.py b/translator/hot/translate_node_templates.py index 7147427..0c2002a 100644 --- a/translator/hot/translate_node_templates.py +++ b/translator/hot/translate_node_templates.py @@ -12,6 +12,7 @@ # under the License. import six + from translator.hot.tosca.tosca_block_storage import ToscaBlockStorage from translator.hot.tosca.tosca_block_storage_attachment import ( ToscaBlockStorageAttachment @@ -19,6 +20,8 @@ from translator.hot.tosca.tosca_block_storage_attachment import ( from translator.hot.tosca.tosca_compute import ToscaCompute from translator.hot.tosca.tosca_database import ToscaDatabase from translator.hot.tosca.tosca_dbms import ToscaDbms +from translator.hot.tosca.tosca_network_network import ToscaNetwork +from translator.hot.tosca.tosca_network_port import ToscaNetworkPort from translator.hot.tosca.tosca_nodejs import ToscaNodejs from translator.hot.tosca.tosca_webserver import ToscaWebserver from translator.hot.tosca.tosca_wordpress import ToscaWordpress @@ -27,6 +30,7 @@ from translator.toscalib.functions import GetInput from translator.toscalib.functions import GetProperty from translator.toscalib.relationship_template import RelationshipTemplate + SECTIONS = (TYPE, PROPERTIES, REQUIREMENTS, INTERFACES, LIFECYCLE, INPUT) = \ ('type', 'properties', 'requirements', 'interfaces', 'lifecycle', 'input') @@ -51,7 +55,9 @@ TOSCA_TO_HOT_TYPE = {'tosca.nodes.Compute': ToscaCompute, 'tosca.nodes.Database': ToscaDatabase, 'tosca.nodes.WebApplication.WordPress': ToscaWordpress, 'tosca.nodes.BlockStorage': ToscaBlockStorage, - 'tosca.nodes.SoftwareComponent.Nodejs': ToscaNodejs} + 'tosca.nodes.SoftwareComponent.Nodejs': ToscaNodejs, + 'tosca.nodes.network.Network': ToscaNetwork, + 'tosca.nodes.network.Port': ToscaNetworkPort} TOSCA_TO_HOT_REQUIRES = {'container': 'server', 'host': 'server', 'dependency': 'depends_on', "connects": 'depends_on'} @@ -74,6 +80,17 @@ class TranslateNodeTemplates(): def translate(self): return self._translate_nodetemplates() + def _recursive_handle_properties(self, resource): + '''Recursively handle the properties of the depens_on_nodes nodes.''' + # Use of hashtable (dict) here should be faster? + if resource in self.processed_resources: + return + self.processed_resources.append(resource) + for depend_on in resource.depends_on_nodes: + self._recursive_handle_properties(depend_on) + + resource.handle_properties() + def _translate_nodetemplates(self): suffix = 0 @@ -126,13 +143,29 @@ class TranslateNodeTemplates(): self.hot_lookup[node].depends_on.append( self.hot_lookup[node_depend].top_of_chain()) + self.hot_lookup[node].depends_on_nodes.append( + self.hot_lookup[node_depend].top_of_chain()) + # handle hosting relationship for resource in self.hot_resources: resource.handle_hosting() # handle built-in properties of HOT resources + # if a resource depends on other resources, + # their properties needs to be handled first. + # Use recursion to handle the properties of the + # dependent nodes in correct order + self.processed_resources = [] for resource in self.hot_resources: - resource.handle_properties() + self._recursive_handle_properties(resource) + + # handle resources that need to expand to more then one HOT resource + expansion_resources = [] + for resource in self.hot_resources: + expanded = resource.handle_expansion() + if expanded: + expansion_resources += expanded + self.hot_resources += expansion_resources # Resolve function calls: GetProperty, GetAttribute, GetInput # at this point, all the HOT resources should have been created diff --git a/translator/tests/data/hot_ouput/tosca_one_server_one_network.yaml b/translator/tests/data/hot_ouput/tosca_one_server_one_network.yaml new file mode 100644 index 0000000..90d4b9b --- /dev/null +++ b/translator/tests/data/hot_ouput/tosca_one_server_one_network.yaml @@ -0,0 +1,43 @@ +heat_template_version: 2013-05-23 + +description: > + TOSCA simple profile with 1 network and 1 attached server + +outputs: {} +parameters: + network_name: + default: net1 + description: Network name + type: string +resources: + my_network: + properties: + name: + get_param: network_name + type: OS::Neutron::Net + my_network_subnet: + properties: + allocation_pools: + - end: 192.168.0.200 + start: 192.168.0.50 + cidr: 192.168.0.0/24 + gateway_ip: 192.168.0.1 + ip_version: 4 + network: { get_resource: my_network} + type: OS::Neutron::Subnet + my_port: + depends_on: + - my_network + properties: + network: { get_resource: my_network} + type: OS::Neutron::Port + my_server: + properties: + flavor: m1.small + image: cirros-0.3.2-x86_64-uec + key_name: userkey + networks: + - port: { get_resource: my_port} + user_data_format: SOFTWARE_CONFIG + type: OS::Nova::Server + diff --git a/translator/tests/data/hot_ouput/tosca_one_server_three_networks.yaml b/translator/tests/data/hot_ouput/tosca_one_server_three_networks.yaml new file mode 100644 index 0000000..ec5ec87 --- /dev/null +++ b/translator/tests/data/hot_ouput/tosca_one_server_three_networks.yaml @@ -0,0 +1,68 @@ +heat_template_version: 2013-05-23 + +description: > + TOSCA simple profile with 3 networks and 1 attached server + +outputs: {} +parameters: {} +resources: + my_network1: + properties: + name: net1 + type: OS::Neutron::Net + my_network1_subnet: + properties: + cidr: 192.168.1.0/24 + ip_version: 4 + network: { get_resource: my_network1 } + type: OS::Neutron::Subnet + my_network2: + properties: + name: net2 + type: OS::Neutron::Net + my_network2_subnet: + properties: + cidr: 192.168.2.0/24 + ip_version: 4 + network: { get_resource: my_network2 } + type: OS::Neutron::Subnet + my_network3: + properties: + name: net3 + type: OS::Neutron::Net + my_network3_subnet: + properties: + cidr: 192.168.3.0/24 + ip_version: 4 + network: { get_resource: my_network3 } + type: OS::Neutron::Subnet + my_port1: + depends_on: + - my_network1 + properties: + network: { get_resource: my_network1 } + type: OS::Neutron::Port + my_port2: + depends_on: + - my_network2 + properties: + network: { get_resource: my_network2 } + type: OS::Neutron::Port + my_port3: + depends_on: + - my_network3 + properties: + network: { get_resource: my_network3 } + type: OS::Neutron::Port + my_server: + properties: + flavor: m1.small + image: cirros-0.3.2-x86_64-uec + key_name: userkey + networks: + - port: { get_resource: my_port1 } + - port: { get_resource: my_port2 } + - port: { get_resource: my_port3 } + user_data_format: SOFTWARE_CONFIG + type: OS::Nova::Server + diff --git a/translator/tests/data/hot_ouput/tosca_server_on_existing_network.yaml b/translator/tests/data/hot_ouput/tosca_server_on_existing_network.yaml new file mode 100644 index 0000000..d502379 --- /dev/null +++ b/translator/tests/data/hot_ouput/tosca_server_on_existing_network.yaml @@ -0,0 +1,27 @@ +heat_template_version: 2013-05-23 + +description: > + TOSCA simple profile with 1 server attached to existing network + +outputs: {} +parameters: + network_name: + default: net1 + description: Network name + type: string +resources: + my_port: + properties: + network: {get_param: network_name} + type: OS::Neutron::Port + my_server: + properties: + flavor: m1.small + image: cirros-0.3.2-x86_64-uec + key_name: userkey + networks: + - port: + Ref: my_port + user_data_format: SOFTWARE_CONFIG + type: OS::Nova::Server + diff --git a/translator/tests/data/hot_ouput/tosca_two_servers_one_network.yaml b/translator/tests/data/hot_ouput/tosca_two_servers_one_network.yaml new file mode 100644 index 0000000..3d745ba --- /dev/null +++ b/translator/tests/data/hot_ouput/tosca_two_servers_one_network.yaml @@ -0,0 +1,72 @@ +heat_template_version: 2013-05-23 + +description: > + TOSCA simple profile with 1 network and 2 attached servers + +outputs: {} +parameters: + network_cidr: + default: 10.0.0.0/24 + description: CIDR for the network + type: string + network_end_ip: + default: 10.0.0.150 + description: End IP for the allocation pool + type: string + network_name: + default: net1 + description: Network name + type: string + network_start_ip: + default: 10.0.0.100 + description: Start IP for the allocation pool + type: string +resources: + my_network: + properties: + name: + get_param: network_name + type: OS::Neutron::Net + my_network_subnet: + properties: + allocation_pools: + - end: + get_param: network_end_ip + start: + get_param: network_start_ip + cidr: + get_param: network_cidr + ip_version: 4 + network: { get_resource: my_network} + type: OS::Neutron::Subnet + my_port: + depends_on: + - my_network + properties: + network: { get_resource: my_network} + type: OS::Neutron::Port + my_port2: + depends_on: + - my_network + properties: + network: { get_resource: my_network} + type: OS::Neutron::Port + my_server: + properties: + flavor: m1.small + image: cirros-0.3.2-x86_64-uec + key_name: userkey + networks: + - port: { get_resource: my_port} + user_data_format: SOFTWARE_CONFIG + type: OS::Nova::Server + my_server2: + properties: + flavor: m1.small + image: cirros-0.3.2-x86_64-uec + key_name: userkey + networks: + - port: { get_resource: my_port2} + user_data_format: SOFTWARE_CONFIG + type: OS::Nova::Server + diff --git a/translator/tests/data/tosca_one_server_one_network.yaml b/translator/tests/data/tosca_one_server_one_network.yaml new file mode 100644 index 0000000..2ff60b5 --- /dev/null +++ b/translator/tests/data/tosca_one_server_one_network.yaml @@ -0,0 +1,41 @@ +tosca_definitions_version: tosca_simple_yaml_1_0_0 + +description: > + TOSCA simple profile with 1 network and 1 attached server + +inputs: + network_name: + type: string + description: Network name + +node_templates: + my_server: + type: tosca.nodes.Compute + properties: + disk_size: 10 + num_cpus: 1 + mem_size: 512 + capabilities: + os: + properties: + architecture: x86_64 + type: Linux + distribution: CirrOS + version: 0.3.2 + + my_network: + type: tosca.nodes.network.Network + properties: + ip_version: 4 + cidr: '192.168.0.0/24' + network_name: { get_input: network_name } + start_ip: '192.168.0.50' + end_ip: '192.168.0.200' + gateway_ip: '192.168.0.1' + + my_port: + type: tosca.nodes.network.Port + requirements: + - binding: my_server + - link: my_network + diff --git a/translator/tests/data/tosca_one_server_three_networks.yaml b/translator/tests/data/tosca_one_server_three_networks.yaml new file mode 100644 index 0000000..5fd7387 --- /dev/null +++ b/translator/tests/data/tosca_one_server_three_networks.yaml @@ -0,0 +1,62 @@ +tosca_definitions_version: tosca_simple_yaml_1_0_0 + +description: > + TOSCA simple profile with 3 networks and 1 attached server + +node_templates: + my_server: + type: tosca.nodes.Compute + properties: + disk_size: 10 + num_cpus: 1 + mem_size: 512 + capabilities: + os: + properties: + architecture: x86_64 + type: Linux + distribution: CirrOS + version: 0.3.2 + + my_network1: + type: tosca.nodes.network.Network + properties: + cidr: '192.168.1.0/24' + network_name: net1 + + my_network2: + type: tosca.nodes.network.Network + properties: + cidr: '192.168.2.0/24' + network_name: net2 + + my_network3: + type: tosca.nodes.network.Network + properties: + cidr: '192.168.3.0/24' + network_name: net3 + + my_port1: + type: tosca.nodes.network.Port + properties: + order: 0 + requirements: + - binding: my_server + - link: my_network1 + + my_port2: + type: tosca.nodes.network.Port + properties: + order: 1 + requirements: + - binding: my_server + - link: my_network2 + + my_port3: + type: tosca.nodes.network.Port + properties: + order: 2 + requirements: + - binding: my_server + - link: my_network3 + diff --git a/translator/tests/data/tosca_server_on_existing_network.yaml b/translator/tests/data/tosca_server_on_existing_network.yaml new file mode 100644 index 0000000..9be3428 --- /dev/null +++ b/translator/tests/data/tosca_server_on_existing_network.yaml @@ -0,0 +1,36 @@ +tosca_definitions_version: tosca_simple_yaml_1_0_0 + +description: > + TOSCA simple profile with 1 server attached to existing network + +inputs: + network_name: + type: string + description: Network name + +node_templates: + my_server: + type: tosca.nodes.Compute + properties: + disk_size: 10 + num_cpus: 1 + mem_size: 512 + capabilities: + os: + properties: + architecture: x86_64 + type: Linux + distribution: CirrOS + version: 0.3.2 + + my_network: + type: tosca.nodes.network.Network + properties: + network_name: { get_input: network_name } + + my_port: + type: tosca.nodes.network.Port + requirements: + - binding: my_server + - link: my_network + diff --git a/translator/tests/data/tosca_two_servers_one_network.yaml b/translator/tests/data/tosca_two_servers_one_network.yaml new file mode 100644 index 0000000..29cc9ed --- /dev/null +++ b/translator/tests/data/tosca_two_servers_one_network.yaml @@ -0,0 +1,72 @@ +tosca_definitions_version: tosca_simple_yaml_1_0_0 + +description: > + TOSCA simple profile with 1 network and 2 attached servers + +inputs: + network_name: + type: string + description: Network name + network_cidr: + type: string + default: 10.0.0.0/24 + description: CIDR for the network + network_start_ip: + type: string + default: 10.0.0.100 + description: Start IP for the allocation pool + network_end_ip: + type: string + default: 10.0.0.150 + description: End IP for the allocation pool + +node_templates: + my_server: + type: tosca.nodes.Compute + properties: + disk_size: 10 + num_cpus: 1 + mem_size: 512 + capabilities: + os: + properties: + architecture: x86_64 + type: Linux + distribution: CirrOS + version: 0.3.2 + + my_server2: + type: tosca.nodes.Compute + properties: + disk_size: 10 + num_cpus: 1 + mem_size: 512 + capabilities: + os: + properties: + architecture: x86_64 + type: Linux + distribution: CirrOS + version: 0.3.2 + + my_network: + type: tosca.nodes.network.Network + properties: + ip_version: 4 + cidr: { get_input: network_cidr } + network_name: { get_input: network_name } + start_ip: { get_input: network_start_ip } + end_ip: { get_input: network_end_ip } + + my_port: + type: tosca.nodes.network.Port + requirements: + - binding: my_server + - link: my_network + + my_port2: + type: tosca.nodes.network.Port + requirements: + - binding: my_server2 + - link: my_network + diff --git a/translator/tests/test_network.py b/translator/tests/test_network.py new file mode 100644 index 0000000..e5c4f81 --- /dev/null +++ b/translator/tests/test_network.py @@ -0,0 +1,228 @@ +# 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. + +import os + +from translator.hot.tosca_translator import TOSCATranslator +from translator.tests.base import TestCase +from translator.toscalib.tosca_template import ToscaTemplate +import translator.toscalib.utils.yamlparser + + +class ToscaNetworkTest(TestCase): + parsed_params = {'network_name': 'net1'} + + def test_translate_single_network_single_server(self): + '''TOSCA template with single Network and single Compute.''' + tosca_tpl = os.path.join( + os.path.dirname(os.path.abspath(__file__)), + 'data/tosca_one_server_one_network.yaml') + + tosca = ToscaTemplate(tosca_tpl) + translate = TOSCATranslator(tosca, self.parsed_params) + output = translate.translate() + + expected_resource_1 = {'type': 'OS::Neutron::Net', + 'properties': + {'name': {'get_param': 'network_name'} + }} + + expected_resource_2 = {'type': 'OS::Neutron::Subnet', + 'properties': + {'cidr': '192.168.0.0/24', + 'ip_version': 4, + 'allocation_pools': [{'start': '192.168.0.50', + 'end': '192.168.0.200'}], + 'gateway_ip': '192.168.0.1', + 'network': {'get_resource': 'my_network'} + }} + + expected_resource_3 = {'type': 'OS::Neutron::Port', + 'depends_on': ['my_network'], + 'properties': + {'network': {'get_resource': 'my_network'} + }} + + expected_resource_4 = [{'port': {'get_resource': 'my_port'}}] + + output_dict = translator.toscalib.utils.yamlparser.simple_parse(output) + + resources = output_dict.get('resources') + + self.assertIn('my_network', resources.keys()) + self.assertIn('my_network_subnet', resources.keys()) + self.assertIn('my_port', resources.keys()) + self.assertIn('my_server', resources.keys()) + + self.assertEqual(resources.get('my_network'), expected_resource_1) + self.assertEqual(resources.get('my_network_subnet'), + expected_resource_2) + self.assertEqual(resources.get('my_port'), expected_resource_3) + + self.assertIn('properties', resources.get('my_server')) + self.assertIn('networks', resources.get('my_server').get('properties')) + translated_resource = resources.get('my_server').\ + get('properties').get('networks') + self.assertEqual(translated_resource, expected_resource_4) + + def test_translate_single_network_two_computes(self): + '''TOSCA template with single Network and two Computes.''' + tosca_tpl = os.path.join( + os.path.dirname(os.path.abspath(__file__)), + 'data/tosca_two_servers_one_network.yaml') + + tosca = ToscaTemplate(tosca_tpl) + translate = TOSCATranslator(tosca, self.parsed_params) + output = translate.translate() + + expected_resource_1 = {'type': 'OS::Neutron::Net', + 'properties': + {'name': {'get_param': 'network_name'} + }} + + expected_resource_2 = {'type': 'OS::Neutron::Subnet', + 'properties': + {'cidr': {'get_param': 'network_cidr'}, + 'ip_version': 4, + 'allocation_pools': [{'start': {'get_param': + 'network_start_ip'}, + 'end': {'get_param': + 'network_end_ip' + }}], + 'network': {'get_resource': 'my_network'} + }} + + expected_resource_3 = {'type': 'OS::Neutron::Port', + 'depends_on': ['my_network'], + 'properties': + {'network': {'get_resource': 'my_network'} + }} + + expected_resource_4 = [{'port': {'get_resource': 'my_port'}}] + + expected_resource_5 = [{'port': {'get_resource': 'my_port2'}}] + + output_dict = translator.toscalib.utils.yamlparser.simple_parse(output) + + resources = output_dict.get('resources') + + self.assertIn('my_network', resources.keys()) + self.assertIn('my_network_subnet', resources.keys()) + self.assertIn('my_port', resources.keys()) + self.assertIn('my_port2', resources.keys()) + self.assertIn('my_server', resources.keys()) + self.assertIn('my_server2', resources.keys()) + + self.assertEqual(resources.get('my_network'), expected_resource_1) + self.assertEqual(resources.get('my_network_subnet'), + expected_resource_2) + self.assertEqual(resources.get('my_port'), expected_resource_3) + self.assertEqual(resources.get('my_port2'), expected_resource_3) + + self.assertIn('properties', resources.get('my_server')) + self.assertIn('networks', resources.get('my_server').get('properties')) + translated_resource = resources.get('my_server').\ + get('properties').get('networks') + self.assertEqual(translated_resource, expected_resource_4) + + self.assertIn('properties', resources.get('my_server2')) + self.assertIn('networks', resources.get('my_server2'). + get('properties')) + translated_resource = resources.get('my_server2').\ + get('properties').get('networks') + self.assertEqual(translated_resource, expected_resource_5) + + def test_translate_server_existing_network(self): + '''TOSCA template with 1 server attached to existing network.''' + tosca_tpl = os.path.join( + os.path.dirname(os.path.abspath(__file__)), + 'data/tosca_server_on_existing_network.yaml') + + tosca = ToscaTemplate(tosca_tpl) + translate = TOSCATranslator(tosca, self.parsed_params) + output = translate.translate() + + expected_resource_1 = {'type': 'OS::Neutron::Port', + 'properties': + {'network': {'get_param': 'network_name'} + }} + + expected_resource_2 = [{'port': {'get_resource': 'my_port'}}] + + output_dict = translator.toscalib.utils.yamlparser.simple_parse(output) + + resources = output_dict.get('resources') + + self.assertItemsEqual(resources.keys(), ['my_server', 'my_port']) + + self.assertEqual(resources.get('my_port'), expected_resource_1) + + self.assertIn('properties', resources.get('my_server')) + self.assertIn('networks', resources.get('my_server').get('properties')) + translated_resource = resources.get('my_server').\ + get('properties').get('networks') + self.assertEqual(translated_resource, expected_resource_2) + + def test_translate_three_networks_single_server(self): + '''TOSCA template with three Networks and single Compute.''' + tosca_tpl = os.path.join( + os.path.dirname(os.path.abspath(__file__)), + 'data/tosca_one_server_three_networks.yaml') + + tosca = ToscaTemplate(tosca_tpl) + translate = TOSCATranslator(tosca, self.parsed_params) + output = translate.translate() + + output_dict = translator.toscalib.utils.yamlparser.simple_parse(output) + + resources = output_dict.get('resources') + + for net_num in range(1, 4): + net_name = 'my_network%d' % (net_num) + subnet_name = '%s_subnet' % (net_name) + port_name = 'my_port%d' % (net_num) + + expected_resource_net = {'type': 'OS::Neutron::Net', + 'properties': + {'name': 'net%d' % (net_num) + }} + + expected_resource_subnet = {'type': 'OS::Neutron::Subnet', + 'properties': + {'cidr': '192.168.%d.0/24' % (net_num), + 'ip_version': 4, + 'network': {'get_resource': net_name} + }} + + expected_resource_port = {'type': 'OS::Neutron::Port', + 'depends_on': [net_name], + 'properties': + {'network': {'get_resource': net_name} + }} + self.assertIn(net_name, resources.keys()) + self.assertIn(subnet_name, resources.keys()) + self.assertIn(port_name, resources.keys()) + + self.assertEqual(resources.get(net_name), expected_resource_net) + self.assertEqual(resources.get(subnet_name), + expected_resource_subnet) + self.assertEqual(resources.get(port_name), expected_resource_port) + + self.assertIn('properties', resources.get('my_server')) + self.assertIn('networks', resources.get('my_server').get('properties')) + translated_resource = resources.get('my_server').\ + get('properties').get('networks') + + expected_srv_networks = [{'port': {'get_resource': 'my_port1'}}, + {'port': {'get_resource': 'my_port2'}}, + {'port': {'get_resource': 'my_port3'}}] + self.assertEqual(translated_resource, expected_srv_networks)