diff --git a/vmware_nsxlib/tests/unit/v3/test_qos_switching_profile.py b/vmware_nsxlib/tests/unit/v3/test_qos_switching_profile.py index a0fe17ee..ffe6a410 100644 --- a/vmware_nsxlib/tests/unit/v3/test_qos_switching_profile.py +++ b/vmware_nsxlib/tests/unit/v3/test_qos_switching_profile.py @@ -107,7 +107,8 @@ class NsxLibQosTestCase(nsxlib_testcase.NsxClientTestCase): update.assert_called_with( 'switching-profiles/%s' % test_constants.FAKE_QOS_PROFILE['id'], - self._body(description=new_description)) + self._body(description=new_description), + headers=None) def _enable_qos_switching_profile_shaping( self, direction=nsx_constants.EGRESS, new_burst_size=100): diff --git a/vmware_nsxlib/tests/unit/v3/test_resources.py b/vmware_nsxlib/tests/unit/v3/test_resources.py index 3d105be0..f2772e5f 100644 --- a/vmware_nsxlib/tests/unit/v3/test_resources.py +++ b/vmware_nsxlib/tests/unit/v3/test_resources.py @@ -331,8 +331,6 @@ class LogicalPortTestCase(BaseTestResource): pkt_classifiers, binding_repr = self._get_pktcls_bindings() - fake_port['address_bindings'] = binding_repr - mocked_resource = self.get_mocked_resource() description = 'dummy' switch_profile = resources.SwitchingProfile @@ -352,7 +350,7 @@ class LogicalPortTestCase(BaseTestResource): 'id': fake_port['attachment']['id'] }, 'admin_state': 'UP', - 'address_bindings': fake_port['address_bindings'], + 'address_bindings': binding_repr, 'description': description } @@ -366,7 +364,7 @@ class LogicalPortTestCase(BaseTestResource): """Test creating a port returns the correct response and 200 status """ - fake_port = test_constants.FAKE_CONTAINER_PORT.copy() + fake_port = copy.deepcopy(test_constants.FAKE_CONTAINER_PORT) profile_dicts = self._get_profile_dicts(fake_port) @@ -451,12 +449,13 @@ class LogicalPortTestCase(BaseTestResource): fake_port['address_bindings'] = ['a', 'b'] mocked_resource = self.get_mocked_resource() - def get_fake_port(*args): - return fake_port + def get_fake_port(*args, **kwargs): + return copy.copy(fake_port) - mocked_resource.get = get_fake_port + mocked_resource.client.get = get_fake_port mocked_resource.update( - fake_port['id'], fake_port['id'], address_bindings=[]) + fake_port['id'], fake_port['attachment']['id'], + address_bindings=[]) fake_port['address_bindings'] = [] test_client.assert_json_call( @@ -483,6 +482,98 @@ class LogicalPortTestCase(BaseTestResource): except exceptions.ManagerError as e: self.assertIn(nsxlib_testcase.NSX_MANAGER, e.msg) + def test_update_logical_port_no_addr_binding(self): + fake_port = copy.deepcopy(test_constants.FAKE_CONTAINER_PORT) + mocked_resource = self.get_mocked_resource() + new_name = 'updated_port' + new_desc = 'updated' + fake_port_ctx = fake_port['attachment']['context'] + fake_container_host_vif_id = fake_port_ctx['container_host_vif_id'] + + def get_fake_port(*args, **kwargs): + return copy.copy(fake_port) + + mocked_resource.client.get = get_fake_port + + mocked_resource.update( + fake_port['id'], + fake_port['attachment']['id'], + name=new_name, + description=new_desc, + parent_vif_id=fake_container_host_vif_id, + traffic_tag=fake_port_ctx['vlan_tag'], + vif_type=fake_port_ctx['vif_type'], + app_id=fake_port_ctx['app_id'], + allocate_addresses=fake_port_ctx['allocate_addresses']) + + fake_port['display_name'] = new_name + fake_port['description'] = new_desc + fake_port['attachment'] = { + 'attachment_type': 'VIF', + 'id': fake_port['attachment']['id'], + 'context': { + 'resource_type': 'VifAttachmentContext', + 'allocate_addresses': 'Both', + 'parent_vif_id': fake_container_host_vif_id, + 'traffic_tag': fake_port_ctx['vlan_tag'], + 'app_id': fake_port_ctx['app_id'], + 'vif_type': 'CHILD', + } + } + + test_client.assert_json_call( + 'put', mocked_resource, + 'https://1.2.3.4/api/v1/logical-ports/%s' % fake_port['id'], + data=jsonutils.dumps(fake_port, sort_keys=True), + headers=self.default_headers()) + + def test_update_logical_port_with_addr_binding(self): + fake_port = copy.deepcopy(test_constants.FAKE_CONTAINER_PORT) + mocked_resource = self.get_mocked_resource() + new_name = 'updated_port' + new_desc = 'updated' + fake_port_ctx = fake_port['attachment']['context'] + fake_container_host_vif_id = fake_port_ctx['container_host_vif_id'] + pkt_classifiers, binding_repr = self._get_pktcls_bindings() + + def get_fake_port(*args, **kwargs): + return copy.copy(fake_port) + + mocked_resource.client.get = get_fake_port + + mocked_resource.update( + fake_port['id'], + fake_port['attachment']['id'], + name=new_name, + description=new_desc, + parent_vif_id=fake_container_host_vif_id, + traffic_tag=fake_port_ctx['vlan_tag'], + vif_type=fake_port_ctx['vif_type'], + app_id=fake_port_ctx['app_id'], + allocate_addresses=fake_port_ctx['allocate_addresses'], + address_bindings=pkt_classifiers) + + fake_port['display_name'] = new_name + fake_port['description'] = new_desc + fake_port['attachment'] = { + 'attachment_type': 'VIF', + 'id': fake_port['attachment']['id'], + 'context': { + 'resource_type': 'VifAttachmentContext', + 'allocate_addresses': 'Both', + 'parent_vif_id': fake_container_host_vif_id, + 'traffic_tag': fake_port_ctx['vlan_tag'], + 'app_id': fake_port_ctx['app_id'], + 'vif_type': 'CHILD', + } + } + fake_port['address_bindings'] = binding_repr + test_client.assert_json_call( + 'put', mocked_resource, + 'https://1.2.3.4/api/v1/logical-ports/%s' % fake_port['id'], + data=jsonutils.dumps(fake_port, sort_keys=True), + headers=self.default_headers()) + class LogicalRouterTestCase(BaseTestResource): @@ -776,7 +867,8 @@ class LogicalRouterPortTestCase(BaseTestResource): uuid = fake_router_port['id'] fake_relay_uuid = uuidutils.generate_uuid() lrport = self.get_mocked_resource() - with mock.patch.object(lrport, 'get', return_value=fake_router_port),\ + with mock.patch.object(lrport.client, 'get', + return_value=fake_router_port),\ mock.patch("vmware_nsxlib.v3.NsxLib.get_version", return_value='2.0.0'): lrport.update(uuid, relay_service_uuid=fake_relay_uuid) @@ -1443,6 +1535,120 @@ class LogicalDhcpServerTestCase(BaseTestResource): super(LogicalDhcpServerTestCase, self).setUp( resources.LogicalDhcpServer) + def test_update_empty_dhcp_server(self): + mocked_resource = self.get_mocked_resource() + server_uuid = 'server-uuid' + ip = '1.1.1.1' + + with mock.patch.object(mocked_resource.client, "get", return_value={}): + mocked_resource.update(server_uuid, server_ip=ip) + body = {'ipv4_dhcp_server': {'dhcp_server_ip': ip}} + + test_client.assert_json_call( + 'put', mocked_resource, + 'https://1.2.3.4/api/v1/%s/%s' % + (mocked_resource.uri_segment, server_uuid), + data=jsonutils.dumps(body, sort_keys=True), + headers=self.default_headers()) + + def test_update_dhcp_server_new_val(self): + mocked_resource = self.get_mocked_resource() + server_uuid = 'server-uuid' + ip = '1.1.1.1' + domain_name = 'dummy' + existing_server = {'ipv4_dhcp_server': {'domain_name': domain_name}} + + # add the server ip + with mock.patch.object(mocked_resource.client, "get", + return_value=existing_server): + mocked_resource.update(server_uuid, server_ip=ip) + + existing_server['ipv4_dhcp_server']['dhcp_server_ip'] = ip + test_client.assert_json_call( + 'put', mocked_resource, + 'https://1.2.3.4/api/v1/%s/%s' % + (mocked_resource.uri_segment, server_uuid), + data=jsonutils.dumps(existing_server, sort_keys=True), + headers=self.default_headers()) + + def test_update_dhcp_server_replace_val(self): + mocked_resource = self.get_mocked_resource() + server_uuid = 'server-uuid' + ip = '1.1.1.1' + domain_name = 'dummy' + existing_server = {'ipv4_dhcp_server': {'domain_name': domain_name, + 'dhcp_server_ip': ip}} + + # replace the server ip + new_ip = '2.2.2.2' + with mock.patch.object(mocked_resource.client, "get", + return_value=existing_server): + mocked_resource.update(server_uuid, server_ip=new_ip) + + existing_server['ipv4_dhcp_server']['dhcp_server_ip'] = new_ip + test_client.assert_json_call( + 'put', mocked_resource, + 'https://1.2.3.4/api/v1/%s/%s' % + (mocked_resource.uri_segment, server_uuid), + data=jsonutils.dumps(existing_server, sort_keys=True), + headers=self.default_headers()) + + def test_create_binding(self): + mocked_resource = self.get_mocked_resource() + server_uuid = 'server-uuid' + mac = 'aa:bb:cc:dd:ee:ff' + ip = '1.1.1.1' + host = 'host' + mocked_resource.create_binding(server_uuid, mac, ip, hostname=host) + body = { + 'mac_address': mac, + 'ip_address': ip, + 'host_name': host, + } + test_client.assert_json_call( + 'post', mocked_resource, + 'https://1.2.3.4/api/v1/%s/%s/static-bindings' % + (mocked_resource.uri_segment, server_uuid), + data=jsonutils.dumps(body, sort_keys=True), + headers=self.default_headers()) + + def test_get_binding(self): + mocked_resource = self.get_mocked_resource() + server_uuid = 'server-uuid' + binding_uuid = 'binding-uuid' + mocked_resource.get_binding(server_uuid, binding_uuid) + test_client.assert_json_call( + 'get', mocked_resource, + 'https://1.2.3.4/api/v1/%s/%s/static-bindings/%s' % + (mocked_resource.uri_segment, server_uuid, binding_uuid), + headers=self.default_headers()) + + def test_update_binding(self): + mocked_resource = self.get_mocked_resource() + server_uuid = 'server-uuid' + binding_uuid = 'binding-uuid' + mac = 'aa:bb:cc:dd:ee:ff' + new_mac = 'dd:bb:cc:dd:ee:ff' + ip = '1.1.1.1' + host = 'host' + body = { + 'mac_address': mac, + 'ip_address': ip, + 'host_name': host, + } + with mock.patch.object(mocked_resource.client, "get", + return_value=body): + mocked_resource.update_binding(server_uuid, + binding_uuid, + mac_address=new_mac) + body['mac_address'] = new_mac + test_client.assert_json_call( + 'put', mocked_resource, + 'https://1.2.3.4/api/v1/%s/%s/static-bindings/%s' % + (mocked_resource.uri_segment, server_uuid, binding_uuid), + data=jsonutils.dumps(body, sort_keys=True), + headers=self.default_headers()) + class DummyCachedResource(utils.NsxLibApiBase): diff --git a/vmware_nsxlib/tests/unit/v3/test_security.py b/vmware_nsxlib/tests/unit/v3/test_security.py index 154b5e2a..46f5cb69 100644 --- a/vmware_nsxlib/tests/unit/v3/test_security.py +++ b/vmware_nsxlib/tests/unit/v3/test_security.py @@ -85,7 +85,7 @@ class TestNsxLibFirewallSection(nsxlib_testcase.NsxLibTestCase): 'display-name', 'section-description', rules=[rule]) resource = 'firewall/sections?operation=insert_bottom' \ '&action=create_with_rules' - create.assert_called_with(resource, expected_body) + create.assert_called_with(resource, expected_body, headers=None) def test_get_excludelist(self): with mock.patch.object(self.nsxlib.client, 'list') as clist: @@ -153,7 +153,7 @@ class TestNsxLibIPSet(nsxlib_testcase.NsxClientTestCase): self.nsxlib.ip_set.update( fake_ip_set['id'], ip_addresses=new_ip_addresses) resource = 'ip-sets/%s' % fake_ip_set['id'] - update.assert_called_with(resource, data) + update.assert_called_with(resource, data, headers=None) def test_update_ip_set_empty_ip_addresses(self): fake_ip_set = test_constants.FAKE_IP_SET.copy() @@ -170,7 +170,7 @@ class TestNsxLibIPSet(nsxlib_testcase.NsxClientTestCase): self.nsxlib.ip_set.update( fake_ip_set['id'], ip_addresses=new_ip_addresses) resource = 'ip-sets/%s' % fake_ip_set['id'] - update.assert_called_with(resource, data) + update.assert_called_with(resource, data, headers=None) class TestNsxLibNSGroup(nsxlib_testcase.NsxClientTestCase): @@ -194,4 +194,4 @@ class TestNsxLibNSGroup(nsxlib_testcase.NsxClientTestCase): self.nsxlib.ns_group.update('nsgroupid', tags_update=nsg_tags) resource = 'ns-groups/nsgroupid' data = {'tags': nsg_tags} - update.assert_called_with(resource, data) + update.assert_called_with(resource, data, headers=None) diff --git a/vmware_nsxlib/v3/core_resources.py b/vmware_nsxlib/v3/core_resources.py index 9905d7f6..6480a4de 100644 --- a/vmware_nsxlib/v3/core_resources.py +++ b/vmware_nsxlib/v3/core_resources.py @@ -186,39 +186,24 @@ class NsxLibLogicalSwitch(utils.NsxLibApiBase): return self.client.create(self.get_path(), body) def delete(self, lswitch_id): - # Using internal method so we can access max_attempts in the decorator - @utils.retry_upon_exception( - exceptions.StaleRevision, - max_attempts=self.nsxlib_config.max_attempts) - def _do_delete(): - resource = '%s?detach=true&cascade=true' % lswitch_id - self.client.delete(self.get_path(resource)) - - _do_delete() + resource = '%s?detach=true&cascade=true' % lswitch_id + self._delete_with_retry(resource) def update(self, lswitch_id, name=None, admin_state=None, tags=None, description=None): - # Using internal method so we can access max_attempts in the decorator - @utils.retry_upon_exception( - exceptions.StaleRevision, - max_attempts=self.nsxlib_config.max_attempts) - def _do_update(): - lswitch = self.get(lswitch_id) - # Assign name to a local variable since 'name' is out of scope - ls_name = name or lswitch.get('display_name') - lswitch['display_name'] = ls_name - if admin_state is not None: - if admin_state: - lswitch['admin_state'] = nsx_constants.ADMIN_STATE_UP - else: - lswitch['admin_state'] = nsx_constants.ADMIN_STATE_DOWN - if tags is not None: - lswitch['tags'] = tags - if description is not None: - lswitch['description'] = description - return self.client.update(self.get_path(lswitch_id), lswitch) - - return _do_update() + body = {} + if name: + body['display_name'] = name + if admin_state is not None: + if admin_state: + body['admin_state'] = nsx_constants.ADMIN_STATE_UP + else: + body['admin_state'] = nsx_constants.ADMIN_STATE_DOWN + if tags is not None: + body['tags'] = tags + if description is not None: + body['description'] = description + return self._update_with_retry(lswitch_id, body) class SwitchingProfileTypes(object): @@ -408,9 +393,8 @@ class NsxLibQosSwitchingProfile(NsxLibSwitchingProfile): return self.client.create(self.get_path(), body) def update(self, profile_id, tags, name=None, description=None): - # get the current configuration - body = self.get(profile_id) # update the relevant fields + body = {} body = self._update_args(body, name, description) if tags is not None: body['tags'] = tags @@ -615,7 +599,7 @@ class NsxLibLogicalRouter(utils.NsxLibApiBase): def update_nat_rule(self, logical_router_id, nat_rule_id, **kwargs): resource = 'logical-routers/%s/nat/rules/%s' % ( logical_router_id, nat_rule_id) - return self._update_resource_with_retry(resource, kwargs) + return self._update_resource(resource, kwargs, retry=True) def update_advertisement(self, logical_router_id, **kwargs): resource = ('logical-routers/%s/routing/advertisement' % @@ -632,12 +616,12 @@ class NsxLibLogicalRouter(utils.NsxLibApiBase): {'arg': arg, 'rtr': logical_router_id}) del kwargs[arg] - return self._update_resource_with_retry(resource, kwargs) + return self._update_resource(resource, kwargs, retry=True) def update_advertisement_rules(self, logical_router_id, rules): resource = ('logical-routers/%s/routing/advertisement/rules' % logical_router_id) - return self._update_resource_with_retry(resource, {'rules': rules}) + return self._update_resource(resource, {'rules': rules}, retry=True) def get_advertisement_rules(self, logical_router_id): resource = ('logical-routers/%s/routing/advertisement/rules' % @@ -666,21 +650,7 @@ class NsxLibLogicalRouter(utils.NsxLibApiBase): return self.client.delete(self.get_path(url)) def update(self, lrouter_id, *args, **kwargs): - # Using internal method so we can access max_attempts in the decorator - @utils.retry_upon_exception( - exceptions.StaleRevision, - max_attempts=self.client.max_attempts) - def _do_update(): - lrouter = self.get(lrouter_id) - for k in kwargs: - lrouter[k] = kwargs[k] - # If revision_id of the payload that we send is older than what - # NSX has, 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(self.get_path(lrouter_id), body=lrouter) - - return _do_update() + return self._update_with_retry(lrouter_id, kwargs) def get_firewall_section_id(self, lrouter_id, router_body=None): """Return the id of the auto created firewall section of the router @@ -808,8 +778,7 @@ class NsxLibMetadataProxy(utils.NsxLibApiBase): return 'MetadataProxy' def update(self, uuid, server_url=None, secret=None, edge_cluster_id=None): - # get the current configuration - body = self.get(uuid) + body = {} # update the relevant fields if server_url is not None: body['metadata_server_url'] = server_url diff --git a/vmware_nsxlib/v3/resources.py b/vmware_nsxlib/v3/resources.py index 10d80b06..dc4cd54e 100644 --- a/vmware_nsxlib/v3/resources.py +++ b/vmware_nsxlib/v3/resources.py @@ -83,7 +83,6 @@ class LogicalPort(utils.NsxLibApiBase): attachment=None, description=None): tags = tags or [] - address_bindings = address_bindings or [] switch_profile_ids = switch_profile_ids or [] body = {} if tags: @@ -108,8 +107,7 @@ class LogicalPort(utils.NsxLibApiBase): address_classifier['vlan'] = int(binding.vlan) bindings.append(address_classifier) body['address_bindings'] = bindings - elif address_bindings == []: - # explicitly clear out address bindings + elif address_bindings is not None: body['address_bindings'] = [] if switch_profile_ids: @@ -151,14 +149,6 @@ class LogicalPort(utils.NsxLibApiBase): else: return False # no attachment change - def _build_address_bindings(self, address_bindings): - addr_bindings = [] - for binding in address_bindings: - addr_bindings.append(PacketAddressClassifier( - binding.get('ip_address'), binding.get('mac_address'), - binding.get('vlan'))) - return addr_bindings - def create(self, lswitch_id, vif_uuid, tags=None, attachment_type=nsx_constants.ATTACHMENT_VIF, admin_state=True, name=None, address_bindings=None, @@ -184,15 +174,7 @@ class LogicalPort(utils.NsxLibApiBase): return self.client.create(self.get_path(), body=body) def delete(self, lport_id): - # Using internal method so we can access max_attempts in the decorator - @utils.retry_upon_exception( - exceptions.StaleRevision, - max_attempts=self.client.max_attempts) - def _do_delete(): - return self.client.url_delete( - self.get_path('%s?detach=true' % lport_id)) - - return _do_delete() + self._delete_with_retry('%s?detach=true' % lport_id) def update(self, lport_id, vif_uuid, name=None, admin_state=None, @@ -203,38 +185,23 @@ class LogicalPort(utils.NsxLibApiBase): vif_type=None, app_id=None, allocate_addresses=nsx_constants.ALLOCATE_ADDRESS_NONE, description=None): - # Using internal method so we can access max_attempts in the decorator - @utils.retry_upon_exception( - exceptions.StaleRevision, - max_attempts=self.client.max_attempts) - def do_update(): - lport = self.get(lport_id) - tags = lport.get('tags', []) - if tags_update: - tags = utils.update_v3_tags(tags, tags_update) - # Assign outer function argument to a local scope - addr_bindings = address_bindings - if addr_bindings is None: - addr_bindings = self._build_address_bindings( - lport.get('address_bindings')) - attachment = self._prepare_attachment(attachment_type, vif_uuid, - allocate_addresses, vif_type, - parent_vif_id, traffic_tag, - app_id) - lport.update(self._build_body_attrs( - display_name=name, - admin_state=admin_state, tags=tags, - address_bindings=addr_bindings, - switch_profile_ids=switch_profile_ids, - attachment=attachment, - description=description)) + attachment = self._prepare_attachment(attachment_type, vif_uuid, + allocate_addresses, vif_type, + parent_vif_id, traffic_tag, + app_id) + lport = {} + if tags_update is not None: + lport['tags_update'] = tags_update + lport.update(self._build_body_attrs( + display_name=name, + admin_state=admin_state, + address_bindings=address_bindings, + switch_profile_ids=switch_profile_ids, + attachment=attachment, + description=description)) - # If revision_id of the payload that we send is older than what - # NSX has, 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(self.get_path(lport_id), body=lport) - return do_update() + return self._update_resource( + self.get_path(lport_id), lport, retry=True) def get_by_attachment(self, attachment_type, attachment_id): """Return all logical port matching the attachment type and Id""" @@ -306,50 +273,34 @@ class LogicalRouterPort(utils.NsxLibApiBase): return self.client.create(self.get_path(), body=body) def update(self, logical_port_id, **kwargs): - # Using internal method so we can access max_attempts in the decorator - @utils.retry_upon_exception( - exceptions.StaleRevision, - max_attempts=self.client.max_attempts) - def _do_update(): - logical_router_port = self.get(logical_port_id) - # special treatment for updating/removing the relay service - if 'relay_service_uuid' in kwargs: - if kwargs['relay_service_uuid']: - if (self.nsxlib and - self.nsxlib.feature_supported( - nsx_constants.FEATURE_DHCP_RELAY)): - logical_router_port['service_bindings'] = [ - self._get_relay_binding( - kwargs['relay_service_uuid'])] - else: - LOG.error("Ignoring relay_service_uuid for router " - "port %s: This feature is not supported.", - logical_port_id) + logical_router_port = {} + # special treatment for updating/removing the relay service + if 'relay_service_uuid' in kwargs: + if kwargs['relay_service_uuid']: + if (self.nsxlib and + self.nsxlib.feature_supported( + nsx_constants.FEATURE_DHCP_RELAY)): + logical_router_port['service_bindings'] = [ + self._get_relay_binding( + kwargs['relay_service_uuid'])] else: - # delete the current one - if 'service_bindings' in logical_router_port: - logical_router_port['service_bindings'] = [] - del kwargs['relay_service_uuid'] + LOG.error("Ignoring relay_service_uuid for router " + "port %s: This feature is not supported.", + logical_port_id) + else: + # delete the current one + if 'service_bindings' in logical_router_port: + logical_router_port['service_bindings'] = [] + del kwargs['relay_service_uuid'] - for k in kwargs: - logical_router_port[k] = kwargs[k] - # If revision_id of the payload that we send is older than what - # NSX has, 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(self.get_path(logical_port_id), - body=logical_router_port) - return _do_update() + for k in kwargs: + logical_router_port[k] = kwargs[k] + + return self._update_resource( + self.get_path(logical_port_id), logical_router_port, retry=True) def delete(self, logical_port_id): - # Using internal method so we can access max_attempts in the decorator - @utils.retry_upon_exception( - exceptions.StaleRevision, - max_attempts=self.client.max_attempts) - def _do_delete(): - return self.client.url_delete(self.get_path(logical_port_id)) - - return _do_delete() + self._delete_with_retry(logical_port_id) def get_by_lswitch_id(self, logical_switch_id): resource = '?logical_switch_id=%s' % logical_switch_id @@ -501,18 +452,11 @@ class LogicalDhcpServer(utils.NsxLibApiBase): def update(self, uuid, dhcp_profile_id=None, server_ip=None, name=None, dns_nameservers=None, domain_name=None, gateway_ip=False, options=None, tags=None): - # Using internal method so we can access max_attempts in the decorator - @utils.retry_upon_exception( - exceptions.StaleRevision, - max_attempts=self.client.max_attempts) - def _do_update(): - body = self.get(uuid) - self._construct_server(body, dhcp_profile_id, server_ip, name, - dns_nameservers, domain_name, gateway_ip, - options, tags) - return self.client.update(self.get_path(uuid), body=body) - - return _do_update() + body = {'ipv4_dhcp_server': {}} + self._construct_server(body, dhcp_profile_id, server_ip, name, + dns_nameservers, domain_name, gateway_ip, + options, tags) + return self._update_with_retry(uuid, body) def create_binding(self, server_uuid, mac, ip, hostname=None, lease_time=None, options=None, gateway_ip=False): @@ -534,17 +478,10 @@ class LogicalDhcpServer(utils.NsxLibApiBase): return self.get(url) def update_binding(self, server_uuid, binding_uuid, **kwargs): - # Using internal method so we can access max_attempts in the decorator - @utils.retry_upon_exception( - exceptions.StaleRevision, - max_attempts=self.client.max_attempts) - def _do_update(): - body = self.get_binding(server_uuid, binding_uuid) - body.update(kwargs) - url = "%s/static-bindings/%s" % (server_uuid, binding_uuid) - return self.client.url_put(self.get_path(url), body) - - return _do_update() + body = {} + body.update(kwargs) + url = "%s/static-bindings/%s" % (server_uuid, binding_uuid) + self._update_resource(self.get_path(url), body, retry=True) def delete_binding(self, server_uuid, binding_uuid): url = "%s/static-bindings/%s" % (server_uuid, binding_uuid) diff --git a/vmware_nsxlib/v3/security.py b/vmware_nsxlib/v3/security.py index 117e75bb..2e670ea8 100644 --- a/vmware_nsxlib/v3/security.py +++ b/vmware_nsxlib/v3/security.py @@ -41,6 +41,14 @@ class NsxLibNsGroup(utils.NsxLibApiBase): super(NsxLibNsGroup, self).__init__(client, nsxlib_config, nsxlib=nsxlib) + @property + def uri_segment(self): + return 'ns-groups' + + @property + def resource_type(self): + return 'NSGroup' + def update_on_backend(self, context, security_group, nsgroup_id, section_id, log_sg_allowed_traffic): @@ -130,35 +138,30 @@ class NsxLibNsGroup(utils.NsxLibApiBase): body.update({'membership_criteria': membership_criteria}) else: body.update({'membership_criteria': [membership_criteria]}) - return self.client.create('ns-groups', body) + return self.client.create(self.get_path(), body) def list(self): return self.client.list( - 'ns-groups?populate_references=false').get('results', []) + '%s?populate_references=false' % self.get_path()).get( + 'results', []) def update(self, nsgroup_id, display_name=None, description=None, membership_criteria=None, members=None, tags_update=None): - # Using internal method so we can access max_attempts in the decorator - @utils.retry_upon_exception( - exceptions.StaleRevision, - max_attempts=self.nsxlib_config.max_attempts) - def _do_update(): - nsgroup = self.read(nsgroup_id) - if display_name is not None: - nsgroup['display_name'] = display_name - if description is not None: - nsgroup['description'] = description - if members is not None: - nsgroup['members'] = members - if membership_criteria is not None: - nsgroup['membership_criteria'] = [membership_criteria] - if tags_update is not None: - nsgroup['tags'] = utils.update_v3_tags(nsgroup.get('tags', []), - tags_update) - return self.client.update( - 'ns-groups/%s' % nsgroup_id, nsgroup) - - return _do_update() + nsgroup = {} + if display_name is not None: + nsgroup['display_name'] = display_name + if description is not None: + nsgroup['description'] = description + if members is not None: + nsgroup['members'] = members + if membership_criteria is not None: + nsgroup['membership_criteria'] = [membership_criteria] + if tags_update is not None: + nsgroup['tags_update'] = tags_update + return self._update_resource( + self.get_path(nsgroup_id), nsgroup, + get_params='?populate_references=true', + retry=True) def get_member_expression(self, target_type, target_id): return { @@ -169,7 +172,7 @@ class NsxLibNsGroup(utils.NsxLibApiBase): 'value': target_id} def _update_with_members(self, nsgroup_id, members, action): - members_update = 'ns-groups/%s?action=%s' % (nsgroup_id, action) + members_update = '%s?action=%s' % (self.get_path(nsgroup_id), action) return self.client.create(members_update, members) def add_members(self, nsgroup_id, target_type, target_ids): @@ -210,12 +213,12 @@ class NsxLibNsGroup(utils.NsxLibApiBase): def read(self, nsgroup_id): return self.client.get( - 'ns-groups/%s?populate_references=true' % nsgroup_id) + '%s?populate_references=true' % self.get_path(nsgroup_id)) def delete(self, nsgroup_id): try: return self.client.delete( - 'ns-groups/%s?force=true' % nsgroup_id) + '%s?force=true' % self.get_path(nsgroup_id)) # FIXME(roeyc): Should only except NotFound error. except Exception: LOG.debug("NSGroup %s does not exists for delete request.", @@ -231,28 +234,24 @@ class NsxLibNsGroup(utils.NsxLibApiBase): class NsxLibFirewallSection(utils.NsxLibApiBase): - def add_member_to_fw_exclude_list(self, target_id, target_type): - @utils.retry_upon_exception( - exceptions.StaleRevision, - max_attempts=self.nsxlib_config.max_attempts) - def _add_member_to_fw_exclude_list(): - resource = 'firewall/excludelist?action=add_member' - body = {"target_id": target_id, - "target_type": target_type} - self.client.create(resource, body) + @property + def uri_segment(self): + return 'firewall/sections' - _add_member_to_fw_exclude_list() + @property + def resource_type(self): + return 'FirewallSection' + + def add_member_to_fw_exclude_list(self, target_id, target_type): + resource = 'firewall/excludelist?action=add_member' + body = {"target_id": target_id, + "target_type": target_type} + self._create_with_retry(resource, body) def remove_member_from_fw_exclude_list(self, target_id, target_type): - @utils.retry_upon_exception( - exceptions.StaleRevision, - max_attempts=self.nsxlib_config.max_attempts) - def _remove_member_from_fw_exclude_list(): - resource = ('firewall/excludelist?action=remove_member&object_id=' - + target_id) - self.client.create(resource) - - _remove_member_from_fw_exclude_list() + resource = ('firewall/excludelist?action=remove_member&object_id=' + + target_id) + self._create_with_retry(resource) def get_excludelist(self): return self.client.list('firewall/excludelist') @@ -332,92 +331,77 @@ class NsxLibFirewallSection(utils.NsxLibApiBase): applied_tos, tags, operation=consts.FW_INSERT_BOTTOM, other_section=None): - @utils.retry_upon_exception( - exceptions.StaleRevision, - max_attempts=self.nsxlib_config.max_attempts) - def _create_empty(): - resource = 'firewall/sections?operation=%s' % operation - body = self._build(display_name, description, - applied_tos, tags) - if other_section: - resource += '&id=%s' % other_section - return self.client.create(resource, body) - return _create_empty() + resource = '%s?operation=%s' % (self.uri_segment, operation) + body = self._build(display_name, description, + applied_tos, tags) + if other_section: + resource += '&id=%s' % other_section + return self._create_with_retry(resource, body) def create_with_rules(self, display_name, description, applied_tos=None, tags=None, operation=consts.FW_INSERT_BOTTOM, other_section=None, rules=None): - @utils.retry_upon_exception( - exceptions.StaleRevision, - max_attempts=self.nsxlib_config.max_attempts) - def _create_with_rules(): - resource = 'firewall/sections?operation=%s' % operation - body = { - 'display_name': display_name, - 'description': description, - 'stateful': True, - 'section_type': consts.FW_SECTION_LAYER3, - 'applied_tos': applied_tos or [], - 'tags': tags or [] - } - if rules is not None: - resource += '&action=create_with_rules' - body['rules'] = rules - if other_section: - resource += '&id=%s' % other_section - return self.client.create(resource, body) - return _create_with_rules() + resource = '%s?operation=%s' % (self.uri_segment, operation) + body = { + 'display_name': display_name, + 'description': description, + 'stateful': True, + 'section_type': consts.FW_SECTION_LAYER3, + 'applied_tos': applied_tos or [], + 'tags': tags or [] + } + if rules is not None: + resource += '&action=create_with_rules' + body['rules'] = rules + if other_section: + resource += '&id=%s' % other_section + return self._create_with_retry(resource, body) def update(self, section_id, display_name=None, description=None, applied_tos=None, rules=None, tags_update=None, force=False): - # Using internal method so we can access max_attempts in the decorator - @utils.retry_upon_exception( - exceptions.StaleRevision, - max_attempts=self.nsxlib_config.max_attempts) - def _do_update(): - resource = 'firewall/sections/%s' % section_id - section = self.read(section_id) + resource = self.get_path(section_id) + params = None + section = {} + if rules is not None: + params = '?action=update_with_rules' + section['rules'] = rules + if display_name is not None: + section['display_name'] = display_name + if description is not None: + section['description'] = description + if applied_tos is not None: + section['applied_tos'] = [self.get_nsgroup_reference(nsg_id) + for nsg_id in applied_tos] + if tags_update is not None: + section['tags_update'] = tags_update - if rules is not None: - resource += '?action=update_with_rules' - section.update({'rules': rules}) - if display_name is not None: - section['display_name'] = display_name - if description is not None: - section['description'] = description - if applied_tos is not None: - section['applied_tos'] = [self.get_nsgroup_reference(nsg_id) - for nsg_id in applied_tos] - if tags_update is not None: - section['tags'] = utils.update_v3_tags(section.get('tags', []), - tags_update) - headers = None - if force: - # shared sections (like default section) can serve multiple - # openstack deployments. If some operate under protected - # identities, force-overwrite is needed. - # REVISIT(annak): find better solution for shared sections - headers = {'X-Allow-Overwrite': 'true'} + headers = None + if force: + # shared sections (like default section) can serve multiple + # openstack deployments. If some operate under protected + # identities, force-overwrite is needed. + # REVISIT(annak): find better solution for shared sections + headers = {'X-Allow-Overwrite': 'true'} - if rules is not None: - return self.client.create(resource, section, headers=headers) + if rules is not None: + return self._update_resource(resource, section, + headers=headers, + create_action=True, + action_params=params, + retry=True) - elif any(p is not None for p in (display_name, description, - applied_tos, tags_update)): - return self.client.update(resource, section, headers=headers) - - return _do_update() - - def read(self, section_id): - resource = 'firewall/sections/%s' % section_id - return self.client.get(resource) + elif any(p is not None for p in (display_name, description, + applied_tos, tags_update)): + return self._update_resource(resource, section, + headers=headers, + action_params=params, + retry=True) def list(self): - resource = 'firewall/sections' - return self.client.list(resource).get('results', []) + return self.client.list(self.get_path()).get('results', []) def delete(self, section_id): - resource = 'firewall/sections/%s?cascade=true' % section_id + resource = '%s?cascade=true' % self.get_path(section_id) return self.client.delete(resource) def get_nsgroup_reference(self, nsgroup_id): @@ -470,36 +454,21 @@ class NsxLibFirewallSection(utils.NsxLibApiBase): return rule_dict def add_rule(self, rule, section_id, operation=consts.FW_INSERT_BOTTOM): - @utils.retry_upon_exception( - exceptions.StaleRevision, - max_attempts=self.nsxlib_config.max_attempts) - def _add_rule(): - resource = 'firewall/sections/%s/rules' % section_id - params = '?operation=%s' % operation - return self.client.create(resource + params, rule) - return _add_rule() + resource = '%s/rules' % self.get_path(section_id) + params = '?operation=%s' % operation + return self._create_with_retry(resource + params, rule) def add_rules(self, rules, section_id, operation=consts.FW_INSERT_BOTTOM): - @utils.retry_upon_exception( - exceptions.StaleRevision, - max_attempts=self.nsxlib_config.max_attempts) - def _add_rules(): - resource = 'firewall/sections/%s/rules' % section_id - params = '?action=create_multiple&operation=%s' % operation - return self.client.create(resource + params, {'rules': rules}) - return _add_rules() + resource = '%s/rules' % self.get_path(section_id) + params = '?action=create_multiple&operation=%s' % operation + return self._create_with_retry(resource + params, {'rules': rules}) def delete_rule(self, section_id, rule_id): - @utils.retry_upon_exception( - exceptions.StaleRevision, - max_attempts=self.nsxlib_config.max_attempts) - def _delete_rule(): - resource = 'firewall/sections/%s/rules/%s' % (section_id, rule_id) - return self.client.delete(resource) - return _delete_rule() + resource = '%s/rules/%s' % (section_id, rule_id) + return self._delete_with_retry(resource) def get_rules(self, section_id): - resource = 'firewall/sections/%s/rules' % section_id + resource = '%s/rules' % self.get_path(section_id) return self.client.get(resource) def get_default_rule(self, section_id): @@ -621,50 +590,43 @@ class NsxLibFirewallSection(utils.NsxLibApiBase): class NsxLibIPSet(utils.NsxLibApiBase): + @property + def uri_segment(self): + return 'ip-sets' + + @property + def resource_type(self): + return 'IPSet' + def create(self, display_name, description=None, ip_addresses=None, tags=None): - resource = 'ip-sets' body = { 'display_name': display_name, 'description': description or '', 'ip_addresses': ip_addresses or [], 'tags': tags or [] } - return self.client.create(resource, body) + return self.client.create(self.get_path(), body) def update(self, ip_set_id, display_name=None, description=None, ip_addresses=None, tags_update=None): - # Using internal method so we can access max_attempts in the decorator - @utils.retry_upon_exception( - exceptions.StaleRevision, - max_attempts=self.nsxlib_config.max_attempts) - def _do_update(): - resource = 'ip-sets/%s' % ip_set_id - ip_set = self.read(ip_set_id) - tags = ip_set.get('tags', []) - if tags_update: - tags = utils.update_v3_tags(tags, tags_update) - if display_name is not None: - ip_set['display_name'] = display_name - if description is not None: - ip_set['description'] = description - if ip_addresses is not None: - ip_set['ip_addresses'] = ip_addresses - return self.client.update(resource, ip_set) - - return _do_update() + ip_set = {} + if tags_update: + ip_set['tags_update'] = tags_update + if display_name is not None: + ip_set['display_name'] = display_name + if description is not None: + ip_set['description'] = description + if ip_addresses is not None: + ip_set['ip_addresses'] = ip_addresses + return self._update_resource(self.get_path(ip_set_id), + ip_set, retry=True) def read(self, ip_set_id): return self.client.get('ip-sets/%s' % ip_set_id) def delete(self, ip_set_id): - # Using internal method so we can access max_attempts in the decorator - @utils.retry_upon_exception( - exceptions.StaleRevision, - max_attempts=self.nsxlib_config.max_attempts) - def _do_delete(): - return self.client.delete('ip-sets/%s' % ip_set_id) - return _do_delete() + self._delete_with_retry(ip_set_id) def get_ipset_reference(self, ip_set_id): return {'target_id': ip_set_id, diff --git a/vmware_nsxlib/v3/utils.py b/vmware_nsxlib/v3/utils.py index ab0f41e4..ebb69ad0 100644 --- a/vmware_nsxlib/v3/utils.py +++ b/vmware_nsxlib/v3/utils.py @@ -316,6 +316,10 @@ class NsxLibApiBase(object): self.cache.update(uuid, result) return result + def read(self, uuid, silent=False): + """The same as get""" + return self.get(uuid, silent=silent) + def delete(self, uuid): if self.use_cache_for_get: self.cache.remove(uuid) @@ -331,19 +335,96 @@ class NsxLibApiBase(object): def _update_with_retry(self, uuid, payload): if self.use_cache_for_get: self.cache.remove(uuid) - return self._update_resource_with_retry(self.get_path(uuid), payload) + return self._update_resource(self.get_path(uuid), payload, retry=True) - def _update_resource_with_retry(self, resource, payload): - # Using internal method so we can access max_attempts in the decorator - @retry_upon_exception(nsxlib_exceptions.StaleRevision, - max_attempts=self.nsxlib_config.max_attempts) - def do_update(): - revised_payload = self.client.get(resource) - for key_name in payload.keys(): + def _internal_update_resource(self, resource, payload, headers=None, + create_action=False, + get_params=None, + action_params=None, + update_payload_cbk=None): + get_path = action_path = resource + if get_params: + get_path = get_path + get_params + if action_params: + action_path = action_path + action_params + revised_payload = self.client.get(get_path) + # custom resource callback for updating the payload + if update_payload_cbk: + update_payload_cbk(revised_payload, payload) + # special treatment for tags (merge old and new) + if 'tags_update' in payload.keys(): + revised_payload['tags'] = update_v3_tags( + revised_payload.get('tags', []), + payload['tags_update']) + del payload['tags_update'] + # update all the rest of the parameters + for key_name in payload.keys(): + # handle 2 levels of dictionary: + if isinstance(payload[key_name], dict): + if key_name not in revised_payload: + revised_payload[key_name] = payload[key_name] + else: + # copy each key + revised_payload[key_name].update(payload[key_name]) + else: revised_payload[key_name] = payload[key_name] - return self.client.update(resource, revised_payload) + if create_action: + return self.client.create(action_path, revised_payload, + headers=headers) + else: + return self.client.update(action_path, revised_payload, + headers=headers) - return do_update() + def _update_resource(self, resource, payload, headers=None, + create_action=False, get_params=None, + action_params=None, update_payload_cbk=None, + retry=False): + if retry: + # If revision_id of the payload that we send is older than what + # NSX has, 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 + @retry_upon_exception( + nsxlib_exceptions.StaleRevision, + max_attempts=self.nsxlib_config.max_attempts) + def do_update(): + return self._internal_update_resource( + resource, payload, + headers=headers, + create_action=create_action, + get_params=get_params, + action_params=action_params, + update_payload_cbk=update_payload_cbk) + + return do_update() + else: + return self._internal_update_resource( + resource, payload, + headers=headers, + create_action=create_action, + get_params=get_params, + action_params=action_params, + update_payload_cbk=update_payload_cbk) + + def _delete_with_retry(self, resource): + # Using internal method so we can access max_attempts in the decorator + @retry_upon_exception( + nsxlib_exceptions.StaleRevision, + max_attempts=self.nsxlib_config.max_attempts) + def _do_delete(): + self.client.delete(self.get_path(resource)) + + _do_delete() + + def _create_with_retry(self, resource, body=None, headers=None): + # Using internal method so we can access max_attempts in the decorator + @retry_upon_exception( + nsxlib_exceptions.StaleRevision, + max_attempts=self.nsxlib_config.max_attempts) + def _do_create(): + return self.client.create(resource, body, headers=headers) + + return _do_create() def _get_resource_by_name_or_id(self, name_or_id, resource): all_results = self.client.list(resource)['results']