def list_entities_by_interface(self, vendor: str, nss: str, version: str): """List entities of a given interface. An interface is uniquely identified by properties vendor, nss and version. :param str vendor: Vendor of the interface :param str nss: nss of the interface :param str version: version of the interface :return: Generator of entities of that interface type :rtype: Generator[DefEntity, None, None] """ # TODO Yet to be verified. Waiting for the build from Extensibility # team. page_num = 0 while True: page_num += 1 response_body = self._cloudapi_client.do_request( method=RequestMethod.GET, cloudapi_version=CloudApiVersion.VERSION_1_0_0, resource_url_relative_path=f"{CloudApiResource.ENTITIES}/" f"{CloudApiResource.INTERFACES}/{vendor}/{nss}/{version}?" # noqa: E501 f"page={page_num}") if len(response_body['values']) == 0: break for entity in response_body['values']: yield DefEntity(**entity)
def resolve_entity(self, entity_id: str, entity_type_id: str = None) -> DefEntity: # noqa: E501 """Resolve the entity. Validates the entity against the schema. Based on the result, entity state will be either changed to "RESOLVED" (or) "RESOLUTION ERROR". :param str entity_id: Id of the entity :return: Defined entity with its state updated. :rtype: DefEntity """ if not entity_type_id: rde: DefEntity = self.get_entity(entity_id) entity_type_id = rde.entityType response_body = self._cloudapi_client.do_request( method=RequestMethod.POST, cloudapi_version=CloudApiVersion.VERSION_1_0_0, resource_url_relative_path=f"{CloudApiResource.ENTITIES}/" f"{entity_id}/{CloudApiResource.ENTITY_RESOLVE}") # noqa: E501 msg = response_body[def_constants.DEF_ERROR_MESSAGE_KEY] del response_body[def_constants.DEF_ERROR_MESSAGE_KEY] entity = DefEntity(entityType=entity_type_id, **response_body) # TODO: Just record the error message; revisit after HTTP response code # is good enough to decide if exception should be thrown or not if entity.state != def_constants.DEF_RESOLVED_STATE: LOGGER.error(msg) return entity
def list_entities_by_entity_type(self, vendor: str, nss: str, version: str, filters: dict = None) -> List[DefEntity]: """List entities of a given entity type. vCD's behavior when invalid filter keys are passed: * It will throw a 400 if invalid first-level filter keys are passed Valid keys : [name, id, externalId, entityType, entity, state]. * It will simply return empty list on passing any invalid nested properties. :param str vendor: Vendor of the entity type :param str nss: nss of the entity type :param str version: version of the entity type :param dict filters: Key-value pairs representing filter options :return: List of entities of that entity type :rtype: Generator[DefEntity, None, None] """ filter_string = utils.construct_filter_string(filters) page_num = 0 while True: page_num += 1 query_string = f"page={page_num}&sortAsc=name" if filter_string: query_string = f"filter={filter_string}&{query_string}" response_body = self._cloudapi_client.do_request( method=RequestMethod.GET, cloudapi_version=CloudApiVersion.VERSION_1_0_0, resource_url_relative_path=f"{CloudApiResource.ENTITIES}/" f"{CloudApiResource.ENTITY_TYPES_TOKEN}/" # noqa: E501 f"{vendor}/{nss}/{version}?{query_string}") # noqa: E501 if len(response_body['values']) == 0: break for entity in response_body['values']: yield DefEntity(**entity)
def get_entities_per_page_by_entity_type( self, vendor: str, nss: str, version: str, # noqa: E501 filters: dict = None, page_number: int = CSE_PAGINATION_FIRST_PAGE_NUMBER, # noqa: E501 page_size: int = CSE_PAGINATION_DEFAULT_PAGE_SIZE): # noqa: E501 """List all the entities per page and entity type. :param str vendor: entity type vendor name :param str nss: entity type namespace :param str version: entity type version :param dict filters: additional filters :param int page_number: page to return :param int page_size: number of records per page :rtype: Generator[(List[DefEntity], int), None, None] """ filter_string = utils.construct_filter_string(filters) query_string = f"page={page_number}&pageSize={page_size}" if filter_string: query_string = f"filter={filter_string}&{query_string}" response_body = self._cloudapi_client.do_request( method=RequestMethod.GET, cloudapi_version=CloudApiVersion.VERSION_1_0_0, resource_url_relative_path=f"{CloudApiResource.ENTITIES}/" f"{CloudApiResource.ENTITY_TYPES_TOKEN}/" # noqa: E501 f"{vendor}/{nss}/{version}?{query_string}") # noqa: E501 result = {} entity_list: list[DefEntity] = [] for v in response_body['values']: entity_list.append(DefEntity(**v)) result[PaginationKey.RESULT_TOTAL] = int(response_body['resultTotal']) result[PaginationKey.VALUES] = entity_list return result
def get_task_href(body): if type(body) != dict: return None if body.get('entity') is not None and \ body['entity'].get('status') is not None: rde = DefEntity(**body) return rde.entity.status.task_href return None
def get_entity(self, entity_id: str) -> DefEntity: """Get the defined entity given entity id. :param str entity_id: Id of the entity. :return: Details of the entity. :rtype: DefEntity """ response_body = self._cloudapi_client.do_request( method=RequestMethod.GET, cloudapi_version=CloudApiVersion.VERSION_1_0_0, resource_url_relative_path=f"{CloudApiResource.ENTITIES}/" f"{entity_id}") return DefEntity(**response_body)
def update_entity( self, entity_id: str, entity: DefEntity, invoke_hooks=False, return_response_headers=False ) -> Union[DefEntity, Tuple[DefEntity, dict]]: # noqa: E501 """Update entity instance. :param str entity_id: Id of the entity to be updated. :param DefEntity entity: Modified entity to be updated. :param bool invoke_hooks: Value indicating whether hook-based-behaviors need to be triggered or not. :param bool return_response_headers: return response headers :return: Updated entity or Updated entity and response headers :rtype: Union[DefEntity, Tuple[DefEntity, dict]] """ resource_url_relative_path = f"{CloudApiResource.ENTITIES}/{entity_id}" vcd_api_version = self._cloudapi_client.get_api_version() # TODO Float conversions must be changed to Semantic versioning. if float(vcd_api_version) >= float(ApiVersion.VERSION_36.value): resource_url_relative_path += f"?invokeHooks={str(invoke_hooks).lower()}" # noqa: E501 if return_response_headers: response_body, headers = self._cloudapi_client.do_request( method=RequestMethod.PUT, cloudapi_version=CloudApiVersion.VERSION_1_0_0, resource_url_relative_path=resource_url_relative_path, payload=entity.to_dict(), return_response_headers=return_response_headers) return DefEntity(**response_body), headers else: response_body = self._cloudapi_client.do_request( method=RequestMethod.PUT, cloudapi_version=CloudApiVersion.VERSION_1_0_0, resource_url_relative_path=resource_url_relative_path, payload=entity.to_dict()) return DefEntity(**response_body)
def update_entity(self, entity_id: str, entity: DefEntity) -> DefEntity: """Update entity instance. :param str entity_id: Id of the entity to be updated. :param DefEntity entity: Modified entity to be updated. :return: Updated entity :rtype: DefEntity """ response_body = self._cloudapi_client.do_request( method=RequestMethod.PUT, cloudapi_version=CloudApiVersion.VERSION_1_0_0, resource_url_relative_path=f"{CloudApiResource.ENTITIES}/" f"{entity_id}", payload=asdict(entity)) return DefEntity(**response_body)
def get_tkg_or_def_entity(self, entity_id: str) -> DefEntity: """Get the tkg or def entity given entity id. :param str entity_id: Id of the entity. :return: Details of the entity. :rtype: DefEntity """ response_body = self._cloudapi_client.do_request( method=RequestMethod.GET, cloudapi_version=CloudApiVersion.VERSION_1_0_0, resource_url_relative_path=f"{CloudApiResource.ENTITIES}/" f"{entity_id}") entity: dict = response_body['entity'] entity_kind: dict = entity['kind'] if entity_kind in [ shared_constants.ClusterEntityKind.NATIVE.value, shared_constants.ClusterEntityKind.TKG_PLUS.value ]: return DefEntity(**response_body) elif entity_kind == shared_constants.ClusterEntityKind.TKG_S.value: return TKGEntity(**entity) raise Exception("Invalid cluster kind.")
def update_entity( self, entity_id: str, entity: DefEntity, invoke_hooks=False, is_request_async=False ) -> Union[DefEntity, Tuple[DefEntity, dict]]: # noqa: E501 """Update entity instance. :param str entity_id: Id of the entity to be updated. :param DefEntity entity: Modified entity to be updated. :param bool invoke_hooks: Value indicating whether hook-based-behaviors need to be triggered or not. :param bool is_request_async: The request is intended to be asynchronous if this flag is set, href of the task is returned in addition to the response body :return: Updated entity or Updated entity and response headers :rtype: Union[DefEntity, Tuple[DefEntity, dict]] """ resource_url_relative_path = f"{CloudApiResource.ENTITIES}/{entity_id}" vcd_api_version = self._cloudapi_client.get_api_version() # TODO Float conversions must be changed to Semantic versioning. # TODO: Also include any persona having Administrator:FullControl # on CSE:nativeCluster if float(vcd_api_version) >= float(ApiVersion.VERSION_36.value) and \ self._cloudapi_client.is_sys_admin and not invoke_hooks: resource_url_relative_path += f"?invokeHooks={str(invoke_hooks).lower()}" # noqa: E501 payload: dict = entity.to_dict() # Prevent users with rights <= EDIT/VIEW on CSE:NATIVECLUSTER from # updating "private" property of RDE "status" section # TODO: Replace sys admin check with FULL CONTROL rights check on # CSE:NATIVECLUSTER. Users with no FULL CONTROL rights cannot update # private property of entity->status. if not self._cloudapi_client.is_sys_admin: payload.get('entity', {}).get('status', {}).pop('private', None) if is_request_async: # if request is async, return the task href in # x_vmware_vcloud_task_location header # TODO: Use the Http response status code to decide which # header name to use for task_href # 202 - location header, # 200 - xvcloud-task-location needs to be used response_body, headers = self._cloudapi_client.do_request( method=RequestMethod.PUT, cloudapi_version=CloudApiVersion.VERSION_1_0_0, resource_url_relative_path=resource_url_relative_path, payload=payload, return_response_headers=is_request_async) return DefEntity(**response_body), headers[ HttpResponseHeader.X_VMWARE_VCLOUD_TASK_LOCATION. value] # noqa: E501 else: response_body = self._cloudapi_client.do_request( method=RequestMethod.PUT, cloudapi_version=CloudApiVersion.VERSION_1_0_0, resource_url_relative_path=resource_url_relative_path, payload=payload) return DefEntity(**response_body)
def update_entity( self, entity_id: str, entity: DefEntity = None, invoke_hooks=False, is_request_async=False, changes: dict = None ) -> Union[DefEntity, Tuple[DefEntity, dict]]: # noqa: E501 """Update entity instance. :param str entity_id: Id of the entity to be updated. :param DefEntity entity: Modified entity to be updated. :param bool invoke_hooks: Value indicating whether hook-based-behaviors need to be triggered or not. :param bool is_request_async: The request is intended to be asynchronous if this flag is set, href of the task is returned in addition to the response body :param dict changes: dictionary of changes for the rde. The key indicates the field updated, e.g. 'entity.status'. The value indicates the value for this field. :return: Updated entity or Updated entity and response headers :rtype: Union[DefEntity, Tuple[DefEntity, dict]] The 'changes' parameter takes precedence over 'entity', and at least one of these fields is required. Also, 'changes' is required when api version >= 36.0; this allows getting the entity so that the current entity is not overwritten. etag usage is only supported for api version >= 36.0. """ if entity is None and changes is None: raise Exception("at least 'entity' or 'changes' must not be None") vcd_api_version = self._cloudapi_client.get_vcd_api_version() api_at_least_36: bool = vcd_api_version >= VCDApiVersion( vcd_client.ApiVersion.VERSION_36.value) # noqa: E501 if api_at_least_36 and changes is None: raise Exception("changes must be specified when client has api " "version >= 36.0") resource_url_relative_path = f"{CloudApiResource.ENTITIES}/{entity_id}" # TODO Float conversions must be changed to Semantic versioning. # TODO: Also include any persona having Administrator:FullControl # on CSE:nativeCluster if api_at_least_36 and self._cloudapi_client.is_sys_admin and not invoke_hooks: # noqa: E501 resource_url_relative_path += f"?invokeHooks={str(invoke_hooks).lower()}" # noqa: E501 for _ in range(server_constants.MAX_RDE_UPDATE_ATTEMPTS): # get entity etag = "" if changes: entity, etag = self._form_updated_entity(entity_id, changes) payload: dict = entity.to_dict() entity_kind = payload.get('entity', {}).get('kind', "") # Prevent users with rights <= EDIT/VIEW on CSE:NATIVECLUSTER from # updating "private" property of RDE "status" section # TODO: Replace sys admin check with FULL CONTROL rights check on # CSE:NATIVECLUSTER. Users with no FULL CONTROL rights cannot # update private property of entity->status. if not self._cloudapi_client.is_sys_admin: # HACK: TKGm uses the principle of least privileges and # does not use a ClusterAuthor role at all. Instead it only # uses a ClusterAdmin role which always has FC rights. # Hence it does not escalate to sys-admin and hence fails # this check. Hence skip TKGm clusters. # The correct fix for this is to implement the # to-do mentioned above. That is covered by VCDA-2969. if entity_kind != shared_constants.ClusterEntityKind.TKG_M.value: # noqa: E501 payload.get('entity', {}).get('status', {}).pop('private', None) # noqa: E501 # if request is async, return the task href in # x_vmware_vcloud_task_location header # TODO: Use the Http response status code to decide which # header name to use for task_href # 202 - location header, # 200 - xvcloud-task-location needs to be used try: additional_request_headers = { "If-Match": etag } if api_at_least_36 else None # noqa: E501 response_body, headers = self._cloudapi_client.do_request( method=RequestMethod.PUT, cloudapi_version=CloudApiVersion.VERSION_1_0_0, resource_url_relative_path=resource_url_relative_path, payload=payload, return_response_headers=True, additional_request_headers=additional_request_headers) def_entity = DefEntity(**response_body) if is_request_async: return def_entity, headers[ HttpResponseHeader.X_VMWARE_VCLOUD_TASK_LOCATION. value] # noqa: E501 return def_entity except Exception: last_cloudapi_response = self._cloudapi_client.get_last_response( ) # noqa: E501 if last_cloudapi_response.status_code == codes.precondition_failed: # noqa: E501 continue raise raise Exception( f"failed to update RDE after {server_constants.MAX_RDE_UPDATE_ATTEMPTS} attempts" ) # noqa: E501