def __init__(self, next_provider=None, provider_config=None): """ Initialize AuthorizationFilter :type next_provider: :class:`vmware.vapi.core.ApiProvider` :param next_provider: API Provider to invoke the requests :type provider_config: :class:`vmware.vapi.settings.config.ProviderConfig` or :class:`None` :param provider_config: Provider configuration object """ handler_names = [] self._metadata = None if provider_config: # Get the registered AuthN handlers from config file (handler_names, metadata_file) = \ provider_config.get_authorization_handlers_and_file() self._metadata = get_metadata(metadata_file) from vmware.vapi.lib.load import dynamic_import self._authz_handlers = [] for handler_name in handler_names: # Dynamically load the AuthZ handler handler_constructor = dynamic_import(handler_name) if handler_constructor is None: raise ImportError('Could not import %s' % handler_name) self._authz_handlers.append(handler_constructor()) self._internal_server_error_def = make_std_error_def( 'com.vmware.vapi.std.errors.internal_server_error') self._unauthorized_error_def = make_std_error_def( 'com.vmware.vapi.std.errors.unauthorized') ApiProviderFilter.__init__( self, next_provider, [self._internal_server_error_def, self._unauthorized_error_def])
def setUp(self): self.error_name = 'suites.test_error.TestErrorValue.SampleError' self.error_def = make_std_error_def(self.error_name) self.message1 = Message('suites.test_error.TestErrorValue.message1', 'Message 1 - %s %s', 'arg1', 'arg2') self.message2 = Message('suites.test_error.TestErrorValue.message2', 'Message 2') self.message3 = Message('suites.test_error.TestErrorValue.message3', 'Message 3') self.single_message = (self.message1, ) self.multiple_messages = (self.message1, self.message2, self.message3)
raise_python_exception) return_invalid_output_id = MethodIdentifier(interface_id, return_invalid_output) method_id_dict = { missing_input_definition: missing_input_definition_id, invalid_input_definition: invalid_input_definition_id, missing_output_definition: missing_output_definition_id, invalid_output_definition: invalid_output_definition_id, report_declared_error: report_declared_error_id, report_undeclared_error: report_undeclared_error_id, raise_python_exception: raise_python_exception_id, return_invalid_output: return_invalid_output_id, } internal_server_error_def = make_std_error_def( 'com.vmware.vapi.std.errors.internal_server_error') invalid_argument_def = make_std_error_def( 'com.vmware.vapi.std.errors.invalid_argument') not_found_error_def = make_std_error_def( 'com.vmware.vapi.std.errors.not_found') operation_not_found_def = make_std_error_def( 'com.vmware.vapi.std.errors.operation_not_found') error_values = ListValue([ convert_data_def_to_data_value(internal_server_error_def), convert_data_def_to_data_value(invalid_argument_def), convert_data_def_to_data_value(operation_not_found_def) ]) _msg = Message('mockup.message.id', 'mockup error message') not_found_error = make_error_value_from_msgs(not_found_error_def, _msg)
instance.set_field(field_name, field_instance) elif data_def.type == Type.LIST: # TODO: Randomize list len? instance.add_all([build_adhoc_data_value(data_def.element_type) for val in range(1,7)]) elif data_def.type == Type.OPTIONAL: # TODO: Randomize optional set or not set? instance = data_def.new_value(build_adhoc_data_value(data_def.element_type)) return instance fake_iface_id = 'vmware.test.fake.iface' null_method_id = 'Null' null_method_def = MethodDefinition(MethodIdentifier( InterfaceIdentifier(fake_iface_id), null_method_id), VoidDefinition(), VoidDefinition(), [make_std_error_def('error1'), make_std_error_def('error2')] ) echo_method_id = 'Echo' echo_method_def = MethodDefinition(MethodIdentifier( InterfaceIdentifier(fake_iface_id), echo_method_id), StructDefinition('struct', [('int', IntegerDefinition()), ('str', StringDefinition())] ), StructDefinition('struct', [('int', IntegerDefinition()), ('str', StringDefinition())] ), [make_std_error_def('e1'), make_std_error_def('e2'),
class InterposerProvider(ApiProviderFilter): """ InterposerProvider is an interposer implementation of the ApiProvider interface. It provides hook in capabilities for adding interposers to invoke method calls """ _internal_server_error_def = make_std_error_def( 'com.vmware.vapi.std.errors.internal_server_error') def __init__(self, next_provider=None): """ Initializer InterposerProvider. :type next_provider: :class:`vmware.vapi.core.ApiProvider` or ``None`` :param next_provider: API Provider to invoke the requests """ ApiProviderFilter.__init__(self, next_provider, [self._internal_server_error_def]) # Key: Interposer type, Value: set of interposer ids of that type self._interposers = {} # Key: Interposer Id, Value: Interposer Map self._interposer_map = {} # Key: Interposer Id, Value: Method Identifier self._interposer_def = {} # Method identifiers of all the interposers self._interposer_methods = set() connector = LocalConnector(self.next_provider) self._introspection = IntrospectableApiProvider(connector) def invoke(self, service_id, operation_id, input_value, ctx): """ Invoke an API request :type service_id: :class:`str` :param service_id: Service identifier :type operation_id: :class:`str` :param operation_id: Operation identifier :type input_value: :class:`vmware.vapi.data.value.StructValue` :param input_value: Method input parameters :type ctx: :class:`vmware.vapi.core.ExecutionContext` :param ctx: Execution context for this method :rtype: :class:`vmware.vapi.core.MethodResult` :return: Result of the method invocation """ try: veto_error = self.execute( 'veto', service_id, operation_id, ctx, input_value) if veto_error is not None: return MethodResult(error=veto_error) else: self.execute('pre', service_id, operation_id, ctx, input_value) method_result = ApiProviderFilter.invoke( self, service_id, operation_id, input_value, ctx) self.execute('post', service_id, operation_id, ctx, input_value) return method_result except Exception as e: logger.exception('service: %s, operation: %s, input_value: %s', service_id, operation_id, input_value) error_value = make_error_value_from_msg_id( self._internal_server_error_def, 'vapi.method.invoke.exception', str(e)) method_result = MethodResult(error=error_value) return method_result def add(self, spec): """ Register a new interposer :type spec: :class:`com.vmware.vapi.admin_provider.Interposer.InterposerInfo` :param spec: The specification of the interposer. This encapsulates details such as the type of the interposer and the list of packages that the interposer operates on. :rtype: :class:`str` :return: ID of the interposer :raise: :class:`com.vmware.vapi.std.errors_provider.NotFound`: If service name or method name specified in the operation identity structure does not exist :raise: :class:`vmware.vapi.provider.interposer.InvalidArgument`: If the interposer type is invalid """ method_def = self._introspection.get_method(spec.service_name, spec.operation_name) if spec.type not in [InterposerType.PRE, InterposerType.POST, InterposerType.VETO]: raise InvalidArgument() interposer_id = str(uuid.uuid4()) self._interposers.setdefault(spec.type, []).append(interposer_id) self._interposer_map[interposer_id] = spec self._interposer_methods.add((spec.service_name, spec.operation_name)) self._interposer_def[interposer_id] = method_def return interposer_id def get(self, interposer_id): """ Get the information about an interposer :type interposerId: :class:`str` :param interposerId: Identifier of the interposer for which the information has to be retrieved :rtype: :class:`com.vmware.vapi.admin_provider.Interposer.InterposerInfo` :return: Information about the interposerId passed in the argument :raise: :class:`vmware.vapi.provider.interposer.NotFound`: If the interposer_id does """ info = self._interposer_map.get(interposer_id) if info is None: raise NotFound() else: return info def list(self): """ List all the registered interposer identifiers :rtype: :class:`list` of :class:`str` :return: Interposer identifiers """ return sum(list(self._interposers.values()), []) def remove(self, interposer_id): """ Unregister an interposer :type interposerId: :class:`str` :param interposerId: Identifier of the interposer that has to be unregistered :raise: :class:`vmware.vapi.provider.interposer.NotFound`: If the interposer_id does not exist """ info = self._interposer_map.get(interposer_id) if info is None: raise NotFound() else: self._interposers[info.type].remove(interposer_id) self._interposer_methods.remove((info.service_name, info.operation_name)) del self._interposer_map[interposer_id] def _execute_int(self, interposer_id, interposer_type, ctx, service_name, operation_name, opeartion_input): """ Execute an interposer :type interposer_id: :class:`str` :param interposer_id: Interposer id :type interposer_type: :class:`InterposerType` :param interposer_type: Interposer type :type ctx: :class:`vmware.vapi.core.ExecutionContext` :param ctx: Execution context for the invocation :type service_name: :class:`str` :param service_name: Name of the service that is being interposed :type operation_name: :class:`str` :param operation_name: Name of the operation that is being interposed :type opeartion_input: :class:`vmware.vapi.data.value.DataValue` :param opeartion_input: Input for the operation that is being interposed :rtype: :class:`vmware.vapi.data.value.ErrorValue` :return: Error is returned if the veto interposer execution reported an error, else None """ in_method_def = self._interposer_def.get(interposer_id) if in_method_def: interposer_input = in_method_def.get_input_definition().new_value() interposer_input.set_field(SERVICE_NAME, StringValue(service_name)) interposer_input.set_field(OPERATION_NAME, StringValue(operation_name)) interposer_input.set_field(OPERATION_INPUT, opeartion_input) in_method_id = in_method_def.get_identifier() method_result = ApiProviderFilter.invoke( self, in_method_id.get_interface_identifier().get_name(), in_method_id.get_name(), interposer_input, ctx) # Only veto interposers can break the control flow if interposer_type == InterposerType.VETO and \ not method_result.success(): return method_result.error def execute(self, interposer_type, service_id, operation_id, ctx, input_): """ Execute an interposer :type interposer_type: :class:`InterposerType` :param interposer_type: Type of the interposer :type ctx: :class:`vmware.vapi.core.ExecutionContext` :param ctx: Execution context for the invocation :type input_: :class:`vmware.vapi.data.value.StructValue` :param input_: Input for the method :rtype: :class:`vmware.vapi.data.value.ErrorValue` :return: Error is returned if the veto interposer execution reported an error, else None """ # Interposers are not allowed to call other interposers if (service_id, operation_id) in self._interposer_methods: return interposer_ids = self._interposers.get(interposer_type) if not interposer_ids: return try: for interposer_id in interposer_ids: info = self._interposer_map.get(interposer_id) pkgs = info.packages execute = True if not pkgs else False for pkg in pkgs: if service_id.startswith(pkg): execute = True if execute: return self._execute_int(interposer_id, info.type, ctx, service_id, operation_id, input_) except Exception as e: logger.error('%s', str(e))
def __call__(self, request): """ The implementation of WsgiApplication :type request: :class:`werkzeug.wrappers.Request` :param request: Request object :rtype: :class:`werkzeug.wrappers.Response` :return: Response object """ try: urls = self.rule_map.bind_to_environ(request.environ) endpoint, args = urls.match() if provider_wire_logger.isEnabledFor(logging.DEBUG): provider_wire_logger.debug('HTTP %s %s ', request.method, request.url) provider_wire_logger.debug('REQUEST HEADERS: %s', request.headers) provider_wire_logger.debug('REQUEST BODY: %s', request.get_data()) if endpoint == JSONRPC: result, headers = self._handle_jsonrpc_call(request) response = Response(result) if headers: response.headers = headers else: status, result, cookies, headers = self._handle_rest_call( request, endpoint, args) response = Response(result) response.status_code = status if headers: response.headers = headers if cookies: path = self._rest_prefix for k, v in six.iteritems(cookies): response.set_cookie(k, v, path=path) response.content_type = JSON_CONTENT_TYPE if provider_wire_logger.isEnabledFor(logging.DEBUG): provider_wire_logger.debug('RESPONSE STATUS: %s', response.status_code) provider_wire_logger.debug('RESPONSE HEADERS: %s', response.headers) provider_wire_logger.debug('RESPONSE BODY: %s', response.response) return response except HTTPException as e: try: internal_server_error_def = make_std_error_def( 'com.vmware.vapi.std.errors.invalid_request') msg = message_factory.get_message( 'vapi.method.invoke.exception', # pylint: disable=line-too-long e.description) error_value = make_error_value_from_msgs( internal_server_error_def, msg) json_output = DataValueConverter.convert_to_json(error_value) response = e.get_response() response.set_data(json_output) response.headers['Content-Type'] = 'application/json' return response except Exception: # if there is error in serialize the error, # just return the original raw exception return e
class ApiInterfaceSkeleton(ApiInterface): """ Skeleton class for :class:`ApiInterface`. This class implements the :class:`ApiInterface` interface. """ # XXX These error definitions should be eliminated when we figure out # where/how to get the error definitions used by the local provider _internal_server_error_def = make_std_error_def( 'com.vmware.vapi.std.errors.internal_server_error') _invalid_argument_def = make_std_error_def( 'com.vmware.vapi.std.errors.invalid_argument') _operation_not_found_def = make_std_error_def( 'com.vmware.vapi.std.errors.operation_not_found') _unexpected_input_def = make_std_error_def( 'com.vmware.vapi.std.errors.unexpected_input') # Errors that are reported by this class _error_defs_to_augment = ( _internal_server_error_def, _invalid_argument_def, _unexpected_input_def ) # Errors that are allowed be reported by provider implementations even # though they are not included in the VMODL2 throws clause. # # XXX: This should be removed when once the runtime/bindings # check for invocations using API features that are disabled by feature # switch and report these errors (so provider implementations don't need # to report them). _unchecked_error_defs = ( _operation_not_found_def, _unexpected_input_def ) def __init__(self, iface_name, impl, operations, error_types): """ Initialize the ApiInterface skeleton class :type iface_name: :class:`str` :param iface_name: Interface name :type impl: :class:`VapiInterface` :param impl: Class that implements this interface :type operations: :class:`dict` :param operations: Description of the operations in this service :type error_types: :class:`list` of :class:`vmware.vapi.bindings.type.ErrorType` :param error_types: error types to be registered in this configuration """ self._operations = operations self._iface_id = InterfaceIdentifier(iface_name) operation_ids = [MethodIdentifier(self._iface_id, operation_name) for operation_name in six.iterkeys(self._operations)] self._iface_def = InterfaceDefinition(self._iface_id, operation_ids) self._impl = impl error_types = error_types or [] self._resolver = NameToTypeResolver( {e.definition.name: e for e in error_types}) ApiInterface.__init__(self) @staticmethod def _pepify_args(meth_args): """ Converts all the keys of given keyword arguments into PEP8 standard names :type meth_args: :class:`dict` :param meth_args: The keyword arguments to be converted :rtype: :class:`dict` :return: The converted keyword arguments """ new_args = {} for k, v in six.iteritems(meth_args): new_args[Converter.canonical_to_pep(k)] = v return new_args @classmethod def is_set_optional_field(cls, value): """ Returns true if the value is OptionalValue and it is set :type value: :class:`vmware.vapi.data.value.DataValue` :param value: value to be checked :rtype: :class:`bool` :return: True if the value is OptionalValue and is set, False otherwise """ return not isinstance(value, OptionalValue) or value.is_set() @classmethod def check_for_unknown_fields(cls, value_type, value): """ Check if the StructValues inside the input DataValue has any unknown fields :type value_type: :class:`vmware.vapi.bindings.type.BindingType` :param value_type: Binding Type :type value: :class:`vmware.vapi.data.value.DataValue` :param value: DataValue :rtype: :class:`vmware.vapi.data.value.ErrorValue` or ``None`` :return: ErrorValue describing the unknown fields or None if no unknown fields were found """ if isinstance(value_type, ReferenceType): value_type = value_type.resolved_type if isinstance(value_type, DynamicStructType): pass elif isinstance(value_type, StructType): expected_field_names = value_type.get_field_names() unexpected_fields = set([ # pylint: disable=consider-using-set-comprehension field for field in value.get_field_names() if field not in expected_field_names and cls.is_set_optional_field(value.get_field(field)) ]) if unexpected_fields: msg = message_factory.get_message( 'vapi.data.structure.field.unexpected', repr(unexpected_fields), value.name) logger.debug(msg) return make_error_value_from_msgs(cls._unexpected_input_def, msg) for field in expected_field_names: field_value = value.get_field(field) field_type = value_type.get_field(field) error = ApiInterfaceSkeleton.check_for_unknown_fields( field_type, field_value) if error: return error elif isinstance(value_type, ListType): element_type = value_type.element_type if isinstance(element_type, ( ListType, OptionalType, StructType, ReferenceType)): for element in value: error = ApiInterfaceSkeleton.check_for_unknown_fields( element_type, element) if error: return error elif isinstance(value_type, OptionalType): if value.is_set(): return ApiInterfaceSkeleton.check_for_unknown_fields( value_type.element_type, value.value) return None def get_identifier(self): return self._iface_id def get_definition(self): return self._iface_def def get_method_definition(self, method_id): op_info = self._operations.get(method_id.get_name()) if op_info is None: return None errors_defs = [e.definition for e in op_info['errors']] for error_def in self._error_defs_to_augment: if error_def not in errors_defs: errors_defs.append(error_def) # XXX: This loop should be removed when once the runtime/bindings # check for invocations using API features that are disabled by feature # switch and report these errors (so provider implementations don't need # to report them). for error_def in self._unchecked_error_defs: if error_def not in errors_defs: errors_defs.append(error_def) return MethodDefinition(method_id, op_info['input_type'].definition, op_info['output_type'].definition, errors_defs) def _invoke_task_operation(self, ctx, method_id, method, method_args): """ Invokes the task operations by adding them in thread pool queue. :type ctx: :class:`vmware.vapi.core.ExecutionContext` :param ctx: Execution context for this method :type method_id: :class:`str` :param method_id: Method id :type method: :class:`str` :param method: Method name :type method_args: :class:`args` :param method_args: Method input parameters :rtype: :class:`tuple` of :class:`str` or :class:`vmware.vapi.core.MethodResult` and :class:`bool` :return: Tuple of task id and boolean value or Method Result """ from vmware.vapi.lib.thread_pool import get_threadpool from vmware.vapi.task.task_manager_impl import get_task_manager task_manager = get_task_manager() thread_pool = get_threadpool() service_id = method_id.get_interface_identifier().get_name() operation_id = method_id.get_name() op_info = self._operations.get(operation_id) task_id = task_manager.create_task( None, service_id, operation_id, False, op_info['errors']) insert_task_id(ctx.application_context, task_id) event = threading.Event() accept_timeout = 30 if is_task_operation(method_id.get_name()): work_item = thread_pool.queue_work( self._invoke_method, ctx, method, method_args, event) event_set = event.wait(accept_timeout) # In case of provider throwing an error before accepting task # remove task from task manager. if work_item.err: task_manager.remove_task(task_id) return work_item.err, None # In case of provider not accepting task remove task from task # manager. if not event_set: task_manager.remove_task(task_id) return None, False return task_id, event_set else: # If $task is not present in method id for a task operation # even then we will still use a separate thread in threadpool # and wait for its completion to return the result that's # to ensure provider implementation can be same irrespective # of client calling for task or non-task version of operation result = thread_pool.queue_work_and_wait(self._invoke_method, ctx, method, method_args, event) return result[1], None def _invoke_method(self, ctx, method, method_args, event): """ Invoke the provider method after setting appropriate context and event :type ctx: :class:`vmware.vapi.core.ExecutionContext` :param ctx: Execution context for this method :type method: :class:`str` :param method: Method name :type method_args: :class:`args` :param method_args: Method input parameters :type event: :class:`threading.Event` :param event: Event object :rtype: :class:`vmware.vapi.core.MethodResult` :return: Result of the method invocation """ logger.debug('Setting execution ctx for the new thread') set_context(ctx) set_event(event) result = method(**method_args) clear_context() return result def invoke(self, ctx, method_id, input_value): """ Invokes the specified method using the execution context and the input provided :type ctx: :class:`vmware.vapi.core.ExecutionContext` :param ctx: Execution context for this method :type input_value: :class:`vmware.vapi.data.value.StructValue` :param input_value: Method input parameters :rtype: :class:`vmware.vapi.core.MethodResult` :return: Result of the method invocation """ op_info = self._operations.get(method_id.get_name()) # Set execution context set_context(ctx) try: # input validators validators = op_info['input_value_validator_list'] input_type = op_info['input_type'] for validator in validators: msg_list = validator.validate(input_value, input_type) if msg_list: error_value = make_error_value_from_msgs( self._invalid_argument_def, *msg_list) return MethodResult(error=error_value) error_value = ApiInterfaceSkeleton.check_for_unknown_fields( input_type, input_value) if error_value: return MethodResult(error=error_value) try: meth_args = TypeConverter.convert_to_python( input_value, binding_type=input_type, resolver=self._resolver) except CoreException as e: error_value = make_error_value_from_msgs( self._invalid_argument_def, *e.messages) return MethodResult(error=error_value) method_name = Converter.canonical_to_pep( get_non_task_operation_name(method_id.get_name())) method = getattr(self._impl, method_name) # If it's a task method then create a new thread to run provider # impl and return task id or result depending upon type of # invocation. if op_info['task_type'] != TaskType.NONE: meth_output, task_accepted = self._invoke_task_operation( ctx, method_id, method, meth_args) if isinstance(meth_output, VapiError): raise meth_output # pylint: disable=raising-bad-type # In case of provider not accepting task throw an error. if task_accepted is False: msg_list = [message_factory.get_message( 'vapi.bindings.skeleton.task.invalidstate')] error_value = make_error_value_from_msgs( self._internal_server_error_def, *msg_list) return MethodResult(error=error_value) else: meth_output = method(**meth_args) output_type = op_info['output_type'] # output validators validators = op_info['output_validator_list'] output = TypeConverter.convert_to_vapi(meth_output, output_type) for validator in validators: msg_list = validator.validate(output, output_type) if msg_list: error_value = make_error_value_from_msgs( self._internal_server_error_def, *msg_list) return MethodResult(error=error_value) result = MethodResult(output=output) except CoreException as e: logger.exception("Error in invoking %s - %s", str(method_id), e) error_value = make_error_value_from_msgs( self._internal_server_error_def, *e.messages) result = MethodResult(error=error_value) except VapiError as e: exc_info = sys.exc_info() error_type = e.__class__.get_binding_type() try: error = TypeConverter.convert_to_vapi(e, error_type) except CoreException as ce: logger.error('Failed to convert %s to error type %s because %s', e, error_type.name, ' because '.join((e.def_msg for e in ce.messages)), exc_info=exc_info) raise result = MethodResult(error=error) finally: # Reset the execution context after the operation clear_context() return result
veto_method_id = MethodIdentifier(interface_id, veto_method_name) method_id_dict = { mock_method_name: mock_method_id, pre_method_name: pre_method_id, post_method_name: post_method_id, veto_method_name: veto_method_id, } input_def = StructDefinition('input', [ ('service_name', StringDefinition()), ('operation_name', StringDefinition()), ('operation_input', OpaqueDefinition()), ]) mock_input_def = StructDefinition('input', [('raise', BooleanDefinition())]) not_found_error_def = make_std_error_def( 'com.vmware.vapi.std.errors.not_found') errors = [ 'com.vmware.vapi.std.errors.not_found', 'com.vmware.vapi.std.errors.internal_server_error', 'com.vmware.vapi.std.errors.invalid_argument', 'com.vmware.vapi.std.errors.operation_not_found', ] error_defs = [make_std_error_def(error) for error in errors] error_values = ListValue( [convert_data_def_to_data_value(error_def) for error_def in error_defs]) class MockupApiInterface(ApiInterface): def __init__(self, interface_id): self._interface_id = interface_id self.counters = {}
MethodDefinition, MethodResult, ExecutionContext) from vmware.vapi.data.definition import (StructDefinition, StringDefinition, ListDefinition, StructRefDefinition, OptionalDefinition) from vmware.vapi.data.serializers.introspection import ( convert_data_def_to_data_value) from vmware.vapi.lib.constants import (Introspection, MAP_ENTRY, OPERATION_INPUT) from vmware.vapi.lib.log import get_vapi_logger from vmware.vapi.lib.std import make_error_value_from_msgs, make_std_error_def from vmware.vapi.data.value import (StringValue, ListValue, StructValue) from vmware.vapi.lib.fingerprint import generate_fingerprint from vmware.vapi.l10n.runtime import message_factory # errors not_found_def = make_std_error_def('com.vmware.vapi.std.errors.not_found') logger = get_vapi_logger(__name__) def get_checksum(api_services): """ Returns the checksum of services registered with LocalProvider :type api_services: :class:`dict` :param api_services: Dictionary of all the services registered with local provider Key is :class:`vmware.vapi.core.InterfaceIdentifier` and value is :class:`vmware.vapi.core.ApiInterface` :rtype: :class:`str` :return: checksum of the service information """
class LocalProvider(ApiProvider): """ LocalProvider is a local in-process implementation of the ApiProvider interface """ # XXX These error definitions should be eliminated when we figure out # where/how to get the error definitions used by the local provider _internal_server_error_def = make_std_error_def( 'com.vmware.vapi.std.errors.internal_server_error') _invalid_argument_def = make_std_error_def( 'com.vmware.vapi.std.errors.invalid_argument') _operation_not_found_def = make_std_error_def( 'com.vmware.vapi.std.errors.operation_not_found') def __init__(self, load_introspection=True): """ Initialize LocalProvider :type load_introspection: :class:`bool` :param load_introspection: If true, load introspection services """ ApiProvider.__init__(self) self._name = "LocalProvider" # key = service id, value = api interface self._service_map = {} self._introspector = introspection.LocalProviderIntrospector( self._name) if load_introspection: self.add_interface(self._introspector.get_introspection_services()) self._error_defs_to_augment = [ self._internal_server_error_def, self._invalid_argument_def, self._operation_not_found_def, ] # these are the errors that should be augmented # when `get` operation is invoked on # com.vmware.vapi.std.introspection.Operation self._error_values_to_augment = [ convert_data_def_to_data_value(error_def) for error_def in self._error_defs_to_augment ] def register_by_properties(self, provider_cfg): """ Register set of interfaces using provider configuration object :type provider_cfg: :class:`vmware.vapi.settings.config.ProviderConfig` :param provider_cfg: Configuration for this vAPI provider """ self._name = provider_cfg.get_provider_name() services = provider_cfg.get_interfaces() for service in services: service = service.strip() if service: # Get the service specific configuration from the config service_cfg = provider_cfg.get_service_config(service) fn = "register_instance" try: module = __import__(service, globals(), locals(), [fn]) register_fn = getattr(module, fn) # XXX: Remove the if condition once all the implementations # have been updated to use the new cfg parameter if inspect.getargspec(register_fn).args: # pylint: disable=W1505 ifaces = register_fn(service_cfg) else: ifaces = register_fn() self.add_interface(ifaces) except Exception: stack_trace = traceback.format_exc() raise Exception('Could not add service %s due to %s' % (service, stack_trace)) def add_interface(self, ifaces): """ Register an interface with LocalProvider :type ifaces: :class:`list` of :class:`vmware.vapi.core.ApiInterface` :param ifaces: Interfaces to be registered """ if not isinstance(ifaces, list): ifaces = [ifaces] for iface in ifaces: # iface could be VapiInterface if bindings layer is registering # it's interfaces. In that case, extract the skeleton from # VapiInterface if isinstance(iface, VapiInterface): api_iface = iface.api_interface # In Dynamic providers, where ApiInterface is implemented directly # instead of using bindings, an instance of ApiInterface is passed elif isinstance(iface, ApiInterface): api_iface = iface else: raise Exception( 'Could not register the interface %s. It has to be either ' 'an instance of vmware.vapi.bindings.VapiInterface or ' 'an instance of vmware.vapi.core.ApiInterface' % str(iface.__class__)) service_id = api_iface.get_identifier().get_name() if service_id in self._service_map: raise Exception('Service already registered: %s' % service_id) logger.info('Registering service: %s', service_id) self._service_map[service_id] = api_iface if self._introspector: self._introspector.add_service(service_id, api_iface) @staticmethod def _validate_error(error_value, method_definition): """ Validate the error_value is allowed to be reported by the method described by method_definition. :type error_value: :class:`vmware.vapi.data.value.ErrorValue` :param error_value: Error value to validate :type method_definition: :class:`vmware.vapi.core.MethodDefinition` :param method_definition: definition of the method to validate against. :rtype: :class:`vmware.vapi.message.Message` or None :return: the messages describing the validation failure or None if validation succeeded """ error_def = method_definition.get_error_definition(error_value.name) if error_def is None: method_id = method_definition.get_identifier() logger.error( "Method %s reported the error %s which is not in " + "MethodDefinition", method_id.get_name(), error_value.name) message = message_factory.get_message( 'vapi.method.status.errors.invalid', error_value.name, str(method_id.get_name())) return [message] messages = error_def.validate(error_value) return messages def _invoke_int(self, service_id, operation_id, input_value, ctx): # pylint: disable=R0911 """ Internal implementation of InvokeMethod :type service_id: :class:`str` :param service_id: Service identifier :type operation_id: :class:`str` :param operation_id: Operation identifier :type ctx: :class:`vmware.vapi.core.ExecutionContext` :param ctx: Execution context for this method :type input_value: :class:`vmware.vapi.data.value.StructValue` :param input_value: Method input parameters :rtype: :class:`vmware.vapi.core.MethodResult` :return: Result of the method invocation """ # Step 0: Verify input types if (input_value and not (input_value.type == Type.STRUCTURE)): logger.error("Invalid inputs") error_value = make_error_value_from_msg_id( self._invalid_argument_def, 'vapi.method.input.invalid') return MethodResult(error=error_value) iface_id = InterfaceIdentifier(service_id) method_id = MethodIdentifier(iface_id, operation_id) # Step 1: Get method definition iface = self._service_map.get(service_id) if not iface: logger.error('Could not find service: %s', service_id) error_value = make_error_value_from_msg_id( self._operation_not_found_def, 'vapi.method.input.invalid.interface', service_id) return MethodResult(error=error_value) method_def = iface.get_method_definition(method_id) if not method_def: logger.error("Could not find method %s", method_id.get_name()) error_value = make_error_value_from_msg_id( self._operation_not_found_def, 'vapi.method.input.invalid.method', method_id.get_name()) return MethodResult(error=error_value) input_def = method_def.get_input_definition() if not isinstance(input_def, StructDefinition): error_value = make_error_value_from_msg_id( self._internal_server_error_def, 'vapi.method.input.invalid.definition') return MethodResult(error=error_value) output_def = method_def.get_output_definition() if not isinstance(output_def, DataDefinition): error_value = make_error_value_from_msg_id( self._internal_server_error_def, 'vapi.method.output.invalid.definition') return MethodResult(error=error_value) # Step 2: Validate input with input def input_def.complete_value(input_value) messages = input_def.validate(input_value) if messages: logger.error("Input validation failed for method %s", method_id.get_name()) error_value = make_error_value_from_msgs( self._invalid_argument_def, *messages) return MethodResult(error=error_value) # Step 3: Execute method method_result = iface.invoke(ctx, method_id, input_value) # Step 4: Validate output with output def or error against errors set if method_result.success(): messages = output_def.validate(method_result.output) if messages: logger.error("Output validation failed for method %s", method_id.get_name()) error_value = make_error_value_from_msgs( self._internal_server_error_def, *messages) return MethodResult(error=error_value) else: error_value = method_result.error messages = self._validate_error(error_value, method_def) if messages: new_error_value = make_error_value_from_error_value_and_msgs( self._internal_server_error_def, error_value, *messages) return MethodResult(error=new_error_value) return method_result def invoke(self, service_id, operation_id, input_value, ctx): logger.debug( 'Operation started: Service: %s, Operation: %s, Ctx: %s, Input: %s', service_id, operation_id, ctx, input_value) try: method_result = self._invoke_int(service_id, operation_id, input_value, ctx) if method_result.success(): method_result = augment_method_result_with_errors( service_id, operation_id, method_result, self._error_values_to_augment) except Exception as e: logger.exception("Error in invoking %s in %s - %s", service_id, operation_id, e) error_value = make_error_value_from_msg_id( self._internal_server_error_def, 'vapi.method.invoke.exception', str(e)) method_result = MethodResult(error=error_value) logger.debug( 'Operation finished: Service: %s, Operation %s, Ctx: %s, ' 'Output: %s', service_id, operation_id, ctx, method_result) return method_result
from vmware.vapi.protocol.client.connector import Connector ### Data used by the mockup classes and the test methods # Method names report_vapi_success = 'report_vapi_success' report_vapi_error = 'report_vapi_error' report_vapi_exception = 'report_vapi_exception' nonexistent_method = 'nonexistent_method' # Type names not_found = 'not_found' operation_not_found = 'operation_not_found' # Error definitions not_found_def = make_std_error_def(not_found) operation_not_found_def = make_std_error_def(operation_not_found) ### Utility functions used by the mockup classes and the test methods def input_struct_name(method_name): return 'Input%s' % method_name class MockupNotFound(VapiError): def __init__(self, **kwargs): self.messages = kwargs.get('messages') VapiError.__init__(self)
class AggregatorProvider(ApiProvider): """ AggregatorProvider is an aggregating implementation of the ApiProvider interface. It aggregates a bunch of ApiProvider instances and expose it. """ def __init__(self): ApiProvider.__init__(self) self._name = 'ApiAggregator' # key = service_id, value = api interface self._service_id_map = {} # key : name, value : provider self._providers = {} # key : name, value : connection info self._provider_data = {} # List of all the services present in the local provider self._local_service_ids = [] self._introspection_service_names = [ Introspection.PROVIDER_SVC, Introspection.SERVICE_SVC, Introspection.OPERATION_SVC ] self._introspector = None self._authn_chain_processors = [] _operation_not_found_def = make_std_error_def( 'com.vmware.vapi.std.errors.operation_not_found') _internal_server_error_def = make_std_error_def( 'com.vmware.vapi.std.errors.internal_server_error') _augmented_errors = (_operation_not_found_def, _internal_server_error_def) ########################################################################### # # Support functions for aggregator services # ########################################################################### @staticmethod def get_service_identifiers(api_provider): """ Invokes introspection service list on the Api Provider and retrieves the list of services :type api_provider: :class:`vmware.vapi.core.ApiProvider` :param api_provider: ApiProvider instance to be used for retrieving service identifiers :rtype: :class:`list` of :class:`str` :return: List of service identifiers :raise: :class:`Exception`: if service identifiers could not be retrieved """ ctx = ExecutionContext() service_name = Introspection.SERVICE_SVC operation_name = 'list' struct_value = StructValue(OPERATION_INPUT) method_result = api_provider.invoke(service_name, operation_name, struct_value, ctx) if method_result.success(): return [service.value for service in method_result.output] else: raise Exception('Could not retrieve service identifiers: %s', repr(method_result.error)) def _process_child(self, provider_cfg, child): """ Process a aggregator child provider_cfg :type provider_cfg: :class:`dict` :param provider_cfg: Properties dictionary :type child: :class:`str` :param child: Child to be processed """ try: url = provider_cfg.get_child_url(child) connector = connect.get_requests_connector( session=requests.session(), url=url) api_provider = connector.get_api_provider() rpc_protocol = get_url_scheme(url) msg_protocol = provider_cfg.get_provider_message_format(child) if api_provider: self.register_provider(api_provider, child, rpc_protocol, msg_protocol, addr=url) except Exception as e: stack_trace = traceback.format_exc() raise Exception('Could not register %s at %s due to %s' % (child, url, stack_trace)) def register_by_properties(self, provider_cfg): """ Register a provider using a provider_cfg dictionary :type provider_cfg: :class:`dict` :param provider_cfg: Properties dictionary """ self._name = provider_cfg.get_provider_name() if not self._name: self._name = 'ApiAggregator' # # We have three providers here: # Aggregator: To aggregate services from local and remote providers # IntrospectionProvider: To serve introspection requests. This has # all the introspection services but it will decide whether to # route the introspection call to LocalProvider or Remote provider # based on where it is located. # LocalProvider: To serve requests for services specified in # 'local.interfaces'. This also has introspection services that # only provides introspection for services in this LocalProvider. # # For non-introspection calls: # - If service is in LocalProvider, flow is Aggregator -> LocalProvider # - If service is in remote ApiProvider, flow is Aggregator -> Remote # api provider # # For introspection calls: # - For a service in remote ApiProvider, flow is # Aggregator -> Introspection services in IntrospectionProvider # - For a service in LocalProvider, flow is # Aggregator -> Introspection services in IntrospectionProvider # - For a service in introspection set of services, flow is # Aggregator -> IntrospectionProvider -> Introspection services in # LocalProvider (If it is handled by IntrospectionProvider, then we # will go into infinite recursive calls) # # Retrieve the local provider singleton instance to hold all the # services present in 'local.interfaces' property of in the properties # file. Even if there are no interfaces in local.interfaces, this is # required as it would serve introspection requests for introspection # api. l_provider = local_provider.get_provider() self._introspector = AggregatorIntrospector(self._name, l_provider) l_provider.register_by_properties(provider_cfg) # Create a local provider to hold the introspection services that # aggregates introspection information from the local provider and # all the remote providers. introspection_provider = LocalProvider(load_introspection=False) for service in self._introspector.get_introspection_services(): # Register the introspection service with the local provider introspection_provider.add_interface(service) # Register the introspection service with this aggregator self.register_service(service.get_identifier().get_name(), introspection_provider) # Registering services from local provider service_ids = self.get_service_identifiers(l_provider) self._local_service_ids = service_ids for service_id in service_ids: if service_id not in self._introspection_service_names: self.register_service(service_id, l_provider) # Register the children for child in provider_cfg.get_child_providers(): self._process_child(provider_cfg, child) def get_providers(self): """ Return the connection information of ApiProviders registered with the AggregatorProvider :rtype: :class:`list` of :class:`tuple` of ( :class:`str`, :class:`str`, :class:`str`) :return: Tuple containing rpc protocol, msg protocol and uri """ return self._provider_data def register_service(self, service_id, provider): """ Register an service with the AggregatorProvider :type service_id: :class:`str` :param service_id: Service identifier :type provider: :class:`vmware.vapi.core.ApiProvider` :param provider: ApiProvider impl. for the specified service identifier """ if service_id in self._service_id_map: logger.error('Service already registered: %s', service_id) else: self._service_id_map[service_id] = provider self._introspector.add_service(service_id, provider) logger.info('Registering service: %s', service_id) def register_provider(self, provider, name, rpc, msg, addr): """ Register a ApiProvider with the AggregatorProvider :type provider: :class:`vmware.vapi.core.ApiProvider` :param provider: ApiProvider to be registered :type name: :class:`str` :param name: Provider name :type rpc: :class:`str` :param rpc: RPC Protocol :type msg: :class:`str` :param msg: Msg Protocol :type addr: :class:`str` :param addr: URI of the ApiProvider """ self._provider_data[name] = (rpc, msg, addr) old_provider = self._providers.get(name) if old_provider: return service_ids = self.get_service_identifiers(provider) for service_id in service_ids: if service_id not in self._introspection_service_names: self.register_service(service_id, provider) self._providers[name] = provider def unregister_service(self, service_id): """ Unregister an service from AggregatorProvider :type service_id: :class:`str` :param service_id: service to be unregistered """ if service_id in self._service_id_map: del self._service_id_map[service_id] self._introspector.remove_service(service_id) def unregister_provider(self, provider_name): """ Unregister a provider from AggregatorProvider :type provider_name: :class:`str` :param provider_name: Provider to be unregistered """ provider = self._providers.get(provider_name) if not provider: return service_ids = self.get_service_identifiers(provider) for service_id in service_ids: self.unregister_service(service_id) del self._providers[provider_name] del self._provider_data[provider_name] logger.info('Unregistering Provider %s', provider.get_definition()) def unregister_provider_by_name(self, provider_name): """ Unregister a provider from AggregatorProvider :type provider_name: :class:`str` :param provider_name: Provider to be unregistered """ self.unregister_provider(provider_name) def reload_providers(self): """ Reload all the providers in the AggregatorProvider """ providers = list( self._providers.values()) + [local_provider.get_provider()] for provider in providers: service_ids = self.get_service_identifiers(provider) for service_id in service_ids: self.register_service(service_id, provider) ########################################################################### # # Implementation of Api Provider interface # ########################################################################### def _invoke_int(self, service_id, operation_id, input_value, ctx): """ Internal implementation of invoke method :type service_id: :class:`str` :param service_id: Service identifier :type operation_id: :class:`str` :param operation_id: Operation identifier :type input_value: :class:`vmware.vapi.data.value.StructValue` :param input_value: Method input parameters :type ctx: :class:`vmware.vapi.core.ExecutionContext` :param ctx: Execution context for this method :rtype: :class:`vmware.vapi.core.MethodResult` :return: Result of the method invocation """ # Check if the provider exists provider = self._service_id_map.get(service_id) if not provider: msg = message_factory.get_message( 'vapi.method.input.invalid.interface', service_id) logger.error(msg) error_value = make_error_value_from_msgs( self._operation_not_found_def, msg) return MethodResult(error=error_value) # Continue the authentication chain only if the target service # is not in the local provider of this process. i.e. for only # remote calls if service_id not in self._local_service_ids: for processor in self._authn_chain_processors: ctx.security_context = processor.next_context( ctx.security_context) # Actual method execution try: method_result = provider.invoke(service_id, operation_id, input_value, ctx) return method_result except Exception as e: stack_trace = traceback.format_exc() logger.error( 'Service: %s, Operation: %s, input_value %s: exception: %s', service_id, operation_id, input_value, stack_trace) msg = message_factory.get_message('vapi.method.invoke.exception', str(e)) error_value = make_error_value_from_msgs( self._internal_server_error_def, msg) method_result = MethodResult(error=error_value) return method_result def invoke(self, service_id, operation_id, input_value, ctx): """ Invokes the specified method using the execution context and the input provided :type service_id: :class:`str` :param service_id: Service identifier :type operation_id: :class:`str` :param operation_id: Operation identifier :type input_value: :class:`vmware.vapi.data.value.StructValue` :param input_value: Method input parameters :type ctx: :class:`vmware.vapi.core.ExecutionContext` :param ctx: Execution context for this method :rtype: :class:`vmware.vapi.core.MethodResult` :return: Result of the method invocation """ logger.info("Started: Service: %s, Operation: %s, Ctx: %s", service_id, operation_id, ctx) method_result = self._invoke_int(service_id, operation_id, input_value, ctx) logger.info("Finished: Service: %s. Operation %s, Ctx: %s", service_id, operation_id, ctx) return method_result
'resources') mock_interface_id = InterfaceIdentifier('mock') mock_method_id = MethodIdentifier(mock_interface_id, 'mock') mock_interface_def = InterfaceDefinition(mock_interface_id, [mock_method_id]) mock_method_def = MethodDefinition( mock_method_id, StructDefinition('input', [('input', VoidDefinition())]), IntegerDefinition(), []) errors = [ 'com.vmware.vapi.std.errors.internal_server_error', 'com.vmware.vapi.std.errors.invalid_argument', 'com.vmware.vapi.std.errors.operation_not_found', 'com.vmware.vapi.std.errors.unauthenticated', ] error_defs = [make_std_error_def(error) for error in errors] error_values = ListValue( [convert_data_def_to_data_value(error_def) for error_def in error_defs]) logging.basicConfig(level=logging.INFO) class MockupApiInterface(ApiInterface): def __init__(self): self.iface_id = InterfaceIdentifier('mock') self.method_id = MethodIdentifier(self.iface_id, 'mock') def get_identifier(self): return self.iface_id def get_definition(self): return InterfaceDefinition(self.iface_id, [self.method_id])
class TaskHandle(object): """ The TaskHandle interface for publishing task info. This class is not thread safe. Info returned from the get is mutable. Task method will not return task ID to client until provider calls publish method with accept=True once. Provider must call publish to save the updates to task state into TaskManager. """ _internal_server_error_def = make_std_error_def( 'com.vmware.vapi.std.errors.internal_server_error') def __init__(self, info_type, result_type): self.task_manager = get_task_manager() self.info_type = info_type self.result_type = result_type self.error_types = [] self.task_id = get_task_id() self.info = info_type() self._initialize_common_info() def _initialize_common_info(self): """ Initialize common task info fields """ summary = self.task_manager.get_summary(self.task_id) published_info = summary.info self.error_types = summary.errors self._override_api_info(published_info) self.info.cancelable = TypeConverter.convert_to_python( published_info.get_field('cancelable'), BooleanType()) desc = published_info.get_field('description') if desc is not None: self.info.description = TypeConverter.convert_to_python( desc, LocalizableMessage.get_binding_type()) self.info.status = TypeConverter.convert_to_python( published_info.get_field('status'), StringType()) try: user = published_info.get_field('user') self.info.user = TypeConverter.convert_to_python( user, StringType()) except: # pylint: disable=W0702 pass def _override_api_info(self, published_info): """ Override service and operation task info fields """ self.info.service = TypeConverter.convert_to_python( published_info.get_field('service'), StringType()) self.info.operation = TypeConverter.convert_to_python( published_info.get_field('operation'), StringType()) def get_info(self): """ Returns the Task Info. """ return self.info def get_published_info(self): """ Returns the current published task Info """ info = self.task_manager.get_info(self.task_id) converted_info = TypeConverter.convert_to_python( info, self.info_type.get_binding_type()) return converted_info def publish(self, accept=False): """ Publish the temporary task info into task manager :type accept: :class:`bool` :param accept: Accept task and return task id to client """ msg_list = None published_info = self.task_manager.get_info(self.task_id) # Override the common info fields which can't be modified by providers self._override_api_info(published_info) if hasattr(self.info, 'error') and self.info.error is not None: err_type = self.info.error.get_binding_type() # Verify if the error set by provider is amongst the ones # defined in VMODL2, if not throw InternalServerError if (err_type not in self.error_types): msg_list = [ message_factory.get_message('vapi.task.invalid.error', err_type.name, self.info.operation) ] if hasattr(self.info, 'result') and self.info.result is not None: # Check if result type is Opaque or actual type res_type = self.info_type.get_binding_type().get_field('result') if isinstance(res_type.element_type.definition, OpaqueDefinition): result = None try: result = TypeConverter.convert_to_vapi( self.info.result, self.result_type.get_binding_type()) except AttributeError: try: result = TypeConverter.convert_to_vapi( self.info.result, self.result_type) except CoreException: msg_list = [ message_factory.get_message( 'vapi.task.invalid.result', self.info.result, self.info.operation) ] self.info.result = None if msg_list is None: self.info.result = result info = TypeConverter.convert_to_vapi(self.info, self.info_type.get_binding_type()) if msg_list is not None: info.set_field('status', FAILED_STRING_VALUE) logger.error(msg_list[0]) error = make_error_value_from_msgs(self._internal_server_error_def, *msg_list) info.set_field('error', OptionalValue(error)) self.task_manager.set_info(self.task_id, info) if accept: event = get_event() event.set() clear_event() # Validate that description is set while accepting the task desc = self.info.description if desc is None or (not desc.id and not desc.default_message): msg = message_factory.get_message( 'vapi.data.structure.field.missing', self.info_type, 'description') logger.debug(msg) raise CoreException(msg) def is_marked_for_cancellation(self): """ Check whether a task is marked for cancellation. """ return self.task_manager.is_marked_for_cancellation(self.task_id)