Ejemplo n.º 1
0
    def __init__(self, client):
        self._client = client
        tkg_s_config = Configuration()
        tkg_s_config.host = f"{self._client.get_cloudapi_uri()}/1.0.0/"
        tkg_s_config.verify_ssl = self._client._verify_ssl_certs

        self._tkg_s_client = ApiClient(configuration=tkg_s_config)
        jwt_token = self._client.get_access_token()
        self._tkg_s_client.set_default_header(
            cli_constants.TKGRequestHeaderKey.AUTHORIZATION,
            f"Bearer {jwt_token}")

        # get highest supported api version
        api_version = self._client.get_api_version()
        supported_api_versions = self._client.get_supported_versions_list()
        max_api_version = utils.get_max_api_version(supported_api_versions)

        # api_version = self._client.get_api_version()
        # use api_version 37.0.0-alpha if VCD 10.3 is used.
        if VCDApiVersion(max_api_version) >= VCDApiVersion(
                ApiVersion.VERSION_36.value):  # noqa: E501
            api_version = '37.0.0-alpha'
        self._tkg_s_client.set_default_header(
            cli_constants.TKGRequestHeaderKey.ACCEPT,  # noqa: E501
            f"application/json;version={api_version}")  # noqa: E501
        self._tkg_s_client_api = TkgClusterApi(api_client=self._tkg_s_client)
Ejemplo n.º 2
0
    def __init__(self,
                 base_url: str,
                 token: str,
                 api_version: str,
                 logger_debug: logging.Logger,
                 logger_wire: logging.Logger,
                 verify_ssl: bool = True,
                 is_sys_admin: bool = False):
        if not base_url.endswith('/'):
            base_url += '/'
        self._base_url = base_url

        self._headers = {
            "Authorization": f"Bearer {token}",
            "Accept": f"application/json;version={api_version}",
            "Content-Type": "application/json"
        }

        self._verify_ssl = verify_ssl
        self.LOGGER = logger_debug
        self.LOGGER_WIRE = logger_wire
        self._last_response = None
        self.is_sys_admin = is_sys_admin
        self._api_version = api_version
        self._vcd_api_version = VCDApiVersion(api_version)
    def force_delete_entity(
        self,
        entity_id: str,
        is_request_async=False
    ) -> Union[dict, Tuple[dict, dict]]:  # noqa: E501
        """Delete the defined entity without invoking hooks.

        :param str entity_id: Id of the entity.
        :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: response body or response body and response headers
        :rtype: Union[dict, Tuple[dict, dict]]
        """
        # response will be a tuple (response_body, response_header) if
        # is_request_async is true. Else, it will be just response_body
        vcd_api_version = self._cloudapi_client.get_vcd_api_version()
        resource_url_relative_path = f"{CloudApiResource.ENTITIES}/{entity_id}"

        if vcd_api_version >= VCDApiVersion(
                vcd_client.ApiVersion.VERSION_36.value):  # noqa: E501
            resource_url_relative_path += "?invokeHooks=false"

        response = self._cloudapi_client.do_request(
            method=RequestMethod.DELETE,
            cloudapi_version=CloudApiVersion.VERSION_1_0_0,
            resource_url_relative_path=resource_url_relative_path,  # noqa: E501
            return_response_headers=is_request_async)
        if is_request_async:
            return response[0], response[1][HttpResponseHeader.LOCATION.value]
        return response
def get_runtime_rde_version_by_vcd_api_version(vcd_api_version: str) -> str:
    major_vcd_api_version = VCDApiVersion(vcd_api_version).major
    val = def_constants.MAP_VCD_API_VERSION_TO_RUNTIME_RDE_VERSION.get(
        major_vcd_api_version)  # noqa: E501
    if not val:
        val = '0.0.0'
    return val
def _raise_error_if_amqp_not_supported(
        is_mqtt_used,
        default_api_version,
        logger=NULL_LOGGER
):
    """Raise error if CSE is configured with AMQP but AMQP bus not supported.

    AMQP cannot be used if CSE 3.1 or above is configured with 10.3 or above.

    :param bool is_mqtt_used: boolean indicating if mqtt is used
    :param str default_api_version: max VCD API version used by CSE.
    :raises: AmqpError
    """
    if not is_mqtt_used and VCDApiVersion(default_api_version) > \
            _MAX_API_VERSION_TO_RUN_CSE_WITH_AMQP_IN_NON_LEGACY_MODE:
        msg = f"Cannot use AMQP message bus when working with api version" \
            f" {default_api_version}. Please upgrade to MQTT."
        logger.error(msg)
        raise AmqpError(msg)
    def delete_entity(
        self,
        entity_id: str,
        invoke_hooks: bool = False,
        is_request_async=False
    ) -> Union[dict, Tuple[dict, dict]]:  # noqa: E501
        """Delete the defined entity.

        :param str entity_id: Id of the entity.
        :param bool invoke_hooks: set to true if hooks need to be invoked
        :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: response body or response body and response headers
        :rtype: Union[dict, Tuple[dict, dict]]
        """
        # response will be a tuple (response_body, response_header) if
        # is_request_async is true. Else, it will be just response_body
        vcd_api_version = self._cloudapi_client.get_vcd_api_version()
        resource_url_relative_path = f"{CloudApiResource.ENTITIES}/{entity_id}"

        # TODO: Also include any persona having Administrator:FullControl
        #  on CSE:nativeCluster
        if (vcd_api_version >= VCDApiVersion(
                vcd_client.ApiVersion.VERSION_36.value)  # noqa: E501
                and self._cloudapi_client.is_sys_admin and not invoke_hooks):
            resource_url_relative_path += f"?invokeHooks={str(invoke_hooks).lower()}"  # noqa: E501

        response = self._cloudapi_client.do_request(
            method=RequestMethod.DELETE,
            cloudapi_version=CloudApiVersion.VERSION_1_0_0,
            resource_url_relative_path=resource_url_relative_path,  # noqa: E501
            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
def _override_client(ctx) -> None:
    """Replace the vcd client in ctx.obj with new one.

    New vcd client takes the CSE server_api_version as api_version param.
    Save profile also with 'cse_server_api_version' for subsequent commands.

    :param <click.core.Context> ctx: click context
    """
    profiles = Profiles.load()
    # if the key CSE_SERVER_RUNNING is not present in the profiles.yaml,
    # we make an assumption that CSE server is running
    is_cse_server_running = profiles.get(CSE_SERVER_RUNNING, default=True)
    cse_server_api_version = profiles.get(CSE_SERVER_API_VERSION)
    if not is_cse_server_running:
        CLIENT_LOGGER.debug(
            "CSE server not running as per profile, restricting CLI to only TKG operations."
        )  # noqa: E501
        restrict_cli_to_tkg_s_operations()
        ctx.obj['profiles'] = profiles
        return

    # Get server_api_version; save it in profiles if doesn't exist
    if not cse_server_api_version:
        try:
            system = syst.System(ctx.obj['client'])
            sys_info = system.get_info()

            is_pre_cse_3_1_server = False
            if CSE_SERVER_LEGACY_MODE not in sys_info or \
                    CSE_SERVER_SUPPORTED_API_VERSIONS not in sys_info:
                is_pre_cse_3_1_server = True

            if not is_pre_cse_3_1_server:
                is_cse_server_running_in_legacy_mode = \
                    sys_info.get(CSE_SERVER_LEGACY_MODE)
                cse_server_supported_api_versions = \
                    set(sys_info.get(CSE_SERVER_SUPPORTED_API_VERSIONS))
                cse_client_supported_api_versions = \
                    set(shared_constants.SUPPORTED_VCD_API_VERSIONS)

                common_supported_api_versions = \
                    list(cse_server_supported_api_versions.intersection(
                        cse_client_supported_api_versions))

                # ToDo: Instead of float use proper version comparison
                if is_cse_server_running_in_legacy_mode:
                    common_supported_api_versions = \
                        [VCDApiVersion(x) for x in common_supported_api_versions  # noqa: E501
                            if VCDApiVersion(x) < VcdApiVersionObj.VERSION_35.value]  # noqa: E501
                else:
                    common_supported_api_versions = \
                        [VCDApiVersion(x) for x in common_supported_api_versions  # noqa: E501
                            if VCDApiVersion(x) >= VcdApiVersionObj.VERSION_35.value]  # noqa: E501

                cse_server_api_version = \
                    str(max(common_supported_api_versions))
                CLIENT_LOGGER.debug(
                    f"Server api versions : {cse_server_supported_api_versions}, "  # noqa: E501
                    f"Client api versions : {cse_client_supported_api_versions}, "  # noqa: E501
                    f"Server in Legacy mode : {is_cse_server_running_in_legacy_mode}, "  # noqa: E501
                    f"Selected api version : {cse_server_api_version}.")
            else:
                cse_server_api_version = \
                    sys_info.get(CSE_SERVER_API_VERSION)
                CLIENT_LOGGER.debug(
                    "Pre CSE 3.1 server detected. Selected api version : "
                    f"{cse_server_api_version}.")

            profiles.set(CSE_SERVER_API_VERSION, cse_server_api_version)
            profiles.set(CSE_SERVER_RUNNING, True)
            profiles.save()
        except (requests.exceptions.Timeout, CseResponseError) as err:
            CLIENT_LOGGER.error(err, exc_info=True)
            CLIENT_LOGGER.debug(
                "Request to CSE server timed out. Restricting CLI to only TKG operations."
            )  # noqa: E501

            profiles.set(CSE_SERVER_RUNNING, False)
            restrict_cli_to_tkg_s_operations()
            ctx.obj['profiles'] = profiles
            profiles.save()
            return
    client = Client(profiles.get('host'),
                    api_version=cse_server_api_version,
                    verify_ssl_certs=profiles.get('verify'),
                    log_file='vcd.log',
                    log_requests=profiles.get('log_request'),
                    log_headers=profiles.get('log_header'),
                    log_bodies=profiles.get('log_body'))
    client.rehydrate_from_token(token=profiles.get('token'),
                                is_jwt_token=profiles.get('is_jwt_token'))
    ctx.obj['client'] = client
    ctx.obj['profiles'] = profiles
def add_additional_details_to_config(
    config: Dict,
    vcd_host: str,
    vcd_username: str,
    vcd_password: str,
    verify_ssl: bool,
    is_legacy_mode: bool,
    is_mqtt_exchange: bool,
    log_wire: bool,
    log_wire_file: str
):
    """Update config dict with computed key-value pairs.

    :param dict config:
    :param str vcd_host:
    :param str vcd_username:
    :param str vcd_password:
    :param bool verify_ssl:
    :param bool is_legacy_mode:
    :param bool is_mqtt_exchange:
    :param bool log_wire:
    :param str log_wire_file:

    :return: the updated config file
    :rtype: dict
    """
    # Compute common supported api versions by the CSE server and vCD
    sysadmin_client = None
    try:
        sysadmin_client = Client(
            vcd_host,
            verify_ssl_certs=verify_ssl,
            log_file=log_wire_file,
            log_requests=log_wire,
            log_headers=log_wire,
            log_bodies=log_wire
        )
        sysadmin_client.set_credentials(
            BasicLoginCredentials(
                vcd_username,
                SYSTEM_ORG_NAME,
                vcd_password
            )
        )

        vcd_supported_api_versions = \
            set(sysadmin_client.get_supported_versions_list())
        cse_supported_api_versions = set(SUPPORTED_VCD_API_VERSIONS)
        common_supported_api_versions = \
            list(cse_supported_api_versions.intersection(vcd_supported_api_versions))  # noqa: E501
        common_supported_api_versions.sort()

        if is_legacy_mode:
            common_supported_api_versions = \
                [x for x in common_supported_api_versions
                 if VCDApiVersion(x) < VcdApiVersionObj.VERSION_35.value]
        else:
            common_supported_api_versions = \
                [x for x in common_supported_api_versions
                 if VCDApiVersion(x) >= VcdApiVersionObj.VERSION_35.value]
        config['service']['supported_api_versions'] = \
            common_supported_api_versions
    finally:
        if sysadmin_client:
            sysadmin_client.logout()

    # Convert legacy_mode flag in service_section to corresponding
    # feature flags
    if 'feature_flags' not in config:
        config['feature_flags'] = {}
    config['feature_flags']['legacy_api'] = str_to_bool(is_legacy_mode)
    config['feature_flags']['non_legacy_api'] = \
        not str_to_bool(is_legacy_mode)

    # Compute the default api version as the max supported version
    # Also compute the RDE version in use
    max_vcd_api_version_supported: str = get_max_api_version(config['service']['supported_api_versions'])  # noqa: E501
    config['service']['default_api_version'] = max_vcd_api_version_supported
    config['service']['rde_version_in_use'] = semantic_version.Version(
        rde_utils.get_runtime_rde_version_by_vcd_api_version(
            max_vcd_api_version_supported
        )
    )

    # Update the config dict with telemetry specific key value pairs
    update_with_telemetry_settings(
        config_dict=config,
        vcd_host=vcd_host,
        vcd_username=vcd_username,
        vcd_password=vcd_password,
        verify_ssl=verify_ssl,
        is_mqtt_exchange=is_mqtt_exchange
    )

    return config
from pyvcloud.vcd.vcd_api_version import VCDApiVersion

import container_service_extension.common.constants.shared_constants as shared_constants  # noqa: E501

CLUSTER_ACL_LIST_FIELDS = [
    shared_constants.AccessControlKey.ACCESS_LEVEL_ID,
    shared_constants.AccessControlKey.MEMBER_ID,
    shared_constants.AccessControlKey.USERNAME
]

# ACL Path
ACTION_CONTROL_ACCESS_PATH = '/action/controlAccess/'
DEF_INTERFACE_ID_PREFIX = 'urn:vcloud:interface'
DEF_NATIVE_ENTITY_TYPE_NSS = 'nativeCluster'
DEF_ENTITY_TYPE_ID_PREFIX = 'urn:vcloud:type'
DEF_API_MIN_VERSION = VCDApiVersion('35.0')
DEF_SCHEMA_DIRECTORY = 'cse_def_schema'
DEF_ERROR_MESSAGE_KEY = 'message'
DEF_RESOLVED_STATE = 'RESOLVED'

PAYLOAD_VERSION_PREFIX = 'cse.vmware.com/'
PAYLOAD_VERSION_2_0 = PAYLOAD_VERSION_PREFIX + 'v2.0'
PAYLOAD_VERSION_2_1 = PAYLOAD_VERSION_PREFIX + 'v2.1'


@unique
class Vendor(str, Enum):
    CSE = 'cse'
    VMWARE = 'vmware'

    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