class ImagesV2Behaviors(BaseBehavior): """ @summary: Base Behaviors class for Images V2 API tests """ def __init__(self, images_client, images_config): super(ImagesV2Behaviors, self).__init__() self.config = images_config self.client = images_client self.resources = ResourcePool() def register_basic_image(self): """Register a basic image and return its id.""" response = self.client.create_image( name=rand_name('basic_image_'), container_format=ImageContainerFormat.BARE, disk_format=ImageDiskFormat.RAW) image = response.entity self.resources.add(self.client.delete_image, image.id_) return image.id_ def register_private_image(self): """Register a private image and return its id.""" response = self.client.create_image( name=rand_name('private_image_'), visibility=ImageVisibility.PRIVATE, container_format=ImageContainerFormat.BARE, disk_format=ImageDiskFormat.RAW) image = response.entity self.resources.add(self.client.delete_image, image.id_) return image.id_ def get_member_ids(self, image_id): """Return the list of ids for all available members for an image.""" response = self.client.list_members(image_id) return [member.member_id for member in response.entity]
class ImagesBehaviors(BaseBehavior): """@summary: Behaviors class for images v2""" def __init__(self, images_client, images_config): super(ImagesBehaviors, self).__init__() self.config = images_config self.client = images_client self.resources = ResourcePool() def create_new_image(self, container_format=None, disk_format=None, name=None, protected=None, tags=None, visibility=None): """@summary: Create new image and add it for deletion""" if container_format is None: container_format = ImageContainerFormat.BARE if disk_format is None: disk_format = ImageDiskFormat.RAW if name is None: name = rand_name('image') if protected is None: protected = False if tags is None: tags = [] if visibility is None: visibility = ImageVisibility.PRIVATE response = self.client.create_image( container_format=container_format, disk_format=disk_format, name=name, protected=protected, tags=tags, visibility=visibility) image = response.entity if image is not None: self.resources.add(image.id_, self.client.delete_image) return image def create_new_images(self, container_format=None, disk_format=None, name=None, protected=None, tags=None, visibility=None, count=1): """@summary: Create new images and add them for deletion""" image_list = [] for i in range(count): image = self.create_new_image( container_format=container_format, disk_format=disk_format, name=name, protected=protected, tags=tags, visibility=visibility) image_list.append(image) return image_list def list_images_pagination(self, name=None, disk_format=None, container_format=None, visibility=None, status=None, checksum=None, owner=None, min_ram=None, min_disk=None, changes_since=None, protected=None, size_min=None, size_max=None, sort_key=None, sort_dir=None, marker=None, limit=None): """@summary: Get images accounting for pagination as needed""" image_list = [] results_limit = self.config.results_limit response = self.client.list_images( name=name, disk_format=disk_format, container_format=container_format, visibility=visibility, status=status, checksum=checksum, owner=owner, min_ram=min_ram, min_disk=min_disk, changes_since=changes_since, protected=protected, size_min=size_min, size_max=size_max, sort_key=sort_key, sort_dir=sort_dir, marker=marker, limit=limit) images = response.entity while len(images) == results_limit: image_list += images marker = images[results_limit - 1].id_ response = self.client.list_images( name=name, disk_format=disk_format, container_format=container_format, visibility=visibility, status=status, checksum=checksum, owner=owner, min_ram=min_ram, min_disk=min_disk, changes_since=changes_since, protected=protected, size_min=size_min, size_max=size_max, sort_key=sort_key, sort_dir=sort_dir, marker=marker, limit=limit) images = response.entity image_list += images return image_list def get_member_ids(self, image_id): """ @summary: Return a complete list of ids for all members for a given image id """ response = self.client.list_members(image_id) members = response.entity return [member.member_id for member in members]
class BobbyBehaviors(BaseBehavior): """ :summary: Behavior Module for the Bobby REST API :note: Should be the primary interface to a test case or external tool """ def __init__(self, bobby_config, bobby_client): """ Instantiate config and client """ super(BobbyBehaviors, self).__init__() self.bobby_config = bobby_config self.bobby_client = bobby_client self.resources = ResourcePool() def create_bobby_group_given(self, group_id=None, notification=None, notification_plan=None): """ Creates a bobby group with the given values """ group_id = group_id or rand_name("012345DIFF-78f3-4543-85bc1-") notification = notification or self.bobby_config.notification notification_plan = notification_plan or self.bobby_config.notification_plan bobby_group = self.bobby_client.create_group( group_id=group_id, notification=notification, notification_plan=notification_plan ) self.resources.add(group_id, self.bobby_client.delete_group) return bobby_group.entity def create_bobby_server_group_given(self, group_id=None, server_id=None, entity_id=None): """ Creates a bobby group with the given values """ group_id = group_id or self.bobby_config.group_id server_id = server_id or rand_name("0123SERVER-78f3-4543-85bc1-") entity_id = entity_id or rand_name("0123ENTITY-78f3-4543-85bc1-") bobby_server_group = self.bobby_client.create_server_group( group_id=group_id, entity_id=entity_id, server_id=server_id ) self.resources.add(group_id, self.bobby_client.delete_group) return bobby_server_group def create_bobby_policy_given(self, group_id=None, policy_id=None, alarm_template=None, check_template=None): """ Creates a policy in bobby with the given values """ group_id = group_id or self.bobby_config.group_id policy_id = policy_id or rand_name("0123POLICY-78f3-4543-85bc1-") check_template = ( check_template or "7867" or { "label": "Website check 1", "type": "remote.http", "details": {"url": "http://www.foo.com", "method": "GET"}, "monitoring_zones_poll": ["mzA"], "timeout": 30, "period": 100, "target_alias": "default", } ) alarm_template = ( alarm_template or "8787" or { "criteria": 'if (metric["duration"] >= 2) { return new AlarmStatus(OK); }' " return new AlarmStatus(CRITICAL);" } ) bobby_policy = self.bobby_client.create_bobby_policy( group_id=group_id, policy_id=policy_id, check_template=check_template, alarm_template=alarm_template ) self.resources.add(group_id, self.bobby_client.delete_group) return bobby_policy
class ImagesBehaviors(BaseBehavior): """@summary: Behaviors for Images""" def __init__(self, images_client, images_config): super(ImagesBehaviors, self).__init__() self.config = images_config self.client = images_client self.resources = ResourcePool() @staticmethod def get_comparison_data(data_file): """ @summary: Create comparison dictionary based on a given set of data @param data_file: File containing data to compare @param data_file: String @return: Comparison_dict @rtype: Dictionary """ comparison_dict = dict() data_columns = [] with open(data_file, "r") as DATA: all_data = DATA.readlines() for line in all_data: # Skip any comments or short lines if line.startswith("#") or len(line) < 5: continue # Get the defined data if line.startswith("+"): line = line.replace("+", "") data_columns = [x.strip().lower() for x in line.split("|")] continue # Process the data each_data = dict() data = [x.strip() for x in line.split("|")] for x, y in zip(data_columns[1:], data[1:]): each_data[x] = y comparison_dict[data[0]] = each_data return comparison_dict def create_image_via_task(self, image_properties=None, import_from=None): """ @summary: Create new image via task @param image_properties: Properties to use for the image creation @type image_properties: Dictionary @param import_from: Location of image @type import_from: String @return: Image @rtype: Object """ image_properties = image_properties or {"name": rand_name("image")} import_from = import_from or self.config.import_from input_ = {"image_properties": image_properties, "import_from": import_from} task = self.create_new_task(input_=input_, type_=TaskTypes.IMPORT) image_id = task.result.image_id self.client.add_image_tag(image_id=image_id, tag=rand_name("tag")) resp = self.client.get_image_details(image_id=image_id) image = self.verify_resp(resp, "get image details", image_id) return image def create_images_via_task(self, image_properties=None, import_from=None, count=2): """ @summary: Create new images via tasks @param image_properties: Properties to use for the image creation @type image_properties: Dictionary @param import_from: Location of image @type import_from: String @param count: Number of images to create @type count: Integer @return: Image_list @rtype: List """ image_list = [] for i in range(count): image = self.create_image_via_task(image_properties=image_properties, import_from=import_from) image_list.append(image) return image_list def register_new_image( self, auto_disk_config=None, checksum=None, container_format=None, created_at=None, disk_format=None, file_=None, id_=None, image_type=None, min_disk=None, min_ram=None, name=None, os_type=None, owner=None, protected=None, schema=None, self_=None, size=None, status=None, tags=None, updated_at=None, user_id=None, visibility=None, additional_properties=None, ): """ @summary: Register new image and add it for deletion @param auto_disk_config: Auto disk config for the image being created @type auto_disk_config: String @param checksum: Checksum for the image being created @type checksum: String @param container_format: Container format for the image being created @type container_format: String @param created_at: Created at for the image being created @type created_at: Datetime @param disk_format: Disk format for the image being created @type disk_format: String @param file_: File location for the image being created @type file_: String @param id_: Id for the image being created @type id_: UUID @param image_type: Image type for the image being created @type image_type: String @param min_disk: Minimum disk for the image being created @type min_disk: String @param min_ram: Minimum ram for the image being created @type min_ram: String @param name: Name for the image being created @type name: String @param os_type: OS type for the image being created @type os_type: String @param owner: Owner for the image being created @type owner: String @param protected: Protected flag for the image being created @type protected: Boolean @param schema: Schema for the image being created @type schema: String @param self_: Self location for the image being created @type self_: String @param size: Size for the image being created @type size: String @param status: Status for the image being created @type status: String @param tags: Tags for the image being created @type tags: Dictionary @param updated_at: Updated at for the image being created @type updated_at: Datetime @param user_id: User id for the image being created @type user_id: String @param visibility: Visibility for the image being created @type visibility: String @param additional_properties: Additional properties for the image being created @type additional_properties: Dictionary @return: Image @rtype: Object """ container_format = container_format or ImageContainerFormat.BARE disk_format = disk_format or ImageDiskFormat.RAW name = name or rand_name("image") resp = self.client.register_image( auto_disk_config=auto_disk_config, checksum=checksum, container_format=container_format, created_at=created_at, disk_format=disk_format, file_=file_, id_=id_, image_type=image_type, min_disk=min_disk, min_ram=min_ram, name=name, os_type=os_type, owner=owner, protected=protected, schema=schema, self_=self_, size=size, status=status, tags=tags, updated_at=updated_at, user_id=user_id, visibility=visibility, additional_properties=additional_properties, ) image = self.verify_resp(resp, "register image") self.resources.add(image.id_, self.client.delete_image) return image def register_new_images( self, auto_disk_config=None, checksum=None, container_format=None, created_at=None, disk_format=None, file_=None, id_=None, image_type=None, min_disk=None, min_ram=None, name=None, os_type=None, owner=None, protected=None, schema=None, self_=None, size=None, status=None, tags=None, updated_at=None, user_id=None, visibility=None, additional_properties=None, count=2, ): """ @summary: Register new images and add them for deletion @param auto_disk_config: Auto disk config for the image being created @type auto_disk_config: String @param checksum: Checksum for the image being created @type checksum: String @param container_format: Container format for the image being created @type container_format: String @param created_at: Created at for the image being created @type created_at: Datetime @param disk_format: Disk format for the image being created @type disk_format: String @param file_: File location for the image being created @type file_: String @param id_: Id for the image being created @type id_: UUID @param image_type: Image type for the image being created @type image_type: String @param min_disk: Minimum disk for the image being created @type min_disk: String @param min_ram: Minimum ram for the image being created @type min_ram: String @param name: Name for the image being created @type name: String @param os_type: OS type for the image being created @type os_type: String @param owner: Owner for the image being created @type owner: String @param protected: Protected flag for the image being created @type protected: Boolean @param schema: Schema for the image being created @type schema: String @param self_: Self location for the image being created @type self_: String @param size: Size for the image being created @type size: String @param status: Status for the image being created @type status: String @param tags: Tags for the image being created @type tags: Dictionary @param updated_at: Updated at for the image being created @type updated_at: Datetime @param user_id: User id for the image being created @type user_id: String @param visibility: Visibility for the image being created @type visibility: String @param additional_properties: Additional properties for the image being created @type additional_properties: Dictionary @return: Image_list @rtype: List """ image_list = [] for i in range(count): image = self.register_new_image( auto_disk_config=auto_disk_config, checksum=checksum, container_format=container_format, created_at=created_at, disk_format=disk_format, file_=file_, id_=id_, image_type=image_type, min_disk=min_disk, min_ram=min_ram, name=name, os_type=os_type, owner=owner, protected=protected, schema=schema, self_=self_, size=size, status=status, tags=tags, updated_at=updated_at, user_id=user_id, visibility=visibility, additional_properties=additional_properties, ) image_list.append(image) return image_list def list_all_images(self, url_addition=None, **params): """ @summary: Retrieve a complete list of images accounting for any query parameters @param url_addition: Additional text to be added to the end of the url to account for duplicate sort parameters @type url_addition: String @param params: Parameters to alter the returned list of images @type params: Dictionary @return: Image_list @rtype: List """ image_list = [] results_limit = self.config.results_limit params.update({"limit": results_limit}) resp = self.client.list_images(params, url_addition) images = self.verify_resp(resp, "list images") while len(images) == results_limit: image_list += images marker = images[results_limit - 1].id_ params.update({"marker": marker}) resp = self.client.list_images(params, url_addition) images = self.verify_resp(resp, "list images") image_list += images return image_list @staticmethod def get_time_delta(time_in_sec, time_property): """ @summary: Calculate the difference between an image attribute's time value and a time_property @param time_in_sec: Current time in seconds @type time_in_sec: Integer @param time_property: Image property containing a time @type time_property: Datetime @return: Time_delta @rtype: Integer """ time_property_in_sec = calendar.timegm(time_property.timetuple()) return abs(time_property_in_sec - time_in_sec) @staticmethod def validate_image(image): """ @summary: Generically validate an image contains crucial expected data @param image: Image to be validated @type image: Object @return: Errors @rtype: List """ id_regex = re.compile(ImageProperties.ID_REGEX) errors = [] # The following properties do not always have values: # checksum, container_format, disk_format, name, tags if image.auto_disk_config is None: errors.append(Messages.PROPERTY_MSG.format("auto_disk_config", "not None", image.auto_disk_config)) if image.created_at is None: errors.append(Messages.PROPERTY_MSG.format("created_at", "not None", image.created_at)) if image.file_ != "/v2/images/{0}/file".format(image.id_): errors.append(Messages.PROPERTY_MSG.format("file", "/v2/images/{0}/file".format(image.id_), image.file_)) if id_regex.match(image.id_) is None: errors.append(Messages.PROPERTY_MSG.format("id", "not None", id_regex)) if image.image_type is None: errors.append(Messages.PROPERTY_MSG.format("image_type", "not None", image.image_type)) if image.min_disk is None: errors.append(Messages.PROPERTY_MSG.format("min_disk", "not None", image.min_disk)) if image.min_ram is None: errors.append(Messages.PROPERTY_MSG.format("min_ram", "not None", image.min_ram)) if image.os_type is None: errors.append(Messages.PROPERTY_MSG.format("os_type", "not None", image.os_type)) if image.owner is None: errors.append(Messages.PROPERTY_MSG.format("owner", "not None", image.owner)) if image.protected is None: errors.append(Messages.PROPERTY_MSG.format("protected", "not None", image.protected)) if image.schema != Schemas.IMAGE_SCHEMA: errors.append(Messages.PROPERTY_MSG.format("schema", Schemas.IMAGE_SCHEMA, image.schema)) if image.self_ != "/v2/images/{0}".format(image.id_): errors.append(Messages.PROPERTY_MSG.format("self", "/v2/images/{0}".format(image.id_), image.self_)) if image.status is None: errors.append(Messages.PROPERTY_MSG.format("status", "not None", image.status)) if image.updated_at is None: errors.append(Messages.PROPERTY_MSG.format("updated_at", "not None", image.updated_at)) if image.user_id is None: errors.append(Messages.PROPERTY_MSG.format("user_id", "not None", image.user_id)) if image.virtual_size is not None: errors.append(Messages.PROPERTY_MSG.format("virtual_size", "None", image.virtual_size)) if image.visibility is None: errors.append(Messages.PROPERTY_MSG.format("visibility", "not None", image.visibility)) return errors @staticmethod def validate_image_member(image_member): """ @summary: Generically validate an image member contains crucial expected data @param image_member: Image member to be validated @type image_member: Object @return: Errors @rtype: List """ errors = [] if image_member.created_at is None: errors.append(Messages.PROPERTY_MSG.format("created_at", "not None", image_member.created_at)) if image_member.image_id is None: errors.append(Messages.PROPERTY_MSG.format("image_id", "not None", image_member.image_id)) if image_member.member_id is None: errors.append(Messages.PROPERTY_MSG.format("member_id", "not None", image_member.member_id)) if image_member.schema != Schemas.IMAGE_MEMBER_SCHEMA: errors.append(Messages.PROPERTY_MSG.format("schema", Schemas.IMAGE_MEMBER_SCHEMA, image_member.schema)) if image_member.status is None: errors.append(Messages.PROPERTY_MSG.format("status", "not None", image_member.status)) if image_member.updated_at is None: errors.append(Messages.PROPERTY_MSG.format("updated_at", "not None", image_member.updated_at)) return errors def wait_for_image_status(self, image_id, desired_status, interval_time=15, timeout=900): """ @summary: Wait for a image to reach a desired status @param image_id: Image id to evaluate @type image_id: UUID @param desired_status: Expected final status of image @type desired_status: String @param interval_time: Amount of time in seconds to wait between polling @type interval_time: Integer @param timeout: Amount of time in seconds to wait before aborting @type timeout: Integer @return: Resp @rtype: Object """ interval_time = interval_time or self.config.image_status_interval timeout = timeout or self.config.snapshot_timeout end_time = time.time() + timeout while time.time() < end_time: resp = self.client.get_image_details(image_id) image = self.verify_resp(resp, "get image details", image_id) if image.status.lower() == ImageStatus.ERROR.lower(): raise BuildErrorException("Image with the uuid {0} entered ERROR " "status.".format(image_id)) if image.status == desired_status: break time.sleep(interval_time) else: raise TimeoutException( "Image with the uuid {0} did not reach the {1} status within " "{2} seconds.".format(image_id, desired_status, timeout) ) return resp def create_new_task(self, input_=None, type_=None): """ @summary: Create new task and wait for success status @param input_: Image properties and location data @type input_: Dictionary @param type_: Type of task @type type_: String @return: Task @rtype: Object """ import_from = self.config.import_from input_ = input_ or {"image_properties": {}, "import_from": import_from} type_ = type_ or TaskTypes.IMPORT failures = [] attempts = self.config.resource_creation_attempts for attempt in range(attempts): try: if type_ == TaskTypes.IMPORT: resp = self.client.task_to_import_image(input_=input_, type_=type_) else: resp = self.client.task_to_export_image(input_=input_, type_=type_) task = resp.entity task = self.wait_for_task_status(task.id_, TaskStatus.SUCCESS) return task except (BuildErrorException, TimeoutException) as ex: failure = "Attempt {0}: Failed to create task with " "the message {1}".format(attempt + 1, ex.message) self._log.error(failure) failures.append(failure) raise RequiredResourceException( "Failed to successfully create a task after {0} attempts: " "{1}".format(attempts, failures) ) def create_new_tasks(self, input_=None, type_=None, count=2): """ @summary: Create new tasks and wait for success status for each @param input_: Image properties and image location @type input_: Dictionary @param type_: Type of task @type type_: String @param count: Number of tasks to create @type count: Integer @return: Task_list @rtype: List """ task_list = [] for i in range(count): task = self.create_new_task(input_=input_, type_=type_) task_list.append(task) return task_list def list_all_tasks(self, **params): """ @summary: Retrieve a complete list of tasks accounting for query parameters @param params: Parameters to alter the returned list of images @type params: Dictionary @return: Task_list @rtype: List """ task_list = [] results_limit = self.config.results_limit params.update({"limit": results_limit}) resp = self.client.list_tasks(params) tasks = self.verify_resp(resp, "list tasks") while len(tasks) == results_limit: task_list += tasks marker = tasks[results_limit - 1].id_ params.update({"marker": marker}) resp = self.client.list_tasks(params) tasks = self.verify_resp(resp, "list tasks") task_list += tasks return task_list @staticmethod def validate_task(task): """ @summary: Generically validate a task contains crucial expected data @param task: Task to be validated @type task: UUID @return: Errors @rtype: List """ id_regex = re.compile(ImageProperties.ID_REGEX) errors = [] if task.status is None: errors.append(Messages.PROPERTY_MSG.format("status", "not None", task.status)) if id_regex.match(task.id_) is None: errors.append(Messages.PROPERTY_MSG.format("id_", "not None", id_regex.match(task.id_))) if task.created_at is None: errors.append(Messages.PROPERTY_MSG.format("created_at", "not None", task.created_at)) if task.type_ == TaskTypes.IMPORT: if task.input_.import_from is None: errors.append(Messages.PROPERTY_MSG.format("import_from", "not None", task.input_.import_from)) if task.result is not None and id_regex.match(task.result.image_id) is None: errors.append( Messages.PROPERTY_MSG.format("image_id", "not None", id_regex.match(task.result.image_id)) ) elif task.type_ == TaskTypes.EXPORT: if task.input_.image_uuid is None: errors.append(Messages.PROPERTY_MSG.format("image_uuid", "not None", task.input_.image_uuid)) if task.input_.receiving_swift_container is None: errors.append( Messages.PROPERTY_MSG.format( "receiving_swift_container", "not None", task.input_.receiving_swift_container ) ) if task.result is not None and task.result.export_location is None: errors.append(Messages.PROPERTY_MSG.format("export_location", "not None", task.result.export_location)) elif task.type_ is None: errors.append(Messages.PROPERTY_MSG.format("type_", "not None", task.type_)) if task.updated_at is None: errors.append(Messages.PROPERTY_MSG.format("updated_at", "not None", task.updated_at)) if task.self_ != "/v2/tasks/{0}".format(task.id_): errors.append(Messages.PROPERTY_MSG.format("self_", "/v2/tasks/{0}".format(task.id_), task.self_)) if task.owner is None: errors.append(Messages.PROPERTY_MSG.format("owner", "not None", task.owner)) if task.message != "": errors.append(Messages.PROPERTY_MSG.format("message", "Empty message", task.message)) if task.schema != "/v2/schemas/task": errors.append(Messages.PROPERTY_MSG.format("schema", "/v2/schemas/task", task.schema)) return errors @staticmethod def validate_exported_files(expect_success, files, image_id): """ @summary: Validate that a given storage location contains a given file or not @param expect_success: Flag to determine if task completed successfully @type expect_success: Boolean @param files: File objects to be validated @type files: List @param image_id: Image id to validate against @type image_id: UUID @return: Errors, file_names @rtype: List, list """ errors = [] file_names = [file_.name for file_ in files] if expect_success: if "{0}.vhd".format(image_id) not in file_names: errors.append( "Expected VHD file not listed. Expected: " "{0}.vhd to be listed Received: File was not " "listed".format(image_id) ) else: if "{0}.vhd".format(image_id) in file_names: errors.append( "Unexpected VHD file listed. Expected: {0}.vhd " "to not be listed Received: File was " "listed".format(image_id) ) return errors, file_names def wait_for_task_status(self, task_id, desired_status, interval_time=10, timeout=1200): """ @summary: Waits for a task to reach a desired status and if the import task is successful, adds the created image to the resource pool for tear down @param task_id: Task id to evaluate @type task_id: UUID @param desired_status: Expected final status of task @type desired_status: String @param interval_time: Amount of time in seconds to wait between polling @type interval_time: Integer @param timeout: Amount of time in seconds to wait before aborting @type timeout: Integer @return: Task @rtype: Object """ interval_time = interval_time or self.config.task_status_interval timeout = timeout or self.config.task_timeout end_time = time.time() + timeout while time.time() < end_time: resp = self.client.get_task_details(task_id) task = self.verify_resp(resp, "get task details", task_id) if (task.status.lower() == TaskStatus.FAILURE and desired_status != TaskStatus.FAILURE) or ( task.status.lower() == TaskStatus.SUCCESS and desired_status != TaskStatus.SUCCESS ): raise BuildErrorException( "Task with the uuid {0} entered the {1} status. Task " "responded with the message {2}".format(task.id_, task.status, task.message.replace("\\", "")) ) if task.status == desired_status: break time.sleep(interval_time) else: raise TimeoutException( "Task with the uuid {0} did not reach the {1} status within " "{2} seconds.".format(task_id, desired_status, timeout) ) if task is not None and task.type_ == TaskTypes.IMPORT and task.status.lower() == TaskStatus.SUCCESS: self.resources.add(task.result.image_id, self.client.delete_image) return task def create_task_with_transitions(self, input_, task_type, final_status=None): """ @summary: Create a task and verify that it transitions through the expected statuses @param input_: Image properties and location data @type input_: Dictionary @param task_type: Type of task @type task_type: String @param final_status: Flag to determine success or failure @type final_status: String @return: Task @rtype: Object """ if task_type == TaskTypes.IMPORT: resp = self.client.task_to_import_image(input_, TaskTypes.IMPORT) else: resp = self.client.task_to_export_image(input_, TaskTypes.EXPORT) task = self.verify_resp(resp, "create task") # Verify task progresses as expected verifier = StatusProgressionVerifier("task", task.id_, self.get_task_status, task.id_) if final_status == TaskStatus.SUCCESS: error_statuses = [TaskStatus.FAILURE] else: error_statuses = [TaskStatus.SUCCESS] verifier.add_state( expected_statuses=[TaskStatus.PENDING], acceptable_statuses=[TaskStatus.PROCESSING, final_status], error_statuses=error_statuses, timeout=self.config.task_timeout, poll_rate=1, ) if final_status == TaskStatus.SUCCESS: error_statuses = [TaskStatus.PENDING, TaskStatus.FAILURE] else: error_statuses = [TaskStatus.PENDING, TaskStatus.SUCCESS] verifier.add_state( expected_statuses=[TaskStatus.PROCESSING], acceptable_statuses=[final_status], error_statuses=error_statuses, timeout=self.config.task_timeout, poll_rate=1, ) if final_status == TaskStatus.SUCCESS: verifier.add_state( expected_statuses=[TaskStatus.SUCCESS], error_statuses=[TaskStatus.PENDING, TaskStatus.FAILURE], timeout=self.config.task_timeout, poll_rate=1, ) else: verifier.add_state( expected_statuses=[TaskStatus.FAILURE], error_statuses=[TaskStatus.PENDING, TaskStatus.SUCCESS], timeout=self.config.task_timeout, poll_rate=1, ) verifier.start() return self.client.get_task_details(task.id_).entity def get_task_status(self, task_id): """ @summary: Retrieve task status for the status progression verifier in the create_task_with_transitions method @param task_id: Task id @type task_id: UUID @return: Status @rtype: String """ resp = self.client.get_task_details(task_id) task = self.verify_resp(resp, "get task details", task_id) return task.status.lower() @staticmethod def verify_resp(resp, req, obj_id=None): """ @summary: Verify that a request was successful and that an entity was properly deserialized @param resp: Response object to verify @type resp: Object @return: Resp.entity @rtype: Object """ if not resp.ok: msg = "Request for {0} failed with the status code " "{1}.".format(req, resp.status_code) raise Exception(msg) if resp.entity is None: if obj_id is None: msg = "Response body for {0} did not deserialize as " "expected.".format(req) else: msg = "Response body for {0} with the uuid {1} did not " "deserialize as expected.".format(req, obj_id) raise Exception(msg) return resp.entity
class ImagesBehaviors(BaseBehavior): """@summary: Behaviors for Images""" def __init__(self, images_client, images_config): super(ImagesBehaviors, self).__init__() self.config = images_config self.client = images_client self.resources = ResourcePool() @staticmethod def read_data_file(file_path): """ @summary: Retrieve data file for a given file path @param file_path: Location of data file @type file_path: String @return: Test_data @rtype: String """ try: with open(file_path, "r") as DATA: test_data = DATA.read().rstrip() except IOError as file_error: raise file_error return test_data def create_image_via_task(self, image_properties=None, import_from=None): """ @summary: Create new image via task @param image_properties: Properties to use for the image creation @type image_properties: Dictionary @param import_from: Location of image @type import_from: String @return: Image @rtype: Object """ image_properties = image_properties or {'name': rand_name('image')} import_from = import_from or self.config.import_from input_ = { 'image_properties': image_properties, 'import_from': import_from } task = self.create_new_task(input_=input_, type_=TaskTypes.IMPORT) image_id = task.result.image_id self.client.add_image_tag(image_id=image_id, tag=rand_name('tag')) response = self.client.get_image_details(image_id=image_id) image = response.entity return image def create_images_via_task(self, image_properties=None, import_from=None, count=2): """ @summary: Create new images via tasks @param image_properties: Properties to use for the image creation @type image_properties: Dictionary @param import_from: Location of image @type import_from: String @param count: Number of images to create @type count: Integer @return: Image_list @rtype: List """ image_list = [] for i in range(count): image = self.create_image_via_task( image_properties=image_properties, import_from=import_from) image_list.append(image) return image_list def register_new_image(self, auto_disk_config=None, checksum=None, container_format=None, created_at=None, disk_format=None, file_=None, id_=None, image_type=None, min_disk=None, min_ram=None, name=None, os_type=None, owner=None, protected=None, schema=None, self_=None, size=None, status=None, tags=None, updated_at=None, user_id=None, visibility=None, additional_properties=None): """ @summary: Register new image and add it for deletion @param auto_disk_config: Auto disk config for the image being created @type auto_disk_config: String @param checksum: Checksum for the image being created @type checksum: String @param container_format: Container format for the image being created @type container_format: String @param created_at: Created at for the image being created @type created_at: Datetime @param disk_format: Disk format for the image being created @type disk_format: String @param file_: File location for the image being created @type file_: String @param id_: Id for the image being created @type id_: UUID @param image_type: Image type for the image being created @type image_type: String @param min_disk: Minimum disk for the image being created @type min_disk: String @param min_ram: Minimum ram for the image being created @type min_ram: String @param name: Name for the image being created @type name: String @param os_type: OS type for the image being created @type os_type: String @param owner: Owner for the image being created @type owner: String @param protected: Protected flag for the image being created @type protected: Boolean @param schema: Schema for the image being created @type schema: String @param self_: Self location for the image being created @type self_: String @param size: Size for the image being created @type size: String @param status: Status for the image being created @type status: String @param tags: Tags for the image being created @type tags: Dictionary @param updated_at: Updated at for the image being created @type updated_at: Datetime @param user_id: User id for the image being created @type user_id: String @param visibility: Visibility for the image being created @type visibility: String @param additional_properties: Additional properties for the image being created @type additional_properties: Dictionary @return: Image @rtype: Object """ container_format = container_format or ImageContainerFormat.BARE disk_format = disk_format or ImageDiskFormat.RAW name = name or rand_name('image') response = self.client.register_image( auto_disk_config=auto_disk_config, checksum=checksum, container_format=container_format, created_at=created_at, disk_format=disk_format, file_=file_, id_=id_, image_type=image_type, min_disk=min_disk, min_ram=min_ram, name=name, os_type=os_type, owner=owner, protected=protected, schema=schema, self_=self_, size=size, status=status, tags=tags, updated_at=updated_at, user_id=user_id, visibility=visibility, additional_properties=additional_properties) image = response.entity if image is not None: self.resources.add(image.id_, self.client.delete_image) return image def register_new_images(self, auto_disk_config=None, checksum=None, container_format=None, created_at=None, disk_format=None, file_=None, id_=None, image_type=None, min_disk=None, min_ram=None, name=None, os_type=None, owner=None, protected=None, schema=None, self_=None, size=None, status=None, tags=None, updated_at=None, user_id=None, visibility=None, additional_properties=None, count=2): """ @summary: Register new images and add them for deletion @param auto_disk_config: Auto disk config for the image being created @type auto_disk_config: String @param checksum: Checksum for the image being created @type checksum: String @param container_format: Container format for the image being created @type container_format: String @param created_at: Created at for the image being created @type created_at: Datetime @param disk_format: Disk format for the image being created @type disk_format: String @param file_: File location for the image being created @type file_: String @param id_: Id for the image being created @type id_: UUID @param image_type: Image type for the image being created @type image_type: String @param min_disk: Minimum disk for the image being created @type min_disk: String @param min_ram: Minimum ram for the image being created @type min_ram: String @param name: Name for the image being created @type name: String @param os_type: OS type for the image being created @type os_type: String @param owner: Owner for the image being created @type owner: String @param protected: Protected flag for the image being created @type protected: Boolean @param schema: Schema for the image being created @type schema: String @param self_: Self location for the image being created @type self_: String @param size: Size for the image being created @type size: String @param status: Status for the image being created @type status: String @param tags: Tags for the image being created @type tags: Dictionary @param updated_at: Updated at for the image being created @type updated_at: Datetime @param user_id: User id for the image being created @type user_id: String @param visibility: Visibility for the image being created @type visibility: String @param additional_properties: Additional properties for the image being created @type additional_properties: Dictionary @return: Image_list @rtype: List """ image_list = [] for i in range(count): image = self.register_new_image( auto_disk_config=auto_disk_config, checksum=checksum, container_format=container_format, created_at=created_at, disk_format=disk_format, file_=file_, id_=id_, image_type=image_type, min_disk=min_disk, min_ram=min_ram, name=name, os_type=os_type, owner=owner, protected=protected, schema=schema, self_=self_, size=size, status=status, tags=tags, updated_at=updated_at, user_id=user_id, visibility=visibility, additional_properties=additional_properties) image_list.append(image) return image_list def list_all_images(self, **params): """ @summary: Retrieve a complete list of images accounting for any query parameters @param params: Parameters to alter the returned list of images @type params: Dictionary @return: Image_list @rtype: List """ image_list = [] results_limit = self.config.results_limit response = self.client.list_images(params) images = response.entity while len(images) == results_limit: image_list += images marker = images[results_limit - 1].id_ params.update({"marker": marker}) response = self.client.list_images(params) images = response.entity image_list += images return image_list @staticmethod def get_time_delta(time_in_sec, time_property): """ @summary: Calculate the difference between an image attribute's time value and a time_property @param time_in_sec: Current time in seconds @type time_in_sec: Integer @param time_property: Image property containing a time @type time_property: Datetime @return: Time_delta @rtype: Integer """ time_property_in_sec = calendar.timegm(time_property.timetuple()) return abs(time_property_in_sec - time_in_sec) @staticmethod def validate_image(image): """ @summary: Generically validate an image contains crucial expected data @param image: Image to be validated @type image: Object @return: Errors @rtype: List """ id_regex = re.compile(ImageProperties.ID_REGEX) errors = [] # The following properties do not always have values: # checksum, container_format, disk_format, name, tags if image.auto_disk_config is None: errors.append( Messages.PROPERTY_MSG.format('auto_disk_config', 'not None', image.auto_disk_config)) if image.created_at is None: errors.append( Messages.PROPERTY_MSG.format('created_at', 'not None', image.created_at)) if image.file_ != '/v2/images/{0}/file'.format(image.id_): errors.append( Messages.PROPERTY_MSG.format( 'file', '/v2/images/{0}/file'.format(image.id_), image.file_)) if id_regex.match(image.id_) is None: errors.append( Messages.PROPERTY_MSG.format('id', 'not None', id_regex)) if image.image_type is None: errors.append( Messages.PROPERTY_MSG.format('image_type', 'not None', image.image_type)) if image.min_disk is None: errors.append( Messages.PROPERTY_MSG.format('min_disk', 'not None', image.min_disk)) if image.min_ram is None: errors.append( Messages.PROPERTY_MSG.format('min_ram', 'not None', image.min_ram)) if image.os_type is None: errors.append( Messages.PROPERTY_MSG.format('os_type', 'not None', image.os_type)) if image.owner is None: errors.append( Messages.PROPERTY_MSG.format('owner', 'not None', image.owner)) if image.protected is None: errors.append( Messages.PROPERTY_MSG.format('protected', 'not None', image.protected)) if image.schema != Schemas.IMAGE_SCHEMA: errors.append( Messages.PROPERTY_MSG.format('schema', Schemas.IMAGE_SCHEMA, image.schema)) if image.self_ != '/v2/images/{0}'.format(image.id_): errors.append( Messages.PROPERTY_MSG.format( 'self', '/v2/images/{0}'.format(image.id_), image.self_)) if image.status is None: errors.append( Messages.PROPERTY_MSG.format('status', 'not None', image.status)) if image.updated_at is None: errors.append( Messages.PROPERTY_MSG.format('updated_at', 'not None', image.updated_at)) if image.user_id is None: errors.append( Messages.PROPERTY_MSG.format('user_id', 'not None', image.user_id)) if image.visibility is None: errors.append( Messages.PROPERTY_MSG.format('visibility', 'not None', image.visibility)) return errors @staticmethod def validate_image_member(image_member): """ @summary: Generically validate an image member contains crucial expected data @param image_member: Image member to be validated @type image_member: Object @return: Errors @rtype: List """ errors = [] if image_member.created_at is None: errors.append( Messages.PROPERTY_MSG.format('created_at', 'not None', image_member.created_at)) if image_member.image_id is None: errors.append( Messages.PROPERTY_MSG.format('image_id', 'not None', image_member.image_id)) if image_member.member_id is None: errors.append( Messages.PROPERTY_MSG.format('member_id', 'not None', image_member.member_id)) if image_member.schema != Schemas.IMAGE_MEMBER_SCHEMA: errors.append( Messages.PROPERTY_MSG.format('schema', Schemas.IMAGE_MEMBER_SCHEMA, image_member.schema)) if image_member.status is None: errors.append( Messages.PROPERTY_MSG.format('status', 'not None', image_member.status)) if image_member.updated_at is None: errors.append( Messages.PROPERTY_MSG.format('updated_at', 'not None', image_member.updated_at)) return errors def wait_for_image_status(self, image_id, desired_status, interval_time=15, timeout=900): """ @summary: Wait for a image to reach a desired status @param image_id: Image id to evaluate @type image_id: UUID @param desired_status: Expected final status of image @type desired_status: String @param interval_time: Amount of time in seconds to wait between polling @type interval_time: Integer @param timeout: Amount of time in seconds to wait before aborting @type timeout: Integer @return: Resp @rtype: Object """ interval_time = interval_time or self.config.image_status_interval timeout = timeout or self.config.snapshot_timeout end_time = time.time() + timeout while time.time() < end_time: resp = self.client.get_image_details(image_id) image = resp.entity if image.status.lower() == ImageStatus.ERROR.lower(): raise BuildErrorException("Build failed. Image with uuid {0} " "entered ERROR status.".format( image.id)) if image.status == desired_status: break time.sleep(interval_time) else: raise TimeoutException( "wait_for_image_status ran for {0} seconds and did not " "observe image {1} reach the {2} status.".format( timeout, image_id, desired_status)) return resp def create_new_task(self, input_=None, type_=None): """ @summary: Create new task and wait for success status @param input_: Image properties and location data @type input_: Dictionary @param type_: Type of task @type type_: String @return: Task @rtype: Object """ import_from = self.config.import_from input_ = input_ or {'image_properties': {}, 'import_from': import_from} type_ = type_ or TaskTypes.IMPORT failures = [] attempts = self.config.resource_creation_attempts for attempt in range(attempts): try: resp = self.client.task_to_import_image(input_=input_, type_=type_) task_id = resp.entity.id_ task = self.wait_for_task_status(task_id, TaskStatus.SUCCESS) return task except (BuildErrorException, TimeoutException) as ex: failure = ('Attempt {0}: Failed to create task with ' 'the message ' '{1}'.format(attempt + 1, ex.message)) self._log.error(failure) failures.append(failure) raise RequiredResourceException( 'Failed to successfully create a task after {0} attempts: ' '{1}'.format(attempts, failures)) def create_new_tasks(self, input_=None, type_=None, count=2): """ @summary: Create new tasks and wait for success status for each @param input_: Image properties and image location @type input_: Dictionary @param type_: Type of task @type type_: String @param count: Number of tasks to create @type count: Integer @return: Task_list @rtype: List """ task_list = [] for i in range(count): task = self.create_new_task(input_=input_, type_=type_) task_list.append(task) return task_list def list_all_tasks(self, limit=None, marker=None, sort_dir=None, status=None, type_=None): """ @summary: Retrieve a complete list of tasks accounting for query parameters @param limit: Number of tasks to return @type limit: Integer @param marker: Task to start from @type marker: UUID @param sort_dir: Direction in which tasks will be returned @type sort_dir: String @param status: Status of tasks to return @type status: String @param type_: Type of tasks to return @type type_: String @return: Task_list @rtype: List """ task_list = [] results_limit = limit or self.config.results_limit response = self.client.list_tasks(limit=limit, marker=marker, sort_dir=sort_dir, status=status, type_=type_) tasks = response.entity while len(tasks) == results_limit: task_list += tasks marker = tasks[results_limit - 1].id_ response = self.client.list_tasks(limit=limit, marker=marker, sort_dir=sort_dir, status=status, type_=type_) tasks = response.entity task_list += tasks return task_list @staticmethod def validate_task(task): """ @summary: Generically validate a task contains crucial expected data @param task: Task to be validated @type task: UUID @return: Errors @rtype: List """ id_regex = re.compile(ImageProperties.ID_REGEX) errors = [] if task.status is None: errors.append( Messages.PROPERTY_MSG.format('status', 'not None', task.status)) if id_regex.match(task.id_) is None: errors.append( Messages.PROPERTY_MSG.format('id_', 'not None', id_regex.match(task.id_))) if task.created_at is None: errors.append( Messages.PROPERTY_MSG.format('created_at', 'not None', task.created_at)) if task.type_ == TaskTypes.IMPORT: if task.input_.import_from is None: errors.append( Messages.PROPERTY_MSG.format('import_from', 'not None', task.input_.import_from)) if (task.result is not None and id_regex.match(task.result.image_id) is None): errors.append( Messages.PROPERTY_MSG.format( 'image_id', 'not None', id_regex.match(task.result.image_id))) elif task.type_ == TaskTypes.EXPORT: if task.input_.image_uuid is None: errors.append( Messages.PROPERTY_MSG.format('image_uuid', 'not None', task.input_.image_uuid)) if task.input_.receiving_swift_container is None: errors.append( Messages.PROPERTY_MSG.format( 'receiving_swift_container', 'not None', task.input_.receiving_swift_container)) if task.result is not None and task.result.export_location is None: errors.append( Messages.PROPERTY_MSG.format('export_location', 'not None', task.result.export_location)) elif task.type_ is None: errors.append( Messages.PROPERTY_MSG.format('type_', 'not None', task.type_)) if task.updated_at is None: errors.append( Messages.PROPERTY_MSG.format('updated_at', 'not None', task.updated_at)) if task.self_ != '/v2/tasks/{0}'.format(task.id_): errors.append( Messages.PROPERTY_MSG.format('self_', '/v2/tasks/{0}'.format(task.id_), task.self_)) if task.owner is None: errors.append( Messages.PROPERTY_MSG.format('owner', 'not None', task.owner)) if task.message != '': errors.append( Messages.PROPERTY_MSG.format('message', '', task.message)) if task.schema != '/v2/schemas/task': errors.append( Messages.PROPERTY_MSG.format('schema', '/v2/schemas/task', task.schema)) return errors @staticmethod def validate_exported_files(expect_success, files, image_id): """ @summary: Validate that a given storage location contains a given file or not @param expect_success: Flag to determine if tasks completed successfully @type expect_success: Boolean @param files: File objects to be validated @type files: List @param image_id: Image id to validate against @type image_id: UUID @return: Errors, file_names @rtype: List, list """ errors = [] file_names = [file_.name for file_ in files] if expect_success: if '{0}.vhd'.format(image_id) not in file_names: errors.append('Unexpected file presence status.' 'Expected: True Received: False') else: if '{0}.vhd'.format(image_id) in file_names: errors.append('Unexpected file presence status. ' 'Expected: False Received: True') return errors, file_names def wait_for_task_status(self, task_id, desired_status, interval_time=10, timeout=1200): """ @summary: Waits for a task to reach a desired status @param task_id: Task id to evaluate @type task_id: UUID @param desired_status: Expected final status of task @type desired_status: String @param interval_time: Amount of time in seconds to wait between polling @type interval_time: Integer @param timeout: Amount of time in seconds to wait before aborting @type timeout: Integer @return: Task @rtype: Object """ interval_time = interval_time or self.config.task_status_interval timeout = timeout or self.config.task_timeout end_time = time.time() + timeout while time.time() < end_time: resp = self.client.get_task_details(task_id) task = resp.entity if ((task.status.lower() == TaskStatus.FAILURE and desired_status != TaskStatus.FAILURE) or (task.status.lower() == TaskStatus.SUCCESS and desired_status != TaskStatus.SUCCESS)): raise BuildErrorException( 'Task with uuid {0} entered {1} status. Task responded ' 'with the message {2}'.format( task.id_, task.status, task.message.replace('\\', ''))) if task.status == desired_status: break time.sleep(interval_time) else: raise TimeoutException( 'Failed to reach the {0} status after {1} seconds for task ' 'with uuid {2}'.format(desired_status, timeout, task_id)) if (task is not None and task.type_ == TaskTypes.IMPORT and task.status.lower() == TaskStatus.SUCCESS): self.resources.add(task.result.image_id, self.client.delete_image) return task def create_task_with_transitions(self, input_, task_type, outcome, final_status=None): """ @summary: Create a task and verify that it transitions through the expected statuses @param input_: Image properties and location data @type input_: Dictionary @param task_type: Type of task @type task_type: String @param outcome: Expected final status @type outcome: Integer @param final_status: Flag to determine success or failure @type final_status: String @return: Task @rtype: Object """ response = self.client.create_task(input_=input_, type_=task_type) task = response.entity response = self.client.get_task_details(task.id_) task_status = response.entity.status.lower() # Verify task progresses as expected verifier = StatusProgressionVerifier('task', task.id_, task_status, task.id_) verifier.add_state( expected_statuses=[TaskStatus.PENDING], acceptable_statuses=[TaskStatus.PROCESSING, outcome], error_statuses=[TaskStatus.SUCCESS], timeout=self.config.task_timeout, poll_rate=1) verifier.add_state( expected_statuses=[TaskStatus.PROCESSING], acceptable_statuses=[outcome], error_statuses=[TaskStatus.PENDING, TaskStatus.SUCCESS], timeout=self.config.task_timeout, poll_rate=1) if final_status == TaskStatus.SUCCESS: verifier.add_state( expected_statuses=[TaskStatus.SUCCESS], error_statuses=[TaskStatus.PENDING, TaskStatus.FAILURE], timeout=self.config.task_timeout, poll_rate=1) if final_status == TaskStatus.FAILURE: verifier.add_state( expected_statuses=[TaskStatus.FAILURE], error_statuses=[TaskStatus.PENDING, TaskStatus.SUCCESS], timeout=self.config.task_timeout, poll_rate=1) verifier.start() return self.client.get_task_details(task.id_).entity
class ImagesBehaviors(BaseBehavior): """@summary: Behaviors class for images v2""" def __init__(self, images_client, images_config): super(ImagesBehaviors, self).__init__() self.config = images_config self.client = images_client self.resources = ResourcePool() self.error_msg = Messages.ERROR_MSG self.id_regex = re.compile(ImageProperties.ID_REGEX) def create_new_image(self, container_format=None, disk_format=None, name=None, protected=None, tags=None, visibility=None): """@summary: Create new image and add it for deletion""" if container_format is None: container_format = ImageContainerFormat.BARE if disk_format is None: disk_format = ImageDiskFormat.RAW if name is None: name = rand_name('image') response = self.client.create_image( container_format=container_format, disk_format=disk_format, name=name, protected=protected, tags=tags, visibility=visibility) image = response.entity if image is not None: self.resources.add(image.id_, self.client.delete_image) return image def create_new_images(self, container_format=None, disk_format=None, name=None, protected=None, tags=None, visibility=None, count=1): """@summary: Create new images and add them for deletion""" image_list = [] for i in range(count): image = self.create_new_image( container_format=container_format, disk_format=disk_format, name=name, protected=protected, tags=tags, visibility=visibility) image_list.append(image) return image_list def list_images_pagination(self, changes_since=None, checksum=None, container_format=None, disk_format=None, limit=None, marker=None, member_status=None, min_disk=None, min_ram=None, name=None, owner=None, protected=None, size_max=None, size_min=None, sort_dir=None, sort_key=None, status=None, visibility=None): """@summary: Get images accounting for pagination as needed""" image_list = [] results_limit = self.config.results_limit response = self.client.list_images( changes_since=changes_since, checksum=checksum, container_format=container_format, disk_format=disk_format, limit=limit, marker=marker, member_status=member_status, min_disk=min_disk, min_ram=min_ram, name=name, owner=owner, protected=protected, size_max=size_max, size_min=size_min, sort_dir=sort_dir, sort_key=sort_key, status=status, visibility=visibility) images = response.entity while len(images) == results_limit: image_list += images marker = images[results_limit - 1].id_ response = self.client.list_images( changes_since=changes_since, checksum=checksum, container_format=container_format, disk_format=disk_format, limit=limit, marker=marker, member_status=member_status, min_disk=min_disk, min_ram=min_ram, name=name, owner=owner, protected=protected, size_max=size_max, size_min=size_min, sort_dir=sort_dir, sort_key=sort_key, status=status, visibility=visibility) images = response.entity image_list += images return image_list def get_member_ids(self, image_id): """ @summary: Return a complete list of ids for all members for a given image id """ response = self.client.list_members(image_id) members = response.entity return [member.member_id for member in members] @staticmethod def get_creation_offset(image_creation_time_in_sec, time_property): """ @summary: Calculate and return the difference between the image creation time and a given image time_property """ time_property_in_sec = calendar.timegm(time_property.timetuple()) return abs(time_property_in_sec - image_creation_time_in_sec) def validate_image(self, image): """@summary: Generically validate an image contains crucial expected data """ errors = [] if image.created_at is None: errors.append(self.error_msg.format('created_at', not None, None)) if image.file_ != '/v2/images/{0}/file'.format(image.id_): errors.append(self.error_msg.format( 'file_', '/v2/images/{0}/file'.format(image.id_), image.file_)) if self.id_regex.match(image.id_) is None: errors.append(self.error_msg.format('id_', not None, None)) if image.min_disk is None: errors.append(self.error_msg.format('min_disk', not None, None)) if image.min_ram is None: errors.append(self.error_msg.format('min_ram', not None, None)) if image.protected is None: errors.append(self.error_msg.format('protected', not None, None)) if image.schema != Schemas.IMAGE_SCHEMA: errors.append(self.error_msg.format( 'schema', Schemas.IMAGE_SCHEMA, image.schema)) if image.self_ != '/v2/images/{0}'.format(image.id_): errors.append(self.error_msg.format( 'schema', '/v2/images/{0}'.format(image.id_), image.self_)) if image.status is None: errors.append(self.error_msg.format('status', not None, None)) if image.updated_at is None: errors.append(self.error_msg.format('updated_at', not None, None)) return errors def validate_image_member(self, image_id, image_member, member_id): """@summary: Generically validate an image member contains crucial expected data """ errors = [] if image_member.created_at is None: errors.append(self.error_msg.format('created_at', not None, None)) if image_member.image_id != image_id: errors.append(self.error_msg.format( 'image_id', image_id, image_member.image_id)) if image_member.member_id != member_id: errors.append(self.error_msg.format( 'member_id', member_id, image_member.member_id)) if image_member.schema != Schemas.IMAGE_MEMBER_SCHEMA: errors.append(self.error_msg.format( 'schema', Schemas.IMAGE_MEMBER_SCHEMA, image_member.schema)) if image_member.status is None: errors.append(self.error_msg.format('status', not None, None)) if image_member.updated_at is None: errors.append(self.error_msg.format('updated_at', not None, None)) return errors def wait_for_image_status(self, image_id, desired_status, interval_time=None, timeout=None): """ @summary: Waits for a image to reach a desired status @param image_id: The uuid of the image @type image_id: String @param desired_status: The desired final status of the image @type desired_status: String @param interval_time: The amount of time in seconds to wait between polling @type interval_time: Integer @param timeout: The amount of time in seconds to wait before aborting @type timeout: Integer @return: Response object containing response and the image domain object @rtype: requests.Response """ interval_time = interval_time or self.config.image_status_interval timeout = timeout or self.config.snapshot_timeout end_time = time.time() + timeout while time.time() < end_time: resp = self.client.get_image(image_id) image = resp.entity if image.status.lower() == ImageStatus.ERROR.lower(): raise BuildErrorException( "Build failed. Image with uuid {0} " "entered ERROR status.".format(image.id)) if image.status == desired_status: break time.sleep(interval_time) else: raise TimeoutException( "wait_for_image_status ran for {0} seconds and did not " "observe image {1} reach the {2} status.".format( timeout, image_id, desired_status)) return resp
class ImagesBehaviors(BaseBehavior): """@summary: Behaviors class for images v2""" def __init__(self, images_client, images_config): super(ImagesBehaviors, self).__init__() self.config = images_config self.client = images_client self.resources = ResourcePool() self.error_msg = Messages.ERROR_MSG self.id_regex = re.compile(ImageProperties.ID_REGEX) def create_image_via_task(self, image_properties=None, import_from=None, import_from_format=None): """ @summary: Create new image via the create new task method and add it for deletion """ image_properties = image_properties or {'name': rand_name('image')} import_from = import_from or self.config.import_from import_from_format = import_from or self.config.import_from_format input_ = {'image_properties': image_properties, 'import_from': import_from, 'import_from_format': import_from_format} task = self.create_new_task(input_=input_, type_=TaskTypes.IMPORT) image_id = task.result.image_id response = self.client.get_image(image_id=image_id) image = response.entity if image is not None: self.resources.add(image.id_, self.client.delete_image) return image def create_images_via_task(self, image_properties=None, import_from=None, import_from_format=None, count=1): """ @summary: Create new images via the create new task method and add them for deletion """ image_list = [] for i in range(count): image = self.create_image_via_task( image_properties=image_properties, import_from=import_from, import_from_format=import_from_format) image_list.append(image) return image_list def create_new_image(self, container_format=None, disk_format=None, name=None, protected=None, tags=None): """@summary: Create new image and add it for deletion""" container_format = container_format or ImageContainerFormat.BARE disk_format = disk_format or ImageDiskFormat.RAW name = name or rand_name('image') response = self.client.create_image( container_format=container_format, disk_format=disk_format, name=name, protected=protected, tags=tags) image = response.entity if image is not None: self.resources.add(image.id_, self.client.delete_image) return image def create_new_images(self, container_format=None, disk_format=None, name=None, protected=None, tags=None, count=1): """@summary: Create new images and add them for deletion""" image_list = [] for i in range(count): image = self.create_new_image( container_format=container_format, disk_format=disk_format, name=name, protected=protected, tags=tags) image_list.append(image) return image_list def list_images_pagination(self, **filters): """ @summary: Get images accounting for pagination as needed with variable number of filter arguments """ image_list = [] results_limit = self.config.results_limit response = self.client.list_images(filters=filters) images = response.entity while len(images) == results_limit: image_list += images marker = images[results_limit - 1].id_ filters.update({"marker": marker}) response = self.client.list_images(filters=filters) images = response.entity image_list += images return image_list def get_member_ids(self, image_id): """ @summary: Return a complete list of ids for all members for a given image id """ response = self.client.list_members(image_id) members = response.entity return [member.member_id for member in members] @staticmethod def get_creation_delta(image_creation_time_in_sec, time_property): """ @summary: Calculate and return the difference between the image creation time and a given image time_property """ time_property_in_sec = calendar.timegm(time_property.timetuple()) return abs(time_property_in_sec - image_creation_time_in_sec) def validate_image(self, image): """@summary: Generically validate an image contains crucial expected data """ errors = [] if image.auto_disk_config is None: errors.append(self.error_msg.format( 'auto_disk_config', 'not None', image.auto_disk_config)) if image.created_at is None: errors.append(self.error_msg.format( 'created_at', 'not None', image.created_at)) if image.file_ != '/v2/images/{0}/file'.format(image.id_): errors.append(self.error_msg.format( 'file_', '/v2/images/{0}/file'.format(image.id_), image.file_)) if image.image_type is None: errors.append(self.error_msg.format( 'image_type', 'not None', image.image_type)) if self.id_regex.match(image.id_) is None: errors.append(self.error_msg.format( 'id_', 'not None', self.id_regex)) if image.min_disk is None: errors.append(self.error_msg.format( 'min_disk', 'not None', image.min_disk)) if image.min_ram is None: errors.append(self.error_msg.format( 'min_ram', 'not None', image.min_ram)) if image.os_type is None: errors.append(self.error_msg.format( 'os_type', 'not None', image.os_type)) if image.protected is None: errors.append(self.error_msg.format( 'protected', 'not None', image.protected)) if image.schema != Schemas.IMAGE_SCHEMA: errors.append(self.error_msg.format( 'schema', Schemas.IMAGE_SCHEMA, image.schema)) if image.self_ != '/v2/images/{0}'.format(image.id_): errors.append(self.error_msg.format( 'schema', '/v2/images/{0}'.format(image.id_), image.self_)) if image.status is None: errors.append(self.error_msg.format( 'status', 'not None', image.status)) if image.updated_at is None: errors.append(self.error_msg.format( 'updated_at', 'not None', image.updated_at)) if image.user_id is None: errors.append(self.error_msg.format( 'user_id', 'not None', image.user_id)) return errors def validate_image_member(self, image_id, image_member, member_id): """@summary: Generically validate an image member contains crucial expected data """ errors = [] if image_member.created_at is None: errors.append(self.error_msg.format( 'created_at', 'not None', image_member.created_at)) if image_member.image_id != image_id: errors.append(self.error_msg.format( 'image_id', image_id, image_member.image_id)) if image_member.member_id != member_id: errors.append(self.error_msg.format( 'member_id', member_id, image_member.member_id)) if image_member.schema != Schemas.IMAGE_MEMBER_SCHEMA: errors.append(self.error_msg.format( 'schema', Schemas.IMAGE_MEMBER_SCHEMA, image_member.schema)) if image_member.status is None: errors.append(self.error_msg.format( 'status', 'not None', image_member.status)) if image_member.updated_at is None: errors.append(self.error_msg.format( 'updated_at', 'not None', image_member.updated_at)) return errors def wait_for_image_status(self, image_id, desired_status, interval_time=None, timeout=None): """ @summary: Waits for a image to reach a desired status @param image_id: The uuid of the image @type image_id: String @param desired_status: The desired final status of the image @type desired_status: String @param interval_time: The amount of time in seconds to wait between polling @type interval_time: Integer @param timeout: The amount of time in seconds to wait before aborting @type timeout: Integer @return: Response object containing response and the image domain object @rtype: requests.Response """ interval_time = interval_time or self.config.image_status_interval timeout = timeout or self.config.snapshot_timeout end_time = time.time() + timeout while time.time() < end_time: resp = self.client.get_image(image_id) image = resp.entity if image.status.lower() == ImageStatus.ERROR.lower(): raise BuildErrorException( "Build failed. Image with uuid {0} " "entered ERROR status.".format(image.id)) if image.status == desired_status: break time.sleep(interval_time) else: raise TimeoutException( "wait_for_image_status ran for {0} seconds and did not " "observe image {1} reach the {2} status.".format( timeout, image_id, desired_status)) return resp def create_new_task(self, input_=None, type_=None): """@summary: Create new task and wait for success status""" import_from = self.config.import_from import_from_format = self.config.import_from_format input_ = input_ or {'image_properties': {}, 'import_from': import_from, 'import_from_format': import_from_format} type_ = type_ or TaskTypes.IMPORT failures = [] attempts = self.config.resource_creation_attempts for attempt in range(attempts): try: response = self.client.create_task(input_=input_, type_=type_) task_id = response.entity.id_ task = self.wait_for_task_status(task_id, TaskStatus.SUCCESS) return task except (TimeoutException, BuildErrorException) as ex: self._log.error('Failed to create task with uuid {0}: ' '{1}'.format(task_id, ex.message)) failures.append(ex.message) raise RequiredResourceException( 'Failed to successfully create a task after {0} attempts: ' '{1}'.format(attempts, failures)) def create_new_tasks(self, input_=None, type_=None, count=1): """@summary: Create new tasks and wait for success status for each""" task_list = [] for i in range(count): task = self.create_new_task(input_=input_, type_=type_) task_list.append(task) return task_list def list_tasks_pagination(self, limit=None, marker=None, sort_dir=None, status=None, type_=None): """@summary: Get tasks accounting for pagination as needed""" task_list = [] results_limit = limit or self.config.results_limit response = self.client.list_tasks( limit=limit, marker=marker, sort_dir=sort_dir, status=status, type_=type_) tasks = response.entity while len(tasks) == results_limit: task_list += tasks marker = tasks[results_limit - 1].id_ response = self.client.list_tasks( limit=limit, marker=marker, sort_dir=sort_dir, status=status, type_=type_) tasks = response.entity task_list += tasks return task_list def validate_task(self, task): """@summary: Generically validate a task contains crucial expected data """ errors = [] if task.status is None: errors.append(self.error_msg.format( 'status', 'not None', task.status)) if self.id_regex.match(task.id_) is None: errors.append(self.error_msg.format( 'id_', 'not None', self.id_regex.match(task.id_))) if task.created_at is None: errors.append(self.error_msg.format( 'created_at', 'not None', task.created_at)) if task.type_ == TaskTypes.IMPORT: if task.input_.import_from is None: errors.append(self.error_msg.format( 'import_from', 'not None', task.input_.import_from)) if (task.result is not None and self.id_regex.match(task.result.image_id) is None): errors.append(self.error_msg.format( 'image_id', 'not None', self.id_regex.match(task.result.image_id))) elif task.type_ == TaskTypes.EXPORT: if task.input_.image_uuid is None: errors.append(self.error_msg.format( 'image_uuid', 'not None', task.input_.image_uuid)) if task.input_.receiving_swift_container is None: errors.append(self.error_msg.format( 'receiving_swift_container', 'not None', task.input_.receiving_swift_container)) if task.result is not None and task.result.export_location is None: errors.append(self.error_msg.format( 'export_location', 'not None', task.result.export_location)) elif task.type_ is None: errors.append(self.error_msg.format( 'type_', 'not None', task.type_)) if task.updated_at is None: errors.append(self.error_msg.format( 'updated_at', 'not None', task.updated_at)) if task.self_ != '/v2/tasks/{0}'.format(task.id_): errors.append(self.error_msg.format( 'self_', '/v2/tasks/{0}'.format(task.id_), task.self_)) if task.owner is None: errors.append(self.error_msg.format( 'owner', 'not None', task.owner)) if task.message != 'None': errors.append(self.error_msg.format( 'message', 'None', task.message)) if task.schema != '/v2/schemas/task': errors.append(self.error_msg.format( 'schema', '/v2/schemas/task', task.schema)) return errors def validate_exported_files(self, export_to, expect_success, files, image_id): """ @summary: Validate that a given cloud files location contains a given file or not """ errors = [] file_names = [file_.name for file_ in files] if expect_success: if '{0}.vhd'.format(image_id) not in file_names: errors.append(self.error_msg.format( 'file present', True, False)) else: if '{0}.vhd'.format(image_id) in file_names: errors.append(self.error_msg.format( 'file present', False, True)) return errors, file_names def wait_for_task_status(self, task_id, desired_status, interval_time=None, timeout=None): """@summary: Waits for a task to reach a desired status""" interval_time = interval_time or self.config.task_status_interval timeout = timeout or self.config.task_timeout end_time = time.time() + timeout while time.time() < end_time: resp = self.client.get_task(task_id) task = resp.entity if ((task.status.lower() == TaskStatus.FAILURE and desired_status != TaskStatus.FAILURE) or (task.status.lower() == TaskStatus.SUCCESS and desired_status != TaskStatus.SUCCESS)): raise BuildErrorException( 'Task with uuid {0} entered {1} status. Task responded ' 'with the message {2}'.format( task.id_, task.status, task.message)) if task.status == desired_status: break time.sleep(interval_time) else: raise TimeoutException( 'Failed to reach the {0} status after {1} seconds for task ' 'with uuid {2}'.format(desired_status, timeout, task_id)) if (task is not None and task.type_ == TaskTypes.IMPORT and task.status.lower() == TaskStatus.SUCCESS): self.resources.add(task.result.image_id, self.client.delete_image) return task def get_task_status(self, task_id): """@summary: Retrieve task status""" response = self.client.get_task(task_id) return response.entity.status.lower() def create_task_with_transitions(self, input_, task_type, final_status=None): """ @summary: Create a task and verify that it transitions through the expected statuses """ response = self.client.create_task( input_=input_, type_=task_type) task = response.entity # Verify task progresses as expected verifier = StatusProgressionVerifier( 'task', task.id_, self.get_task_status, task.id_) verifier.add_state( expected_statuses=[TaskStatus.PENDING], acceptable_statuses=[TaskStatus.PROCESSING, TaskStatus.SUCCESS], error_statuses=[TaskStatus.FAILURE], timeout=self.config.task_timeout, poll_rate=1) verifier.add_state( expected_statuses=[TaskStatus.PROCESSING], acceptable_statuses=[TaskStatus.SUCCESS], error_statuses=[TaskStatus.FAILURE], timeout=self.config.task_timeout, poll_rate=1) if final_status == TaskStatus.SUCCESS: verifier.add_state( expected_statuses=[TaskStatus.SUCCESS], error_statuses=[TaskStatus.FAILURE], timeout=self.config.task_timeout, poll_rate=1) verifier.start() response = self.client.get_task(task.id_) return response.entity
class ImagesBehaviors(BaseBehavior): """@summary: Behaviors for Images""" def __init__(self, images_client, images_config): super(ImagesBehaviors, self).__init__() self.config = images_config self.client = images_client self.resources = ResourcePool() @staticmethod def get_comparison_data(data_file): """ @summary: Create comparison dictionary based on a given set of data @param data_file: File containing data to compare @type data_file: String @return: Comparison_dict @rtype: Dictionary """ comparison_dict = dict() data_columns = [] with open(data_file, 'r') as DATA: all_data = DATA.readlines() for line in all_data: # Skip any comments or short lines if line.startswith('#') or len(line) < 5: continue # Get the defined data if line.startswith('+'): line = line.replace('+', '') data_columns = [x.strip().lower() for x in line.split('|')] continue # Process the data each_data = dict() data = [x.strip() for x in line.split('|')] for x, y in zip(data_columns[1:], data[1:]): each_data[x] = y comparison_dict[data[0]] = each_data return comparison_dict def create_image_via_task(self, image_properties=None, import_from=None): """ @summary: Create new image via task @param image_properties: Properties to use for the image creation @type image_properties: Dictionary @param import_from: Location of image @type import_from: String @return: Image @rtype: Object """ image_properties = image_properties or {'name': rand_name('image')} import_from = import_from or self.config.import_from input_ = { 'image_properties': image_properties, 'import_from': import_from } task = self.create_new_task(input_=input_, type_=TaskTypes.IMPORT) image_id = task.result.image_id self.client.add_image_tag(image_id=image_id, tag=rand_name('tag')) resp = self.client.get_image_details(image_id=image_id) image = self.verify_resp(resp, 'get image details', image_id) return image def create_images_via_task(self, image_properties=None, import_from=None, count=2): """ @summary: Create new images via tasks @param image_properties: Properties to use for the image creation @type image_properties: Dictionary @param import_from: Location of image @type import_from: String @param count: Number of images to create @type count: Integer @return: Image_list @rtype: List """ image_list = [] for i in range(count): image = self.create_image_via_task( image_properties=image_properties, import_from=import_from) image_list.append(image) return image_list def register_new_image(self, auto_disk_config=None, checksum=None, container_format=None, created_at=None, disk_format=None, file_=None, id_=None, image_type=None, min_disk=None, min_ram=None, name=None, os_type=None, owner=None, protected=None, schema=None, self_=None, size=None, status=None, tags=None, updated_at=None, user_id=None, visibility=None, additional_properties=None): """ @summary: Register new image and add it for deletion @param auto_disk_config: Auto disk config for the image being created @type auto_disk_config: String @param checksum: Checksum for the image being created @type checksum: String @param container_format: Container format for the image being created @type container_format: String @param created_at: Created at for the image being created @type created_at: Datetime @param disk_format: Disk format for the image being created @type disk_format: String @param file_: File location for the image being created @type file_: String @param id_: Id for the image being created @type id_: UUID @param image_type: Image type for the image being created @type image_type: String @param min_disk: Minimum disk for the image being created @type min_disk: String @param min_ram: Minimum ram for the image being created @type min_ram: String @param name: Name for the image being created @type name: String @param os_type: OS type for the image being created @type os_type: String @param owner: Owner for the image being created @type owner: String @param protected: Protected flag for the image being created @type protected: Boolean @param schema: Schema for the image being created @type schema: String @param self_: Self location for the image being created @type self_: String @param size: Size for the image being created @type size: String @param status: Status for the image being created @type status: String @param tags: Tags for the image being created @type tags: Dictionary @param updated_at: Updated at for the image being created @type updated_at: Datetime @param user_id: User id for the image being created @type user_id: String @param visibility: Visibility for the image being created @type visibility: String @param additional_properties: Additional properties for the image being created @type additional_properties: Dictionary @return: Image @rtype: Object """ container_format = container_format or ImageContainerFormat.BARE disk_format = disk_format or ImageDiskFormat.RAW name = name or rand_name('image') resp = self.client.register_image( auto_disk_config=auto_disk_config, checksum=checksum, container_format=container_format, created_at=created_at, disk_format=disk_format, file_=file_, id_=id_, image_type=image_type, min_disk=min_disk, min_ram=min_ram, name=name, os_type=os_type, owner=owner, protected=protected, schema=schema, self_=self_, size=size, status=status, tags=tags, updated_at=updated_at, user_id=user_id, visibility=visibility, additional_properties=additional_properties) image = self.verify_resp(resp, 'register image') self.resources.add(image.id_, self.client.delete_image) return image def register_new_images(self, auto_disk_config=None, checksum=None, container_format=None, created_at=None, disk_format=None, file_=None, id_=None, image_type=None, min_disk=None, min_ram=None, name=None, os_type=None, owner=None, protected=None, schema=None, self_=None, size=None, status=None, tags=None, updated_at=None, user_id=None, visibility=None, additional_properties=None, count=2): """ @summary: Register new images and add them for deletion @param auto_disk_config: Auto disk config for the image being created @type auto_disk_config: String @param checksum: Checksum for the image being created @type checksum: String @param container_format: Container format for the image being created @type container_format: String @param created_at: Created at for the image being created @type created_at: Datetime @param disk_format: Disk format for the image being created @type disk_format: String @param file_: File location for the image being created @type file_: String @param id_: Id for the image being created @type id_: UUID @param image_type: Image type for the image being created @type image_type: String @param min_disk: Minimum disk for the image being created @type min_disk: String @param min_ram: Minimum ram for the image being created @type min_ram: String @param name: Name for the image being created @type name: String @param os_type: OS type for the image being created @type os_type: String @param owner: Owner for the image being created @type owner: String @param protected: Protected flag for the image being created @type protected: Boolean @param schema: Schema for the image being created @type schema: String @param self_: Self location for the image being created @type self_: String @param size: Size for the image being created @type size: String @param status: Status for the image being created @type status: String @param tags: Tags for the image being created @type tags: Dictionary @param updated_at: Updated at for the image being created @type updated_at: Datetime @param user_id: User id for the image being created @type user_id: String @param visibility: Visibility for the image being created @type visibility: String @param additional_properties: Additional properties for the image being created @type additional_properties: Dictionary @return: Image_list @rtype: List """ image_list = [] for i in range(count): image = self.register_new_image( auto_disk_config=auto_disk_config, checksum=checksum, container_format=container_format, created_at=created_at, disk_format=disk_format, file_=file_, id_=id_, image_type=image_type, min_disk=min_disk, min_ram=min_ram, name=name, os_type=os_type, owner=owner, protected=protected, schema=schema, self_=self_, size=size, status=status, tags=tags, updated_at=updated_at, user_id=user_id, visibility=visibility, additional_properties=additional_properties) image_list.append(image) return image_list def list_all_images(self, url_addition=None, **params): """ @summary: Retrieve a complete list of images accounting for any query parameters @param url_addition: Additional text to be added to the end of the url to account for duplicate sort parameters @type url_addition: String @param params: Parameters to alter the returned list of images @type params: Dictionary @return: Image_list @rtype: List """ image_list = [] results_limit = self.config.results_limit params.update({'limit': results_limit}) resp = self.client.list_images(params, url_addition) images = self.verify_resp(resp, 'list images') while len(images) == results_limit: image_list += images marker = images[results_limit - 1].id_ params.update({'marker': marker}) resp = self.client.list_images(params, url_addition) images = self.verify_resp(resp, 'list images') image_list += images return image_list @staticmethod def get_time_delta(time_in_sec, time_property): """ @summary: Calculate the difference between an image attribute's time value and a time_property @param time_in_sec: Current time in seconds @type time_in_sec: Integer @param time_property: Image property containing a time @type time_property: Datetime @return: Time_delta @rtype: Integer """ time_property_in_sec = calendar.timegm(time_property.timetuple()) return abs(time_property_in_sec - time_in_sec) @staticmethod def validate_image(image): """ @summary: Generically validate an image contains crucial expected data @param image: Image to be validated @type image: Object @return: Errors @rtype: List """ id_regex = re.compile(ImageProperties.ID_REGEX) errors = [] # The following properties do not always have values: # checksum, container_format, disk_format, name, tags if image.auto_disk_config is None: errors.append( Messages.PROPERTY_MSG.format('auto_disk_config', 'not None', image.auto_disk_config)) if image.created_at is None: errors.append( Messages.PROPERTY_MSG.format('created_at', 'not None', image.created_at)) if image.file_ != '/v2/images/{0}/file'.format(image.id_): errors.append( Messages.PROPERTY_MSG.format( 'file', '/v2/images/{0}/file'.format(image.id_), image.file_)) if id_regex.match(image.id_) is None: errors.append( Messages.PROPERTY_MSG.format('id', 'not None', id_regex)) if image.image_type is None: errors.append( Messages.PROPERTY_MSG.format('image_type', 'not None', image.image_type)) if image.min_disk is None: errors.append( Messages.PROPERTY_MSG.format('min_disk', 'not None', image.min_disk)) if image.min_ram is None: errors.append( Messages.PROPERTY_MSG.format('min_ram', 'not None', image.min_ram)) if image.os_type is None: errors.append( Messages.PROPERTY_MSG.format('os_type', 'not None', image.os_type)) if image.owner is None: errors.append( Messages.PROPERTY_MSG.format('owner', 'not None', image.owner)) if image.protected is None: errors.append( Messages.PROPERTY_MSG.format('protected', 'not None', image.protected)) if image.schema != Schemas.IMAGE_SCHEMA: errors.append( Messages.PROPERTY_MSG.format('schema', Schemas.IMAGE_SCHEMA, image.schema)) if image.self_ != '/v2/images/{0}'.format(image.id_): errors.append( Messages.PROPERTY_MSG.format( 'self', '/v2/images/{0}'.format(image.id_), image.self_)) if image.status is None: errors.append( Messages.PROPERTY_MSG.format('status', 'not None', image.status)) if image.updated_at is None: errors.append( Messages.PROPERTY_MSG.format('updated_at', 'not None', image.updated_at)) if image.user_id is None: errors.append( Messages.PROPERTY_MSG.format('user_id', 'not None', image.user_id)) if image.virtual_size is not None: errors.append( Messages.PROPERTY_MSG.format('virtual_size', 'None', image.virtual_size)) if image.visibility is None: errors.append( Messages.PROPERTY_MSG.format('visibility', 'not None', image.visibility)) return errors @staticmethod def validate_image_member(image_member): """ @summary: Generically validate an image member contains crucial expected data @param image_member: Image member to be validated @type image_member: Object @return: Errors @rtype: List """ errors = [] if image_member.created_at is None: errors.append( Messages.PROPERTY_MSG.format('created_at', 'not None', image_member.created_at)) if image_member.image_id is None: errors.append( Messages.PROPERTY_MSG.format('image_id', 'not None', image_member.image_id)) if image_member.member_id is None: errors.append( Messages.PROPERTY_MSG.format('member_id', 'not None', image_member.member_id)) if image_member.schema != Schemas.IMAGE_MEMBER_SCHEMA: errors.append( Messages.PROPERTY_MSG.format('schema', Schemas.IMAGE_MEMBER_SCHEMA, image_member.schema)) if image_member.status is None: errors.append( Messages.PROPERTY_MSG.format('status', 'not None', image_member.status)) if image_member.updated_at is None: errors.append( Messages.PROPERTY_MSG.format('updated_at', 'not None', image_member.updated_at)) return errors def wait_for_image_status(self, image_id, desired_status, interval_time=15, timeout=900): """ @summary: Wait for a image to reach a desired status @param image_id: Image id to evaluate @type image_id: UUID @param desired_status: Expected final status of image @type desired_status: String @param interval_time: Amount of time in seconds to wait between polling @type interval_time: Integer @param timeout: Amount of time in seconds to wait before aborting @type timeout: Integer @return: Resp @rtype: Object """ interval_time = interval_time or self.config.image_status_interval timeout = timeout or self.config.snapshot_timeout end_time = time.time() + timeout while time.time() < end_time: resp = self.client.get_image_details(image_id) image = self.verify_resp(resp, 'get image details', image_id) if image.status.lower() == ImageStatus.ERROR.lower(): raise BuildErrorException( 'Image with the uuid {0} entered ERROR ' 'status.'.format(image_id)) if image.status == desired_status: break time.sleep(interval_time) else: raise TimeoutException( 'Image with the uuid {0} did not reach the {1} status within ' '{2} seconds.'.format(image_id, desired_status, timeout)) return resp def create_new_task(self, input_=None, type_=None): """ @summary: Create new task and wait for success status @param input_: Image properties and location data @type input_: Dictionary @param type_: Type of task @type type_: String @return: Task @rtype: Object """ import_from = self.config.import_from input_ = input_ or {'image_properties': {}, 'import_from': import_from} type_ = type_ or TaskTypes.IMPORT failures = [] attempts = self.config.resource_creation_attempts for attempt in range(attempts): try: resp = self.client.task_to_import_image(input_=input_, type_=type_) task = resp.entity task = self.wait_for_task_status(task.id_, TaskStatus.SUCCESS) return task except (BuildErrorException, TimeoutException) as ex: failure = ('Attempt {0}: Failed to create task with ' 'the message {1}'.format(attempt + 1, ex.message)) self._log.error(failure) failures.append(failure) raise RequiredResourceException( 'Failed to successfully create a task after {0} attempts: ' '{1}'.format(attempts, failures)) def create_new_tasks(self, input_=None, type_=None, count=2): """ @summary: Create new tasks and wait for success status for each @param input_: Image properties and image location @type input_: Dictionary @param type_: Type of task @type type_: String @param count: Number of tasks to create @type count: Integer @return: Task_list @rtype: List """ task_list = [] for i in range(count): task = self.create_new_task(input_=input_, type_=type_) task_list.append(task) return task_list def list_all_tasks(self, **params): """ @summary: Retrieve a complete list of tasks accounting for query parameters @param params: Parameters to alter the returned list of images @type params: Dictionary @return: Task_list @rtype: List """ task_list = [] results_limit = self.config.results_limit params.update({'limit': results_limit}) resp = self.client.list_tasks(params) tasks = self.verify_resp(resp, 'list tasks') while len(tasks) == results_limit: task_list += tasks marker = tasks[results_limit - 1].id_ params.update({'marker': marker}) resp = self.client.list_tasks(params) tasks = self.verify_resp(resp, 'list tasks') task_list += tasks return task_list @staticmethod def validate_task(task): """ @summary: Generically validate a task contains crucial expected data @param task: Task to be validated @type task: UUID @return: Errors @rtype: List """ id_regex = re.compile(ImageProperties.ID_REGEX) errors = [] if task.status is None: errors.append( Messages.PROPERTY_MSG.format('status', 'not None', task.status)) if id_regex.match(task.id_) is None: errors.append( Messages.PROPERTY_MSG.format('id_', 'not None', id_regex.match(task.id_))) if task.created_at is None: errors.append( Messages.PROPERTY_MSG.format('created_at', 'not None', task.created_at)) if task.input_ is not None and task.input_.import_from is None: errors.append( Messages.PROPERTY_MSG.format('import_from', 'not None', task.input_.import_from)) if (task.result is not None and id_regex.match(task.result.image_id) is None): errors.append( Messages.PROPERTY_MSG.format( 'image_id', 'not None', id_regex.match(task.result.image_id))) elif task.type_ is None: errors.append( Messages.PROPERTY_MSG.format('type_', 'not None', task.type_)) if task.updated_at is None: errors.append( Messages.PROPERTY_MSG.format('updated_at', 'not None', task.updated_at)) if task.self_ != '/v2/tasks/{0}'.format(task.id_): errors.append( Messages.PROPERTY_MSG.format('self_', '/v2/tasks/{0}'.format(task.id_), task.self_)) if task.owner is None: errors.append( Messages.PROPERTY_MSG.format('owner', 'not None', task.owner)) if task.status == TaskStatus.SUCCESS and task.message != '': errors.append( Messages.PROPERTY_MSG.format('message', 'Empty message', task.message)) if task.schema != '/v2/schemas/task': errors.append( Messages.PROPERTY_MSG.format('schema', '/v2/schemas/task', task.schema)) return errors def wait_for_task_status(self, task_id, desired_status, interval_time=10, timeout=1200, response_entity_type=None): """ @summary: Waits for a task to reach a desired status and if the import task is successful, adds the created image to the resource pool for tear down @param task_id: Task id to evaluate @type task_id: UUID @param desired_status: Expected final status of task @type desired_status: String @param interval_time: Amount of time in seconds to wait between polling @type interval_time: Integer @param timeout: Amount of time in seconds to wait before aborting @type timeout: Integer @param response_entity_type: Response entity type to be passed on to python requests @type response_entity_type: Type @return: Task @rtype: Object """ interval_time = interval_time or self.config.task_status_interval timeout = timeout or self.config.task_timeout end_time = time.time() + timeout if not response_entity_type: response_entity_type = Task while time.time() < end_time: resp = self.client.get_task_details( task_id, response_entity_type=response_entity_type) task = self.verify_resp(resp, 'get task details', task_id) if ((task.status.lower() == TaskStatus.FAILURE and desired_status != TaskStatus.FAILURE) or (task.status.lower() == TaskStatus.SUCCESS and desired_status != TaskStatus.SUCCESS)): raise BuildErrorException( 'Task with the uuid {0} entered the {1} status. Task ' 'responded with the message {2}'.format( task.id_, task.status, task.message.replace('\\', ''))) if task.status == desired_status: break time.sleep(interval_time) else: raise TimeoutException( 'Task with the uuid {0} did not reach the {1} status within ' '{2} seconds.'.format(task_id, desired_status, timeout)) if (task is not None and task.type_ == TaskTypes.IMPORT and task.status.lower() == TaskStatus.SUCCESS): self.resources.add(task.result.image_id, self.client.delete_image) return task def create_task_with_transitions(self, create_task_resp, final_status=None, response_entity_type=None): """ @summary: Create a task and verify that it transitions through the expected statuses @param create_task_resp: Create task api response @type create_task_resp: Object @param final_status: Flag to determine success or failure @type final_status: String @param response_entity_type: Response entity type to be passed on to python requests @type response_entity_type: Type @return: Task @rtype: Object """ if not response_entity_type: response_entity_type = Task task = self.verify_resp(create_task_resp, 'create task') # Verify task progresses as expected verifier = StatusProgressionVerifier( 'task', task.id_, self.get_task_status, task.id_, response_entity_type=response_entity_type) if final_status == TaskStatus.SUCCESS: error_statuses = [TaskStatus.FAILURE] else: error_statuses = [TaskStatus.SUCCESS] verifier.add_state( expected_statuses=[TaskStatus.PENDING], acceptable_statuses=[TaskStatus.PROCESSING, final_status], error_statuses=error_statuses, timeout=self.config.task_timeout, poll_rate=1) if final_status == TaskStatus.SUCCESS: error_statuses = [TaskStatus.PENDING, TaskStatus.FAILURE] else: error_statuses = [TaskStatus.PENDING, TaskStatus.SUCCESS] verifier.add_state(expected_statuses=[TaskStatus.PROCESSING], acceptable_statuses=[final_status], error_statuses=error_statuses, timeout=self.config.task_timeout, poll_rate=1) if final_status == TaskStatus.SUCCESS: verifier.add_state( expected_statuses=[TaskStatus.SUCCESS], error_statuses=[TaskStatus.PENDING, TaskStatus.FAILURE], timeout=self.config.task_timeout, poll_rate=1) else: verifier.add_state( expected_statuses=[TaskStatus.FAILURE], error_statuses=[TaskStatus.PENDING, TaskStatus.SUCCESS], timeout=self.config.task_timeout, poll_rate=1) verifier.start() return self.client.get_task_details( task.id_, response_entity_type=response_entity_type).entity def get_task_status(self, task_id, response_entity_type=None): """ @summary: Retrieve task status for the status progression verifier in the create_task_with_transitions method @param task_id: Task id @type task_id: UUID @param response_entity_type: Response entity type to be passed on to python requests @type response_entity_type: Type @return: Status @rtype: String """ if not response_entity_type: response_entity_type = Task resp = self.client.get_task_details( task_id, response_entity_type=response_entity_type) task = self.verify_resp(resp, 'get task details', task_id) return task.status.lower() @staticmethod def verify_resp(resp, req, obj_id=None): """ @summary: Verify that a request was successful and that an entity was properly deserialized @param resp: Response object to verify @type resp: Object @return: Resp.entity @rtype: Object """ if not resp.ok: msg = ('Request for {0} failed with the status code ' '{1}.'.format(req, resp.status_code)) raise Exception(msg) if resp.entity is None: if obj_id is None: msg = ('Response body for {0} did not deserialize as ' 'expected.'.format(req)) else: msg = ('Response body for {0} with the uuid {1} did not ' 'deserialize as expected.'.format(req, obj_id)) raise Exception(msg) return resp.entity