diff --git a/vmware_nsx/nsxlib/v3/__init__.py b/vmware_nsx/nsxlib/v3/__init__.py index 17c7f2d6bf..6d00cb17c8 100644 --- a/vmware_nsx/nsxlib/v3/__init__.py +++ b/vmware_nsx/nsxlib/v3/__init__.py @@ -124,73 +124,6 @@ def update_logical_switch(lswitch_id, name=None, admin_state=None): return client.update_resource(resource, lswitch) -def create_logical_port(lswitch_id, vif_uuid, tags, - attachment_type=nsx_constants.ATTACHMENT_VIF, - admin_state=True, name=None, address_bindings=None, - parent_name=None, parent_tag=None): - - # NOTE(arosen): if a parent_name is specified we need to use the - # CIF's attachment. - key_values = None - if parent_name: - attachment_type = nsx_constants.ATTACHMENT_CIF - 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']}] - # NOTE(arosen): The above api body structure might change in the future - - resource = 'logical-ports' - body = {'logical_switch_id': lswitch_id, - 'attachment': {'attachment_type': attachment_type, - 'id': vif_uuid}, - '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 - - return client.create_resource(resource, body) - - -def delete_logical_port(logical_port_id): - resource = 'logical-ports/%s?detach=true' % logical_port_id - client.delete_resource(resource) - - -def get_logical_port(logical_port_id): - resource = "logical-ports/%s" % logical_port_id - return client.get_resource(resource) - - -@utils.retry_upon_exception_nsxv3(nsx_exc.StaleRevision, - max_attempts=cfg.CONF.nsx_v3.retries) -def update_logical_port(lport_id, name=None, admin_state=None): - resource = "logical-ports/%s" % lport_id - lport = get_logical_port(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 - # 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 new revision_id - return client.update_resource(resource, lport) - - def create_logical_router(display_name, tags, edge_cluster_uuid=None, tier_0=False): # TODO(salv-orlando): If possible do not manage edge clusters in the main diff --git a/vmware_nsx/nsxlib/v3/resources.py b/vmware_nsx/nsxlib/v3/resources.py index 50a751120e..7de2fb298a 100644 --- a/vmware_nsx/nsxlib/v3/resources.py +++ b/vmware_nsx/nsxlib/v3/resources.py @@ -16,6 +16,11 @@ import abc import six +from oslo_config import cfg +from vmware_nsx.common import exceptions as nsx_exc +from vmware_nsx.common import nsx_constants +from vmware_nsx.common import utils + @six.add_metaclass(abc.ABCMeta) class AbstractRESTResource(object): @@ -105,3 +110,86 @@ class SwitchingProfile(AbstractRESTResource): description=description, white_list_providers=whitelist_providers, tags=tags or []) + + def build_switch_profile_ids(self, *profiles): + ids = [] + for profile in profiles: + if type(profile) is str: + profile = self.get(profile) + ids.append({ + 'value': profile['id'], + 'key': profile['resource_type'] + }) + return ids + + +class LogicalPort(AbstractRESTResource): + + @property + def uri_segment(self): + return 'logical-ports' + + def create(self, lswitch_id, vif_uuid, tags=[], + attachment_type=nsx_constants.ATTACHMENT_VIF, + admin_state=True, name=None, address_bindings=None, + parent_name=None, parent_tag=None, + switch_profile_ids=None): + + # NOTE(arosen): if a parent_name is specified we need to use the + # CIF's attachment. + key_values = None + if parent_name: + attachment_type = nsx_constants.ATTACHMENT_CIF + 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']}] + # NOTE(arosen): The above api body structure might change + # in the future + + body = {'logical_switch_id': lswitch_id, + '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 + + return self._client.create(body=body) + + def delete(self, lport_id): + return self._client.url_delete('%s?detach=true' % lport_id) + + @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): + 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 + # 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 + # new revision_id + return self._client.update(lport_id, body=lport) diff --git a/vmware_nsx/plugins/nsx_v3/plugin.py b/vmware_nsx/plugins/nsx_v3/plugin.py index a2e8bcbfbc..afbbd6464f 100644 --- a/vmware_nsx/plugins/nsx_v3/plugin.py +++ b/vmware_nsx/plugins/nsx_v3/plugin.py @@ -62,7 +62,9 @@ from vmware_nsx.common import nsx_constants from vmware_nsx.common import utils from vmware_nsx.db import db as nsx_db from vmware_nsx.nsxlib import v3 as nsxlib +from vmware_nsx.nsxlib.v3 import client as nsx_client from vmware_nsx.nsxlib.v3 import dfw_api as firewall +from vmware_nsx.nsxlib.v3 import resources as nsx_resources from vmware_nsx.nsxlib.v3 import router as routerlib from vmware_nsx.nsxlib.v3 import security @@ -104,6 +106,7 @@ class NsxV3Plugin(db_base_plugin_v2.NeutronDbPluginV2, 'security-group' in self.supported_extension_aliases}} self.tier0_groups_dict = {} self._setup_rpc() + self._nsx_client = nsx_client.NSX3Client() self.nsgroup_container, self.default_section = ( security.init_nsgroup_container_and_default_section_rules()) @@ -426,9 +429,11 @@ class NsxV3Plugin(db_base_plugin_v2.NeutronDbPluginV2, # for ports plugged into a Bridge Endpoint. vif_uuid = port_data.get('device_id') attachment_type = port_data.get('device_owner') - result = nsxlib.create_logical_port( - lswitch_id=port_data['network_id'], - vif_uuid=vif_uuid, name=port_data['name'], tags=tags, + port_client = nsx_resources.LogicalPort(self._nsx_client) + result = port_client.create( + port_data['network_id'], vif_uuid, + tags=tags, + name=port_data['name'], admin_state=port_data['admin_state_up'], address_bindings=address_bindings, attachment_type=attachment_type, @@ -517,7 +522,7 @@ class NsxV3Plugin(db_base_plugin_v2.NeutronDbPluginV2, updated_port = {'port': {ext_sg.SECURITYGROUPS: [], 'admin_state_up': False}} self.update_port(context, port_id, updated_port) - nsxlib.delete_logical_port(nsx_port_id) + nsx_resources.LogicalPort(self._nsx_client).delete(nsx_port_id) self.disassociate_floatingips(context, port_id) ret_val = super(NsxV3Plugin, self).delete_port(context, port_id) @@ -535,7 +540,7 @@ class NsxV3Plugin(db_base_plugin_v2.NeutronDbPluginV2, sec_grp_updated = self.update_security_group_on_port( context, id, port, original_port, updated_port) try: - nsxlib.update_logical_port( + nsx_resources.LogicalPort(self._nsx_client).update( nsx_lport_id, name=port['port'].get('name'), admin_state=port['port'].get('admin_state_up')) security.update_lport_with_security_groups( diff --git a/vmware_nsx/tests/unit/vmware/nsx_v3_mocks.py b/vmware_nsx/tests/unit/vmware/nsx_v3_mocks.py index 651f5fdce9..ff44ea3dab 100644 --- a/vmware_nsx/tests/unit/vmware/nsx_v3_mocks.py +++ b/vmware_nsx/tests/unit/vmware/nsx_v3_mocks.py @@ -87,7 +87,7 @@ def update_logical_switch(lswitch_id, name=None, admin_state=None): return lswitch -def create_logical_port(lswitch_id, vif_uuid, tags, +def create_logical_port(client, lswitch_id, vif_uuid, tags=[], attachment_type=nsx_constants.ATTACHMENT_VIF, admin_state=True, name=None, address_bindings=None, parent_name=None, parent_tag=None): @@ -130,7 +130,7 @@ def create_logical_port(lswitch_id, vif_uuid, tags, return FAKE_PORT -def get_logical_port(lport_id): +def get_logical_port(client, lport_id): FAKE_SWITCH_UUID = uuidutils.generate_uuid() FAKE_PORT = { "id": lport_id, @@ -169,8 +169,8 @@ def get_logical_port(lport_id): return FAKE_PORT -def update_logical_port(lport_id, name=None, admin_state=None): - lport = get_logical_port(lport_id) +def update_logical_port(client, lport_id, name=None, admin_state=None): + lport = get_logical_port(client, lport_id) if name: lport['display_name'] = name if admin_state is not None: diff --git a/vmware_nsx/tests/unit/vmware/nsxlib/v3/test_port.py b/vmware_nsx/tests/unit/vmware/nsxlib/v3/test_port.py deleted file mode 100644 index 3c519ba7c3..0000000000 --- a/vmware_nsx/tests/unit/vmware/nsxlib/v3/test_port.py +++ /dev/null @@ -1,71 +0,0 @@ -# Copyright (c) 2015 VMware, Inc. -# -# 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 mock - -from oslo_log import log - -from vmware_nsx.nsxlib import v3 as nsxlib -from vmware_nsx.tests.unit.vmware.nsxlib.v3 import nsxlib_testcase -from vmware_nsx.tests.unit.vmware import test_constants_v3 - -LOG = log.getLogger(__name__) - - -class NsxLibPortTestCase(nsxlib_testcase.NsxLibTestCase): - - @mock.patch("vmware_nsx.nsxlib.v3" - ".client.create_resource") - def test_create_logical_port(self, mock_create_resource): - """ - Test creating a port returns the correct response and 200 status - """ - mock_create_resource.return_value = test_constants_v3.FAKE_PORT - - result = nsxlib.create_logical_port( - test_constants_v3.FAKE_PORT['logical_switch_id'], - test_constants_v3.FAKE_PORT['attachment']['id'], - tags={}) - - self.assertEqual(test_constants_v3.FAKE_PORT, result) - - @mock.patch("vmware_nsx.nsxlib.v3" - ".client.create_resource") - def test_create_logical_port_admin_down(self, mock_create_resource): - """ - Test creating port with admin_state down - """ - fake_port = test_constants_v3.FAKE_PORT - fake_port['admin_state'] = "DOWN" - mock_create_resource.return_value = fake_port - - result = nsxlib.create_logical_port( - test_constants_v3.FAKE_PORT['logical_switch_id'], - test_constants_v3.FAKE_PORT['attachment']['id'], - tags={}, admin_state=False) - - self.assertEqual(fake_port, result) - - @mock.patch("vmware_nsx.nsxlib.v3" - ".client.delete_resource") - def test_delete_logical_port(self, mock_delete_resource): - """ - Test deleting port - """ - mock_delete_resource.return_value = None - - result = nsxlib.delete_logical_port(test_constants_v3.FAKE_PORT['id']) - self.assertIsNone(result) diff --git a/vmware_nsx/tests/unit/vmware/nsxlib/v3/test_resources.py b/vmware_nsx/tests/unit/vmware/nsxlib/v3/test_resources.py index 1732cd9811..68cd8900e7 100644 --- a/vmware_nsx/tests/unit/vmware/nsxlib/v3/test_resources.py +++ b/vmware_nsx/tests/unit/vmware/nsxlib/v3/test_resources.py @@ -20,6 +20,7 @@ from oslo_serialization import jsonutils from vmware_nsx.nsxlib.v3 import client from vmware_nsx.nsxlib.v3 import resources from vmware_nsx.tests.unit.vmware.nsxlib.v3 import test_client +from vmware_nsx.tests.unit.vmware import test_constants_v3 CLIENT_PKG = test_client.CLIENT_PKG @@ -142,3 +143,87 @@ class TestSwitchingProfileTestCase(test_client.BaseClientTestCase): api = resources.SwitchingProfile(client.NSX3Client()) self.assertEqual(resp_resources['results'], api.find_by_display_name('resource-1')) + + +class LogicalPortTestCase(test_client.BaseClientTestCase): + + @mock.patch("%s.%s" % (CLIENT_PKG, 'requests.Session.post')) + @mock.patch(CLIENT_PKG + '.RESTClient._validate_result') + def test_create_logical_port(self, mock_validate, mock_post): + """ + Test creating a port returns the correct response and 200 status + """ + fake_port = test_constants_v3.FAKE_PORT + mock_post.return_value = mocks.MockRequestsResponse( + 200, jsonutils.dumps(fake_port)) + + profile_client = resources.SwitchingProfile(client.NSX3Client()) + profile_dicts = [] + for profile_id in fake_port['switching_profile_ids']: + profile_dicts.append({'resource_type': profile_id['key'], + 'id': profile_id['value']}) + + result = resources.LogicalPort(client.NSX3Client()).create( + fake_port['logical_switch_id'], + fake_port['attachment']['id'], + switch_profile_ids=profile_client.build_switch_profile_ids( + *profile_dicts)) + + resp_body = { + 'logical_switch_id': fake_port['logical_switch_id'], + 'attachment': { + 'attachment_type': 'VIF', + 'id': fake_port['attachment']['id'] + }, + 'admin_state': 'UP', + 'switching_profile_ids': fake_port['switching_profile_ids'] + } + + self.assertEqual(fake_port, result) + self.assertEqual(fake_port['switching_profile_ids'], + resp_body['switching_profile_ids']) + test_client.assert_session_call( + mock_post, + 'https://1.2.3.4/api/v1/logical-ports', + False, + jsonutils.dumps(resp_body), + client.JSONRESTClient._DEFAULT_HEADERS, + test_client.BaseClientTestCase.ca_file) + + @mock.patch("%s.%s" % (CLIENT_PKG, 'requests.Session.post')) + @mock.patch(CLIENT_PKG + '.RESTClient._validate_result') + def test_create_logical_port_admin_down(self, mock_validate, mock_post): + """ + Test creating port with admin_state down + """ + fake_port = test_constants_v3.FAKE_PORT + fake_port['admin_state'] = "DOWN" + mock_post.return_value = mocks.MockRequestsResponse( + 200, jsonutils.dumps(fake_port)) + + result = resources.LogicalPort(client.NSX3Client()).create( + test_constants_v3.FAKE_PORT['logical_switch_id'], + test_constants_v3.FAKE_PORT['attachment']['id'], + tags={}, admin_state=False) + + self.assertEqual(fake_port, result) + + @mock.patch("%s.%s" % (CLIENT_PKG, 'requests.Session.delete')) + @mock.patch(CLIENT_PKG + '.RESTClient._validate_result') + def test_delete_logical_port(self, mock_validate, mock_delete): + """ + Test deleting port + """ + mock_delete.return_value = mocks.MockRequestsResponse( + 200, None) + + uuid = test_constants_v3.FAKE_PORT['id'] + result = resources.LogicalPort(client.NSX3Client()).delete(uuid) + self.assertIsNone(result.content) + test_client.assert_session_call( + mock_delete, + 'https://1.2.3.4/api/v1/logical-ports/%s?detach=true' % uuid, + False, + None, + client.JSONRESTClient._DEFAULT_HEADERS, + test_client.BaseClientTestCase.ca_file) diff --git a/vmware_nsx/tests/unit/vmware/test_nsx_v3_plugin.py b/vmware_nsx/tests/unit/vmware/test_nsx_v3_plugin.py index 33994b6044..dffcdc72d7 100644 --- a/vmware_nsx/tests/unit/vmware/test_nsx_v3_plugin.py +++ b/vmware_nsx/tests/unit/vmware/test_nsx_v3_plugin.py @@ -36,6 +36,7 @@ from neutron import version from vmware_nsx.common import utils from vmware_nsx.nsxlib import v3 as nsxlib from vmware_nsx.nsxlib.v3 import dfw_api as firewall +from vmware_nsx.nsxlib.v3 import resources as nsx_resources from vmware_nsx.tests.unit import vmware from vmware_nsx.tests.unit.vmware import nsx_v3_mocks @@ -56,10 +57,10 @@ class NsxPluginV3TestCase(test_plugin.NeutronDbPluginV2TestCase): nsxlib.delete_logical_switch = mock.Mock() nsxlib.get_logical_switch = nsx_v3_mocks.get_logical_switch nsxlib.update_logical_switch = nsx_v3_mocks.update_logical_switch - nsxlib.create_logical_port = nsx_v3_mocks.create_logical_port - nsxlib.delete_logical_port = mock.Mock() - nsxlib.get_logical_port = nsx_v3_mocks.get_logical_port - nsxlib.update_logical_port = nsx_v3_mocks.update_logical_port + nsx_resources.LogicalPort.create = nsx_v3_mocks.create_logical_port + nsx_resources.LogicalPort.delete = mock.Mock() + nsx_resources.LogicalPort.get = nsx_v3_mocks.get_logical_port + nsx_resources.LogicalPort.update = nsx_v3_mocks.update_logical_port firewall.add_rules_in_section = nsx_v3_mocks.add_rules_in_section firewall.nsxclient.create_resource = nsx_v3_mocks.create_resource firewall.nsxclient.update_resource = nsx_v3_mocks.update_resource @@ -114,12 +115,11 @@ class SecurityGroupsTestCase(ext_sg.SecurityGroupDBTestCase): plugin=PLUGIN_NAME, ext_mgr=None): nsxlib.create_logical_switch = nsx_v3_mocks.create_logical_switch - nsxlib.create_logical_port = nsx_v3_mocks.create_logical_port - nsxlib.update_logical_port = nsx_v3_mocks.update_logical_port - nsxlib.delete_logical_port = mock.Mock() nsxlib.delete_logical_switch = mock.Mock() - nsxlib.get_logical_port = nsx_v3_mocks.get_logical_port - nsxlib.update_logical_port = nsx_v3_mocks.update_logical_port + nsx_resources.LogicalPort.create = nsx_v3_mocks.create_logical_port + nsx_resources.LogicalPort.delete = mock.Mock() + nsx_resources.LogicalPort.get = nsx_v3_mocks.get_logical_port + nsx_resources.LogicalPort.update = nsx_v3_mocks.update_logical_port firewall.add_rules_in_section = nsx_v3_mocks.add_rules_in_section firewall.nsxclient.create_resource = nsx_v3_mocks.create_resource firewall.nsxclient.update_resource = nsx_v3_mocks.update_resource @@ -193,7 +193,7 @@ class L3NatTest(test_l3_plugin.L3BaseForIntTests, NsxPluginV3TestCase): self.plugin_instance.__module__, self.plugin_instance.__class__.__name__) self._plugin_class = self.plugin_instance.__class__ - nsxlib.create_logical_port = self.v3_mock.create_logical_port + nsx_resources.LogicalPort.create = self.v3_mock.create_logical_port nsxlib.create_logical_router = self.v3_mock.create_logical_router nsxlib.update_logical_router = self.v3_mock.update_logical_router nsxlib.delete_logical_router = self.v3_mock.delete_logical_router