Ejemplo n.º 1
0
    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
Ejemplo n.º 2
0
    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
Ejemplo n.º 3
0
    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)
Ejemplo n.º 4
0
    def create_entity(
        self,
        entity_type_id: str,
        entity: DefEntity,
        tenant_org_context: str = None,
        delete_status_from_payload=True,
        return_response_headers=False
    ) -> Union[dict, Tuple[dict, dict]]:  # noqa: E501
        """Create defined entity instance of an entity type.

        :param str entity_type_id: ID of the DefEntityType
        :param DefEntity entity: Defined entity instance
        :param bool delete_status_from_payload: should delete status from payload?  # noqa: E501
        :param bool return_response_headers: return response headers
        :return: created entity or created entity with response headers
        :rtype: Union[dict, Tuple[dict, dict]]
        """
        additional_request_headers = {}
        if tenant_org_context:
            additional_request_headers[
                'x-vmware-vcloud-tenant-context'] = tenant_org_context  # noqa: E501

        payload: dict = entity.to_dict()
        if delete_status_from_payload:
            payload.get('entity', {}).pop('status', None)

        return self._cloudapi_client.do_request(
            method=RequestMethod.POST,
            cloudapi_version=CloudApiVersion.VERSION_1_0_0,
            resource_url_relative_path=f"{CloudApiResource.ENTITY_TYPES}/"
            f"{entity_type_id}",
            payload=payload,
            additional_request_headers=additional_request_headers,
            return_response_headers=return_response_headers)
Ejemplo n.º 5
0
    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)
Ejemplo n.º 6
0
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
Ejemplo n.º 7
0
    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)
Ejemplo n.º 8
0
    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 create_entity(
        self,
        entity_type_id: str,
        entity: DefEntity,
        tenant_org_context: str = None,
        delete_status_from_payload=True,
        is_request_async=False
    ) -> Union[dict, Tuple[dict, dict]]:  # noqa: E501
        """Create defined entity instance of an entity type.

        :param str entity_type_id: ID of the DefEntityType
        :param DefEntity entity: Defined entity instance
        :param str tenant_org_context:
        :param bool delete_status_from_payload: should delete status from payload?  # noqa: E501
        :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: created entity or created entity with response headers
        :rtype: Union[dict, Tuple[dict, dict]]
        """
        additional_request_headers = {}
        if tenant_org_context:
            additional_request_headers[
                'x-vmware-vcloud-tenant-context'] = tenant_org_context  # noqa: E501

        payload: dict = entity.to_dict()
        if delete_status_from_payload:
            payload.get('entity', {}).pop('status', None)

        resource_url_relative_path = f"{CloudApiResource.ENTITY_TYPES}/{entity_type_id}"  # noqa: E501
        # response will be a tuple (response_body, response_header) if
        # is_request_async is true. Else, it will be just response_body
        response = self._cloudapi_client.do_request(
            method=RequestMethod.POST,
            cloudapi_version=CloudApiVersion.VERSION_1_0_0,
            resource_url_relative_path=resource_url_relative_path,
            payload=payload,
            additional_request_headers=additional_request_headers,
            return_response_headers=is_request_async)

        if is_request_async:
            # if request is async, return the location header as well
            # 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
            return response[0], response[1][HttpResponseHeader.LOCATION.value]
        return response
Ejemplo n.º 10
0
    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)
Ejemplo n.º 11
0
    def create_entity(self,
                      entity_type_id: str,
                      entity: DefEntity,
                      tenant_org_context: str = None) -> None:
        """Create defined entity instance of an entity type.

        :param str entity_type_id: ID of the DefEntityType
        :param DefEntity entity: Defined entity instance
        :return: None
        """
        additional_headers = {}
        if tenant_org_context:
            additional_headers[
                'x-vmware-vcloud-tenant-context'] = tenant_org_context  # noqa: E501
        self._cloudapi_client.do_request(
            method=RequestMethod.POST,
            cloudapi_version=CloudApiVersion.VERSION_1_0_0,
            resource_url_relative_path=f"{CloudApiResource.ENTITY_TYPES}/"
            f"{entity_type_id}",
            payload=entity.to_dict(),
            additional_headers=additional_headers)
    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