def raise_error_if_unsupported_payload_version(payload_version: str): input_rde_version = def_constants.MAP_INPUT_PAYLOAD_VERSION_TO_RDE_VERSION.get( payload_version, def_constants.PayloadKey.UNKNOWN) # noqa: E501 runtime_rde_version = server_utils.get_rde_version_in_use() if input_rde_version == def_constants.PayloadKey.UNKNOWN or semantic_version.Version( input_rde_version) > semantic_version.Version( runtime_rde_version): # noqa: E501 raise exceptions.BadRequestError( f"Unsupported payload version: {payload_version}") # noqa: E501
def exception_handler_wrapper(*args, **kwargs): try: result = func(*args, **kwargs) except (KeyError, TypeError, ValueError) as error: LOGGER.error(error) raise cse_exception.BadRequestError(error_message=str(error)) except Exception as error: LOGGER.error(error) raise error return result
def update_status(self, server_action: shared_constants.ServerAction): def graceful_shutdown(): message = 'Shutting down CSE' n = self.active_requests_count() if n > 0: message += f" CSE will finish processing {n} requests." self._state = ServerState.STOPPING return message if self._state == ServerState.RUNNING: if server_action == shared_constants.ServerAction.ENABLE: return 'CSE is already enabled and running.' if server_action == shared_constants.ServerAction.DISABLE: self._state = ServerState.DISABLED return 'CSE has been disabled.' if server_action == shared_constants.ServerAction.STOP: raise cse_exception.BadRequestError( error_message='CSE must be disabled before ' 'it can be stopped.') raise cse_exception.BadRequestError( error_message=f"Invalid server action: '{server_action}'") if self._state == ServerState.DISABLED: if server_action == shared_constants.ServerAction.ENABLE: self._state = ServerState.RUNNING return 'CSE has been enabled and is running.' if server_action == shared_constants.ServerAction.DISABLE: return 'CSE is already disabled.' if server_action == shared_constants.ServerAction.STOP: return graceful_shutdown() if self._state == ServerState.STOPPING: if server_action == shared_constants.ServerAction.ENABLE: raise cse_exception.BadRequestError( error_message='Cannot enable CSE while it is being' 'stopped.') if server_action == shared_constants.ServerAction.DISABLE: raise cse_exception.BadRequestError( error_message='Cannot disable CSE while it is being' ' stopped.') if server_action == shared_constants.ServerAction.STOP: return graceful_shutdown() raise cse_exception.CseServerError( f"Invalid server state: '{self._state}'") # noqa: E501
def _raise_error_if_unsupported_payload_version(payload_version: str): input_rde_version = rde_constants.MAP_INPUT_PAYLOAD_VERSION_TO_RDE_VERSION.get( payload_version, rde_constants.RDEVersion.RDE_1_0_0.value) # noqa: E501 supported_rde_version = rde_utils.get_rde_version_introduced_at_api_version( _SUPPORTED_API_VERSION) # noqa: E501 if semantic_version.Version(input_rde_version) > semantic_version.Version( supported_rde_version): # noqa: E501 raise exceptions.BadRequestError( f"Unsupported payload version: {payload_version}") # noqa: E501
def exception_handler_wrapper(*args, **kwargs): try: result = func(*args, **kwargs) except (KeyError, TypeError, ValueError) as error: LOGGER.error(error) raise cse_exception.BadRequestError(error_message=str(error)) except Exception as error: LOGGER.error(error) if not isinstance(error, cse_exception.CseRequestError): raise cse_exception.InternalServerRequestError( error_message=str(error)) # noqa: E501 raise error return result
def ovdc_compute_policy_update(request_data, op_ctx: ctx.OperationContext): """Request handler for ovdc compute-policy update operation. Required data: ovdc_id, compute_policy_action, compute_policy_names :return: Dictionary with task href. """ required = [ RequestKey.OVDC_ID, RequestKey.COMPUTE_POLICY_ACTION, RequestKey.COMPUTE_POLICY_NAME ] defaults = { RequestKey.REMOVE_COMPUTE_POLICY_FROM_VMS: False, } validated_data = {**defaults, **request_data} req_utils.validate_payload(validated_data, required) action = validated_data[RequestKey.COMPUTE_POLICY_ACTION] cp_name = validated_data[RequestKey.COMPUTE_POLICY_NAME] ovdc_id = validated_data[RequestKey.OVDC_ID] remove_compute_policy_from_vms = validated_data[ RequestKey.REMOVE_COMPUTE_POLICY_FROM_VMS] # noqa: E501 try: config = server_utils.get_server_runtime_config() cpm = compute_policy_manager.ComputePolicyManager( op_ctx.sysadmin_client, log_wire=utils.str_to_bool( config['service'].get('log_wire'))) # noqa: E501 cp_href = None cp_id = None if cp_name == SYSTEM_DEFAULT_COMPUTE_POLICY_NAME: for _cp in cpm.list_compute_policies_on_vdc(ovdc_id): if _cp['name'] == cp_name: cp_href = _cp['href'] cp_id = _cp['id'] else: try: _cp = compute_policy_manager.get_cse_vdc_compute_policy( cpm, cp_name) # noqa: E501 cp_href = _cp['href'] cp_id = _cp['id'] except vcd_e.EntityNotFoundException: pass if cp_href is None: raise e.BadRequestError(f"Compute policy '{cp_name}' not found.") if action == ComputePolicyAction.ADD: cpm.add_compute_policy_to_vdc(ovdc_id, cp_href) # Record telemetry data record_user_action(CseOperation.OVDC_COMPUTE_POLICY_ADD) return f"Added compute policy '{cp_name}' ({cp_id}) to ovdc " \ f"({ovdc_id})" if action == ComputePolicyAction.REMOVE: # TODO: fix remove_compute_policy by implementing a proper way # for calling async methods without having to pass op_ctx # outside handlers. op_ctx.is_async = True response = cpm.remove_vdc_compute_policy_from_vdc( ovdc_id, cp_href, force=remove_compute_policy_from_vms) # Follow task_href to completion in a different thread and end # operation context _follow_task(op_ctx, response['task_href'], ovdc_id) # Record telemetry data record_user_action(CseOperation.OVDC_COMPUTE_POLICY_REMOVE) return response raise e.BadRequestError("Unsupported compute policy action") except Exception as err: # Record telemetry data failure` if action == ComputePolicyAction.ADD: record_user_action(CseOperation.OVDC_COMPUTE_POLICY_ADD, status=OperationStatus.FAILED) elif action == ComputePolicyAction.REMOVE: record_user_action(CseOperation.OVDC_COMPUTE_POLICY_REMOVE, status=OperationStatus.FAILED) raise err
def process_request(message): """ Determine the correct api handler to invoke and invoke it. The request URI, api version and HTTP verb are used to determine the request operation and the corresponding handler. Additionally support for payload verification, query param verification will be added in a later point of time. URL template matching is also performed to compute values of url template parameters. These computed values, request body and query params are all sent to handlers in form of a dictionary. :param dict message: message received over AMQP/MQTT bus representing the incoming REST request. :returns: response computed by the handler after processing the request """ LOGGER.debug(f"Incoming request message: {json.dumps(message)}") api_version_header = _parse_accept_header( accept_header=message['headers'].get('Accept')) api_version = _get_api_version_from_accept_header( api_version_header=api_version_header) # Convert to upper case for matching the ENUM values method = RequestMethod(message['method'].upper()) url = message['requestUri'] url_tokens = url.split("/") # ignore the vcd host and /api in the url if len(url_tokens) > 2: url_tokens = url_tokens[2:] query_params = None if message['queryString']: query_params = dict(parse_qsl(message['queryString'])) request_body = None # Should we do a content-type check? and allow only application/json content? # noqa: E501 # Process request body only for requests with HTTP verbs that allow body if method in [RequestMethod.POST, RequestMethod.PUT, RequestMethod.DELETE] and \ len(message['body']) > 0: raw_body = base64.b64decode(message['body']).decode( sys.getfilesystemencoding()) # noqa: E501 request_body = json.loads(raw_body) import container_service_extension.server.service as cse_service server_config = cse_service.Service().get_service_config() found = False operation = None handler_method = None url_data = {} for entry in CSE_REQUEST_DISPATCHER_LIST: if found: break if len(entry['url_tokens']) != len(url_tokens): continue url_matched = True for i in range(0, len(url_tokens)): token = entry['url_tokens'][i] if token.startswith("$"): url_data[token[1:]] = url_tokens[i] elif token.lower() != url_tokens[i].lower(): url_matched = False url_data.clear() break if not url_matched: continue if method not in entry.keys(): raise cse_exception.MethodNotAllowedRequestError() handlers = entry[method] matched_handler = None supported_api_versions = [] for versions in handlers.keys(): supported_api_versions.extend(list(versions)) if api_version in versions or '*' in versions: matched_handler = handlers[versions] break if not matched_handler: raise cse_exception.NotAcceptableRequestError( error_message="Invalid api version specified. Expected " f"api version '{supported_api_versions}'.") required_feature_flags = matched_handler.get('feature_flags', {}) feature_flags_satisfied = True for feature_flag in required_feature_flags: value = server_config['feature_flags'].get(feature_flag, False) if not value: LOGGER.debug("Url matched but failed to satisfy feature " f"flag {feature_flag}") feature_flags_satisfied = False break if feature_flags_satisfied: operation = matched_handler['operation'] handler_method = matched_handler['handler'] # ToDo: Extra validation based on allowed query params, content type etc. # noqa: E501 found = True else: break if not found: raise cse_exception.NotFoundRequestError() # /system operations are excluded from these checks if operation not in (CseOperation.SYSTEM_INFO, CseOperation.SYSTEM_UPDATE): if not cse_service.Service().is_running(): raise cse_exception.BadRequestError( error_message='CSE service is disabled. ' 'Contact the System Administrator.') # create request data dict from incoming message data request_data = {} # update request data dict with query params data if query_params: request_data[RequestKey.QUERY_PARAMS] = query_params LOGGER.debug(f"query parameters: {query_params}") # update request_data with request_body if request_body: request_data[RequestKey.INPUT_SPEC] = request_body LOGGER.debug(f"request body: {request_body}") # update request_data with url template param key-values request_data.update(url_data) request_data['url'] = url # extract out the authorization token tenant_auth_token = message['headers'].get('x-vcloud-authorization') is_jwt_token = False auth_header = message['headers'].get('Authorization') if auth_header: tokens = auth_header.split(" ") if len(tokens) == 2 and tokens[0].lower() == 'bearer': tenant_auth_token = tokens[1] is_jwt_token = True # create operation context operation_ctx = ctx.OperationContext(tenant_auth_token, is_jwt=is_jwt_token, request_id=message['id']) try: body_content = handler_method(request_data, operation_ctx) finally: if not operation_ctx.is_async: operation_ctx.end() if not isinstance(body_content, (list, dict)): body_content = {RESPONSE_MESSAGE_KEY: str(body_content)} response = { 'status_code': operation.ideal_response_code, 'body': body_content, } LOGGER.debug(f"Outgoing response: {str(response)}") return response
def process_request(message): from container_service_extension.server.service import Service LOGGER.debug(f"Incoming request message: {json.dumps(message)}") api_version_header = _parse_accept_header( accept_header=message['headers'].get('Accept')) api_version = _get_api_version_from_accept_header( api_version_header=api_version_header) url_data = _get_url_data(method=message['method'], url=message['requestUri'], api_version=api_version) # noqa: E501 operation = url_data[_OPERATION_KEY] # Check api version and if server is disabled or not # /system operations are excluded from these checks if operation not in (CseOperation.SYSTEM_INFO, CseOperation.SYSTEM_UPDATE): if not Service().is_running(): raise cse_exception.BadRequestError( error_message='CSE service is disabled. ' 'Contact the System Administrator.') else: server_api_version = server_utils.get_server_api_version() if api_version != server_api_version: raise cse_exception.NotAcceptableRequestError( error_message="Invalid api version specified. Expected " f"api version '{server_api_version}'.") # create request data dict from incoming message data request_data = {} is_cse_3_0_request = _is_cse_3_0_endpoint(message['requestUri']) if len(message['body']) > 0: raw_body = base64.b64decode(message['body']).decode( sys.getfilesystemencoding()) # noqa: E501 request_body = json.loads(raw_body) if is_cse_3_0_request: request_data[shared_constants.RequestKey.INPUT_SPEC] = request_body else: request_data.update(request_body) LOGGER.debug(f"request body: {request_data}") # update request data dict with query params data if message['queryString']: query_params = dict(parse_qsl(message['queryString'])) if is_cse_3_0_request: request_data[shared_constants.RequestKey.V35_QUERY] = query_params else: request_data.update(query_params) LOGGER.debug(f"query parameters: {query_params}") # update request spec with operation specific data in the url request_data.update(url_data) # remove None values from request payload data = {k: v for k, v in request_data.items() if v is not None} # extract out the authorization token tenant_auth_token = message['headers'].get('x-vcloud-authorization') is_jwt_token = False auth_header = message['headers'].get('Authorization') if auth_header: tokens = auth_header.split(" ") if len(tokens) == 2 and tokens[0].lower() == 'bearer': tenant_auth_token = tokens[1] is_jwt_token = True # create operation context operation_ctx = ctx.OperationContext(tenant_auth_token, is_jwt=is_jwt_token, request_id=message['id']) try: body_content = OPERATION_TO_HANDLER[operation](data, operation_ctx) finally: if not operation_ctx.is_async: operation_ctx.end() if not isinstance(body_content, (list, dict)): body_content = \ {shared_constants.RESPONSE_MESSAGE_KEY: str(body_content)} response = { 'status_code': operation.ideal_response_code, 'body': body_content, } LOGGER.debug(f"Outgoing response: {str(response)}") return response
def raise_error_if_tkgm_cluster_operation(cluster_kind: str): if cluster_kind and cluster_kind == shared_constants.ClusterEntityKind.TKG_M.value: # noqa: E501 raise exceptions.BadRequestError( f"This operation is not supported for {cluster_kind} clusters" ) # noqa: E501