def parse_headers(headers, default_version, latest_version): """Determine the API version requested based on the headers supplied. :param headers: webob headers :param default_version: version to use if not specified in headers :param latest_version: version to use if latest is requested :returns: a tuple of (major, minor) version numbers :raises: webob.HTTPNotAcceptable """ version_hdr = headers.get(Version.string, default_version) try: version_service, version_str = version_hdr.split() except ValueError: raise exc.HTTPNotAcceptable(_( "Invalid service type for %s header") % Version.string) if version_str.lower() == 'latest': version_service, version_str = latest_version.split() if version_service != Version.service_string: raise exc.HTTPNotAcceptable(_( "Invalid service type for %s header") % Version.string) try: version = tuple(int(i) for i in version_str.split('.')) except ValueError: version = () if len(version) != 2: raise exc.HTTPNotAcceptable(_( "Invalid value for %s header") % Version.string) return version
def search_image(self, context, repo, tag, exact_match): image_ref = docker_image.Reference.parse(repo) registry, image_name = image_ref.split_hostname() if registry and registry != 'docker.io': # Images searching is only supported in DockerHub msg = _('Image searching is not supported in registry: {0}') raise exception.OperationNotSupported(msg.format(registry)) with docker_utils.docker_client() as docker: try: # TODO(hongbin): search image by both name and tag images = docker.search(image_name) except errors.APIError as api_error: raise exception.ZunException(six.text_type(api_error)) except Exception as e: msg = _('Cannot search image in docker: {0}') raise exception.ZunException(msg.format(e)) if exact_match: images = [i for i in images if i['name'] == image_name] for image in images: image['metadata'] = {} for key in ('is_official', 'star_count'): value = image.pop(key, None) if value is not None: image['metadata'][key] = value # TODO(hongbin): convert images to a list of Zun Image object return images
def _parse_white_list_from_config(self, whitelists): """Parse and validate the pci whitelist from the zun config.""" specs = [] for jsonspec in whitelists: try: dev_spec = jsonutils.loads(jsonspec) except ValueError: raise exception.PciConfigInvalidWhitelist( reason=_("Invalid entry: '%s'") % jsonspec) if isinstance(dev_spec, dict): dev_spec = [dev_spec] elif not isinstance(dev_spec, list): raise exception.PciConfigInvalidWhitelist( reason=_("Invalid entry: '%s'; " "Expecting list or dict") % jsonspec) for ds in dev_spec: if not isinstance(ds, dict): raise exception.PciConfigInvalidWhitelist( reason=_("Invalid entry: '%s'; " "Expecting dict") % ds) spec = devspec.PciDeviceSpec(ds) specs.append(spec) return specs
def _check_version(self, version, headers=None): if headers is None: headers = {} # ensure that major version in the URL matches the header if version.major != BASE_VERSION: raise http_error.HTTPNotAcceptableAPIVersion(_( "Mutually exclusive versions requested. Version %(ver)s " "requested but not supported by this service. " "The supported version range is: " "[%(min)s, %(max)s].") % {'ver': version, 'min': MIN_VER_STR, 'max': MAX_VER_STR}, headers=headers, max_version=str(MAX_VER), min_version=str(MIN_VER)) # ensure the minor version is within the supported range if version < MIN_VER or version > MAX_VER: raise http_error.HTTPNotAcceptableAPIVersion(_( "Version %(ver)s was requested but the minor version is not " "supported by this service. The supported version range is: " "[%(min)s, %(max)s].") % {'ver': version, 'min': MIN_VER_STR, 'max': MAX_VER_STR}, headers=headers, max_version=str(MAX_VER), min_version=str(MIN_VER))
def _verify_origin(self, access_url): # Verify Origin expected_origin_hostname = self.headers.get('Host') if ':' in expected_origin_hostname: e = expected_origin_hostname if '[' in e and ']' in e: expected_origin_hostname = e.split(']')[0][1:] else: expected_origin_hostname = e.split(':')[0] expected_origin_hostnames = CONF.websocket_proxy.allowed_origins expected_origin_hostnames.append(expected_origin_hostname) origin_url = self.headers.get('Origin') # missing origin header indicates non-browser client which is OK if origin_url is not None: origin = urlparse.urlparse(origin_url) origin_hostname = origin.hostname origin_scheme = origin.scheme if origin_hostname == '' or origin_scheme == '': detail = _("Origin header not valid.") raise exception.ValidationError(detail) if origin_hostname not in expected_origin_hostnames: detail = _("Origin header does not match this host.") raise exception.ValidationError(detail) if not self.verify_origin_proto(access_url, origin_scheme): detail = _("Origin header protocol does not match this host.") raise exception.ValidationError(detail)
def pull_image(self, context, repo, tag, image_pull_policy, registry): image_loaded = True image = self._search_image_on_host(repo, tag) if not utils.should_pull_image(image_pull_policy, bool(image)): if image: LOG.debug('Image %s present locally', repo) return image, image_loaded else: message = _('Image %s not present with pull policy of Never' ) % repo raise exception.ImageNotFound(message) try: LOG.debug('Pulling image from docker %(repo)s,' ' context %(context)s', {'repo': repo, 'context': context}) self._pull_image(repo, tag, registry) return {'image': repo, 'path': None}, image_loaded except exception.ImageNotFound: with excutils.save_and_reraise_exception(): LOG.error('Image %s was not found in docker repo', repo) except exception.DockerError: with excutils.save_and_reraise_exception(): LOG.error('Docker API error occurred during downloading ' 'image %s', repo) except Exception as e: msg = _('Cannot download image from docker: {0}') raise exception.ZunException(msg.format(e))
def validate_limit(limit): try: if limit is not None and int(limit) <= 0: raise exception.InvalidValue(_("Limit must be positive integer")) except ValueError: raise exception.InvalidValue(_("Limit must be positive integer")) if limit is not None: return min(CONF.api.max_limit, int(limit)) else: return CONF.api.max_limit
def container_create(self, context, new_container, extra_spec, requested_networks, requested_volumes, run, pci_requests=None): try: host_state = self._schedule_container(context, new_container, extra_spec) except exception.NoValidHost: new_container.status = consts.ERROR new_container.status_reason = _( "There are not enough hosts available.") new_container.save(context) return except Exception: new_container.status = consts.ERROR new_container.status_reason = _("Unexpected exception occurred.") new_container.save(context) raise # NOTE(mkrai): Intent here is to check the existence of image # before proceeding to create container. If image is not found, # container create will fail with 400 status. if CONF.api.enable_image_validation: try: images = self.rpcapi.image_search( context, new_container.image, new_container.image_driver, True, new_container.registry, host_state['host']) if not images: raise exception.ImageNotFound(image=new_container.image) if len(images) > 1: raise exception.Conflict('Multiple images exist with same ' 'name. Please use the container ' 'uuid instead.') except exception.OperationNotSupported: LOG.info("Skip validation since search is not supported for " "image '%(image)s' and image driver '%(driver)s'.", {'image': new_container.image, 'driver': new_container.image_driver}) except exception.ReferenceInvalidFormat: raise exception.InvalidValue(_("The format of image name '%s' " "is invalid.") % new_container.image) except Exception as e: new_container.status = consts.ERROR new_container.status_reason = str(e) new_container.save(context) raise self._record_action_start(context, new_container, container_actions.CREATE) self.rpcapi.container_create(context, host_state['host'], new_container, host_state['limits'], requested_networks, requested_volumes, run, pci_requests)
def decorated_function(self, context, volume, **kwargs): provider = volume.volume_provider if provider not in supported_providers: msg = _("The volume provider '%s' is not supported") % provider raise exception.ZunException(msg) return function(self, context, volume, **kwargs)
def load_image_driver(image_driver=None): """Load an image driver module. Load the container image driver module specified by the image_driver configuration option or, if supplied, the driver name supplied as an argument. :param image_driver: container image driver name to override config opt :returns: a ContainerImageDriver instance """ if not image_driver: image_driver = CONF.default_image_driver if not image_driver: LOG.error("Container image driver option required, " "but not specified") sys.exit(1) LOG.info("Loading container image driver '%s'", image_driver) try: driver = stevedore.driver.DriverManager( "zun.image.driver", image_driver, invoke_on_load=True).driver if not isinstance(driver, ContainerImageDriver): raise Exception(_('Expected driver of type: %s') % six.text_type(ContainerImageDriver)) return driver except Exception: LOG.exception("Unable to load the container image driver") sys.exit(1)
def read_mounts(self, filter_device=None, filter_fstype=None): """Read all mounted filesystems. Read all mounted filesystems except filtered option. :param filter_device: Filter for device, the result will not contain the mounts whose device argument in it. :param filter_fstype: Filter for mount point. :return: All mounts. """ if filter_device is None: filter_device = () if filter_fstype is None: filter_fstype = () try: (out, err) = utils.execute('cat', PROC_MOUNTS_PATH, check_exit_code=0) except exception.CommandError: msg = _("Failed to read mounts.") raise exception.FileNotFound(msg) lines = out.split('\n') mounts = [] for line in lines: if not line: continue tokens = line.split() if len(tokens) < 4: continue if tokens[0] in filter_device or tokens[1] in filter_fstype: continue mounts.append(MountInfo(device=tokens[0], mountpoint=tokens[1], fstype=tokens[2], opts=tokens[3])) return mounts
def _test(self, type_, unit, total, used, requested, limit): """Test if the type resource needed for a claim can be allocated.""" LOG.info('Total %(type)s: %(total)d %(unit)s, used: %(used).02f ' '%(unit)s', {'type': type_, 'total': total, 'unit': unit, 'used': used}) if limit is None: # treat resource as unlimited: LOG.info('%(type)s limit not specified, defaulting to ' 'unlimited', {'type': type_}) return free = limit - used # Oversubscribed resource policy info: LOG.info('%(type)s limit: %(limit).02f %(unit)s, ' 'free: %(free).02f %(unit)s', {'type': type_, 'limit': limit, 'free': free, 'unit': unit}) if requested > free: return (_('Free %(type)s %(free).02f ' '%(unit)s < requested %(requested)s %(unit)s') % {'type': type_, 'free': free, 'unit': unit, 'requested': requested})
def _new_websocket_client(self, container, token, uuid): if token != container.websocket_token: raise exception.InvalidWebsocketToken(token) access_url = '%s?token=%s&uuid=%s' % (CONF.websocket_proxy.base_url, token, uuid) self._verify_origin(access_url) if container.websocket_url: target_url = container.websocket_url escape = "~" close_wait = 0.5 wscls = WebSocketClient(host_url=target_url, escape=escape, close_wait=close_wait) wscls.connect() self.target = wscls else: raise exception.InvalidWebsocketUrl() # Start proxying try: self.do_websocket_proxy(self.target.ws) except Exception: if self.target.ws: self.target.ws.close() self.vmsg(_("Websocket client or target closed")) raise
def load_container_driver(container_driver=None): """Load a container driver module. Load the container driver module specified by the container_driver configuration option or, if supplied, the driver name supplied as an argument. :param container_driver: a container driver name to override the config opt :returns: a ContainerDriver instance """ if not container_driver: container_driver = CONF.container_driver if not container_driver: LOG.error("Container driver option required, " "but not specified") sys.exit(1) LOG.info("Loading container driver '%s'", container_driver) try: if not container_driver.startswith('zun.'): container_driver = 'zun.container.%s' % container_driver driver = importutils.import_object(container_driver) if not isinstance(driver, ContainerDriver): raise Exception(_('Expected driver of type: %s') % str(ContainerDriver)) return driver except ImportError: LOG.exception("Unable to load the container driver") sys.exit(1)
def new_websocket_client(self): """Called after a new WebSocket connection has been established.""" # Reopen the eventlet hub to make sure we don't share an epoll # fd with parent and/or siblings, which would be bad from eventlet import hubs hubs.use_hub() # The zun expected behavior is to have token # passed to the method GET of the request parse = urlparse.urlparse(self.path) if parse.scheme not in ('http', 'https'): # From a bug in urlparse in Python < 2.7.4 we cannot support # special schemes (cf: https://bugs.python.org/issue9374) if sys.version_info < (2, 7, 4): raise exception.ZunException( _("We do not support scheme '%s' under Python < 2.7.4, " "please use http or https") % parse.scheme) query = parse.query token = urlparse.parse_qs(query).get("token", [""]).pop() uuid = urlparse.parse_qs(query).get("uuid", [""]).pop() exec_id = urlparse.parse_qs(query).get("exec_id", [""]).pop() ctx = context.get_admin_context(all_projects=True) if uuidutils.is_uuid_like(uuid): container = objects.Container.get_by_uuid(ctx, uuid) else: container = objects.Container.get_by_name(ctx, uuid) if exec_id: self._new_exec_client(container, token, uuid, exec_id) else: self._new_websocket_client(container, token, uuid)
def delete_volume(self, volmap): volume_id = volmap.cinder_volume_id try: self.cinder_api.delete_volume(volume_id) except cinder_exception as e: raise exception.Invalid(_("Delete Volume failed: %s") % six.text_type(e))
def obj_load_attr(self, attrname): if attrname not in CONTAINER_OPTIONAL_ATTRS: raise exception.ObjectActionError( action='obj_load_attr', reason=_('attribute %s not lazy-loadable') % attrname) if not self._context: raise exception.OrphanedObjectError(method='obj_load_attr', objtype=self.obj_name()) LOG.debug("Lazy-loading '%(attr)s' on %(name)s uuid %(uuid)s", {'attr': attrname, 'name': self.obj_name(), 'uuid': self.uuid, }) # NOTE(danms): We handle some fields differently here so that we # can be more efficient if attrname == 'pci_devices': self._load_pci_devices() if attrname == 'exec_instances': self._load_exec_instances() if attrname == 'registry': self._load_registry() self.obj_reset_changes([attrname])
def search(self, image, image_driver=None, exact_match=False): """Search a specific image :param image: Name of the image. :param image_driver: Name of the image driver (glance, docker). :param exact_match: if True, exact match the image name. """ context = pecan.request.context policy.enforce(context, "image:search", action="image:search") LOG.debug('Calling compute.image_search with %s', image) try: exact_match = strutils.bool_from_string(exact_match, strict=True) except ValueError: bools = ', '.join(strutils.TRUE_STRINGS + strutils.FALSE_STRINGS) raise exception.InvalidValue(_('Valid exact_match values are: %s') % bools) # Valiadtion accepts 'None' so need to convert it to None image_driver = api_utils.string_or_none(image_driver) if not image_driver: image_driver = CONF.default_image_driver return pecan.request.compute_api.image_search(context, image, image_driver, exact_match)
def _test_disk(self, resources, limit): type_ = _("disk") unit = "GB" total = resources.disk_total used = resources.disk_used requested = self.disk return self._test(type_, unit, total, used, requested, limit)
def _schedule(self, context): """Picks a host that is up at random.""" hosts = self.hosts_up(context) if not hosts: msg = _("Is the appropriate service running?") raise exception.NoValidHost(reason=msg) return random.choice(hosts)
def _test_memory(self, resources, limit): type_ = _("memory") unit = "MB" total = resources.mem_total used = resources.mem_used requested = self.memory return self._test(type_, unit, total, used, requested, limit)
def _test_cpu(self, resources, limit): type_ = _("vcpu") unit = "VCPU" total = resources.cpus used = resources.cpu_used requested = self.cpu return self._test(type_, unit, total, used, requested, limit)
def unmount(self, mountpoint): try: utils.execute('umount', mountpoint, run_as_root=True) except exception.CommandError as e: raise exception.UnmountException(_( "Unexpected err while unmount block device. " "Mountpoint: %(mountpoint)s, " "Error: %(error)s") % {'mountpoint': mountpoint, 'error': e})
def validate(self, *args, **kwargs): try: self.validator.validate(*args, **kwargs) except jsonschema.ValidationError as ex: if len(ex.path) > 0: if self.is_body: detail = _("Invalid input for field '%(path)s'." "Value: '%(value)s'. %(message)s") else: detail = _("Invalid input for query parameters " "'%(path)s'. Value: '%(value)s'. %(message)s") detail = detail % { 'path': ex.path.pop(), 'value': ex.instance, 'message': six.text_type(ex) } else: detail = six.text_type(ex) raise exception.SchemaValidationError(detail=detail)
def get_available_network(self): search_opts = {'tenant_id': self.context.project_id, 'shared': False} # NOTE(kiennt): Pick shared network if no tenant network nets = self.list_networks(**search_opts).get('networks', []) or \ self.list_networks(**{'shared': True}).get('networks', []) if not nets: raise exception.Conflict(_( "There is no neutron network available")) nets.sort(key=lambda x: x['created_at']) return nets[0]
def _get_subnet(self, subnets, ip_version): subnets = [s for s in subnets if s['ip_version'] == ip_version] if len(subnets) == 0: return None elif len(subnets) == 1: return subnets[0] else: raise exception.ZunException(_( "Multiple Neutron subnets exist with ip version %s") % ip_version)
def make_filesystem(self, devpath, fstype): try: utils.execute('mkfs', '-t', fstype, '-F', devpath, run_as_root=True) except exception.CommandError as e: raise exception.MakeFileSystemException(_( "Unexpected error while make filesystem. " "Devpath: %(devpath)s, " "Fstype: %(fstype)s, " "Error: %(error)s") % {'devpath': devpath, 'fstype': fstype, 'error': e})
def driver(driver_name, *args, **kwargs): LOG.info("Loading volume driver '%s'", driver_name) volume_driver = stevedore_driver.DriverManager( "zun.volume.driver", driver_name, invoke_on_load=True, invoke_args=args, invoke_kwds=kwargs).driver if not isinstance(volume_driver, VolumeDriver): raise exception.ZunException(_("Invalid volume driver type")) return volume_driver
def exec_resize(self, exec_id, height=None, width=None): # NOTE(hongbin): This is a temporary work-around for a docker issue # See: https://github.com/moby/moby/issues/35561 try: super(DockerHTTPClient, self).exec_resize( exec_id, height=height, width=width) except errors.APIError as e: if "process not found for container" in str(e): raise exception.Invalid(_( "no such exec instance: %s") % six.text_type(e)) raise
def mount(self, devpath, mountpoint, fstype=None): try: utils.execute('mount', '-t', fstype, devpath, mountpoint, run_as_root=True) except exception.CommandError as e: raise exception.MountException(_( "Unexpected error while mount block device. " "Devpath: %(devpath)s, " "Mountpoint: %(mountpoint)s, " "Error: %(error)s") % {'devpath': devpath, 'mountpoint': mountpoint, 'error': e})
class InvalidParameterValue(Invalid): message = _("%(err)s")
class InvalidParam(Invalid): message = _('Invalid param %(param)s')
class ContainerHostNotUp(ZunException): message = _("Container %(container)s host %(host)s is not up.")
class InvalidDiscoveryURL(Invalid): message = _("Received invalid discovery URL '%(discovery_url)s' for " "discovery endpoint '%(discovery_endpoint)s'.")
class InvalidUuidOrName(Invalid): message = _("Expected a name or uuid but received %(uuid)s.")
class ResourceProviderNotFound(HTTPNotFound): message = _("Resource provider %(resource_provider)s could not be found.")
class ResourceClassNotFound(HTTPNotFound): message = _("Resource class %(resource_class)s could not be found.")
class InvalidName(Invalid): message = _("Expected a name but received %(uuid)s.")
class InvalidValue(Invalid): message = _("Received value '%(value)s' is invalid for type %(type)s.")
class NotAuthorized(ZunException): message = _("Not authorized.") code = 403
class ConfigInvalid(ZunException): message = _("Invalid configuration file. %(error_msg)s")
class PolicyNotAuthorized(NotAuthorized): message = _("Policy doesn't allow %(action)s to be performed.")
class InvalidState(Conflict): message = _("Invalid resource state.")
class InventoryNotFound(HTTPNotFound): message = _("Inventory %(inventory)s could not be found.")
class Conflict(ZunException): message = _('Conflict.') code = 409
class AllocationNotFound(HTTPNotFound): message = _("Allocation %(allocation)s could not be found.")
class InvalidCsr(Invalid): message = _("Received invalid csr %(csr)s.")
class CarrierNotFound(HTTPNotFound): message = _("Carrier account %(carrier)s could not be found.")
class InvalidIdentity(Invalid): message = _("Expected an uuid or int but received %(identity)s.")
class OrphanedObjectError(ZunException): message = _('Cannot call %(method)s on orphaned %(objtype)s object')
class GetDiscoveryUrlFailed(ZunException): message = _("Failed to get discovery url from '%(discovery_endpoint)s'.")
class PatchError(Invalid): message = _("Couldn't apply patch '%(patch)s'. Reason: %(reason)s")
class InvalidUUID(Invalid): message = _("Expected a uuid but received %(uuid)s.")
class ContainerNotFound(HTTPNotFound): message = _("Container %(container)s could not be found.")
class ImageNotFound(HTTPNotFound): message = _("Image %(image)s could not be found.")
class ZunServiceNotFound(HTTPNotFound): message = _("Zun service %(binary)s on host %(host)s could not be found.")
class PortNotFound(HTTPNotFound): message = _("Neutron port %(port)s could not be found.")
class Invalid(ZunException): message = _("Unacceptable parameters.") code = 400
class ComputeNodeNotFound(HTTPNotFound): message = _("Compute node %(compute_node)s could not be found.")
class NetworkNotFound(HTTPNotFound): message = _("Neutron network %(network)s could not be found.")