def test__update_router_provider_invalid(self): test_dc = driver_controller.DriverController(self.fake_l3) with mock.patch.object(test_dc, "_get_provider_for_router"): with mock.patch.object( driver_controller, "_ensure_driver_supports_request") as _ensure: _ensure.side_effect = lib_exc.InvalidInput( error_message='message') self.assertRaises( lib_exc.InvalidInput, test_dc._update_router_provider, None, None, None, None, None, {'name': 'testname'}, {'flavor_id': 'old_fid'}, None)
def _validate_router_interface_plugin(self, context, router_plugin, interface_info): is_port, is_sub = self._validate_interface_info(interface_info) if is_port: net_id = self._get_port(context, interface_info['port_id'])['network_id'] elif is_sub: net_id = self._get_subnet( context, interface_info['subnet_id'])['network_id'] net_plugin = self._get_plugin_from_net_id(context, net_id) if net_plugin.plugin_type() != router_plugin.plugin_type(): err_msg = (_('Router interface should belong to the %s plugin ' 'as the router') % router_plugin.plugin_type()) raise n_exc.InvalidInput(error_message=err_msg)
def _validate_network_plugin( self, context, network_info, plugin_type=projectpluginmap.NsxPlugins.NSX_V): """Make sure the network belongs to the NSX0-V plugin""" if not network_info.get('network_id'): msg = _("network_id must be specified") raise n_exc.BadRequest(resource=bgp_ext.BGP_SPEAKER_RESOURCE_NAME, msg=msg) net_id = network_info['network_id'] p = self._core_plugin._get_plugin_from_net_id(context, net_id) if p.plugin_type() != plugin_type: msg = (_('Network should belong to the %s plugin as the bgp ' 'speaker') % plugin_type) raise n_exc.InvalidInput(error_message=msg)
def _update_router_provider(self, resource, event, trigger, context, router_id, router, old_router, router_db, **kwargs): """Handle transition between providers. The provider can currently be changed only by the caller updating 'ha' and/or 'distributed' attributes. If we allow updates of flavor_id directly in the future those requests will also land here. """ drv = self.get_provider_for_router(context, router_id) new_drv = None if _flavor_specified(router): if router['flavor_id'] != old_router['flavor_id']: # TODO(kevinbenton): this is currently disallowed by the API # so we shouldn't hit it but this is a placeholder to add # support later. raise NotImplementedError() # the following is to support updating the 'ha' and 'distributed' # attributes via the API. try: _ensure_driver_supports_request(drv, router) except lib_exc.InvalidInput: # the current driver does not support this request, we need to # migrate to a new provider. populate the distributed and ha # flags from the previous state if not in the update so we can # determine the target provider appropriately. # NOTE(kevinbenton): if the router is associated with a flavor # we bail because changing the provider without changing # the flavor will make things inconsistent. We can probably # update the flavor automatically in the future. if old_router['flavor_id']: raise lib_exc.InvalidInput(error_message=_( "Changing the 'ha' and 'distributed' attributes on a " "router associated with a flavor is not supported")) if 'distributed' not in router: router['distributed'] = old_router['distributed'] if 'ha' not in router: router['ha'] = old_router['ha'] new_drv = self._attrs_to_driver(router) if new_drv: LOG.debug("Router %(id)s migrating from %(old)s provider to " "%(new)s provider.", {'id': router_id, 'old': drv, 'new': new_drv}) _ensure_driver_supports_request(new_drv, router) # TODO(kevinbenton): notify old driver explicitly of driver change with context.session.begin(subtransactions=True): self._stm.del_resource_associations(context, [router_id]) self._stm.add_resource_association( context, plugin_constants.L3, new_drv.name, router_id)
def _validate_port_vnic_type(self, context, port_data, network_id): vnic_type = port_data.get(pbin.VNIC_TYPE) if vnic_type and vnic_type not in SUPPORTED_VNIC_TYPES: err_msg = _("Invalid port vnic-type '%(vnic_type)s'." "Supported vnic-types are %(valid_types)s." ) % {'vnic_type': vnic_type, 'valid_types': SUPPORTED_VNIC_TYPES} raise exceptions.InvalidInput(error_message=err_msg) direct_vnic_type = vnic_type in VNIC_TYPES_DIRECT_PASSTHROUGH if direct_vnic_type: self._validate_vnic_type_direct_passthrough_for_network( context, network_id) return direct_vnic_type
def convert_to_boolean(data): """Convert a data value into a python bool. :param data: The data value to convert to a python bool. This function supports string types, bools, and ints for conversion of representation to python bool. :returns: The bool value of 'data' if it can be coerced. :raises InvalidInput: If the value can't be coerced to a python bool. """ try: return strutils.bool_from_string(data, strict=True) except ValueError: msg = _("'%s' cannot be converted to boolean") % data raise n_exc.InvalidInput(error_message=msg)
def process_create_address_scope(self, plugin_context, data, result): if (data.get(cisco_apic.DIST_NAMES) and data[cisco_apic.DIST_NAMES].get(cisco_apic.VRF)): dn = data[cisco_apic.DIST_NAMES][cisco_apic.VRF] try: vrf = aim_res.VRF.from_dn(dn) except aim_exc.InvalidDNForAciResource: raise n_exc.InvalidInput( error_message=('%s is not valid VRF DN' % dn)) # Check if another address scope already maps to this VRF. session = plugin_context.session mappings = self._get_address_scope_mappings_for_vrf(session, vrf) vrf_owned = False for mapping in mappings: if mapping.address_scope.ip_version == data['ip_version']: raise n_exc.InvalidInput(error_message=( 'VRF %s is already in use by address-scope %s' % (dn, mapping.scope_id))) vrf_owned = mapping.vrf_owned self._add_address_scope_mapping(session, result['id'], vrf, vrf_owned)
def _test_fixed_ips_for_port(self, context, network_id, fixed_ips, device_owner, subnets): """Test fixed IPs for port. Check that configured subnets are valid prior to allocating any IPs. Include the subnet_id in the result if only an IP address is configured. :raises: InvalidInput, IpAddressInUse, InvalidIpForNetwork, InvalidIpForSubnet """ fixed_ip_set = [] for fixed in fixed_ips: subnet = self._get_subnet_for_fixed_ip(context, fixed, subnets) is_auto_addr_subnet = ipv6_utils.is_auto_address_subnet(subnet) if ('ip_address' in fixed and subnet['cidr'] != n_const.PROVISIONAL_IPV6_PD_PREFIX): # Ensure that the IP's are unique if not IpamNonPluggableBackend._check_unique_ip( context, network_id, subnet['id'], fixed['ip_address']): raise n_exc.IpAddressInUse(net_id=network_id, ip_address=fixed['ip_address']) if (is_auto_addr_subnet and device_owner not in constants.ROUTER_INTERFACE_OWNERS): msg = (_("IPv6 address %(address)s can not be directly " "assigned to a port on subnet %(id)s since the " "subnet is configured for automatic addresses") % { 'address': fixed['ip_address'], 'id': subnet['id'] }) raise n_exc.InvalidInput(error_message=msg) fixed_ip_set.append({ 'subnet_id': subnet['id'], 'ip_address': fixed['ip_address'] }) else: # A scan for auto-address subnets on the network is done # separately so that all such subnets (not just those # listed explicitly here by subnet ID) are associated # with the port. if (device_owner in constants.ROUTER_INTERFACE_OWNERS_SNAT or not is_auto_addr_subnet): fixed_ip_set.append({'subnet_id': subnet['id']}) self._validate_max_ips_per_port(fixed_ip_set, device_owner) return fixed_ip_set
def _get_allocate_mock(self, subnet_id, auto_ip='10.0.0.2', fail_ip='127.0.0.1', exception=n_exc.InvalidInput( error_message='SomeError')): def allocate_mock(request): if isinstance(request, ipam_req.SpecificAddressRequest): if request.address == netaddr.IPAddress(fail_ip): raise exception else: return str(request.address), subnet_id else: return auto_ip, subnet_id return allocate_mock
def _process_provider_segment(self, segment): (network_type, physical_network, segmentation_id) = (self._get_attribute(segment, attr) for attr in provider.ATTRIBUTES) if validators.is_attr_set(network_type): segment = {ml2_api.NETWORK_TYPE: network_type, ml2_api.PHYSICAL_NETWORK: physical_network, ml2_api.SEGMENTATION_ID: segmentation_id} self.validate_provider_segment(segment) return segment msg = _("network_type required") raise exc.InvalidInput(error_message=msg)
def create_wan_tc(self, context, wan_tc): LOG.debug('got WAN_TC: %s' % wan_tc) wan_tc_req = wan_tc['wan_tc'] filter_db = self.get_wan_tc_filters( context, filters={'network': [wan_tc_req['network']]}) if filter_db: raise exceptions.InvalidInput( error_message='Network already has limiter') network = self._core_plugin.get_network(context, wan_tc_req['network']) if network['provider:network_type'] != 'vxlan': raise exceptions.InvalidInput() vni = network['provider:segmentation_id'] tc_class = { 'wan_tc_class': { 'direction': 'both', 'min': wan_tc_req['min'] } } if 'max' in wan_tc_req: tc_class['wan_tc_class']['max'] = wan_tc_req['max'] tc_class_db = self.create_wan_tc_class(context, tc_class) tc_filter_req = { 'wan_tc_filter': { 'protocol': 'vxlan', 'match': 'vni=%s' % vni, 'class_id': tc_class_db['id'], 'network': network['id'] } } tc_filter_db = self.create_wan_tc_filter(context, tc_filter_req) tc_filter_db['min'] = tc_class_db['min'] tc_filter_db['max'] = tc_class_db['max'] return tc_filter_db
def _validate_network(self, context, net_data): network_type = net_data.get(pnet.NETWORK_TYPE) network_type_set = validators.is_attr_set(network_type) segmentation_id = net_data.get(pnet.SEGMENTATION_ID) segmentation_id_set = validators.is_attr_set(segmentation_id) if not context.is_admin: err_msg = _("Only an admin can create a DVS provider " "network") raise n_exc.InvalidInput(error_message=err_msg) err_msg = None if not network_type_set: err_msg = _("Network provider information must be " "specified") raise n_exc.InvalidInput(error_message=err_msg) if (network_type == c_utils.NetworkTypes.FLAT or network_type == c_utils.NetworkTypes.PORTGROUP): if segmentation_id_set: err_msg = (_("Segmentation ID cannot be specified with " "%s network type"), network_type) elif network_type == c_utils.NetworkTypes.VLAN: if not segmentation_id_set: err_msg = _("Segmentation ID must be specified with " "vlan network type") if (segmentation_id_set and not utils.is_valid_vlan_tag(segmentation_id)): err_msg = (_("%(segmentation_id)s out of range " "(%(min_id)s through %(max_id)s)") % { 'segmentation_id': segmentation_id, 'min_id': constants.MIN_VLAN_TAG, 'max_id': constants.MAX_VLAN_TAG }) else: err_msg = (_("%(net_type_param)s %(net_type_value)s not " "supported") % { 'net_type_param': pnet.NETWORK_TYPE, 'net_type_value': network_type }) if err_msg: raise n_exc.InvalidInput(error_message=err_msg)
def _get_subnet_for_fixed_ip(self, context, fixed, subnets): # Subnets are all the subnets belonging to the same network. if not subnets: msg = _('IP allocation requires subnets for network') raise exc.InvalidInput(error_message=msg) if 'subnet_id' in fixed: def get_matching_subnet(): for subnet in subnets: if subnet['id'] == fixed['subnet_id']: return subnet subnet = get_matching_subnet() if not subnet: subnet = self._get_subnet(context, fixed['subnet_id']) msg = (_("Failed to create port on network %(network_id)s" ", because fixed_ips included invalid subnet " "%(subnet_id)s") % { 'network_id': subnet['network_id'], 'subnet_id': fixed['subnet_id'] }) raise exc.InvalidInput(error_message=msg) # Ensure that the IP is valid on the subnet if ('ip_address' in fixed and not ipam_utils.check_subnet_ip( subnet['cidr'], fixed['ip_address'])): raise exc.InvalidIpForSubnet(ip_address=fixed['ip_address']) return subnet if 'ip_address' not in fixed: msg = _('IP allocation requires subnet_id or ip_address') raise exc.InvalidInput(error_message=msg) for subnet in subnets: if ipam_utils.check_subnet_ip(subnet['cidr'], fixed['ip_address']): return subnet raise exc.InvalidIpForNetwork(ip_address=fixed['ip_address'])
def query_with_hooks(context, model, field=None): """Query with hooks using the said context and model. :param context: The context to use for the DB session. :param model: The model to query. :param field: The column. :returns: The query with hooks applied to it. """ if field: if hasattr(model, field): field = getattr(model, field) else: msg = _("'%s' is not supported as field") % field raise n_exc.InvalidInput(error_message=msg) query = context.session.query(field) else: query = context.session.query(model) # define basic filter condition for model query query_filter = None if db_utils.model_query_scope_is_project(context, model): if hasattr(model, 'rbac_entries'): query = query.outerjoin(model.rbac_entries) rbac_model = model.rbac_entries.property.mapper.class_ query_filter = ( (model.tenant_id == context.tenant_id) | (rbac_model.action.in_( [constants.ACCESS_SHARED, constants.ACCESS_READONLY]) & ((rbac_model.target_tenant == context.tenant_id) | (rbac_model.target_tenant == '*')))) elif hasattr(model, 'shared'): query_filter = ((model.tenant_id == context.tenant_id) | (model.shared == sql.true())) else: query_filter = (model.tenant_id == context.tenant_id) # Execute query hooks registered from mixins and plugins for hook in get_hooks(model): query_hook = helpers.resolve_ref(hook.get('query')) if query_hook: query = query_hook(context, model, query) filter_hook = helpers.resolve_ref(hook.get('filter')) if filter_hook: query_filter = filter_hook(context, model, query_filter) # NOTE(salvatore-orlando): 'if query_filter' will try to evaluate the # condition, raising an exception if query_filter is not None: query = query.filter(query_filter) return query
def validate_obj_azs(self, availability_zones): """Verify that the availability zones exist, and only 1 hint was set. """ # For now we support only 1 hint per network/router # TODO(asarfaty): support multiple hints if len(availability_zones) > 1: err_msg = _("Can't use multiple availability zone hints") raise n_exc.InvalidInput(error_message=err_msg) # check that all hints appear in the predefined list of availability # zones diff = (set(availability_zones) - set(self.get_azs_names())) if diff: raise az_exc.AvailabilityZoneNotFound(availability_zone=diff.pop())
def _validate_multicast_ip_range(self, network_profile): """ Validate multicast ip range values. :param network_profile: network profile object """ try: min_ip, max_ip = (network_profile['multicast_ip_range'].split( '-', 1)) except ValueError: msg = _LE("Invalid multicast ip address range. " "example range: 224.1.1.1-224.1.1.10") LOG.error(msg) raise n_exc.InvalidInput(error_message=msg) for ip in [min_ip, max_ip]: try: if not netaddr.IPAddress(ip).is_multicast(): msg = _LE("%s is not a valid multicast ip address") % ip LOG.error(msg) raise n_exc.InvalidInput(error_message=msg) if netaddr.IPAddress(ip) <= netaddr.IPAddress('224.0.0.255'): msg = _LE("%s is reserved multicast ip address") % ip LOG.error(msg) raise n_exc.InvalidInput(error_message=msg) except netaddr.AddrFormatError: msg = _LE("%s is not a valid ip address") % ip LOG.error(msg) raise n_exc.InvalidInput(error_message=msg) if netaddr.IPAddress(min_ip) > netaddr.IPAddress(max_ip): msg = (_LE("Invalid multicast IP range '%(min_ip)s-%(max_ip)s':" " Range should be from low address to high address") % { 'min_ip': min_ip, 'max_ip': max_ip }) LOG.error(msg) raise n_exc.InvalidInput(error_message=msg)
def create_network_precommit(self, context): """Allocate resources for a new network. :param context: NetworkContext instance describing the new network. Create a new network, allocating resources as necessary in the database. Called inside transaction context on session. Call cannot block. Raising an exception will result in a rollback of the current transaction. """ network = context.current # TODO(rtheis): Add support for multi-provider networks when # routed networks are supported. if self._get_attribute(network, mpnet.SEGMENTS): msg = _('Multi-provider networks are not supported') raise n_exc.InvalidInput(error_message=msg) network_segments = context.network_segments network_type = network_segments[0]['network_type'] segmentation_id = network_segments[0]['segmentation_id'] physical_network = network_segments[0]['physical_network'] LOG.debug('Creating network with type %(network_type)s, ' 'segmentation ID %(segmentation_id)s, ' 'physical network %(physical_network)s' % {'network_type': network_type, 'segmentation_id': segmentation_id, 'physical_network': physical_network}) if network_type not in [plugin_const.TYPE_LOCAL, plugin_const.TYPE_FLAT, plugin_const.TYPE_GENEVE, plugin_const.TYPE_VLAN]: msg = _('Network type %s is not supported') % network_type raise n_exc.InvalidInput(error_message=msg)
def _validate(self, context, subport): # Check that the subport doesn't reference the same port_id as a # trunk we may be in the middle of trying to create, in other words # make the validation idiot proof. if subport['port_id'] == self.trunk_port_id: raise trunk_exc.ParentPortInUse(port_id=subport['port_id']) # If the segmentation details are missing, we will need to # figure out defaults when the time comes to support Ironic. # We can reasonably expect segmentation details to be provided # in all other cases for now. try: segmentation_type = subport["segmentation_type"] segmentation_id = ( converters.convert_to_int(subport["segmentation_id"])) except KeyError: msg = _("Invalid subport details '%s': missing segmentation " "information. Must specify both segmentation_id and " "segmentation_type") % subport raise n_exc.InvalidInput(error_message=msg) except n_exc.InvalidInput: msg = _("Invalid subport details: segmentation_id '%s' is " "not an integer") % subport["segmentation_id"] raise n_exc.InvalidInput(error_message=msg) if segmentation_type not in self._segmentation_types: msg = _("Unknown segmentation_type '%s'") % segmentation_type raise n_exc.InvalidInput(error_message=msg) if not self._segmentation_types[segmentation_type](segmentation_id): msg = _("Segmentation ID '%s' is not in range") % segmentation_id raise n_exc.InvalidInput(error_message=msg) trunk_validator = TrunkPortValidator(subport['port_id']) trunk_validator.validate(context) return subport
def _validate_network_segments(self, network_segments): for network_segment in network_segments: network_type = network_segment['network_type'] segmentation_id = network_segment['segmentation_id'] physical_network = network_segment['physical_network'] LOG.debug('Validating network segment with ' 'type %(network_type)s, ' 'segmentation ID %(segmentation_id)s, ' 'physical network %(physical_network)s' % {'network_type': network_type, 'segmentation_id': segmentation_id, 'physical_network': physical_network}) if not self._is_network_type_supported(network_type): msg = _('Network type %s is not supported') % network_type raise n_exc.InvalidInput(error_message=msg)
def get_lb_flavor_size(flavor_plugin, context, flavor_id): if not flavor_id: return lb_const.DEFAULT_LB_SIZE else: flavor = flavors_plugin.FlavorsPlugin.get_flavor( flavor_plugin, context, flavor_id) flavor_size = flavor['name'] if flavor_size in lb_const.LB_FLAVOR_SIZES: return flavor_size.upper() else: err_msg = (_("Invalid flavor size %(flavor)s, only 'small', " "'medium', or 'large' are supported") % { 'flavor': flavor_size }) raise n_exc.InvalidInput(error_message=err_msg)
def _process_provider_create(self, network): net_type = network.get(pnet.NETWORK_TYPE) if not validators.is_attr_set(net_type): return None if net_type == m_const.TYPE_MIDONET: return None if net_type != m_const.TYPE_UPLINK: msg = _('Unsupported network type %(type)s detected ' 'in a create network request.') % {'type': net_type} raise n_exc.InvalidInput(error_message=msg) return net_type
def _process_ext_policy_create(self, resource, event, trigger, context, object_type, policy, **kwargs): if (object_type != 'network' or policy['action'] != 'access_as_external'): return net = self.get_network(context, policy['object_id']) if not context.is_admin and net['tenant_id'] != context.tenant_id: msg = _("Only admins can manipulate policies on networks they " "do not own.") raise n_exc.InvalidInput(error_message=msg) if not self._network_is_external(context, policy['object_id']): # we automatically convert the network into an external network self._process_l3_update(context, net, {external_net.EXTERNAL: True}, allow_all=False)
def _get_lb_flavor_size(self, context, flavor_id): if not flavor_id: return vcns_const.SERVICE_SIZE_MAPPING['lb'] else: flavor = flavors_plugin.FlavorsPlugin.get_flavor( self.flavor_plugin, context, flavor_id) flavor_size = flavor['name'] if flavor_size.lower() in vcns_const.ALLOWED_EDGE_SIZES: return flavor_size.lower() else: err_msg = (_("Invalid flavor size %(flavor)s, only %(sizes)s " "are supported") % {'flavor': flavor_size, 'sizes': vcns_const.ALLOWED_EDGE_SIZES}) raise n_exc.InvalidInput(error_message=err_msg)
def convert_string_to_case_insensitive(data): """Convert a string value into a lower case string. This effectively makes the string case-insensitive. :param data: The value to convert. :return: The lower-cased string representation of the value, or None is 'data' is None. :raises InvalidInput: If the value is not a string. """ try: return data.lower() except AttributeError: error_message = _("Input value %s must be string type") % data raise n_exc.InvalidInput(error_message=error_message)
def convert_to_boolean(data): if isinstance(data, six.string_types): val = data.lower() if val == "true" or val == "1": return True if val == "false" or val == "0": return False elif isinstance(data, bool): return data elif isinstance(data, int): if data == 0: return False elif data == 1: return True msg = _("'%s' cannot be converted to boolean") % data raise n_exc.InvalidInput(error_message=msg)
def _validate_vnic_type_direct_passthrough_for_network( self, context, network_id): supported_network_types = (c_utils.NsxVNetworkTypes.VLAN, c_utils.NsxVNetworkTypes.FLAT, c_utils.NsxVNetworkTypes.PORTGROUP) if not self._validate_network_type(context, network_id, supported_network_types): msg_info = { 'vnic_types': VNIC_TYPES_DIRECT_PASSTHROUGH, 'networks': supported_network_types } err_msg = _("%(vnic_types)s port vnic-types are only supported " "for ports on networks of types " "%(networks)s.") % msg_info raise exceptions.InvalidInput(error_message=err_msg)
def convert_to_positive_float_or_none(val): # NOTE(salv-orlando): This conversion function is currently used by # a vendor specific extension only at the moment It is used for # port's RXTX factor in neutron.plugins.vmware.extensions.qos. # It is deemed however generic enough to be in this module as it # might be used in future for other API attributes. if val is None: return try: val = float(val) if val < 0: raise ValueError() except (ValueError, TypeError): msg = _("'%s' must be a non negative decimal.") % val raise n_exc.InvalidInput(error_message=msg) return val
def validate(self, context, basic_validation=False, trunk_validation=True): """Validate that subports can be used in a trunk.""" # Perform basic validation on subports, in case subports # are not automatically screened by the API layer. if basic_validation: msg = validators.validate_subports(self.subports) if msg: raise n_exc.InvalidInput(error_message=msg) if trunk_validation: trunk_port_mtu = self._get_port_mtu(context, self.trunk_port_id) return [ self._validate(context, s, trunk_port_mtu) for s in self.subports ] else: return self.subports
def add_bgp_peer(self, context, bgp_speaker_id, bgp_peer_info): # speaker & peer must belong to the same driver if not bgp_peer_info.get('bgp_peer_id'): msg = _("bgp_peer_id must be specified") raise n_exc.BadRequest(resource='bgp-peer', msg=msg) peer_driver = self._get_driver_by_peer(context, bgp_peer_info['bgp_peer_id']) speaker_driver = self._get_driver_by_speaker(context, bgp_speaker_id) if peer_driver != speaker_driver: msg = _("Peer and Speaker must belong to the same plugin") raise n_exc.InvalidInput(error_message=msg) with locking.LockManager.get_lock(str(bgp_speaker_id)): speaker_driver.add_bgp_peer(context, bgp_speaker_id, bgp_peer_info) return super(NSXBgpPlugin, self).add_bgp_peer(context, bgp_speaker_id, bgp_peer_info)
def convert_cidr_to_canonical_format(value): """CIDR is validated and converted to canonical format. :param value: The CIDR which needs to be checked. :returns: - 'value' if 'value' is CIDR with IPv4 address, - CIDR with canonical IPv6 address if 'value' is IPv6 CIDR. :raises: - InvalidInput if 'value' is None, not a valid CIDR or invalid IP Format. """ error_message = _("%s is not in a CIDR format") % value try: cidr = netaddr.IPNetwork(value) return str(convert_ip_to_canonical_format(cidr.ip)) + "/" + str( cidr.prefixlen) except netaddr.core.AddrFormatError: raise n_exc.InvalidInput(error_message=error_message)