diff --git a/cloudcafe/networking/networks/common/behaviors.py b/cloudcafe/networking/networks/common/behaviors.py index bd41181c..283e5aa8 100644 --- a/cloudcafe/networking/networks/common/behaviors.py +++ b/cloudcafe/networking/networks/common/behaviors.py @@ -18,11 +18,14 @@ import requests import time from cafe.engine.behaviors import BaseBehavior +from cloudcafe.common.tools.datagen import rand_name from cloudcafe.networking.networks.common.config import NetworkingBaseConfig from cloudcafe.networking.networks.common.constants \ - import NeutronResourceTypes, NeutronResponseCodes + import NeutronResponseCodes, NeutronResource from cloudcafe.networking.networks.common.exceptions \ - import UnhandledMethodCaseException + import ResourceBuildException, ResourceDeleteException, \ + ResourceGetException, ResourceListException, ResourceUpdateException, \ + UnhandledMethodCaseException class NetworkingBaseBehaviors(BaseBehavior): @@ -155,13 +158,535 @@ class NetworkingBaseBehaviors(BaseBehavior): id_list = [entity.id for entity in entity_list] return id_list - def _delete_resources(self, resource_type, resource_list=None, name=None, - tenant_id=None, skip_delete=None): + def __over_limit_retry(self, resource_type, timeout, poll_interval, + status_code, resp, fn_name, fn_kwargs): """ - @summary: deletes multiple resources (for ex. networks) + @summary: Retry mechanism for API rate limited calls @param resource_type: type of resource for ex. networks, subnets, etc. See NeutronResourceTypes in the networks constants @type resource_type: str + @param timeout: timeout for over limit retries + @type timeout: int + @param poll_interval: sleep time interval between API retries + @type poll_interval: int + @param status_code: over limit API HTTP response code + @type: int + @param resp: API call response object + @type resp: Requests.response + @param fn_name: API function name + @type fn_name: str + @param fn_kwargs: API function arguments + @type fn_kwargs: dict + """ + endtime = time.time() + int(timeout) + retry_msg = ('OverLimit retry with a {timeout}s timeout ' + 'calling {resource_type} function {fn_name}').format( + timeout=timeout, resource_type=resource_type, + fn_name=fn_name) + self._log.info(retry_msg) + while (resp.status_code == status_code and + time.time() < endtime): + resp = getattr(self.client, fn_name)(**fn_kwargs) + time.sleep(poll_interval) + return resp + + def _create_resource(self, resource, resource_build_attempts=None, + raise_exception=True, poll_interval=None, + has_name=True, use_exact_name=False, + attrs_kwargs=None, timeout=None, + use_over_limit_retry=False): + """ + @summary: Creates and verifies a resource is created as expected + @param resource: type of resource for ex. network, subnet, port, etc. + See NeutronResource in the networks constants + @type resource: resource instance with singular and plural forms + @param resource_build_attempts: number of API retries + @type resource_build_attempts: int + @param raise_exception: flag to raise an exception if the + resource was not created or to return None + @type raise_exception: bool + @param poll_interval: sleep time interval between API retries + @type poll_interval: int + @param has_name: if the resource has a name attribute + @type has_name: bool + @param use_exact_name: flag if the exact name given should be used + @type use_exact_name: bool + @param attrs_kwargs: resource attributes to create with, for ex. name + @type attrs_kwargs: dict + @param timeout: resource create timeout for over limit retries + @type timeout: int + @param use_over_limit_retry: flag to enable/disable the create + over limits retries + @type use_over_limit_retry: bool + @return: NetworkingResponse object with api response and failure list + @rtype: common.behaviors.NetworkingResponse + """ + + # Defining the resource type in singular form (for ex. network) + resource_type = resource.singular + + # If has_name is False name can be used as a reference for log messages + name = attrs_kwargs.get('name') + if has_name == True: + if name is None: + name = rand_name(self.config.starts_with_name) + elif not use_exact_name: + name = rand_name(name) + attrs_kwargs['name'] = name + else: + # In case name is NOT used as a reference for log messages + if name is None: + name = '' + + poll_interval = poll_interval or self.config.api_poll_interval + resource_build_attempts = (resource_build_attempts or + self.config.api_retries) + use_over_limit_retry = (use_over_limit_retry or + self.config.use_over_limit_retry) + timeout = timeout or self.config.resource_create_timeout + + result = NetworkingResponse() + err_msg = '{0} Create failure'.format(resource_type) + for attempt in range(resource_build_attempts): + self._log.debug( + 'Attempt {attempt_n} of {attempts} creating ' + '{resource_type}'.format(attempt_n=attempt + 1, + attempts=resource_build_attempts, + resource_type=resource_type)) + + # Method uses resource type in singular form (slicing the ending s) + create_fn_name = 'create_{0}'.format(resource_type) + resp = getattr(self.client, create_fn_name)(**attrs_kwargs) + + if use_over_limit_retry: + entity_too_large_status_code = (getattr(self.response_codes, + 'REQUEST_ENTITY_TOO_LARGE')) + if resp.status_code == entity_too_large_status_code: + fn_kwargs = attrs_kwargs + + resp = self.__over_limit_retry( + resource_type=resource_type, timeout=timeout, + poll_interval=poll_interval, + status_code=entity_too_large_status_code, + resp=resp, fn_name=create_fn_name, + fn_kwargs=fn_kwargs) + + response_code = create_fn_name.upper() + status_code = getattr(self.response_codes, response_code) + resp_check = self.check_response( + resp=resp, status_code=status_code, 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 + result.failures.append(resp_check) + time.sleep(poll_interval) + + else: + err_msg = ( + 'Unable to CREATE {name} {resource_type} after ' + '{attempts} attempts: {failures}').format( + name=name, resource_type=resource_type, + attempts=resource_build_attempts, + failures=result.failures) + self._log.error(err_msg) + if raise_exception: + raise ResourceBuildException(err_msg) + return result + + def _update_resource(self, resource, resource_id, + resource_update_attempts=None, raise_exception=False, + poll_interval=None, attrs_kwargs=None, + timeout=None, use_over_limit_retry=False): + """ + @summary: Updates and verifies a specified resource + @param resource: type of resource for ex. network, subnet, port, etc. + See NeutronResource in the networks constants + @type resource: resource instance with singular and plural forms + @param resource_id: The UUID for the resource + @type resource_id: str + @param resource_update_attempts: number of API retries + @type resource_update_attempts: int + @param raise_exception: flag to raise an exception if the + resource was not updated or to return None + @type raise_exception: bool + @param poll_interval: sleep time interval between API retries + @type poll_interval: int + @param attrs_kwargs: resource attributes to update + @type attrs_kwargs: dict + @param timeout: resource update timeout for over limit retries + @type timeout: int + @param use_over_limit_retry: flag to enable/disable the update + over limits retries + @type use_over_limit_retry: bool + @return: NetworkingResponse object with api response and failure list + @rtype: common.behaviors.NetworkingResponse + """ + + # Defining the resource type in singular form (for ex. network) + resource_type = resource.singular + + poll_interval = poll_interval or self.config.api_poll_interval + resource_update_attempts = (resource_update_attempts or + self.config.api_retries) + use_over_limit_retry = (use_over_limit_retry or + self.config.use_over_limit_retry) + timeout = timeout or self.config.resource_update_timeout + + result = NetworkingResponse() + err_msg = '{0} Update failure'.format(resource_type) + for attempt in range(resource_update_attempts): + self._log.debug( + 'Attempt {attempt_n} of {attempts} updating {resource_type} ' + '{resource_id}'.format(attempt_n=attempt + 1, + attempts=resource_update_attempts, + resource_type=resource_type, + resource_id=resource_id)) + + # Method uses resource type in singular form (slicing the ending s) + update_fn_name = 'update_{0}'.format(resource_type) + + # Resource ID is expected to be the first client method parameter + resp = getattr(self.client, update_fn_name)(resource_id, + **attrs_kwargs) + + if use_over_limit_retry: + entity_too_large_status_code = (getattr(self.response_codes, + 'REQUEST_ENTITY_TOO_LARGE')) + if resp.status_code == entity_too_large_status_code: + fn_kwargs = attrs_kwargs + + # Adding the resource id to the function kwargs + resource_id_name = '{0}_id'.format(resource_type) + fn_kwargs[resource_id_name] = resource_id + + resp = self.__over_limit_retry( + resource_type=resource_type, timeout=timeout, + poll_interval=poll_interval, + status_code=entity_too_large_status_code, + resp=resp, fn_name=update_fn_name, + fn_kwargs=fn_kwargs) + + response_code = update_fn_name.upper() + status_code = getattr(self.response_codes, response_code) + resp_check = self.check_response( + resp=resp, status_code=status_code, label=resource_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 {resource_id} {resource_type} after ' + '{attempts} attempts: {failures}').format( + resource_id=resource_id, resource_type=resource_type, + attempts=resource_update_attempts, + failures=result.failures) + self._log.error(err_msg) + if raise_exception: + raise ResourceUpdateException(err_msg) + return result + + def _get_resource(self, resource, resource_id, + resource_get_attempts=None, raise_exception=False, + poll_interval=None, timeout=None, + use_over_limit_retry=False): + """ + @summary: Shows and verifies a specified resource + @param resource: type of resource for ex. network, subnet, port, etc. + See NeutronResource in the networks constants + @type resource: resource instance with singular and plural forms + @param resource_id: The UUID for the resource + @type resource_id: str + @param resource_get_attempts: number of API retries + @type resource_get_attempts: int + @param raise_exception: flag to raise an exception if the get + resource 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 + @param timeout: resource get timeout for over limit retries + @type timeout: int + @param use_over_limit_retry: flag to enable/disable the get + over limits retries + @type use_over_limit_retry: bool + @return: NetworkingResponse object with api response and failure list + @rtype: common.behaviors.NetworkingResponse + """ + + # Defining the resource type in singular form (for ex. network) + resource_type = resource.singular + + poll_interval = poll_interval or self.config.api_poll_interval + resource_get_attempts = (resource_get_attempts or + self.config.api_retries) + use_over_limit_retry = (use_over_limit_retry or + self.config.use_over_limit_retry) + timeout = timeout or self.config.resource_get_timeout + + result = NetworkingResponse() + err_msg = '{0} Get failure'.format(resource_type) + for attempt in range(resource_get_attempts): + self._log.debug( + 'Attempt {attempt_n} of {attempts} getting {resource_type} ' + '{resource_id}'.format(attempt_n=attempt + 1, + attempts=resource_get_attempts, + resource_type=resource_type, + resource_id=resource_id)) + + # Method uses resource type in singular form (slicing the ending s) + get_fn_name = 'get_{0}'.format(resource_type) + resp = getattr(self.client, get_fn_name)(resource_id) + + if use_over_limit_retry: + entity_too_large_status_code = (getattr(self.response_codes, + 'REQUEST_ENTITY_TOO_LARGE')) + if resp.status_code == entity_too_large_status_code: + fn_kwargs = {} + + # Adding the resource id to the function kwargs + resource_id_name = '{0}_id'.format(resource_type) + fn_kwargs[resource_id_name] = resource_id + + resp = self.__over_limit_retry( + resource_type=resource_type, timeout=timeout, + poll_interval=poll_interval, + status_code=entity_too_large_status_code, + resp=resp, fn_name=get_fn_name, + fn_kwargs=fn_kwargs) + + response_code = get_fn_name.upper() + status_code = getattr(self.response_codes, response_code) + resp_check = self.check_response(resp=resp, + status_code=status_code, + label=resource_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 {resource_id} {resource_type} after {attempts} ' + 'attempts: {failures}').format(resource_id=resource_id, + resource_type=resource_type, + attempts=resource_get_attempts, + failures=result.failures) + self._log.error(err_msg) + if raise_exception: + raise ResourceGetException(err_msg) + return result + + def _list_resources(self, resource, resource_list_attempts=None, + raise_exception=False, poll_interval=None, + params_kwargs=None, timeout=None, + use_over_limit_retry=False): + """ + @summary: Lists resources and verifies the response is the expected + @param resource: type of resource for ex. network, subnet, port, etc. + See NeutronResource in the networks constants + @type resource: resource instance with singular and plural forms + @param resource_list_attempts: number of API retries + @type resource_list_attempts: int + @param raise_exception: flag to raise an exception if the resource list + 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 + @param params_kwargs: key value params to filter by the list results + @type params_kwargs: dict + @param timeout: resource list timeout for over limit retries + @type timeout: int + @param use_over_limit_retry: flag to enable/disable the list + over limits retries + @type use_over_limit_retry: bool + @return: NetworkingResponse object with api response and failure list + @rtype: common.behaviors.NetworkingResponse + """ + + # Defining the resource type in plural form (for ex. networks) + resource_type = resource.plural + + poll_interval = poll_interval or self.config.api_poll_interval + resource_list_attempts = (resource_list_attempts or + self.config.api_retries) + use_over_limit_retry = (use_over_limit_retry or + self.config.use_over_limit_retry) + timeout = timeout or self.config.resource_get_timeout + + result = NetworkingResponse() + err_msg = '{0} list failure'.format(resource_type) + for attempt in range(resource_list_attempts): + self._log.debug( + 'Attempt {attempt_n} of {attempts} with {resource_type} ' + 'list'.format(attempt_n=attempt + 1, + attempts=resource_list_attempts, + resource_type=resource_type)) + + list_fn_name = 'list_{0}'.format(resource_type) + resp = getattr(self.client, list_fn_name)(**params_kwargs) + + if use_over_limit_retry: + entity_too_large_status_code = (getattr(self.response_codes, + 'REQUEST_ENTITY_TOO_LARGE')) + if resp.status_code == entity_too_large_status_code: + fn_kwargs = params_kwargs + + resp = self.__over_limit_retry( + resource_type=resource_type, timeout=timeout, + poll_interval=poll_interval, + status_code=entity_too_large_status_code, + resp=resp, fn_name=list_fn_name, + fn_kwargs=fn_kwargs) + + response_code = list_fn_name.upper() + status_code = getattr(self.response_codes, response_code) + resp_check = self.check_response( + resp=resp, status_code=status_code, 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 {resource_type} after {attempts} attempts: ' + '{failures}').format(resource_type=resource_type, + attempts=resource_list_attempts, + failures=result.failures) + self._log.error(err_msg) + if raise_exception: + raise ResourceListException(err_msg) + return result + + def _delete_resource(self, resource, resource_id, + resource_delete_attempts=None, raise_exception=False, + poll_interval=None, timeout=None, + use_over_limit_retry=False): + """ + @summary: Deletes and verifies a specified resource is deleted + @param resource: type of resource for ex. network, subnet, port, etc. + See NeutronResource in the networks constants + @type resource: resource instance with singular and plural forms + @param resource_id: The UUID for the resource + @type resource_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 + resource 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 + @param timeout: resource delete timeout for over limit retries + @type timeout: int + @param use_over_limit_retry: flag to enable/disable the delete + over limits retries + @type use_over_limit_retry: bool + @return: NetworkingResponse object with api response and failure list + @rtype: common.behaviors.NetworkingResponse + """ + + # Defining the resource type in singular form (for ex. network) + resource_type = resource.singular + + poll_interval = poll_interval or self.config.api_poll_interval + resource_delete_attempts = (resource_delete_attempts or + self.config.api_retries) + use_over_limit_retry = (use_over_limit_retry or + self.config.use_over_limit_retry) + timeout = timeout or self.config.resource_get_timeout + + result = NetworkingResponse() + for attempt in range(resource_delete_attempts): + self._log.debug( + 'Attempt {attempt_n} of {attempts} deleting {resource_type} ' + '{resource_id}'.format(attempt_n=attempt + 1, + attempts=resource_delete_attempts, + resource_type=resource_type, + resource_id=resource_id)) + + # Method uses resource type in singular form (slicing the ending s) + delete_fn_name = 'delete_{0}'.format(resource_type) + resp = getattr(self.client, delete_fn_name)(resource_id) + + if use_over_limit_retry: + entity_too_large_status_code = (getattr(self.response_codes, + 'REQUEST_ENTITY_TOO_LARGE')) + if resp.status_code == entity_too_large_status_code: + fn_kwargs = {} + + # Adding the resource id to the function kwargs + resource_id_name = '{0}_id'.format(resource_type) + fn_kwargs[resource_id_name] = resource_id + + resp = self.__over_limit_retry( + resource_type=resource_type, timeout=timeout, + poll_interval=poll_interval, + status_code=entity_too_large_status_code, + resp=resp, fn_name=delete_fn_name, + fn_kwargs=fn_kwargs) + + result.response = resp + + # Delete response is without entity so resp_check can not be used + response_code = delete_fn_name.upper() + status_code = getattr(self.response_codes, response_code) + if resp.ok and resp.status_code == status_code: + return result + + err_msg = ('{resource_id} {resource_type} Delete failure, expected' + 'status code: {expected_status}. Response: {status} ' + '{reason} {content}').format( + resource_id=resource_id, + resource_type=resource_type, + expected_status=status_code, + 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 {resource_id} {resource_type} after ' + '{attempts} attempts: {failures}').format( + resource_id=resource_id, resource_type=resource_type, + attempts=resource_delete_attempts, + failures=result.failures) + self._log.error(err_msg) + if raise_exception: + raise ResourceDeleteException(err_msg) + return result + + def _delete_resources(self, resource, resource_list=None, name=None, + tenant_id=None, skip_delete=None): + """ + @summary: deletes multiple resources (for ex. networks) + @param resource: type of resource for ex. network, subnet, port, etc. + See NeutronResource in the networks constants + @type resource: resource instance with singular and plural forms @param resource_list: list of resource UUIDs @type resource_list: list(str) @param name: resource name to filter by, asterisk can be used at the @@ -175,18 +700,23 @@ class NetworkingBaseBehaviors(BaseBehavior): @return: failed delete list with resource UUIDs and failures @rtype: list(dict) """ + + # Defining the resource type in singular and plural forms + resource_type_singular = resource.singular + resource_type_plural = resource.plural + # Getting the resource list based on the resource type (if not given) if resource_list is None: - list_fn_name = 'list_{0}'.format(resource_type) + list_fn_name = 'list_{0}'.format(resource_type_plural) resp = getattr(self, list_fn_name)(tenant_id=tenant_id) # Getting the Neutron expected response based on the fn name response_code = list_fn_name.upper() - status_code = getattr(NeutronResponseCodes, response_code) + status_code = getattr(self.response_codes, response_code) if resp.response.status_code != status_code: get_msg = 'Unable to get {0} for delete_{0} call'.format( - resource_type) + resource_type_plural) self._log.info(get_msg) return None resources = resp.response.entity @@ -205,7 +735,7 @@ class NetworkingBaseBehaviors(BaseBehavior): if skip_delete is not None: do_not_delete.extend(skip_delete) - if resource_type == NeutronResourceTypes.NETWORKS: + if resource_type_plural == NeutronResource.NETWORKS: property_list = ['public_network_id', 'service_network_id'] for prop in property_list: if (hasattr(self.config, prop) and @@ -217,16 +747,114 @@ class NetworkingBaseBehaviors(BaseBehavior): if resource_to_skip in resource_list: resource_list.remove(resource_to_skip) - log_msg = 'Deleting {0}: {1}'.format(resource_type, resource_list) + log_msg = 'Deleting {0}: {1}'.format(resource_type_plural, + resource_list) self._log.info(log_msg) failed_deletes = [] - delete_fn_name = 'delete_{0}'.format(resource_type[:-1]) + delete_fn_name = 'delete_{0}'.format(resource_type_singular) for resource_id in resource_list: result = getattr(self, delete_fn_name)(resource_id) if result.failures: failed_deletes.append(result.failures) return failed_deletes + def _clean_resource(self, resource, resource_id, timeout=None, + poll_interval=None): + """ + @summary: deletes a resource within a time out + @param resource: type of resource for ex. network, subnet, port, etc. + See NeutronResource in the networks constants + @type resource: resource instance with singular and plural forms + @param resource_id: The UUID for the for the resource + @type resource_id: str + @param timeout: seconds to wait for the resource 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 resource_id + @rtype: None or string + """ + + # Defining the resource type in singular form (for ex. network) + resource_type = resource.singular + + 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 {resource_id} {resource_type} within a {timeout}s' + ' timeout').format(resource_id=resource_id, + resource_type=resource_type, + timeout=timeout) + self._log.info(log_msg) + + # Method uses resource type in singular form (slicing the ending s) + delete_fn_name = 'delete_{0}'.format(resource_type) + get_fn_name = 'get_{0}'.format(resource_type) + resp = None + while time.time() < endtime: + try: + getattr(self.client, delete_fn_name)(resource_id) + resp = getattr(self.client, get_fn_name)(resource_id) + except Exception as err: + err_msg = ('Encountered an exception deleting a ' + '{resource_type} within the _clean_resource method.' + ' Exception: {error}').format( + resource_type=resource_type, error=err) + self._log.error(err_msg) + + if (resp is not None and + resp.status_code == NeutronResponseCodes.NOT_FOUND): + return None + time.sleep(poll_interval) + + err_msg = ('Unable to delete {resource_id} {resource_type} within a ' + '{timeout}s timeout').format(resource_id=resource_id, + resource_type=resource_type, + timeout=timeout) + self._log.error(err_msg) + return resource_id + + def _clean_resources(self, resource, resource_list, timeout=None, + poll_interval=None): + """ + @summary: deletes each resource from a list calling _clean_resource + @param resource: type of resource for ex. network, subnet, port, etc. + See NeutronResource in the networks constants + @type resource: resource instance with singular and plural forms + @param resource_list: list of resource UUIDs to delete + @type resource_list: list(str) + @param timeout: seconds to wait for the resource to be deleted + @type timeout: int + @param poll_interval: sleep time interval between API delete/get calls + @type poll_interval: int + @return: list of undeleted resource UUIDs + @rtype: list(str) + """ + + # Defining the resource type in plural form (for ex. networks) + resource_type_plural = resource.plural + + log_msg = 'Deleting {resource_type}: {resource_list}'.format( + resource_type=resource_type_plural, resource_list=resource_list) + self._log.info(log_msg) + undeleted_resources = [] + for resource_id in resource_list: + + # _cleanup_resource takes the resource obj + result = self._clean_resource(resource=resource, + resource_id=resource_id, + timeout=timeout, + poll_interval=poll_interval) + if result: + undeleted_resources.append(result) + if undeleted_resources: + err_msg = ('Unable to delete {resource_type}: ' + '{undeleted_resources}').format( + resource_type=resource_type_plural, + undeleted_resources=undeleted_resources) + self._log.error(err_msg) + return undeleted_resources + class NetworkingResponse(object): """ diff --git a/cloudcafe/networking/networks/common/config.py b/cloudcafe/networking/networks/common/config.py index 9d644600..b356e99e 100644 --- a/cloudcafe/networking/networks/common/config.py +++ b/cloudcafe/networking/networks/common/config.py @@ -60,6 +60,11 @@ class NetworkingBaseConfig(ConfigSectionInterface): """Number of times to retry an API call by a behavior method""" return int(self.get("api_retries", 1)) + @property + def use_over_limit_retry(self): + """Flag to enable/disable retries due to over limits responses""" + return self.get_boolean("use_over_limit_retry", False) + @property def resource_create_timeout(self): """Seconds to wait for creating a resource""" diff --git a/cloudcafe/networking/networks/common/constants.py b/cloudcafe/networking/networks/common/constants.py index 7e650448..5b0a3a28 100644 --- a/cloudcafe/networking/networks/common/constants.py +++ b/cloudcafe/networking/networks/common/constants.py @@ -15,13 +15,25 @@ limitations under the License. """ -class NeutronResourceTypes(object): +class NeutronResource(object): """Neutron resource types""" + NETWORK = 'network' NETWORKS = 'networks' + SUBNET = 'subnet' SUBNETS = 'subnets' + PORT = 'port' PORTS = 'ports' + PLURALS = {NETWORK: NETWORKS, SUBNET: SUBNETS, PORT: PORTS} + + def __init__(self, singular_type): + self.singular = singular_type + + @property + def plural(self): + return self.PLURALS.get(self.singular, self.singular) + class NeutronResponseCodes(object): """HTTP Neutron API Response codes""" diff --git a/cloudcafe/networking/networks/networks_api/behaviors.py b/cloudcafe/networking/networks/networks_api/behaviors.py index d2fa27d5..c88123d4 100644 --- a/cloudcafe/networking/networks/networks_api/behaviors.py +++ b/cloudcafe/networking/networks/networks_api/behaviors.py @@ -14,16 +14,10 @@ See the License for the specific language governing permissions and limitations under the License. """ -import time - -from cloudcafe.common.tools.datagen import rand_name from cloudcafe.networking.networks.common.behaviors \ - import NetworkingBaseBehaviors, NetworkingResponse + import NetworkingBaseBehaviors from cloudcafe.networking.networks.common.constants \ - import NeutronResourceTypes, NeutronResponseCodes -from cloudcafe.networking.networks.common.exceptions \ - import ResourceBuildException, ResourceDeleteException,\ - ResourceGetException, ResourceListException, ResourceUpdateException + import NeutronResource, NeutronResponseCodes class NetworksBehaviors(NetworkingBaseBehaviors): @@ -32,6 +26,8 @@ class NetworksBehaviors(NetworkingBaseBehaviors): super(NetworksBehaviors, self).__init__() self.config = networks_config self.client = networks_client + self.response_codes = NeutronResponseCodes + self.networks_resource = NeutronResource(NeutronResource.NETWORK) def create_network(self, name=None, admin_state_up=None, shared=None, tenant_id=None, resource_build_attempts=None, @@ -59,46 +55,16 @@ class NetworksBehaviors(NetworkingBaseBehaviors): @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) - elif not use_exact_name: - name = rand_name(name) + attrs_kwargs = dict(name=name, admin_state_up=admin_state_up, + shared=shared, tenant_id=tenant_id) - poll_interval = poll_interval or self.config.api_poll_interval - resource_build_attempts = (resource_build_attempts or - self.config.api_retries) + result = self._create_resource( + resource=self.networks_resource, + resource_build_attempts=resource_build_attempts, + raise_exception=raise_exception, use_exact_name=use_exact_name, + poll_interval=poll_interval, attrs_kwargs=attrs_kwargs) - result = NetworkingResponse() - err_msg = 'Network Create failure' - for attempt in range(resource_build_attempts): - self._log.debug('Attempt {0} of {1} building network {2}'.format( - attempt + 1, resource_build_attempts, name)) - - resp = self.client.create_network( - name=name, admin_state_up=admin_state_up, shared=shared, - tenant_id=tenant_id) - - resp_check = self.check_response(resp=resp, - 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 - result.failures.append(resp_check) - time.sleep(poll_interval) - - else: - err_msg = ( - 'Unable to create {0} network after {1} attempts: ' - '{2}').format(name, resource_build_attempts, result.failures) - self._log.error(err_msg) - if raise_exception: - raise ResourceBuildException(err_msg) - return result + return result def update_network(self, network_id, name=None, admin_state_up=None, shared=None, tenant_id=None, @@ -130,43 +96,17 @@ class NetworksBehaviors(NetworkingBaseBehaviors): @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) + attrs_kwargs = dict(name=name, admin_state_up=admin_state_up, + shared=shared, tenant_id=tenant_id) - result = NetworkingResponse() - err_msg = 'Network Update failure' - for attempt in range(resource_update_attempts): - self._log.debug('Attempt {0} of {1} updating network {2}'.format( - attempt + 1, resource_update_attempts, network_id)) + result = self._update_resource( + resource=self.networks_resource, + resource_id=network_id, + resource_update_attempts=resource_update_attempts, + raise_exception=raise_exception, poll_interval=poll_interval, + attrs_kwargs=attrs_kwargs) - resp = self.client.update_network( - network_id=network_id, name=name, - admin_state_up=admin_state_up, shared=shared, - tenant_id=tenant_id) - - resp_check = self.check_response(resp=resp, - 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 - result.failures.append(resp_check) - time.sleep(poll_interval) - - else: - err_msg = ( - 'Unable to update {0} network after {1} attempts: ' - '{2}').format(network_id, resource_update_attempts, - result.failures) - self._log.error(err_msg) - if raise_exception: - raise ResourceUpdateException(err_msg) - return result + return result def get_network(self, network_id, resource_get_attempts=None, raise_exception=False, poll_interval=None): @@ -184,40 +124,13 @@ class NetworksBehaviors(NetworkingBaseBehaviors): @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 = self._get_resource( + resource=self.networks_resource, + resource_id=network_id, + resource_get_attempts=resource_get_attempts, + raise_exception=raise_exception, poll_interval=poll_interval) - result = NetworkingResponse() - err_msg = 'Network 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, network_id)) - - resp = self.client.get_network(network_id=network_id) - - resp_check = self.check_response(resp=resp, - 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 - result.failures.append(resp_check) - time.sleep(poll_interval) - - else: - err_msg = ( - 'Unable to GET {0} network after {1} attempts: ' - '{2}').format(network_id, resource_get_attempts, - result.failures) - self._log.error(err_msg) - if raise_exception: - raise ResourceGetException(err_msg) - return result + return result def list_networks(self, network_id=None, name=None, status=None, admin_state_up=None, shared=None, tenant_id=None, @@ -254,43 +167,18 @@ class NetworksBehaviors(NetworkingBaseBehaviors): @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) + params_kwargs = dict(network_id=network_id, name=name, status=status, + admin_state_up=admin_state_up, shared=shared, + tenant_id=tenant_id, limit=limit, marker=marker, + page_reverse=page_reverse) - result = NetworkingResponse() - err_msg = 'Network List failure' - for attempt in range(resource_list_attempts): - self._log.debug('Attempt {0} of {1} with network list'.format( - attempt + 1, resource_list_attempts)) + result = self._list_resources( + resource=self.networks_resource, + resource_list_attempts=resource_list_attempts, + raise_exception=raise_exception, poll_interval=poll_interval, + params_kwargs=params_kwargs) - resp = self.client.list_networks( - network_id=network_id, name=name, status=status, - admin_state_up=admin_state_up, shared=shared, - tenant_id=tenant_id, limit=limit, marker=marker, - page_reverse=page_reverse) - - resp_check = self.check_response(resp=resp, - 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 - result.failures.append(resp_check) - time.sleep(poll_interval) - - else: - err_msg = ( - 'Unable to LIST networks after {0} attempts: ' - '{1}').format(resource_list_attempts, result.failures) - self._log.error(err_msg) - if raise_exception: - raise ResourceListException(err_msg) - return result + return result def delete_network(self, network_id, resource_delete_attempts=None, raise_exception=False, poll_interval=None): @@ -308,43 +196,13 @@ class NetworksBehaviors(NetworkingBaseBehaviors): @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 = self._delete_resource( + resource=self.networks_resource, + resource_id=network_id, + resource_delete_attempts=resource_delete_attempts, + raise_exception=raise_exception, poll_interval=poll_interval) - result = NetworkingResponse() - for attempt in range(resource_delete_attempts): - self._log.debug('Attempt {0} of {1} deleting network {2}'.format( - 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): - return result - - err_msg = ('{network} Network Delete failure, expected status ' - 'code: {expected_status}. Response: {status} {reason} ' - '{content}').format( - network=network_id, - expected_status=NeutronResponseCodes.DELETE_NETWORK, - 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} network after {1} attempts: ' - '{2}').format(network_id, resource_delete_attempts, - result.failures) - self._log.error(err_msg) - if raise_exception: - raise ResourceDeleteException(err_msg) - return result + return result def delete_networks(self, network_list=None, name=None, tenant_id=None, skip_delete=None): @@ -366,7 +224,8 @@ class NetworksBehaviors(NetworkingBaseBehaviors): result = self._delete_resources( resource_list=network_list, name=name, tenant_id=tenant_id, skip_delete=skip_delete, - resource_type=NeutronResourceTypes.NETWORKS) + resource=self.networks_resource) + return result def clean_network(self, network_id, timeout=None, poll_interval=None): @@ -381,31 +240,12 @@ class NetworksBehaviors(NetworkingBaseBehaviors): @return: None if delete was successful or the undeleted network_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} network within a {1}s timeout '.format( - network_id, timeout) - self._log.info(log_msg) - resp = None - while time.time() < endtime: - try: - self.client.delete_network(network_id=network_id) - resp = self.client.get_network(network_id=network_id) - 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) + result = self._clean_resource( + resource=self.networks_resource, + resource_id=network_id, + timeout=timeout, poll_interval=poll_interval) - if (resp is not None and - 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) - return network_id + return result def clean_networks(self, networks_list, timeout=None, poll_interval=None): """ @@ -419,16 +259,9 @@ class NetworksBehaviors(NetworkingBaseBehaviors): @return: list of undeleted networks UUIDs @rtype: list(str) """ - log_msg = 'Deleting networks: {0}'.format(networks_list) - self._log.info(log_msg) - undeleted_networks = [] - for network in networks_list: - result = self.clean_network(network_id=network, timeout=timeout, - poll_interval=poll_interval) - if result: - undeleted_networks.append(result) - if undeleted_networks: - err_msg = 'Unable to delete networks: {0}'.format( - undeleted_networks) - self._log.error(err_msg) - return undeleted_networks + result = self._clean_resources( + resource=self.networks_resource, + resource_list=networks_list, + timeout=timeout, poll_interval=poll_interval) + + return result diff --git a/cloudcafe/networking/networks/ports_api/behaviors.py b/cloudcafe/networking/networks/ports_api/behaviors.py index 198c48af..14ffe2df 100644 --- a/cloudcafe/networking/networks/ports_api/behaviors.py +++ b/cloudcafe/networking/networks/ports_api/behaviors.py @@ -16,15 +16,12 @@ limitations under the License. import netaddr import time -from cloudcafe.common.tools.datagen import rand_name from cloudcafe.networking.networks.common.behaviors \ - import NetworkingBaseBehaviors, NetworkingResponse + import NetworkingBaseBehaviors from cloudcafe.networking.networks.common.constants \ - import NeutronResponseCodes, NeutronResourceTypes + import NeutronResponseCodes, NeutronResource from cloudcafe.networking.networks.common.exceptions \ - import NetworkIDMissingException, ResourceBuildException,\ - ResourceDeleteException, ResourceGetException, ResourceListException,\ - ResourceUpdateException + import NetworkIDMissingException class PortsBehaviors(NetworkingBaseBehaviors): @@ -33,6 +30,8 @@ class PortsBehaviors(NetworkingBaseBehaviors): super(PortsBehaviors, self).__init__() self.config = ports_config self.client = ports_client + self.response_codes = NeutronResponseCodes + self.ports_resource = NeutronResource(NeutronResource.PORT) def get_subnet_ids_from_fixed_ips(self, fixed_ips): """ @@ -70,7 +69,7 @@ class PortsBehaviors(NetworkingBaseBehaviors): device_owner=None, tenant_id=None, security_groups=None, resource_build_attempts=None, raise_exception=True, use_exact_name=False, poll_interval=None, - timeout=None, use_over_limit_retry=None): + timeout=None, use_over_limit_retry=False): """ @summary: Creates and verifies a Port is created as expected @param network_id: network port is associated with (CRUD: CR) @@ -104,9 +103,9 @@ class PortsBehaviors(NetworkingBaseBehaviors): @type use_exact_name: bool @param poll_interval: sleep time interval between API retries @type poll_interval: int - @param timeout: port update timeout for over limit retries + @param timeout: port create timeout for over limit retries @type timeout: int - @param use_over_limit_retry: flag to enable/disable the port update + @param use_over_limit_retry: flag to enable/disable the port create over limits retries @type use_over_limit_retry: bool @return: NetworkingResponse object with api response and failure list @@ -115,74 +114,27 @@ class PortsBehaviors(NetworkingBaseBehaviors): if not network_id: raise NetworkIDMissingException - if name is None: - name = rand_name(self.config.starts_with_name) - elif not use_exact_name: - name = rand_name(name) + attrs_kwargs = dict( + network_id=network_id, name=name, + admin_state_up=admin_state_up, mac_address=mac_address, + fixed_ips=fixed_ips, device_id=device_id, + device_owner=device_owner, tenant_id=tenant_id, + security_groups=security_groups) - poll_interval = poll_interval or self.config.api_poll_interval - resource_build_attempts = (resource_build_attempts or - self.config.api_retries) - use_over_limit_retry = (use_over_limit_retry or - self.config.use_over_limit_retry) - timeout = timeout or self.config.resource_create_timeout + result = self._create_resource( + resource=self.ports_resource, + resource_build_attempts=resource_build_attempts, + raise_exception=raise_exception, use_exact_name=use_exact_name, + poll_interval=poll_interval, attrs_kwargs=attrs_kwargs, + timeout=timeout, use_over_limit_retry=use_over_limit_retry) - result = NetworkingResponse() - err_msg = 'Port Create failure' - for attempt in range(resource_build_attempts): - self._log.debug('Attempt {0} of {1} building port {2}'.format( - attempt + 1, resource_build_attempts, name)) - - resp = self.client.create_port( - network_id=network_id, name=name, - admin_state_up=admin_state_up, mac_address=mac_address, - fixed_ips=fixed_ips, device_id=device_id, - device_owner=device_owner, tenant_id=tenant_id, - security_groups=security_groups) - - if use_over_limit_retry: - endtime = time.time() + int(timeout) - retry_msg = ('OverLimit retry with a {0}s timeout creating a ' - 'port on network {1}').format(timeout, network_id) - self._log.info(retry_msg) - while (resp.status_code == - NeutronResponseCodes.REQUEST_ENTITY_TOO_LARGE and - time.time() < endtime): - resp = self.client.create_port( - network_id=network_id, name=name, - admin_state_up=admin_state_up, mac_address=mac_address, - fixed_ips=fixed_ips, device_id=device_id, - device_owner=device_owner, tenant_id=tenant_id, - security_groups=security_groups) - time.sleep(poll_interval) - - resp_check = self.check_response(resp=resp, - 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 - result.failures.append(resp_check) - time.sleep(poll_interval) - - else: - err_msg = ( - 'Unable to create {0} port after {1} attempts: ' - '{2}').format(name, resource_build_attempts, result.failures) - self._log.error(err_msg) - if raise_exception: - raise ResourceBuildException(err_msg) - return result + 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, - timeout=None, use_over_limit_retry=None): + timeout=None, use_over_limit_retry=False): """ @summary: Updates and verifies a specified Port @param port_id: The UUID for the port @@ -219,66 +171,24 @@ class PortsBehaviors(NetworkingBaseBehaviors): @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) - use_over_limit_retry = (use_over_limit_retry or - self.config.use_over_limit_retry) - timeout = timeout or self.config.resource_update_timeout - - 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, + attrs_kwargs = dict( + name=name, admin_state_up=admin_state_up, fixed_ips=fixed_ips, device_id=device_id, device_owner=device_owner, security_groups=security_groups) - if use_over_limit_retry: - endtime = time.time() + int(timeout) - retry_msg = ('OverLimit retry with a {0}s timeout updating ' - 'port {1}').format(timeout, port_id) - self._log.info(retry_msg) - while (resp.status_code == - NeutronResponseCodes.REQUEST_ENTITY_TOO_LARGE and - time.time() < endtime): - 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) - time.sleep(poll_interval) + result = self._update_resource( + resource=self.ports_resource, + resource_id=port_id, + resource_update_attempts=resource_update_attempts, + raise_exception=raise_exception, poll_interval=poll_interval, + attrs_kwargs=attrs_kwargs, timeout=timeout, + use_over_limit_retry=use_over_limit_retry) - 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 + return result def get_port(self, port_id, resource_get_attempts=None, raise_exception=False, poll_interval=None, - timeout=None, use_over_limit_retry=None): + timeout=None, use_over_limit_retry=False): """ @summary: Shows and verifies a specified port @param port_id: The UUID for the port @@ -292,67 +202,27 @@ class PortsBehaviors(NetworkingBaseBehaviors): @type poll_interval: int @param timeout: port get timeout for over limit retries @type timeout: int - @param use_over_limit_retry: flag to enable/disable the port update + @param use_over_limit_retry: flag to enable/disable the port get over limits retries @type use_over_limit_retry: bool @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) - poll_interval = poll_interval or self.config.api_poll_interval - use_over_limit_retry = (use_over_limit_retry or - self.config.use_over_limit_retry) - timeout = timeout or self.config.resource_get_timeout + result = self._get_resource( + resource=self.ports_resource, + resource_id=port_id, + resource_get_attempts=resource_get_attempts, + raise_exception=raise_exception, poll_interval=poll_interval, + timeout=timeout, use_over_limit_retry=use_over_limit_retry) - 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) - - if use_over_limit_retry: - endtime = time.time() + int(timeout) - retry_msg = ('OverLimit retry with a {0}s timeout getting ' - 'port {1}').format(timeout, port_id) - self._log.info(retry_msg) - while (resp.status_code == - NeutronResponseCodes.REQUEST_ENTITY_TOO_LARGE and - time.time() < endtime): - resp = self.client.get_port(port_id=port_id) - time.sleep(poll_interval) - - 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 + 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, timeout=None, - use_over_limit_retry=None): + use_over_limit_retry=False): """ @summary: Lists ports and verifies the response is the expected @param port_id: The UUID for the port to filter by @@ -394,67 +264,25 @@ class PortsBehaviors(NetworkingBaseBehaviors): @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) - use_over_limit_retry = (use_over_limit_retry or - self.config.use_over_limit_retry) - timeout = timeout or self.config.resource_get_timeout + params_kwargs = dict( + 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) - 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)) + result = self._list_resources( + resource=self.ports_resource, + resource_list_attempts=resource_list_attempts, + raise_exception=raise_exception, poll_interval=poll_interval, + params_kwargs=params_kwargs, timeout=timeout, + use_over_limit_retry=use_over_limit_retry) - 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) - - if use_over_limit_retry: - endtime = time.time() + int(timeout) - retry_msg = ('OverLimit retry with a {0}s timeout listing ' - 'ports').format(timeout, port_id) - self._log.info(retry_msg) - while (resp.status_code == - NeutronResponseCodes.REQUEST_ENTITY_TOO_LARGE and - time.time() < endtime): - 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) - time.sleep(poll_interval) - - 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 + return result def delete_port(self, port_id, resource_delete_attempts=None, raise_exception=False, poll_interval=None, - timeout=None, use_over_limit_retry=None): + timeout=None, use_over_limit_retry=False): """ @summary: Deletes and verifies a specified port is deleted @param string port_id: The UUID for the port @@ -474,58 +302,14 @@ class PortsBehaviors(NetworkingBaseBehaviors): @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) - use_over_limit_retry = (use_over_limit_retry or - self.config.use_over_limit_retry) - timeout = timeout or self.config.resource_delete_timeout + result = self._delete_resource( + resource=self.ports_resource, + resource_id=port_id, + resource_delete_attempts=resource_delete_attempts, + raise_exception=raise_exception, poll_interval=poll_interval, + timeout=timeout, use_over_limit_retry=use_over_limit_retry) - 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) - - if use_over_limit_retry: - endtime = time.time() + int(timeout) - retry_msg = ('OverLimit retry with a {0}s timeout deleting ' - 'port {1}').format(timeout, port_id) - self._log.info(retry_msg) - while (resp.status_code == - NeutronResponseCodes.REQUEST_ENTITY_TOO_LARGE and - time.time() < endtime): - resp = self.client.delete_port(port_id=port_id) - time.sleep(poll_interval) - - 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 + return result def delete_ports(self, port_list=None, name=None, tenant_id=None, skip_delete=None): @@ -547,7 +331,7 @@ class PortsBehaviors(NetworkingBaseBehaviors): result = self._delete_resources( resource_list=port_list, name=name, tenant_id=tenant_id, skip_delete=skip_delete, - resource_type=NeutronResourceTypes.PORTS) + resource=self.ports_resource) return result def clean_port(self, port_id, timeout=None, poll_interval=None): @@ -562,32 +346,12 @@ class PortsBehaviors(NetworkingBaseBehaviors): @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 + result = self._clean_resource( + resource=self.ports_resource, + resource_id=port_id, + timeout=timeout, poll_interval=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) - resp = None - 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 is not None and - 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 + return result def clean_ports(self, ports_list, timeout=None, poll_interval=None): """ @@ -601,16 +365,9 @@ class PortsBehaviors(NetworkingBaseBehaviors): @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, timeout=timeout, - poll_interval=poll_interval) - 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 + result = self._clean_resources( + resource=self.ports_resource, + resource_list=ports_list, + timeout=timeout, poll_interval=poll_interval) + + return result diff --git a/cloudcafe/networking/networks/ports_api/config.py b/cloudcafe/networking/networks/ports_api/config.py index 1b24e2d3..fbaecfd3 100644 --- a/cloudcafe/networking/networks/ports_api/config.py +++ b/cloudcafe/networking/networks/ports_api/config.py @@ -47,11 +47,6 @@ class PortsConfig(NetworkingBaseConfig): """Ports fixed IPs quota""" return int(self.get("fixed_ips_per_port", 5)) - @property - def use_over_limit_retry(self): - """Flag to enable/disable retries due to over limits responses""" - return self.get_boolean("use_over_limit_retry", False) - @property def api_poll_interval(self): """Time interval for api calls on retry loops""" diff --git a/cloudcafe/networking/networks/subnets_api/behaviors.py b/cloudcafe/networking/networks/subnets_api/behaviors.py index bc417fea..807f5aac 100644 --- a/cloudcafe/networking/networks/subnets_api/behaviors.py +++ b/cloudcafe/networking/networks/subnets_api/behaviors.py @@ -21,15 +21,13 @@ import IPy import netaddr -from cloudcafe.common.tools.datagen import rand_name, random_cidr +from cloudcafe.common.tools.datagen import random_cidr from cloudcafe.networking.networks.common.behaviors \ - import NetworkingBaseBehaviors, NetworkingResponse + import NetworkingBaseBehaviors from cloudcafe.networking.networks.common.constants \ - import NeutronResponseCodes, NeutronResourceTypes + import NeutronResponseCodes, NeutronResource from cloudcafe.networking.networks.common.exceptions \ - import InvalidIPException, NetworkIDMissingException,\ - ResourceBuildException, ResourceDeleteException, ResourceGetException,\ - ResourceListException, ResourceUpdateException + import InvalidIPException, NetworkIDMissingException class SubnetsBehaviors(NetworkingBaseBehaviors): @@ -38,6 +36,8 @@ class SubnetsBehaviors(NetworkingBaseBehaviors): super(SubnetsBehaviors, self).__init__() self.config = subnets_config self.client = subnets_client + self.response_codes = NeutronResponseCodes + self.subnets_resource = NeutronResource(NeutronResource.SUBNET) def verify_ip(self, ip_cidr, ip_range=None): """ @@ -517,49 +517,20 @@ class SubnetsBehaviors(NetworkingBaseBehaviors): ip_version = 4 cidr = self.create_ipv4_cidr() - if name is None: - name = rand_name(self.config.starts_with_name) - elif not use_exact_name: - name = rand_name(name) + attrs_kwargs = dict( + network_id=network_id, ip_version=ip_version, cidr=cidr, + name=name, tenant_id=tenant_id, gateway_ip=gateway_ip, + dns_nameservers=dns_nameservers, + allocation_pools=allocation_pools, host_routes=host_routes, + enable_dhcp=enable_dhcp) - poll_interval = poll_interval or self.config.api_poll_interval - resource_build_attempts = (resource_build_attempts or - self.config.api_retries) + result = self._create_resource( + resource=self.subnets_resource, + resource_build_attempts=resource_build_attempts, + raise_exception=raise_exception, use_exact_name=use_exact_name, + poll_interval=poll_interval, attrs_kwargs=attrs_kwargs) - result = NetworkingResponse() - err_msg = 'Subnet Create failure' - for attempt in range(resource_build_attempts): - self._log.debug('Attempt {0} of {1} building subnet {2}'.format( - attempt + 1, resource_build_attempts, name)) - - resp = self.client.create_subnet( - network_id=network_id, ip_version=ip_version, cidr=cidr, - name=name, tenant_id=tenant_id, gateway_ip=gateway_ip, - dns_nameservers=dns_nameservers, - allocation_pools=allocation_pools, host_routes=host_routes, - enable_dhcp=enable_dhcp) - - resp_check = self.check_response(resp=resp, - status_code=NeutronResponseCodes.CREATE_SUBNET, 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 update was successful the - # first time - result.failures.append(resp_check) - time.sleep(poll_interval) - - else: - err_msg = ( - 'Unable to create {0} subnet after {1} attempts: ' - '{2}').format(name, resource_build_attempts, result.failures) - self._log.error(err_msg) - if raise_exception: - raise ResourceBuildException(err_msg) - return result + return result def update_subnet(self, subnet_id, name=None, gateway_ip=None, dns_nameservers=None, host_routes=None, @@ -597,43 +568,19 @@ class SubnetsBehaviors(NetworkingBaseBehaviors): @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) + attrs_kwargs = dict( + name=name, gateway_ip=gateway_ip, + dns_nameservers=dns_nameservers, host_routes=host_routes, + enable_dhcp=enable_dhcp, allocation_pools=allocation_pools) - 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)) + result = self._update_resource( + resource=self.subnets_resource, + resource_id=subnet_id, + resource_update_attempts=resource_update_attempts, + raise_exception=raise_exception, poll_interval=poll_interval, + attrs_kwargs=attrs_kwargs) - 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 + return result def get_subnet(self, subnet_id, resource_get_attempts=None, raise_exception=False, poll_interval=None): @@ -651,40 +598,13 @@ class SubnetsBehaviors(NetworkingBaseBehaviors): @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 = self._get_resource( + resource=self.subnets_resource, + resource_id=subnet_id, + resource_get_attempts=resource_get_attempts, + raise_exception=raise_exception, poll_interval=poll_interval) - 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 + return result def list_subnets(self, subnet_id=None, network_id=None, cidr=None, tenant_id=None, gateway_ip=None, ip_version=None, @@ -725,43 +645,19 @@ class SubnetsBehaviors(NetworkingBaseBehaviors): @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) + params_kwargs = dict( + 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) - 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)) + result = self._list_resources( + resource=self.subnets_resource, + resource_list_attempts=resource_list_attempts, + raise_exception=raise_exception, poll_interval=poll_interval, + params_kwargs=params_kwargs) - 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 + return result def delete_subnet(self, subnet_id, resource_delete_attempts=None, raise_exception=False, poll_interval=None): @@ -779,43 +675,13 @@ class SubnetsBehaviors(NetworkingBaseBehaviors): @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 = self._delete_resource( + resource=self.subnets_resource, + resource_id=subnet_id, + resource_delete_attempts=resource_delete_attempts, + raise_exception=raise_exception, poll_interval=poll_interval) - 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 + return result def delete_subnets(self, subnet_list=None, name=None, tenant_id=None, skip_delete=None): @@ -837,7 +703,8 @@ class SubnetsBehaviors(NetworkingBaseBehaviors): result = self._delete_resources( resource_list=subnet_list, name=name, tenant_id=tenant_id, skip_delete=skip_delete, - resource_type=NeutronResourceTypes.SUBNETS) + resource=self.subnets_resource) + return result def clean_subnet(self, subnet_id, timeout=None, poll_interval=None): @@ -852,30 +719,12 @@ class SubnetsBehaviors(NetworkingBaseBehaviors): @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) - resp = None - 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 is not None and - resp.status_code == NeutronResponseCodes.NOT_FOUND): - return None - time.sleep(poll_interval) + result = self._clean_resource( + resource=self.subnets_resource, + resource_id=subnet_id, + timeout=timeout, poll_interval=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 + return result def clean_subnets(self, subnets_list, timeout=None, poll_interval=None): """ @@ -889,16 +738,9 @@ class SubnetsBehaviors(NetworkingBaseBehaviors): @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, timeout=timeout, - poll_interval=poll_interval) - 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 + result = self._clean_resources( + resource=self.subnets_resource, + resource_list=subnets_list, + timeout=timeout, poll_interval=poll_interval) + + return result