diff --git a/vmware_nsx/nsxlib/v3/resources.py b/vmware_nsx/nsxlib/v3/resources.py index 7de2fb298a..5ccfae6658 100644 --- a/vmware_nsx/nsxlib/v3/resources.py +++ b/vmware_nsx/nsxlib/v3/resources.py @@ -14,6 +14,7 @@ # under the License. # import abc +import collections import six from oslo_config import cfg @@ -22,6 +23,14 @@ from vmware_nsx.common import nsx_constants from vmware_nsx.common import utils +SwitchingProfileTypeId = collections.namedtuple( + 'SwitchingProfileTypeId', 'profile_type, profile_id') + + +PacketAddressClassifier = collections.namedtuple( + 'PacketAddressClassifier', 'ip_address, mac_address, vlan') + + @six.add_metaclass(abc.ABCMeta) class AbstractRESTResource(object): @@ -111,15 +120,17 @@ class SwitchingProfile(AbstractRESTResource): white_list_providers=whitelist_providers, tags=tags or []) - def build_switch_profile_ids(self, *profiles): + @classmethod + def build_switch_profile_ids(cls, client, *profiles): ids = [] for profile in profiles: if type(profile) is str: - profile = self.get(profile) - ids.append({ - 'value': profile['id'], - 'key': profile['resource_type'] - }) + profile = client.get(profile) + if not isinstance(profile, SwitchingProfileTypeId): + profile = SwitchingProfileTypeId( + profile.get('key', profile.get('resource_type')), + profile.get('value', profile.get('id'))) + ids.append(profile) return ids @@ -129,6 +140,46 @@ class LogicalPort(AbstractRESTResource): def uri_segment(self): return 'logical-ports' + def _build_body_attrs( + self, display_name=None, + admin_state=True, tags=[], + address_bindings=[], + switch_profile_ids=[]): + + body = {} + if tags: + body['tags'] = tags + if display_name is not None: + body['display_name'] = display_name + + if admin_state: + body['admin_state'] = nsx_constants.ADMIN_STATE_UP + else: + body['admin_state'] = nsx_constants.ADMIN_STATE_DOWN + + if address_bindings: + bindings = [] + for binding in address_bindings: + address_classifier = { + 'ip_address': binding.ip_address, + 'mac_address': binding.mac_address + } + if binding.vlan is not None: + address_classifier['vlan'] = int(binding.vlan) + bindings.append(address_classifier) + body['address_bindings'] = bindings + + if switch_profile_ids: + profiles = [] + for profile in switch_profile_ids: + profiles.append({ + 'value': profile.profile_id, + 'key': profile.profile_type + }) + body['switching_profile_ids'] = profiles + + return body + def create(self, lswitch_id, vif_uuid, tags=[], attachment_type=nsx_constants.ATTACHMENT_VIF, admin_state=True, name=None, address_bindings=None, @@ -143,8 +194,8 @@ class LogicalPort(AbstractRESTResource): key_values = [ {'key': 'VLAN_ID', 'value': parent_tag}, {'key': 'Host_VIF_ID', 'value': parent_name}, - {'key': 'IP', 'value': address_bindings[0]['ip_address']}, - {'key': 'MAC', 'value': address_bindings[0]['mac_address']}] + {'key': 'IP', 'value': address_bindings[0].ip_address}, + {'key': 'MAC', 'value': address_bindings[0].mac_address}] # NOTE(arosen): The above api body structure might change # in the future @@ -152,25 +203,16 @@ class LogicalPort(AbstractRESTResource): 'attachment': {'attachment_type': attachment_type, 'id': vif_uuid}} - if tags: - body['tags'] = tags - if name: - body['display_name'] = name - if admin_state: - body['admin_state'] = nsx_constants.ADMIN_STATE_UP - else: - body['admin_state'] = nsx_constants.ADMIN_STATE_DOWN - if key_values: body['attachment']['context'] = {'key_values': key_values} body['attachment']['context']['resource_type'] = \ nsx_constants.CIF_RESOURCE_TYPE - if address_bindings: - body['address_bindings'] = address_bindings - - if switch_profile_ids: - body['switching_profile_ids'] = switch_profile_ids + body.update(self._build_body_attrs( + display_name=name, + admin_state=admin_state, tags=tags, + address_bindings=address_bindings, + switch_profile_ids=switch_profile_ids)) return self._client.create(body=body) def delete(self, lport_id): @@ -179,15 +221,17 @@ class LogicalPort(AbstractRESTResource): @utils.retry_upon_exception_nsxv3( nsx_exc.StaleRevision, max_attempts=cfg.CONF.nsx_v3.retries) - def update(self, lport_id, name=None, admin_state=None): + def update(self, lport_id, name=None, admin_state=None, + address_bindings=None, switch_profile_ids=None, + tags=None): lport = self.get(lport_id) - if name is not None: - lport['display_name'] = name - if admin_state is not None: - if admin_state: - lport['admin_state'] = nsx_constants.ADMIN_STATE_UP - else: - lport['admin_state'] = nsx_constants.ADMIN_STATE_DOWN + + lport.update(self._build_body_attrs( + display_name=name, + admin_state=admin_state, tags=tags, + address_bindings=address_bindings, + switch_profile_ids=switch_profile_ids)) + # If revision_id of the payload that we send is older than what NSX has # then we will get a 412: Precondition Failed. In that case we need to # re-fetch, patch the response and send it again with the diff --git a/vmware_nsx/plugins/nsx_v3/plugin.py b/vmware_nsx/plugins/nsx_v3/plugin.py index c2da327c26..5584ab9b3a 100644 --- a/vmware_nsx/plugins/nsx_v3/plugin.py +++ b/vmware_nsx/plugins/nsx_v3/plugin.py @@ -345,9 +345,8 @@ class NsxV3Plugin(db_base_plugin_v2.NeutronDbPluginV2, # them to the backend which would raise an error. if(netaddr.IPNetwork(fixed_ip['ip_address']).version == 6): continue - address_bindings.append( - {'mac_address': port['mac_address'], - 'ip_address': fixed_ip['ip_address']}) + address_bindings.append(nsx_resources.PacketAddressClassifier( + fixed_ip['ip_address'], port['mac_address'], None)) return address_bindings def get_network(self, context, id, fields=None): diff --git a/vmware_nsx/tests/unit/nsxlib/v3/test_resources.py b/vmware_nsx/tests/unit/nsxlib/v3/test_resources.py index 80d928c079..c8a71e7add 100644 --- a/vmware_nsx/tests/unit/nsxlib/v3/test_resources.py +++ b/vmware_nsx/tests/unit/nsxlib/v3/test_resources.py @@ -13,6 +13,8 @@ # License for the specific language governing permissions and limitations # under the License. # +import mock + from oslo_serialization import jsonutils from vmware_nsx.nsxlib.v3 import client @@ -154,28 +156,43 @@ class LogicalPortTestCase(nsxlib_testcase.NsxClientTestCase): profile_dicts.append({'resource_type': profile_id['key'], 'id': profile_id['value']}) + pkt_classifiers = [] + binding_repr = [] + for i in range(0, 3): + ip = "9.10.11.%s" % i + mac = "00:0c:29:35:4a:%sc" % i + pkt_classifiers.append(resources.PacketAddressClassifier( + ip, mac, None)) + binding_repr.append({ + 'ip_address': ip, + 'mac_address': mac + }) + + fake_port['address_bindings'] = binding_repr + api = resources.LogicalPort(client.NSX3Client()) with self.mocked_resource(api) as mocked: mocked.get('post').return_value = mocks.MockRequestsResponse( 200, jsonutils.dumps(fake_port)) + switch_profile = resources.SwitchingProfile result = api.create( fake_port['logical_switch_id'], fake_port['attachment']['id'], - switch_profile_ids=[ - {'value': profile['id'], - 'key': profile['resource_type']} - for profile in profile_dicts]) + address_bindings=pkt_classifiers, + switch_profile_ids=switch_profile.build_switch_profile_ids( + mock.Mock(), *profile_dicts)) resp_body = { 'logical_switch_id': fake_port['logical_switch_id'], - 'admin_state': 'UP', 'switching_profile_ids': fake_port['switching_profile_ids'], 'attachment': { 'attachment_type': 'VIF', 'id': fake_port['attachment']['id'] - } + }, + 'admin_state': 'UP', + 'address_bindings': fake_port['address_bindings'] } self.assertEqual(fake_port, result)