From cd47e56366f680c91aed9ede47618f27d76f0d71 Mon Sep 17 00:00:00 2001 From: Leonardo Maycotte Date: Fri, 19 Sep 2014 08:48:52 -0500 Subject: [PATCH] Subnets and Ports behaviors client and cleanUp methods with retries and timeouts - Adding subnets and ports behaviors methods with retries and response checks for update, get, list and delete api client calls - Adding subnets and ports behaviors delete clean up methods based on delete timeouts, intended for tearDown and cleanUP test methods Change-Id: I50e3fc678c46d8b6c4bf6afda0e43af020c9ce7c --- .../networking/networks/common/behaviors.py | 4 + .../networking/networks/common/config.py | 4 +- cloudcafe/networking/networks/config.py | 4 +- .../networks/networks_api/behaviors.py | 70 ++-- .../networks/ports_api/behaviors.py | 332 +++++++++++++++++- .../networks/subnets_api/behaviors.py | 328 ++++++++++++++++- 6 files changed, 690 insertions(+), 52 deletions(-) diff --git a/cloudcafe/networking/networks/common/behaviors.py b/cloudcafe/networking/networks/common/behaviors.py index 597e38c3..b6aaaccb 100644 --- a/cloudcafe/networking/networks/common/behaviors.py +++ b/cloudcafe/networking/networks/common/behaviors.py @@ -168,3 +168,7 @@ class NetworkingResponse(object): def __init__(self): self.response = None self.failures = list() + + def __repr__(self): + return 'response: {0}\nfailures: {1}'.format(self.response, + self.failures) diff --git a/cloudcafe/networking/networks/common/config.py b/cloudcafe/networking/networks/common/config.py index 99e92fe3..f91d647f 100644 --- a/cloudcafe/networking/networks/common/config.py +++ b/cloudcafe/networking/networks/common/config.py @@ -46,12 +46,12 @@ class NetworkingBaseConfig(ConfigSectionInterface): @property def resource_create_timeout(self): """Seconds to wait for creating a resource""" - return int(self.get("resource_create_timeout", 10)) + return int(self.get("resource_create_timeout", 7)) @property def resource_delete_timeout(self): """Seconds to wait for deleting a resource""" - return int(self.get("resource_delete_timeout", 10)) + return int(self.get("resource_delete_timeout", 7)) @property def resource_change_status_timeout(self): diff --git a/cloudcafe/networking/networks/config.py b/cloudcafe/networking/networks/config.py index 3a47765b..c1eee520 100644 --- a/cloudcafe/networking/networks/config.py +++ b/cloudcafe/networking/networks/config.py @@ -45,12 +45,12 @@ class NetworkingEndpointConfig(ConfigSectionInterface): @property def networking_endpoint_url(self): """Optional override of the Networking url""" - return self.get("networking_endpoint_url") + return self.get("networking_endpoint_url", '') @property def header_tenant_id(self): """Optional tenant ID to set in client request headers""" - return self.get("header_tenant_id") + return self.get("header_tenant_id", '') class NetworkingAdminEndpointConfig(NetworkingEndpointConfig): diff --git a/cloudcafe/networking/networks/networks_api/behaviors.py b/cloudcafe/networking/networks/networks_api/behaviors.py index 6e5d222f..ebc8487b 100644 --- a/cloudcafe/networking/networks/networks_api/behaviors.py +++ b/cloudcafe/networking/networks/networks_api/behaviors.py @@ -59,9 +59,8 @@ class NetworksBehaviors(NetworkingBaseBehaviors): @type use_exact_name: bool @param poll_interval: sleep time interval between API retries @type poll_interval: int - @return: Network entity and the failure list if created successful, or - None and the failure list if the raise_exception flag was False - @rtype: tuple with Network or None and failure list (may be empty) + @return: NetworkingResponse object with api response and failure list + @rtype: common.behaviors.NetworkingResponse """ if name is None: name = rand_name(self.config.starts_with_name) @@ -86,11 +85,12 @@ class NetworksBehaviors(NetworkingBaseBehaviors): status_code=NeutronResponseCodes.CREATE_NETWORK, label=name, message=err_msg) + result.response = resp + if not resp_check: + return result + # Failures will be an empty list if the create was successful the # first time - if not resp_check: - result.response = resp - return result result.failures.append(resp_check) time.sleep(poll_interval) @@ -130,9 +130,8 @@ class NetworksBehaviors(NetworkingBaseBehaviors): @type raise_exception: bool @param poll_interval: sleep time interval between API retries @type poll_interval: int - @return: Network entity and the failure list if updated successful, or - None and the failure list if the raise_exception flag was False - @rtype: tuple with Network or None and failure list (may be empty) + @return: NetworkingResponse object with api response and failure list + @rtype: common.behaviors.NetworkingResponse """ poll_interval = poll_interval or self.config.api_poll_interval resource_update_attempts = (resource_update_attempts or @@ -153,11 +152,12 @@ class NetworksBehaviors(NetworkingBaseBehaviors): status_code=NeutronResponseCodes.UPDATE_NETWORK, label=network_id, message=err_msg) + result.response = resp + if not resp_check: + return result + # Failures will be an empty list if the update was successful the # first time - if not resp_check: - result.response = resp - return result result.failures.append(resp_check) time.sleep(poll_interval) @@ -184,9 +184,8 @@ class NetworksBehaviors(NetworkingBaseBehaviors): @type raise_exception: bool @param poll_interval: sleep time interval between API retries @type poll_interval: int - @return: Network entity and the failure list if the get successful, or - None and the failure list if the raise_exception flag was False - @rtype: tuple with Network or None and failure list (may be empty) + @return: NetworkingResponse object with api response and failure list + @rtype: common.behaviors.NetworkingResponse """ poll_interval = poll_interval or self.config.api_poll_interval resource_get_attempts = (resource_get_attempts or @@ -204,11 +203,12 @@ class NetworksBehaviors(NetworkingBaseBehaviors): status_code=NeutronResponseCodes.GET_NETWORK, label=network_id, message=err_msg) + result.response = resp + if not resp_check: + return result + # Failures will be an empty list if the get was successful the # first time - if not resp_check: - result.response = resp - return result result.failures.append(resp_check) time.sleep(poll_interval) @@ -254,9 +254,8 @@ class NetworksBehaviors(NetworkingBaseBehaviors): @type raise_exception: bool @param poll_interval: sleep time interval between API retries @type poll_interval: int - @return: Network entity and the failure list if the list was successful - or None and the failure list if the raise_exception flag was False - @rtype: tuple with Network list or None and failure list (may be empty) + @return: NetworkingResponse object with api response and failure list + @rtype: common.behaviors.NetworkingResponse """ poll_interval = poll_interval or self.config.api_poll_interval resource_list_attempts = (resource_list_attempts or @@ -278,11 +277,12 @@ class NetworksBehaviors(NetworkingBaseBehaviors): status_code=NeutronResponseCodes.LIST_NETWORKS, label='', message=err_msg) + result.response = resp + if not resp_check: + return result + # Failures will be an empty list if the list was successful the # first time - if not resp_check: - result.response = resp - return result result.failures.append(resp_check) time.sleep(poll_interval) @@ -308,9 +308,8 @@ class NetworksBehaviors(NetworkingBaseBehaviors): @type raise_exception: bool @param poll_interval: sleep time interval between API retries @type poll_interval: int - @return: True and the failure list if the delete was successful - or None and the failure list if the raise_exception flag was False - @rtype: tuple with True or None and failure list (may be empty) + @return: NetworkingResponse object with api response and failure list + @rtype: common.behaviors.NetworkingResponse """ poll_interval = poll_interval or self.config.api_poll_interval resource_delete_attempts = (resource_delete_attempts or @@ -322,11 +321,11 @@ class NetworksBehaviors(NetworkingBaseBehaviors): attempt + 1, resource_delete_attempts, network_id)) resp = self.client.delete_network(network_id=network_id) + result.response = resp # Delete response is without entity so resp_check can not be used if (resp.ok and resp.status_code == NeutronResponseCodes.DELETE_NETWORK): - result.response = True return result err_msg = ('{network} Network Delete failure, expected status ' @@ -372,14 +371,15 @@ class NetworksBehaviors(NetworkingBaseBehaviors): try: self.client.delete_network(network_id=network_id) resp = self.client.get_network(network_id=network_id) - if resp.status_code == NeutronResponseCodes.NOT_FOUND: - return None except Exception as err: err_msg = ('Encountered an exception deleting a network with' 'the clean_network method. Exception: {0}').format(err) self._log.error(err_msg) - finally: - time.sleep(poll_interval) + + if resp.status_code == NeutronResponseCodes.NOT_FOUND: + return None + time.sleep(poll_interval) + err_msg = 'Unable to delete {0} network within a {1}s timeout'.format( network_id, timeout) self._log.error(err_msg) @@ -388,9 +388,9 @@ class NetworksBehaviors(NetworkingBaseBehaviors): def clean_networks(self, networks_list): """ @summary: deletes each network from a list calling clean_network - @param network_list: list of network UUIDs - @type network_list: list(str) - @return: list of undeleted network UUIDs + @param networks_list: list of network UUIDs + @type networks_list: list(str) + @return: list of undeleted networks UUIDs @rtype: list(str) """ log_msg = 'Deleting networks: {0}'.format(networks_list) diff --git a/cloudcafe/networking/networks/ports_api/behaviors.py b/cloudcafe/networking/networks/ports_api/behaviors.py index f8318bd4..749e386c 100644 --- a/cloudcafe/networking/networks/ports_api/behaviors.py +++ b/cloudcafe/networking/networks/ports_api/behaviors.py @@ -15,13 +15,17 @@ limitations under the License. """ import time +import time + from cloudcafe.common.tools.datagen import rand_name from cloudcafe.networking.networks.common.behaviors \ import NetworkingBaseBehaviors, NetworkingResponse from cloudcafe.networking.networks.common.constants \ import NeutronResponseCodes from cloudcafe.networking.networks.common.exceptions \ - import ResourceBuildException, NetworkIDMissingException + import NetworkIDMissingException, ResourceBuildException,\ + ResourceDeleteException, ResourceGetException, ResourceListException,\ + ResourceUpdateException class PortsBehaviors(NetworkingBaseBehaviors): @@ -72,9 +76,8 @@ class PortsBehaviors(NetworkingBaseBehaviors): @type use_exact_name: bool @param poll_interval: sleep time interval between API retries @type poll_interval: int - @return: Port entity if created successful and the failure list, or - None and the failure list if the raise_exception flag was False - @rtype: tuple with Port or None and failure list (may be empty) + @return: NetworkingResponse object with api response and failure list + @rtype: common.behaviors.NetworkingResponse """ if not network_id: raise NetworkIDMissingException @@ -105,11 +108,12 @@ class PortsBehaviors(NetworkingBaseBehaviors): status_code=NeutronResponseCodes.CREATE_PORT, label=name, message=err_msg, network_id=network_id) + result.response = resp + if not resp_check: + return result + # Failures will be an empty list if the create was successful the # first time - if not resp_check: - result.response = resp - return result result.failures.append(resp_check) time.sleep(poll_interval) @@ -121,3 +125,317 @@ class PortsBehaviors(NetworkingBaseBehaviors): if raise_exception: raise ResourceBuildException(err_msg) return result + + def update_port(self, port_id, name=None, admin_state_up=None, + fixed_ips=None, device_id=None, device_owner=None, + security_groups=None, resource_update_attempts=None, + raise_exception=False, poll_interval=None): + """ + @summary: Updates and verifies a specified Port + @param port_id: The UUID for the port + @type port_id: string + @param name: human readable name for the port, may not be unique + (CRUD: CRU) + @type name: string + @param admin_state_up: true or false (default true), the admin state + of the port. If down, the port does not forward packets (CRUD: CRU) + @type admin_state_up: bool + @param fixed_ips: ip addresses for the port associating the port with + the subnets where the IPs come from (CRUD: CRU) + @type fixed_ips: list(dict) + @param device_id: id of device using this port (CRUD: CRUD) + @type device_id: string + @param string device_owner: entity using this port (ex. dhcp agent, + CRUD: CRUD) + @type device_owner: string + @param security_groups: ids of any security groups associated with the + port (CRUD: CRUD) + @type security_groups: list(dict) + @param resource_update_attempts: number of API retries + @type resource_update_attempts: int + @param raise_exception: flag to raise an exception if the + Port was not updated or to return None + @type raise_exception: bool + @param poll_interval: sleep time interval between API retries + @type poll_interval: int + @return: NetworkingResponse object with api response and failure list + @rtype: common.behaviors.NetworkingResponse + """ + poll_interval = poll_interval or self.config.api_poll_interval + resource_update_attempts = (resource_update_attempts or + self.config.api_retries) + + result = NetworkingResponse() + err_msg = 'Port Update failure' + for attempt in range(resource_update_attempts): + self._log.debug('Attempt {0} of {1} updating port {2}'.format( + attempt + 1, resource_update_attempts, port_id)) + + resp = self.client.update_port( + port_id=port_id, name=name, admin_state_up=admin_state_up, + fixed_ips=fixed_ips, device_id=device_id, + device_owner=device_owner, security_groups=security_groups) + + resp_check = self.check_response(resp=resp, + status_code=NeutronResponseCodes.UPDATE_PORT, + label=port_id, message=err_msg) + + result.response = resp + if not resp_check: + return result + + # Failures will be an empty list if the update was successful the + # first time + result.failures.append(resp_check) + time.sleep(poll_interval) + + else: + err_msg = ( + 'Unable to update {0} port after {1} attempts: ' + '{2}').format(port_id, resource_update_attempts, + result.failures) + self._log.error(err_msg) + if raise_exception: + raise ResourceUpdateException(err_msg) + return result + + def get_port(self, port_id, resource_get_attempts=None, + raise_exception=False, poll_interval=None): + """ + @summary: Shows and verifies a specified port + @param port_id: The UUID for the port + @type port_id: string + @param resource_get_attempts: number of API retries + @type resource_get_attempts: int + @param raise_exception: flag to raise an exception if the get + Port was not as expected or to return None + @type raise_exception: bool + @param poll_interval: sleep time interval between API retries + @type poll_interval: int + @return: NetworkingResponse object with api response and failure list + @rtype: common.behaviors.NetworkingResponse + """ + poll_interval = poll_interval or self.config.api_poll_interval + resource_get_attempts = (resource_get_attempts or + self.config.api_retries) + + result = NetworkingResponse() + err_msg = 'Port Get failure' + for attempt in range(resource_get_attempts): + self._log.debug('Attempt {0} of {1} getting network {2}'.format( + attempt + 1, resource_get_attempts, port_id)) + + resp = self.client.get_port(port_id=port_id) + + resp_check = self.check_response(resp=resp, + status_code=NeutronResponseCodes.GET_PORT, + label=port_id, message=err_msg) + + result.response = resp + if not resp_check: + return result + + # Failures will be an empty list if the get was successful the + # first time + result.failures.append(resp_check) + time.sleep(poll_interval) + + else: + err_msg = ( + 'Unable to GET {0} port after {1} attempts: ' + '{2}').format(port_id, resource_get_attempts, result.failures) + self._log.error(err_msg) + if raise_exception: + raise ResourceGetException(err_msg) + return result + + def list_ports(self, port_id=None, network_id=None, name=None, status=None, + admin_state_up=None, device_id=None, tenant_id=None, + device_owner=None, mac_address=None, limit=None, + marker=None, page_reverse=None, resource_list_attempts=None, + raise_exception=False, poll_interval=None): + """ + @summary: Lists ports and verifies the response is the expected + @param port_id: The UUID for the port to filter by + @type port_id: string + @param network_id: network ID to filter by + @type network_id: string + @param name: port name to filter by + @type name: string + @param status: port status to filter by + @type status: string + @param admin_state_up: Admin state of the port to filter by + @type admin_state_up: bool + @param device_id: id of device to filter by + @type device_id: string + @param tenant_id: owner of the port to filter by + @type tenant_id: string + @param device_owner: device owner to filter by + @type device_owner: string + @param mac_address: mac address to filter by + @type mac_address: string + @param limit: page size + @type limit: int + @param marker: Id of the last item of the previous page + @type marker: string + @param page_reverse: direction of the page + @type page_reverse: bool + @param resource_list_attempts: number of API retries + @type resource_list_attempts: int + @param raise_exception: flag to raise an exception if the list + Port was not as expected or to return None + @type raise_exception: bool + @param poll_interval: sleep time interval between API retries + @type poll_interval: int + @return: NetworkingResponse object with api response and failure list + @rtype: common.behaviors.NetworkingResponse + """ + poll_interval = poll_interval or self.config.api_poll_interval + resource_list_attempts = (resource_list_attempts or + self.config.api_retries) + + result = NetworkingResponse() + err_msg = 'Port List failure' + for attempt in range(resource_list_attempts): + self._log.debug('Attempt {0} of {1} with port list'.format( + attempt + 1, resource_list_attempts)) + + resp = self.client.list_ports( + port_id=port_id, network_id=network_id, name=name, + status=status, admin_state_up=admin_state_up, + device_id=device_id, tenant_id=tenant_id, + device_owner=device_owner, mac_address=mac_address, + limit=limit, marker=marker, page_reverse=page_reverse) + + resp_check = self.check_response(resp=resp, + status_code=NeutronResponseCodes.LIST_PORTS, + label='', message=err_msg) + + result.response = resp + if not resp_check: + return result + + # Failures will be an empty list if the list was successful the + # first time + result.failures.append(resp_check) + time.sleep(poll_interval) + + else: + err_msg = ( + 'Unable to LIST ports after {0} attempts: ' + '{1}').format(resource_list_attempts, result.failures) + self._log.error(err_msg) + if raise_exception: + raise ResourceListException(err_msg) + return result + + def delete_port(self, port_id, resource_delete_attempts=None, + raise_exception=False, poll_interval=None): + """ + @summary: Deletes and verifies a specified port is deleted + @param string port_id: The UUID for the port + @type port_id: string + @param resource_delete_attempts: number of API retries + @type resource_delete_attempts: int + @param raise_exception: flag to raise an exception if the deleted + Port was not as expected or to return None + @type raise_exception: bool + @param poll_interval: sleep time interval between API retries + @type poll_interval: int + @return: NetworkingResponse object with api response and failure list + @rtype: common.behaviors.NetworkingResponse + """ + poll_interval = poll_interval or self.config.api_poll_interval + resource_delete_attempts = (resource_delete_attempts or + self.config.api_retries) + + result = NetworkingResponse() + for attempt in range(resource_delete_attempts): + self._log.debug('Attempt {0} of {1} deleting port {2}'.format( + attempt + 1, resource_delete_attempts, port_id)) + + resp = self.client.delete_port(port_id=port_id) + result.response = resp + + # Delete response is without entity so resp_check can not be used + if (resp.ok and + resp.status_code == NeutronResponseCodes.DELETE_PORT): + return result + + err_msg = ('{port} Port Delete failure, expected status ' + 'code: {expected_status}. Response: {status} {reason} ' + '{content}').format( + port=port_id, + expected_status=NeutronResponseCodes.DELETE_PORT, + status=resp.status_code, reason=resp.reason, + content=resp.content) + self._log.error(err_msg) + result.failures.append(err_msg) + time.sleep(poll_interval) + + else: + err_msg = ( + 'Unable to DELETE {0} port after {1} attempts: ' + '{2}').format(port_id, resource_delete_attempts, + result.failures) + self._log.error(err_msg) + if raise_exception: + raise ResourceDeleteException(err_msg) + return result + + def clean_port(self, port_id, timeout=None, poll_interval=None): + """ + @summary: deletes a port within a time out + @param string port_id: The UUID for the port + @type port_id: string + @param timeout: seconds to wait for the port to be deleted + @type timeout: int + @param poll_interval: sleep time interval between API delete/get calls + @type poll_interval: int + @return: None if delete was successful or the undeleted port_id + @rtype: None or string + """ + timeout = timeout or self.config.resource_delete_timeout + poll_interval = poll_interval or self.config.api_poll_interval + endtime = time.time() + int(timeout) + log_msg = 'Deleting {0} port within a {1}s timeout '.format( + port_id, timeout) + self._log.info(log_msg) + while time.time() < endtime: + try: + self.client.delete_port(port_id=port_id) + resp = self.client.get_port(port_id=port_id) + except Exception as err: + err_msg = ('Encountered an exception deleting a port with' + 'the clean_network method. Exception: {0}').format(err) + self._log.error(err_msg) + + if resp.status_code == NeutronResponseCodes.NOT_FOUND: + return None + time.sleep(poll_interval) + + err_msg = 'Unable to delete {0} port within a {1}s timeout'.format( + port_id, timeout) + self._log.error(err_msg) + return port_id + + def clean_ports(self, ports_list): + """ + @summary: deletes each port from a list calling clean_port + @param ports_list: list of ports UUIDs + @type ports_list: list(str) + @return: list of undeleted ports UUIDs + @rtype: list(str) + """ + log_msg = 'Deleting ports: {0}'.format(ports_list) + self._log.info(log_msg) + undeleted_ports = [] + for port in ports_list: + result = self.clean_port(port_id=port) + if result: + undeleted_ports.append(result) + if undeleted_ports: + err_msg = 'Unable to delete ports: {0}'.format( + undeleted_ports) + self._log.error(err_msg) + return undeleted_ports diff --git a/cloudcafe/networking/networks/subnets_api/behaviors.py b/cloudcafe/networking/networks/subnets_api/behaviors.py index 647f4832..704c05c5 100644 --- a/cloudcafe/networking/networks/subnets_api/behaviors.py +++ b/cloudcafe/networking/networks/subnets_api/behaviors.py @@ -24,8 +24,9 @@ from cloudcafe.networking.networks.common.behaviors \ from cloudcafe.networking.networks.common.constants \ import NeutronResponseCodes from cloudcafe.networking.networks.common.exceptions \ - import ResourceBuildException, NetworkIDMissingException, \ - InvalidIPException + import InvalidIPException, NetworkIDMissingException,\ + ResourceBuildException, ResourceDeleteException, ResourceGetException,\ + ResourceListException, ResourceUpdateException class SubnetsBehaviors(NetworkingBaseBehaviors): @@ -240,9 +241,8 @@ class SubnetsBehaviors(NetworkingBaseBehaviors): @type use_exact_name: bool @param poll_interval: sleep time interval between API retries @type poll_interval: int - @return: Subnet entity and failure list if created successful, or - None and the failure list if the raise_exception flag was False - @rtype: tuple with Subnet or None and failure list (may be empty) + @return: NetworkingResponse object with api response and failure list + @rtype: common.behaviors.NetworkingResponse """ if not network_id: raise NetworkIDMissingException @@ -286,9 +286,12 @@ class SubnetsBehaviors(NetworkingBaseBehaviors): status_code=NeutronResponseCodes.CREATE_SUBNET, label=name, message=err_msg, network_id=network_id) + result.response = resp if not resp_check: - result.response = resp return result + + # Failures will be an empty list if the update was successful the + # first time result.failures.append(resp_check) time.sleep(poll_interval) @@ -300,3 +303,316 @@ class SubnetsBehaviors(NetworkingBaseBehaviors): if raise_exception: raise ResourceBuildException(err_msg) return result + + def update_subnet(self, subnet_id, name=None, gateway_ip=None, + dns_nameservers=None, host_routes=None, + enable_dhcp=None, allocation_pools=None, + resource_update_attempts=None, raise_exception=False, + poll_interval=None): + """ + @summary: Updates and verifies a specified Subnet + @param subnet_id: The UUID for the subnet + @type subnet_id: string + @param name: human readable name for the subnet, may not be unique + (CRUD: CRU) + @type name: string + @param gateway_ip: default gateway used by devices in the subnet + (CRUD: CRUD) + @type gateway_ip: string + @param dns_nameservers: DNS name servers used by subnet hosts + (CRUD: CRU) + @type dns_nameservers: list(str) + @param host_routes: routes that should be used by devices with IPs + from this subnet (does not includes the local route (CRUD: CRU) + @type host_routes: list(dict) + @param enable_dhcp: whether DHCP is enabled (CRUD:CRU) + @type enable_dhcp: bool + @param allocation_pools: sub range of cidr available for dynamic + allocation to ports (CRUD: CRU) + @type allocation_pools: list(dict) + @param resource_update_attempts: number of API retries + @type resource_update_attempts: int + @param raise_exception: flag to raise an exception if the + Subnet was not updated or to return None + @type raise_exception: bool + @param poll_interval: sleep time interval between API retries + @type poll_interval: int + @return: NetworkingResponse object with api response and failure list + @rtype: common.behaviors.NetworkingResponse + """ + poll_interval = poll_interval or self.config.api_poll_interval + resource_update_attempts = (resource_update_attempts or + self.config.api_retries) + + result = NetworkingResponse() + err_msg = 'Subnet Update failure' + for attempt in range(resource_update_attempts): + self._log.debug('Attempt {0} of {1} updating subnet {2}'.format( + attempt + 1, resource_update_attempts, subnet_id)) + + resp = self.client.update_subnet( + subnet_id=subnet_id, name=name, gateway_ip=gateway_ip, + dns_nameservers=dns_nameservers, host_routes=host_routes, + enable_dhcp=enable_dhcp, allocation_pools=allocation_pools) + + resp_check = self.check_response(resp=resp, + status_code=NeutronResponseCodes.UPDATE_SUBNET, + label=subnet_id, message=err_msg) + + result.response = resp + if not resp_check: + return result + + # Failures will be an empty list if the update was successful the + # first time + result.failures.append(resp_check) + time.sleep(poll_interval) + + else: + err_msg = ( + 'Unable to update {0} subnet after {1} attempts: ' + '{2}').format(subnet_id, resource_update_attempts, + result.failures) + self._log.error(err_msg) + if raise_exception: + raise ResourceUpdateException(err_msg) + return result + + def get_subnet(self, subnet_id, resource_get_attempts=None, + raise_exception=False, poll_interval=None): + """ + @summary: Shows and verifies a specified subnet + @param subnet_id: The UUID for the subnet + @type subnet_id: string + @param resource_get_attempts: number of API retries + @type resource_get_attempts: int + @param raise_exception: flag to raise an exception if the get + Subnet was not as expected or to return None + @type raise_exception: bool + @param poll_interval: sleep time interval between API retries + @type poll_interval: int + @return: NetworkingResponse object with api response and failure list + @rtype: common.behaviors.NetworkingResponse + """ + poll_interval = poll_interval or self.config.api_poll_interval + resource_get_attempts = (resource_get_attempts or + self.config.api_retries) + + result = NetworkingResponse() + err_msg = 'Subnet Get failure' + for attempt in range(resource_get_attempts): + self._log.debug('Attempt {0} of {1} getting subnet {2}'.format( + attempt + 1, resource_get_attempts, subnet_id)) + + resp = self.client.get_subnet(subnet_id=subnet_id) + + resp_check = self.check_response(resp=resp, + status_code=NeutronResponseCodes.GET_SUBNET, + label=subnet_id, message=err_msg) + + result.response = resp + if not resp_check: + return result + + # Failures will be an empty list if the get was successful the + # first time + result.failures.append(resp_check) + time.sleep(poll_interval) + + else: + err_msg = ( + 'Unable to GET {0} subnet after {1} attempts: ' + '{2}').format(subnet_id, resource_get_attempts, + result.failures) + self._log.error(err_msg) + if raise_exception: + raise ResourceGetException(err_msg) + return result + + def list_subnets(self, subnet_id=None, network_id=None, cidr=None, + tenant_id=None, gateway_ip=None, ip_version=None, + enable_dhcp=None, name=None, limit=None, marker=None, + page_reverse=None, resource_list_attempts=None, + raise_exception=False, poll_interval=None): + """ + @summary: Lists subnets and verifies the response is the expected + @param subnet_id: subnet ID to filter by + @type subnet_id: string + @param network_id: network ID to filter by + @type network_id: string + @param cidr: cider to filter by + @type cidr: string + @param tenant_id: owner of the network to filter by + @type tenant_id: string + @param gateway_ip: gateway_ip to filter by + @type gateway_ip: string + @param ip_version: IP version 4 or 6 to filter by + @type ip_version: int + @param enable_dhcp: enable_dhcp status to filter by + @type enable_dhcp: bool + @param name: subnet name to filter by + @type name: string + @param limit: page size + @type limit: int + @param marker: Id of the last item of the previous page + @type marker: string + @param page_reverse: direction of the page + @type page_reverse: bool + @param resource_list_attempts: number of API retries + @type resource_list_attempts: int + @param raise_exception: flag to raise an exception if the list + Subnet was not as expected or to return None + @type raise_exception: bool + @param poll_interval: sleep time interval between API retries + @type poll_interval: int + @return: NetworkingResponse object with api response and failure list + @rtype: common.behaviors.NetworkingResponse + """ + poll_interval = poll_interval or self.config.api_poll_interval + resource_list_attempts = (resource_list_attempts or + self.config.api_retries) + + result = NetworkingResponse() + err_msg = 'Subnet List failure' + for attempt in range(resource_list_attempts): + self._log.debug('Attempt {0} of {1} with subnet list'.format( + attempt + 1, resource_list_attempts)) + + resp = self.client.list_subnets( + subnet_id=subnet_id, network_id=network_id, cidr=cidr, + tenant_id=tenant_id, gateway_ip=gateway_ip, + ip_version=ip_version, enable_dhcp=enable_dhcp, name=name, + limit=limit, marker=marker, page_reverse=page_reverse) + + resp_check = self.check_response(resp=resp, + status_code=NeutronResponseCodes.LIST_SUBNETS, + label='', message=err_msg) + + result.response = resp + if not resp_check: + return result + + # Failures will be an empty list if the list was successful the + # first time + result.failures.append(resp_check) + time.sleep(poll_interval) + + else: + err_msg = ( + 'Unable to LIST subnets after {0} attempts: ' + '{1}').format(resource_list_attempts, result.failures) + self._log.error(err_msg) + if raise_exception: + raise ResourceListException(err_msg) + return result + + def delete_subnet(self, subnet_id, resource_delete_attempts=None, + raise_exception=False, poll_interval=None): + """ + @summary: Deletes and verifies a specified subnet is deleted + @param subnet_id: The UUID for the subnet + @type subnet_id: string + @param resource_delete_attempts: number of API retries + @type resource_delete_attempts: int + @param raise_exception: flag to raise an exception if the deleted + Subnet was not as expected or to return None + @type raise_exception: bool + @param poll_interval: sleep time interval between API retries + @type poll_interval: int + @return: NetworkingResponse object with api response and failure list + @rtype: common.behaviors.NetworkingResponse + """ + poll_interval = poll_interval or self.config.api_poll_interval + resource_delete_attempts = (resource_delete_attempts or + self.config.api_retries) + + result = NetworkingResponse() + for attempt in range(resource_delete_attempts): + self._log.debug('Attempt {0} of {1} deleting subnet {2}'.format( + attempt + 1, resource_delete_attempts, subnet_id)) + + resp = self.client.delete_subnet(subnet_id=subnet_id) + result.response = resp + + # Delete response is without entity so resp_check can not be used + if (resp.ok and + resp.status_code == NeutronResponseCodes.DELETE_SUBNET): + return result + + err_msg = ('{subnet} Subnet Delete failure, expected status ' + 'code: {expected_status}. Response: {status} {reason} ' + '{content}').format( + subnet=subnet_id, + expected_status=NeutronResponseCodes.DELETE_SUBNET, + status=resp.status_code, reason=resp.reason, + content=resp.content) + self._log.error(err_msg) + result.failures.append(err_msg) + time.sleep(poll_interval) + + else: + err_msg = ( + 'Unable to DELETE {0} subnet after {1} attempts: ' + '{2}').format(subnet_id, resource_delete_attempts, + result.failures) + self._log.error(err_msg) + if raise_exception: + raise ResourceDeleteException(err_msg) + return result + + def clean_subnet(self, subnet_id, timeout=None, poll_interval=None): + """ + @summary: deletes a subnet within a time out + @param subnet_id: The UUID for the subnet + @type subnet_id: string + @param timeout: seconds to wait for the subnet to be deleted + @type timeout: int + @param poll_interval: sleep time interval between API delete/get calls + @type poll_interval: int + @return: None if delete was successful or the undeleted subnet_id + @rtype: None or string + """ + timeout = timeout or self.config.resource_delete_timeout + poll_interval = poll_interval or self.config.api_poll_interval + endtime = time.time() + int(timeout) + log_msg = 'Deleting {0} subnet within a {1}s timeout '.format( + subnet_id, timeout) + self._log.info(log_msg) + while time.time() < endtime: + try: + self.client.delete_subnet(subnet_id=subnet_id) + resp = self.client.get_subnet(subnet_id=subnet_id) + except Exception as err: + err_msg = ('Encountered an exception deleting a subnet with' + 'the clean_subnet method. Exception: {0}').format(err) + self._log.error(err_msg) + + if resp.status_code == NeutronResponseCodes.NOT_FOUND: + return None + time.sleep(poll_interval) + + err_msg = 'Unable to delete {0} subnet within a {1}s timeout'.format( + subnet_id, timeout) + self._log.error(err_msg) + return subnet_id + + def clean_subnets(self, subnets_list): + """ + @summary: deletes each subnet from a list calling clean_subnet + @param subnets_list: list of subnets UUIDs + @type subnets_list: list(str) + @return: list of undeleted subnets UUIDs + @rtype: list(str) + """ + log_msg = 'Deleting subnets: {0}'.format(subnets_list) + self._log.info(log_msg) + undeleted_subnets = [] + for subnet in subnets_list: + result = self.clean_subnet(subnet_id=subnet) + if result: + undeleted_subnets.append(result) + if undeleted_subnets: + err_msg = 'Unable to delete subnets: {0}'.format( + undeleted_subnets) + self._log.error(err_msg) + return undeleted_subnets