From 93feb3453940cf87cd08dacc151fb41db3c0cd72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Harald=20Jens=C3=A5s?= Date: Thu, 17 May 2018 08:32:13 +0200 Subject: [PATCH] OS::Neutron::Port: Add network attribute Adds attribute 'network' to the port resource. Similar to the existing subnets attribute which returns a list of all subnets. The network attribute returns the properties of the neutron network. Story: 1766946 Task: 18792 Change-Id: I6c667a0ff2c15aa27ca0d7943359e7f595630f87 --- .../resources/openstack/neutron/port.py | 26 +++++++- .../openstack/neutron/test_neutron_port.py | 60 ++++++++++++++++++- ...rt-network-attribute-14d2eeb481b25fa8.yaml | 13 ++++ 3 files changed, 97 insertions(+), 2 deletions(-) create mode 100644 releasenotes/notes/neutron-port-network-attribute-14d2eeb481b25fa8.yaml diff --git a/heat/engine/resources/openstack/neutron/port.py b/heat/engine/resources/openstack/neutron/port.py index 70043e3720..59a9ac8414 100644 --- a/heat/engine/resources/openstack/neutron/port.py +++ b/heat/engine/resources/openstack/neutron/port.py @@ -78,11 +78,12 @@ class Port(neutron.NeutronResource): MAC_ADDRESS_ATTR, NAME_ATTR, NETWORK_ID_ATTR, SECURITY_GROUPS_ATTR, STATUS, TENANT_ID, ALLOWED_ADDRESS_PAIRS_ATTR, SUBNETS_ATTR, PORT_SECURITY_ENABLED_ATTR, QOS_POLICY_ATTR, DNS_ASSIGNMENT, + NETWORK_ATTR, ) = ( 'admin_state_up', 'device_id', 'device_owner', 'fixed_ips', 'mac_address', 'name', 'network_id', 'security_groups', 'status', 'tenant_id', 'allowed_address_pairs', 'subnets', - 'port_security_enabled', 'qos_policy_id', 'dns_assignment', + 'port_security_enabled', 'qos_policy_id', 'dns_assignment', 'network', ) properties_schema = { @@ -379,6 +380,22 @@ class Port(neutron.NeutronResource): type=attributes.Schema.MAP, support_status=support.SupportStatus(version='7.0.0'), ), + NETWORK_ATTR: attributes.Schema( + _("The attributes of the network owning the port. (The full list " + "of response parameters can be found in the `Openstack " + "Networking service API reference " + "`_.) The " + "following examples demonstrate some (not all) possible " + "expressions. (Obtains the network, the MTU (Maximum " + "transmission unit), the network tags and the l2_adjacency " + "property): " + "``{get_attr: [, network]}``, " + "``{get_attr: [, network, mtu]}``, " + "``{get_attr: [, network, tags]}?``, " + "``{get_attr: [, network, l2_adjacency]}``."), + type=attributes.Schema.MAP, + support_status=support.SupportStatus(version='11.0.0'), + ), } def translation_rules(self, props): @@ -551,6 +568,13 @@ class Port(neutron.NeutronResource): LOG.warning("Failed to fetch resource attributes: %s", ex) return return subnets + if name == self.NETWORK_ATTR: + try: + return self.client().show_network( + self._show_resource().get('network_id'))['network'] + except Exception as ex: + LOG.warning("Failed to fetch resource attributes: %s", ex) + return return super(Port, self)._resolve_attribute(name) def needs_replace(self, after_props): diff --git a/heat/tests/openstack/neutron/test_neutron_port.py b/heat/tests/openstack/neutron/test_neutron_port.py index 4230781090..8f39d9f96e 100644 --- a/heat/tests/openstack/neutron/test_neutron_port.py +++ b/heat/tests/openstack/neutron/test_neutron_port.py @@ -80,6 +80,8 @@ class NeutronPortTest(common.HeatTestCase): neutronclient.Client, 'update_port') self.subnet_show_mock = self.patchobject( neutronclient.Client, 'show_subnet') + self.network_show_mock = self.patchobject( + neutronclient.Client, 'show_network') self.find_mock = self.patchobject( neutronV20, 'find_resourceid_by_name_or_id') @@ -568,13 +570,26 @@ class NeutronPortTest(common.HeatTestCase): 'end': u'10.0.0.254'}], 'gateway_ip': '10.0.0.1', 'ipv6_address_mode': None, 'ip_version': 4, 'host_routes': [], - 'id': '6dd609ad-d52a-4587-b1a0-b335f76062a5'} + 'id': 'd0e971a6-a6b4-4f4c-8c88-b75e9c120b7e'} + network_dict = {'name': 'test-network', 'status': 'ACTIVE', + 'router:external': False, + 'availability_zone_hints': [], + 'availability_zones': ['nova'], + 'ipv4_address_scope': None, 'description': '', + 'subnets': [subnet_dict['id']], + 'port_security_enabled': True, + 'tenant_id': '58a61fc3992944ce971404a2ece6ff98', + 'tags': [], 'ipv6_address_scope': None, + 'project_id': '58a61fc3992944ce971404a2ece6ff98', + 'revision_number': 4, 'admin_state_up': True, + 'shared': False, 'mtu': 1450, 'id': 'net1234'} self.find_mock.return_value = 'net1234' self.create_mock.return_value = {'port': { 'status': 'BUILD', 'id': 'fc68ea2c-b60b-4b4f-bd82-94ec81110766' }} self.subnet_show_mock.return_value = {'subnet': subnet_dict} + self.network_show_mock.return_value = {'network': network_dict} self.port_show_mock.return_value = {'port': { 'status': 'DOWN', 'name': utils.PhysName(stack.name, 'port'), @@ -616,6 +631,7 @@ class NeutronPortTest(common.HeatTestCase): 'ip_address': '10.0.0.2'}], port.FnGetAtt('fixed_ips')) self.assertEqual([subnet_dict], port.FnGetAtt('subnets')) + self.assertEqual(network_dict, port.FnGetAtt('network')) self.assertRaises(exception.InvalidTemplateAttribute, port.FnGetAtt, 'Foo') @@ -661,6 +677,48 @@ class NeutronPortTest(common.HeatTestCase): 'device_id': ''}} ) + def test_network_attribute_exception(self): + t = template_format.parse(neutron_port_template) + t['resources']['port']['properties'].pop('fixed_ips') + stack = utils.parse_stack(t) + + self.find_mock.return_value = 'net1234' + self.create_mock.return_value = {'port': { + 'status': 'BUILD', + 'id': 'fc68ea2c-b60b-4b4f-bd82-94ec81110766' + }} + self.port_show_mock.return_value = {'port': { + 'status': 'DOWN', + 'name': utils.PhysName(stack.name, 'port'), + 'allowed_address_pairs': [], + 'admin_state_up': True, + 'network_id': 'net1234', + 'device_id': 'dc68eg2c-b60g-4b3f-bd82-67ec87650532', + 'mac_address': 'fa:16:3e:75:67:60', + 'tenant_id': '58a61fc3992944ce971404a2ece6ff98', + 'security_groups': ['5b15d80c-6b70-4a1c-89c9-253538c5ade6'], + 'fixed_ips': [{'subnet_id': 'd0e971a6-a6b4-4f4c-8c88-b75e9c120b7e', + 'ip_address': '10.0.0.2'}] + }} + self.network_show_mock.side_effect = (qe.NeutronClientException( + 'ConnectionFailed: Connection to neutron failed: Maximum ' + 'attempts reached')) + + port = stack['port'] + scheduler.TaskRunner(port.create)() + self.assertIsNone(port.FnGetAtt('network')) + log_msg = ('Failed to fetch resource attributes: ConnectionFailed: ' + 'Connection to neutron failed: Maximum attempts reached') + self.assertIn(log_msg, self.LOG.output) + self.create_mock.assert_called_once_with({'port': { + 'network_id': u'net1234', + 'name': utils.PhysName(stack.name, 'port'), + 'admin_state_up': True, + 'device_owner': u'network:dhcp', + 'binding:vnic_type': 'normal', + 'device_id': ''}} + ) + def test_prepare_for_replace_port_not_created(self): t = template_format.parse(neutron_port_template) stack = utils.parse_stack(t) diff --git a/releasenotes/notes/neutron-port-network-attribute-14d2eeb481b25fa8.yaml b/releasenotes/notes/neutron-port-network-attribute-14d2eeb481b25fa8.yaml new file mode 100644 index 0000000000..2cbadda11a --- /dev/null +++ b/releasenotes/notes/neutron-port-network-attribute-14d2eeb481b25fa8.yaml @@ -0,0 +1,13 @@ +--- +features: + - | + Added ``network`` attribute to OS::Neutron::Port resource. The new + attribute returns the neutron network that owns the port. The following + examples demonstrate some (not all) possible expressions. (Obtains the + network, the MTU (Maximum transmission unit), the network tags and finally + the l2_adjacency property):: + + {get_attr: [, network]} + {get_attr: [, network, mtu]} + {get_attr: [, network, tags]} + {get_attr: [, network, l2_adjacency]}