def _check_default_cgsnapshot_type(self, group_type_id): if group_types.is_default_cgsnapshot_type(group_type_id): msg = _("Group_type %(group_type)s is reserved for migrating " "CGs to groups. Migrated group can only be operated by " "CG APIs.") % {'group_type': group_type_id} raise exc.HTTPBadRequest(explanation=msg)
def _validate_access_ipv6(self, address): if not netutils.is_valid_ipv6(address): expl = _('accessIPv6 is not proper IPv6 format') raise exc.HTTPBadRequest(explanation=expl)
def create(self, req, body): """Creates a new server for a given user.""" if not self.is_valid_body(body, 'server'): raise exc.HTTPUnprocessableEntity() context = req.environ['nova.context'] server_dict = body['server'] password = self._get_server_admin_password(server_dict) if 'name' not in server_dict: msg = _("Server name is not defined") raise exc.HTTPBadRequest(explanation=msg) name = server_dict['name'] self._validate_server_name(name) name = name.strip() image_uuid = self._image_from_req_data(body) personality = server_dict.get('personality') config_drive = None if self.ext_mgr.is_loaded('os-config-drive'): config_drive = server_dict.get('config_drive') injected_files = [] if personality: injected_files = self._get_injected_files(personality) sg_names = [] if self.ext_mgr.is_loaded('os-security-groups'): security_groups = server_dict.get('security_groups') if security_groups is not None: try: sg_names = [ sg['name'] for sg in security_groups if sg.get('name') ] except AttributeError: msg = _("Invalid input for field/attribute %(path)s." " Value: %(value)s. %(message)s") % { 'path': 'security_groups', 'value': security_groups, 'message': '' } raise exc.HTTPBadRequest(explanation=msg) if not sg_names: sg_names.append('default') sg_names = list(set(sg_names)) requested_networks = self._determine_requested_networks(server_dict) (access_ip_v4, ) = server_dict.get('accessIPv4'), if access_ip_v4 is not None: self._validate_access_ipv4(access_ip_v4) (access_ip_v6, ) = server_dict.get('accessIPv6'), if access_ip_v6 is not None: self._validate_access_ipv6(access_ip_v6) flavor_id = self._flavor_id_from_req_data(body) # optional openstack extensions: key_name = self._extract(server_dict, 'os-keypairs', 'key_name') availability_zone = self._extract(server_dict, 'os-availability-zone', 'availability_zone') user_data = self._extract(server_dict, 'os-user-data', 'user_data') self._validate_user_data(user_data) image_uuid_specified = bool(image_uuid) legacy_bdm, block_device_mapping = self._extract_bdm( server_dict, image_uuid_specified) ret_resv_id = False # min_count and max_count are optional. If they exist, they may come # in as strings. Verify that they are valid integers and > 0. # Also, we want to default 'min_count' to 1, and default # 'max_count' to be 'min_count'. min_count = 1 max_count = 1 if self.ext_mgr.is_loaded('os-multiple-create'): ret_resv_id = server_dict.get('return_reservation_id', False) min_count = server_dict.get('min_count', 1) max_count = server_dict.get('max_count', min_count) try: min_count = utils.validate_integer(min_count, "min_count", min_value=1) max_count = utils.validate_integer(max_count, "max_count", min_value=1) except exception.InvalidInput as e: raise exc.HTTPBadRequest(explanation=e.format_message()) if min_count > max_count: msg = _('min_count must be <= max_count') raise exc.HTTPBadRequest(explanation=msg) auto_disk_config = False if self.ext_mgr.is_loaded('OS-DCF'): auto_disk_config = server_dict.get('auto_disk_config') scheduler_hints = {} if self.ext_mgr.is_loaded('OS-SCH-HNT'): scheduler_hints = server_dict.get('scheduler_hints', {}) check_server_group_quota = self.ext_mgr.is_loaded( 'os-server-group-quotas') try: _get_inst_type = flavors.get_flavor_by_flavor_id inst_type = _get_inst_type(flavor_id, ctxt=context, read_deleted="no") (instances, resv_id) = self.compute_api.create( context, inst_type, image_uuid, display_name=name, display_description=name, key_name=key_name, metadata=server_dict.get('metadata', {}), access_ip_v4=access_ip_v4, access_ip_v6=access_ip_v6, injected_files=injected_files, admin_password=password, min_count=min_count, max_count=max_count, requested_networks=requested_networks, security_group=sg_names, user_data=user_data, availability_zone=availability_zone, config_drive=config_drive, block_device_mapping=block_device_mapping, auto_disk_config=auto_disk_config, scheduler_hints=scheduler_hints, legacy_bdm=legacy_bdm, check_server_group_quota=check_server_group_quota) except (exception.QuotaError, exception.PortLimitExceeded) as error: raise exc.HTTPForbidden(explanation=error.format_message(), headers={'Retry-After': 0}) except messaging.RemoteError as err: msg = "%(err_type)s: %(err_msg)s" % { 'err_type': err.exc_type, 'err_msg': err.value } raise exc.HTTPBadRequest(explanation=msg) except UnicodeDecodeError as error: msg = "UnicodeError: %s" % error raise exc.HTTPBadRequest(explanation=msg) except Exception: # The remaining cases can be handled in a standard fashion. self._handle_create_exception(*sys.exc_info()) # If the caller wanted a reservation_id, return it if ret_resv_id: return wsgi.ResponseObject({'reservation_id': resv_id}) req.cache_db_instances(instances) server = self._view_builder.create(req, instances[0]) if CONF.enable_instance_password: server['server']['adminPass'] = password robj = wsgi.ResponseObject(server) return self._add_location(robj)
def _extract_bool_param(self, name, value): try: return param_utils.extract_bool(name, value) except ValueError as e: raise exc.HTTPBadRequest(six.text_type(e))
def stack_name(self): """Return the stack name.""" if self.PARAM_STACK_NAME not in self.data: raise exc.HTTPBadRequest(_("No stack name specified")) return self.data[self.PARAM_STACK_NAME]
def _action_create_image(self, req, id, body): """Snapshot a server instance.""" context = req.environ['nova.context'] entity = body.get("createImage", {}) image_name = entity.get("name") if not image_name: msg = _("createImage entity requires name attribute") raise exc.HTTPBadRequest(explanation=msg) props = {} metadata = entity.get('metadata', {}) common.check_img_metadata_properties_quota(context, metadata) try: props.update(metadata) except ValueError: msg = _("Invalid metadata") raise exc.HTTPBadRequest(explanation=msg) instance = self._get_server(context, req, id) bdms = objects.BlockDeviceMappingList.get_by_instance_uuid( context, instance.uuid) try: if self.compute_api.is_volume_backed_instance(context, instance, bdms): img = instance['image_ref'] if not img: properties = bdms.root_metadata( context, self.compute_api.image_api, self.compute_api.volume_api) image_meta = {'properties': properties} else: image_meta = self.compute_api.image_api.get(context, img) image = self.compute_api.snapshot_volume_backed( context, instance, image_meta, image_name, extra_properties=props) else: image = self.compute_api.snapshot(context, instance, image_name, extra_properties=props) except exception.InstanceInvalidState as state_error: common.raise_http_conflict_for_instance_invalid_state(state_error, 'createImage', id) except exception.Invalid as err: raise exc.HTTPBadRequest(explanation=err.format_message()) # build location of newly-created image entity image_id = str(image['id']) image_ref = glance.generate_image_url(image_id) resp = webob.Response(status_int=202) resp.headers['Location'] = image_ref return resp
def get(self): input = util.get_required_param(self, 'input') if input not in INPUTS: raise exc.HTTPBadRequest('Invalid input: %s, expected one of %r' % (input, INPUTS)) url, body = self._fetch(util.get_required_param(self, 'url')) # decode data if input in ('activitystreams', 'as1', 'as2', 'mf2-json', 'json-mf2', 'jsonfeed'): try: body_json = json.loads(body) body_items = (body_json if isinstance(body_json, list) else body_json.get('items') or [body_json]) except (TypeError, ValueError): raise exc.HTTPBadRequest('Could not decode %s as JSON' % url) mf2 = None if input == 'html': mf2 = mf2py.parse(doc=body, url=url) elif input in ('mf2-json', 'json-mf2'): mf2 = body_json mf2.setdefault('rels', {}) # mf2util expects rels actor = None title = None if mf2: def fetch_mf2_func(url): if util.domain_or_parent_in( urlparse.urlparse(url).netloc, SILO_DOMAINS): return { 'items': [{ 'type': ['h-card'], 'properties': { 'url': [url] } }] } _, doc = self._fetch(url) return mf2py.parse(doc=doc, url=url) actor = microformats2.find_author(mf2, fetch_mf2_func=fetch_mf2_func) title = microformats2.get_title(mf2) if input in ('as1', 'activitystreams'): activities = body_items elif input == 'as2': activities = [as2.to_as1(obj) for obj in body_items] elif input == 'atom': try: activities = atom.atom_to_activities(body) except ElementTree.ParseError as e: raise exc.HTTPBadRequest('Could not parse %s as XML: %s' % (url, e)) except ValueError as e: raise exc.HTTPBadRequest('Could not parse %s as Atom: %s' % (url, e)) elif input == 'html': activities = microformats2.html_to_activities(body, url, actor) elif input in ('mf2-json', 'json-mf2'): activities = [ microformats2.json_to_object(item, actor=actor) for item in mf2.get('items', []) ] elif input == 'jsonfeed': try: activities, actor = jsonfeed.jsonfeed_to_activities(body_json) except ValueError as e: logging.exception('jsonfeed_to_activities failed') raise exc.HTTPBadRequest('Could not parse %s as JSON Feed' % url) self.write_response( source.Source.make_activities_base_response(activities), url=url, actor=actor, title=title)
def _revert(self, req, id, body=None): """Revert a share to a snapshot.""" context = req.environ['manila.context'] revert_data = self._validate_revert_parameters(context, body) try: share_id = id snapshot_id = revert_data['snapshot_id'] share = self.share_api.get(context, share_id) snapshot = self.share_api.get_snapshot(context, snapshot_id) # Ensure share supports reverting to a snapshot if not share['revert_to_snapshot_support']: msg_args = {'share_id': share_id, 'snap_id': snapshot_id} msg = _('Share %(share_id)s may not be reverted to snapshot ' '%(snap_id)s, because the share does not have that ' 'capability.') raise exc.HTTPBadRequest(explanation=msg % msg_args) # Ensure requested share & snapshot match. if share['id'] != snapshot['share_id']: msg_args = {'share_id': share_id, 'snap_id': snapshot_id} msg = _('Snapshot %(snap_id)s is not associated with share ' '%(share_id)s.') raise exc.HTTPBadRequest(explanation=msg % msg_args) # Ensure share status is 'available'. if share['status'] != constants.STATUS_AVAILABLE: msg_args = { 'share_id': share_id, 'state': share['status'], 'available': constants.STATUS_AVAILABLE, } msg = _("Share %(share_id)s is in '%(state)s' state, but it " "must be in '%(available)s' state to be reverted to a " "snapshot.") raise exc.HTTPConflict(explanation=msg % msg_args) # Ensure snapshot status is 'available'. if snapshot['status'] != constants.STATUS_AVAILABLE: msg_args = { 'snap_id': snapshot_id, 'state': snapshot['status'], 'available': constants.STATUS_AVAILABLE, } msg = _("Snapshot %(snap_id)s is in '%(state)s' state, but it " "must be in '%(available)s' state to be restored.") raise exc.HTTPConflict(explanation=msg % msg_args) # Ensure a long-running task isn't active on the share if share.is_busy: msg_args = {'share_id': share_id} msg = _("Share %(share_id)s may not be reverted while it has " "an active task.") raise exc.HTTPConflict(explanation=msg % msg_args) # Ensure the snapshot is the most recent one. latest_snapshot = self.share_api.get_latest_snapshot_for_share( context, share_id) if not latest_snapshot: msg_args = {'share_id': share_id} msg = _("Could not determine the latest snapshot for share " "%(share_id)s.") raise exc.HTTPBadRequest(explanation=msg % msg_args) if latest_snapshot['id'] != snapshot_id: msg_args = { 'share_id': share_id, 'snap_id': snapshot_id, 'latest_snap_id': latest_snapshot['id'], } msg = _("Snapshot %(snap_id)s may not be restored because " "it is not the most recent snapshot of share " "%(share_id)s. Currently the latest snapshot is " "%(latest_snap_id)s.") raise exc.HTTPConflict(explanation=msg % msg_args) # Ensure the access rules are not in the process of updating for instance in share['instances']: access_rules_status = instance['access_rules_status'] if access_rules_status != constants.ACCESS_STATE_ACTIVE: msg_args = { 'share_id': share_id, 'snap_id': snapshot_id, 'state': constants.ACCESS_STATE_ACTIVE } msg = _("Snapshot %(snap_id)s belongs to a share " "%(share_id)s which has access rules that are " "not %(state)s.") raise exc.HTTPConflict(explanation=msg % msg_args) msg_args = {'share_id': share_id, 'snap_id': snapshot_id} msg = 'Reverting share %(share_id)s to snapshot %(snap_id)s.' LOG.info(msg, msg_args) self.share_api.revert_to_snapshot(context, share, snapshot) except exception.ShareNotFound as e: raise exc.HTTPNotFound(explanation=e.msg) except exception.ShareSnapshotNotFound as e: raise exc.HTTPBadRequest(explanation=e.msg) except exception.ShareSizeExceedsAvailableQuota as e: raise exc.HTTPForbidden(explanation=e.msg) except exception.ReplicationException as e: raise exc.HTTPBadRequest(explanation=e.msg) return webob.Response(status_int=http_client.ACCEPTED)
def wrapper(*args, **kwargs): (is_valid, message) = func(*args, **kwargs) if not is_valid: raise exc.HTTPBadRequest(explanation=(error_message % message)) return (is_valid, message)
def _http_request(self, method, url, headers, body, ignore_result_body=False): """Perform an HTTP request against the server. method: the HTTP method to use url: the URL to request (not including server portion) headers: headers for the request body: body to send with the request ignore_result_body: the body of the result will be ignored Returns: a http_client response object """ if self.auth_token: headers.setdefault('x-auth-token', self.auth_token) LOG.debug( 'Request: %(method)s http://%(server)s:%(port)s' '%(url)s with headers %(headers)s', { 'method': method, 'server': self.conn.host, 'port': self.conn.port, 'url': url, 'headers': repr(headers) }) self.conn.request(method, url, body, headers) response = self.conn.getresponse() headers = self._header_list_to_dict(response.getheaders()) code = response.status code_description = http.responses[code] LOG.debug('Response: %(code)s %(status)s %(headers)s', { 'code': code, 'status': code_description, 'headers': repr(headers) }) if code == http.BAD_REQUEST: raise exc.HTTPBadRequest(explanation=response.read()) if code == http.INTERNAL_SERVER_ERROR: raise exc.HTTPInternalServerError(explanation=response.read()) if code == http.UNAUTHORIZED: raise exc.HTTPUnauthorized(explanation=response.read()) if code == http.FORBIDDEN: raise exc.HTTPForbidden(explanation=response.read()) if code == http.CONFLICT: raise exc.HTTPConflict(explanation=response.read()) if ignore_result_body: # NOTE: because we are pipelining requests through a single HTTP # connection, http_client requires that we read the response body # before we can make another request. If the caller knows they # don't care about the body, they can ask us to do that for them. response.read() return response
def migration_start(self, req, id, body): """Migrate a share to the specified host.""" context = req.environ['manila.context'] try: share = self.share_api.get(context, id) except exception.NotFound: msg = _("Share %s not found.") % id raise exc.HTTPNotFound(explanation=msg) params = body.get('migration_start') if not params: raise exc.HTTPBadRequest(explanation=_("Request is missing body.")) driver_assisted_params = [ 'preserve_metadata', 'writable', 'nondisruptive', 'preserve_snapshots' ] bool_params = (driver_assisted_params + ['force_host_assisted_migration']) mandatory_params = driver_assisted_params + ['host'] utils.check_params_exist(mandatory_params, params) bool_param_values = utils.check_params_are_boolean(bool_params, params) new_share_network = None new_share_type = None new_share_network_id = params.get('new_share_network_id', None) if new_share_network_id: try: new_share_network = db.share_network_get( context, new_share_network_id) except exception.NotFound: msg = _("Share network %s not " "found.") % new_share_network_id raise exc.HTTPBadRequest(explanation=msg) common.check_share_network_is_active(new_share_network) else: share_network_id = share.get('share_network_id', None) if share_network_id: current_share_network = db.share_network_get( context, share_network_id) common.check_share_network_is_active(current_share_network) new_share_type_id = params.get('new_share_type_id', None) if new_share_type_id: try: new_share_type = db.share_type_get(context, new_share_type_id) except exception.NotFound: msg = _("Share type %s not found.") % new_share_type_id raise exc.HTTPBadRequest(explanation=msg) try: return_code = self.share_api.migration_start( context, share, params['host'], bool_param_values['force_host_assisted_migration'], bool_param_values['preserve_metadata'], bool_param_values['writable'], bool_param_values['nondisruptive'], bool_param_values['preserve_snapshots'], new_share_network=new_share_network, new_share_type=new_share_type) except exception.Conflict as e: raise exc.HTTPConflict(explanation=e.msg) return webob.Response(status_int=return_code)
def _create_backup(self, req, id, body): """Backup a server instance. Images now have an `image_type` associated with them, which can be 'snapshot' or the backup type, like 'daily' or 'weekly'. If the image_type is backup-like, then the rotation factor can be included and that will cause the oldest backups that exceed the rotation factor to be deleted. """ context = req.environ["nova.context"] try: entity = body["createBackup"] except (KeyError, TypeError): raise exc.HTTPBadRequest(_("Malformed request body")) try: image_name = entity["name"] backup_type = entity["backup_type"] rotation = entity["rotation"] except KeyError as missing_key: msg = _("createBackup entity requires %s attribute") % missing_key raise exc.HTTPBadRequest(explanation=msg) except TypeError: msg = _("Malformed createBackup entity") raise exc.HTTPBadRequest(explanation=msg) try: rotation = int(rotation) except ValueError: msg = _("createBackup attribute 'rotation' must be an integer") raise exc.HTTPBadRequest(explanation=msg) props = {} metadata = entity.get('metadata', {}) common.check_img_metadata_quota_limit(context, metadata) try: props.update(metadata) except ValueError: msg = _("Invalid metadata") raise exc.HTTPBadRequest(explanation=msg) try: instance = self.compute_api.get(context, id) except exception.NotFound: raise exc.HTTPNotFound(_("Instance not found")) try: image = self.compute_api.backup(context, instance, image_name, backup_type, rotation, extra_properties=props) except exception.InstanceInvalidState as state_error: common.raise_http_conflict_for_instance_invalid_state( state_error, 'createBackup') # build location of newly-created image entity image_id = str(image['id']) image_ref = os.path.join(req.application_url, 'images', image_id) resp = webob.Response(status_int=202) resp.headers['Location'] = image_ref return resp
def _index(self, req, add_link=False, next_link=False, add_uuid=False, sort_dirs=None, sort_keys=None, limit=None, marker=None, allow_changes_since=False, allow_changes_before=False): context = req.environ['nova.context'] context.can(migrations_policies.POLICY_ROOT % 'index') search_opts = {} search_opts.update(req.GET) if 'changes-since' in search_opts: if allow_changes_since: search_opts['changes-since'] = timeutils.parse_isotime( search_opts['changes-since']) else: # Before microversion 2.59, the changes-since filter was not # supported in the DB API. However, the schema allowed # additionalProperties=True, so a user could pass it before # 2.59 and filter by the updated_at field if we don't remove # it from search_opts. del search_opts['changes-since'] if 'changes-before' in search_opts: if allow_changes_before: search_opts['changes-before'] = timeutils.parse_isotime( search_opts['changes-before']) changes_since = search_opts.get('changes-since') if (changes_since and search_opts['changes-before'] < search_opts['changes-since']): msg = _('The value of changes-since must be less than ' 'or equal to changes-before.') raise exc.HTTPBadRequest(explanation=msg) else: # Before microversion 2.59 the schema allowed # additionalProperties=True, so a user could pass # changes-before before 2.59 and filter by the updated_at # field if we don't remove it from search_opts. del search_opts['changes-before'] if sort_keys: try: migrations = self.compute_api.get_migrations_sorted( context, search_opts, sort_dirs=sort_dirs, sort_keys=sort_keys, limit=limit, marker=marker) except exception.MarkerNotFound as e: raise exc.HTTPBadRequest(explanation=e.format_message()) else: migrations = self.compute_api.get_migrations(context, search_opts) migrations = self._output(req, migrations, add_link, add_uuid) migrations_dict = {'migrations': migrations} if next_link: migrations_links = self._view_builder.get_links(req, migrations) if migrations_links: migrations_dict['migrations_links'] = migrations_links return migrations_dict
def _create(self, req, body): """Creates a new share.""" context = req.environ['manila.context'] if not self.is_valid_body(body, 'share'): raise exc.HTTPUnprocessableEntity() share = body['share'] # NOTE(rushiagr): Manila API allows 'name' instead of 'display_name'. if share.get('name'): share['display_name'] = share.get('name') del share['name'] # NOTE(rushiagr): Manila API allows 'description' instead of # 'display_description'. if share.get('description'): share['display_description'] = share.get('description') del share['description'] size = share['size'] share_proto = share['share_proto'].upper() msg = (_LI("Create %(share_proto)s share of %(size)s GB") % { 'share_proto': share_proto, 'size': size }) LOG.info(msg, context=context) availability_zone = share.get('availability_zone') if availability_zone: try: db.availability_zone_get(context, availability_zone) except exception.AvailabilityZoneNotFound as e: raise exc.HTTPNotFound(explanation=six.text_type(e)) kwargs = { 'availability_zone': availability_zone, 'metadata': share.get('metadata'), 'is_public': share.get('is_public', False), 'consistency_group_id': share.get('consistency_group_id') } snapshot_id = share.get('snapshot_id') if snapshot_id: snapshot = self.share_api.get_snapshot(context, snapshot_id) else: snapshot = None kwargs['snapshot_id'] = snapshot_id share_network_id = share.get('share_network_id') if snapshot: # Need to check that share_network_id from snapshot's # parents share equals to share_network_id from args. # If share_network_id is empty than update it with # share_network_id of parent share. parent_share = self.share_api.get(context, snapshot['share_id']) parent_share_net_id = parent_share.instance['share_network_id'] if share_network_id: if share_network_id != parent_share_net_id: msg = "Share network ID should be the same as snapshot's" \ " parent share's or empty" raise exc.HTTPBadRequest(explanation=msg) elif parent_share_net_id: share_network_id = parent_share_net_id if share_network_id: try: self.share_api.get_share_network(context, share_network_id) except exception.ShareNetworkNotFound as e: raise exc.HTTPNotFound(explanation=six.text_type(e)) kwargs['share_network_id'] = share_network_id display_name = share.get('display_name') display_description = share.get('display_description') if 'share_type' in share and 'volume_type' in share: msg = 'Cannot specify both share_type and volume_type' raise exc.HTTPBadRequest(explanation=msg) req_share_type = share.get('share_type', share.get('volume_type')) share_type = None if req_share_type: try: if not uuidutils.is_uuid_like(req_share_type): share_type = share_types.get_share_type_by_name( context, req_share_type) else: share_type = share_types.get_share_type( context, req_share_type) except exception.ShareTypeNotFound: msg = _("Share type not found.") raise exc.HTTPNotFound(explanation=msg) elif not snapshot: def_share_type = share_types.get_default_share_type() if def_share_type: share_type = def_share_type # Only use in create share feature. Create share from snapshot # and create share with consistency group features not # need this check. if (not share_network_id and not snapshot and not share.get('consistency_group_id') and share_type and share_type.get('extra_specs') and (strutils.bool_from_string( share_type.get('extra_specs').get( 'driver_handles_share_servers')))): msg = _('Share network must be set when the ' 'driver_handles_share_servers is true.') raise exc.HTTPBadRequest(explanation=msg) if share_type: kwargs['share_type'] = share_type new_share = self.share_api.create(context, share_proto, size, display_name, display_description, **kwargs) return self._view_builder.detail(req, new_share)
def create(self, req, body): """Creates a new server for a given user.""" context = req.environ['nova.context'] server_dict = body['server'] password = self._get_server_admin_password(server_dict) name = server_dict['name'] # Arguments to be passed to instance create function create_kwargs = {} # Query extensions which want to manipulate the keyword # arguments. # NOTE(cyeoh): This is the hook that extensions use # to replace the extension specific code below. # When the extensions are ported this will also result # in some convenience function from this class being # moved to the extension if list(self.create_extension_manager): self.create_extension_manager.map(self._create_extension_point, server_dict, create_kwargs, body) image_uuid = self._image_from_req_data(server_dict, create_kwargs) # NOTE(cyeoh): Although an extension can set # return_reservation_id in order to request that a reservation # id be returned to the client instead of the newly created # instance information we do not want to pass this parameter # to the compute create call which always returns both. We use # this flag after the instance create call to determine what # to return to the client return_reservation_id = create_kwargs.pop('return_reservation_id', False) requested_networks = None if ('os-networks' in self.extension_info.get_extensions() or utils.is_neutron()): requested_networks = server_dict.get('networks') if requested_networks is not None: requested_networks = self._get_requested_networks( requested_networks) try: flavor_id = self._flavor_id_from_req_data(body) except ValueError as error: msg = _("Invalid flavorRef provided.") raise exc.HTTPBadRequest(explanation=msg) try: inst_type = flavors.get_flavor_by_flavor_id( flavor_id, ctxt=context, read_deleted="no") (instances, resv_id) = self.compute_api.create(context, inst_type, image_uuid, display_name=name, display_description=name, metadata=server_dict.get('metadata', {}), admin_password=password, requested_networks=requested_networks, check_server_group_quota=True, **create_kwargs) except (exception.QuotaError, exception.PortLimitExceeded) as error: raise exc.HTTPForbidden( explanation=error.format_message(), headers={'Retry-After': 0}) except exception.ImageNotFound as error: msg = _("Can not find requested image") raise exc.HTTPBadRequest(explanation=msg) except exception.FlavorNotFound as error: msg = _("Invalid flavorRef provided.") raise exc.HTTPBadRequest(explanation=msg) except exception.KeypairNotFound as error: msg = _("Invalid key_name provided.") raise exc.HTTPBadRequest(explanation=msg) except exception.ConfigDriveInvalidValue: msg = _("Invalid config_drive provided.") raise exc.HTTPBadRequest(explanation=msg) except exception.ExternalNetworkAttachForbidden as error: raise exc.HTTPForbidden(explanation=error.format_message()) except messaging.RemoteError as err: msg = "%(err_type)s: %(err_msg)s" % {'err_type': err.exc_type, 'err_msg': err.value} raise exc.HTTPBadRequest(explanation=msg) except UnicodeDecodeError as error: msg = "UnicodeError: %s" % error raise exc.HTTPBadRequest(explanation=msg) except (exception.ImageNotActive, exception.FlavorDiskTooSmall, exception.FlavorMemoryTooSmall, exception.InvalidMetadata, exception.InvalidRequest, exception.InvalidVolume, exception.MultiplePortsNotApplicable, exception.InvalidFixedIpAndMaxCountRequest, exception.InstanceUserDataMalformed, exception.InstanceUserDataTooLarge, exception.PortNotFound, exception.FixedIpAlreadyInUse, exception.SecurityGroupNotFound, exception.PortRequiresFixedIP, exception.NetworkRequiresSubnet, exception.NetworkNotFound, exception.InvalidBDMVolumeNotBootable, exception.InvalidBDMSnapshot, exception.InvalidBDMVolume, exception.InvalidBDMImage, exception.InvalidBDMBootSequence, exception.InvalidBDMLocalsLimit, exception.InvalidBDMVolumeNotBootable, exception.AutoDiskConfigDisabledByImage) as error: raise exc.HTTPBadRequest(explanation=error.format_message()) except (exception.PortInUse, exception.InstanceExists, exception.NetworkAmbiguous, exception.NoUniqueMatch) as error: raise exc.HTTPConflict(explanation=error.format_message()) # If the caller wanted a reservation_id, return it if return_reservation_id: # NOTE(cyeoh): In v3 reservation_id was wrapped in # servers_reservation but this is reverted for V2 API # compatibility. In the long term with the tasks API we # will probably just drop the concept of reservation_id return wsgi.ResponseObject({'reservation_id': resv_id}) req.cache_db_instances(instances) server = self._view_builder.create(req, instances[0]) if CONF.enable_instance_password: server['server']['adminPass'] = password robj = wsgi.ResponseObject(server) return self._add_location(robj)
def _evacuate(self, req, id, body): """Permit admins to evacuate a server from a failed host to a new one. """ context = req.environ["nova.context"] instance = common.get_instance(self.compute_api, context, id) context.can(evac_policies.BASE_POLICY_NAME, target={ 'user_id': instance.user_id, 'project_id': instance.project_id }) evacuate_body = body["evacuate"] host = evacuate_body.get("host") force = None on_shared_storage = self._get_on_shared_storage(req, evacuate_body) if api_version_request.is_supported(req, min_version='2.29'): force = body["evacuate"].get("force", False) force = strutils.bool_from_string(force, strict=True) if force is True and not host: message = _("Can't force to a non-provided destination") raise exc.HTTPBadRequest(explanation=message) if api_version_request.is_supported(req, min_version='2.14'): password = self._get_password_v214(req, evacuate_body) else: password = self._get_password(req, evacuate_body, on_shared_storage) if host is not None: try: self.host_api.service_get_by_compute_host(context, host) except (exception.ComputeHostNotFound, exception.HostMappingNotFound): msg = _("Compute host %s not found.") % host raise exc.HTTPNotFound(explanation=msg) if instance.host == host: msg = _("The target host can't be the same one.") raise exc.HTTPBadRequest(explanation=msg) # We could potentially move this check to conductor and avoid the # extra API call to neutron when we support move operations with ports # having resource requests. if (common.instance_has_port_with_resource_request( context, instance.uuid, self.network_api) and not common.supports_port_resource_request_during_move(req)): msg = _("The evacuate action on a server with ports having " "resource requests, like a port with a QoS minimum " "bandwidth policy, is not supported with this " "microversion") raise exc.HTTPBadRequest(explanation=msg) try: self.compute_api.evacuate(context, instance, host, on_shared_storage, password, force) except exception.InstanceUnknownCell as e: raise exc.HTTPNotFound(explanation=e.format_message()) except exception.InstanceInvalidState as state_error: common.raise_http_conflict_for_instance_invalid_state( state_error, 'evacuate', id) except exception.ComputeServiceInUse as e: raise exc.HTTPBadRequest(explanation=e.format_message()) if (not api_version_request.is_supported(req, min_version='2.14') and CONF.api.enable_instance_password): return {'adminPass': password} else: return None
def _action_rebuild(self, req, id, body): """Rebuild an instance with the given attributes.""" rebuild_dict = body['rebuild'] image_href = rebuild_dict["imageRef"] image_href = self._image_uuid_from_href(image_href) password = self._get_server_admin_password(rebuild_dict) context = req.environ['nova.context'] instance = self._get_server(context, req, id) attr_map = { 'name': 'display_name', 'metadata': 'metadata', } rebuild_kwargs = {} if 'preserve_ephemeral' in rebuild_dict: rebuild_kwargs['preserve_ephemeral'] = strutils.bool_from_string( rebuild_dict['preserve_ephemeral'], strict=True) if list(self.rebuild_extension_manager): self.rebuild_extension_manager.map(self._rebuild_extension_point, rebuild_dict, rebuild_kwargs) for request_attribute, instance_attribute in attr_map.items(): try: rebuild_kwargs[instance_attribute] = rebuild_dict[ request_attribute] except (KeyError, TypeError): pass try: self.compute_api.rebuild(context, instance, image_href, password, **rebuild_kwargs) except exception.InstanceIsLocked as e: raise exc.HTTPConflict(explanation=e.format_message()) except exception.InstanceInvalidState as state_error: common.raise_http_conflict_for_instance_invalid_state(state_error, 'rebuild', id) except exception.InstanceNotFound: msg = _("Instance could not be found") raise exc.HTTPNotFound(explanation=msg) except exception.ImageNotFound: msg = _("Cannot find image for rebuild") raise exc.HTTPBadRequest(explanation=msg) except exception.QuotaError as error: raise exc.HTTPForbidden(explanation=error.format_message()) except (exception.ImageNotActive, exception.FlavorDiskTooSmall, exception.FlavorMemoryTooSmall, exception.InvalidMetadata, exception.AutoDiskConfigDisabledByImage) as error: raise exc.HTTPBadRequest(explanation=error.format_message()) instance = self._get_server(context, req, id) view = self._view_builder.show(req, instance) # Add on the admin_password attribute since the view doesn't do it # unless instance passwords are disabled if CONF.enable_instance_password: view['server']['adminPass'] = password robj = wsgi.ResponseObject(view) return self._add_location(robj)
def _action_rebuild(self, req, id, body): """Rebuild an instance with the given attributes.""" body = body['rebuild'] try: image_href = body["imageRef"] except (KeyError, TypeError): msg = _("Could not parse imageRef from request.") raise exc.HTTPBadRequest(explanation=msg) image_href = common.image_uuid_from_href(image_href, 'imageRef') password = self._get_server_admin_password(body) context = req.environ['nova.context'] instance = self._get_server(context, req, id) attr_map = { 'personality': 'files_to_inject', 'name': 'display_name', 'accessIPv4': 'access_ip_v4', 'accessIPv6': 'access_ip_v6', 'metadata': 'metadata', 'auto_disk_config': 'auto_disk_config', } kwargs = {} # take the preserve_ephemeral value into account only when the # corresponding extension is active if (self.ext_mgr.is_loaded('os-preserve-ephemeral-rebuild') and 'preserve_ephemeral' in body): kwargs['preserve_ephemeral'] = strutils.bool_from_string( body['preserve_ephemeral'], strict=True) if 'accessIPv4' in body: self._validate_access_ipv4(body['accessIPv4']) if 'accessIPv6' in body: self._validate_access_ipv6(body['accessIPv6']) if 'name' in body: self._validate_server_name(body['name']) for request_attribute, instance_attribute in attr_map.items(): try: kwargs[instance_attribute] = body[request_attribute] except (KeyError, TypeError): pass self._validate_metadata(kwargs.get('metadata', {})) if 'files_to_inject' in kwargs: personality = kwargs.pop('files_to_inject') files_to_inject = self._get_injected_files(personality) else: files_to_inject = None try: self.compute_api.rebuild(context, instance, image_href, password, files_to_inject=files_to_inject, **kwargs) except exception.InstanceIsLocked as e: raise exc.HTTPConflict(explanation=e.format_message()) except exception.InstanceInvalidState as state_error: common.raise_http_conflict_for_instance_invalid_state(state_error, 'rebuild', id) except exception.InstanceNotFound: msg = _("Instance could not be found") raise exc.HTTPNotFound(explanation=msg) except exception.InvalidMetadataSize as error: raise exc.HTTPRequestEntityTooLarge( explanation=error.format_message()) except exception.ImageNotFound: msg = _("Cannot find image for rebuild") raise exc.HTTPBadRequest(explanation=msg) except exception.QuotaError as error: raise exc.HTTPForbidden(explanation=error.format_message()) except (exception.ImageNotActive, exception.FlavorDiskTooSmall, exception.FlavorMemoryTooSmall, exception.InvalidMetadata, exception.AutoDiskConfigDisabledByImage) as error: raise exc.HTTPBadRequest(explanation=error.format_message()) instance = self._get_server(context, req, id, is_detail=True) view = self._view_builder.show(req, instance) # Add on the adminPass attribute since the view doesn't do it # unless instance passwords are disabled if CONF.enable_instance_password: view['server']['adminPass'] = password robj = wsgi.ResponseObject(view) return self._add_location(robj)
def create(self, req, body): """Creates a new volume.""" self.assert_valid_body(body, 'volume') LOG.debug('Create volume request body: %s', body) context = req.environ['cinder.context'] volume = body['volume'] kwargs = {} self.validate_name_and_description(volume) # NOTE(thingee): v2 API allows name instead of display_name if 'name' in volume: volume['display_name'] = volume.pop('name') # NOTE(thingee): v2 API allows description instead of # display_description if 'description' in volume: volume['display_description'] = volume.pop('description') if 'image_id' in volume: volume['imageRef'] = volume.pop('image_id') req_volume_type = volume.get('volume_type', None) if req_volume_type: # Not found exception will be handled at the wsgi level kwargs['volume_type'] = (volume_types.get_by_name_or_id( context, req_volume_type)) kwargs['metadata'] = volume.get('metadata', None) snapshot_id = volume.get('snapshot_id') if snapshot_id is not None: if not uuidutils.is_uuid_like(snapshot_id): msg = _("Snapshot ID must be in UUID form.") raise exc.HTTPBadRequest(explanation=msg) # Not found exception will be handled at the wsgi level kwargs['snapshot'] = self.volume_api.get_snapshot( context, snapshot_id) else: kwargs['snapshot'] = None source_volid = volume.get('source_volid') if source_volid is not None: # Not found exception will be handled at the wsgi level kwargs['source_volume'] = \ self.volume_api.get_volume(context, source_volid) else: kwargs['source_volume'] = None source_replica = volume.get('source_replica') if source_replica is not None: # Not found exception will be handled at the wsgi level src_vol = self.volume_api.get_volume(context, source_replica) if src_vol['replication_status'] == 'disabled': explanation = _('source volume id:%s is not' ' replicated') % source_replica raise exc.HTTPBadRequest(explanation=explanation) kwargs['source_replica'] = src_vol else: kwargs['source_replica'] = None consistencygroup_id = volume.get('consistencygroup_id') if consistencygroup_id is not None: # Not found exception will be handled at the wsgi level kwargs['consistencygroup'] = \ self.consistencygroup_api.get(context, consistencygroup_id) else: kwargs['consistencygroup'] = None size = volume.get('size', None) if size is None and kwargs['snapshot'] is not None: size = kwargs['snapshot']['volume_size'] elif size is None and kwargs['source_volume'] is not None: size = kwargs['source_volume']['size'] elif size is None and kwargs['source_replica'] is not None: size = kwargs['source_replica']['size'] LOG.info(_LI("Create volume of %s GB"), size) if self.ext_mgr.is_loaded('os-image-create'): image_ref = volume.get('imageRef') if image_ref is not None: image_uuid = self._image_uuid_from_ref(image_ref, context) kwargs['image_id'] = image_uuid kwargs['availability_zone'] = volume.get('availability_zone', None) kwargs['scheduler_hints'] = volume.get('scheduler_hints', None) kwargs['multiattach'] = utils.get_bool_param('multiattach', volume) new_volume = self.volume_api.create(context, size, volume.get('display_name'), volume.get('display_description'), **kwargs) retval = self._view_builder.detail(req, new_volume) return retval
def _get_requested_networks(self, requested_networks): """Create a list of requested networks from the networks attribute.""" networks = [] for network in requested_networks: try: # fixed IP address is optional # if the fixed IP address is not provided then # it will use one of the available IP address from the network address = network.get('fixed_ip', None) if address is not None and not utils.is_valid_ip_address( address): msg = _("Invalid fixed IP address (%s)") % address raise exc.HTTPBadRequest(explanation=msg) port_id = network.get('port', None) if port_id: network_uuid = None if not utils.is_neutron(): # port parameter is only for neutron v2.0 msg = _("Unknown argument: port") raise exc.HTTPBadRequest(explanation=msg) if not uuidutils.is_uuid_like(port_id): msg = _("Bad port format: port uuid is " "not in proper format " "(%s)") % port_id raise exc.HTTPBadRequest(explanation=msg) if address is not None: msg = _("Specified Fixed IP '%(addr)s' cannot be used " "with port '%(port)s': port already has " "a Fixed IP allocated.") % { "addr": address, "port": port_id } raise exc.HTTPBadRequest(explanation=msg) else: network_uuid = network['uuid'] if not port_id and not uuidutils.is_uuid_like(network_uuid): br_uuid = network_uuid.split('-', 1)[-1] if not uuidutils.is_uuid_like(br_uuid): msg = _("Bad networks format: network uuid is " "not in proper format " "(%s)") % network_uuid raise exc.HTTPBadRequest(explanation=msg) # For neutronv2, requested_networks # should be tuple of (network_uuid, fixed_ip, port_id) if utils.is_neutron(): networks.append((network_uuid, address, port_id)) else: # check if the network id is already present in the list, # we don't want duplicate networks to be passed # at the boot time for id, ip in networks: if id == network_uuid: expl = (_("Duplicate networks" " (%s) are not allowed") % network_uuid) raise exc.HTTPBadRequest(explanation=expl) networks.append((network_uuid, address)) except KeyError as key: expl = _('Bad network format: missing %s') % key raise exc.HTTPBadRequest(explanation=expl) except TypeError: expl = _('Bad networks format') raise exc.HTTPBadRequest(explanation=expl) return networks
def __call__(self, req, body): """ Executes the command """ if not isinstance(body, list): msg = _("Request must be a list of commands") raise exc.HTTPBadRequest(explanation=msg) def validate(cmd): if not isinstance(cmd, dict): msg = _("Bad Command: %s") % str(cmd) raise exc.HTTPBadRequest(explanation=msg) command, kwargs = cmd.get("command"), cmd.get("kwargs") if (not command or not isinstance(command, six.string_types) or (kwargs and not isinstance(kwargs, dict))): msg = _("Wrong command structure: %s") % (str(cmd)) raise exc.HTTPBadRequest(explanation=msg) method = self._registered.get(command) if not method: # Just raise 404 if the user tries to # access a private method. No need for # 403 here since logically the command # is not registered to the rpc dispatcher raise exc.HTTPNotFound(explanation=_("Command not found")) return True # If more than one command were sent then they might # be intended to be executed sequentially, that for, # lets first verify they're all valid before executing # them. commands = filter(validate, body) results = [] for cmd in commands: # kwargs is not required command, kwargs = cmd["command"], cmd.get("kwargs", {}) method = self._registered[command] try: result = method(req.context, **kwargs) except Exception as e: if self.raise_exc: raise cls, val = e.__class__, utils.exception_to_str(e) msg = (_("RPC Call Error: %(val)s\n%(tb)s") % dict(val=val, tb=traceback.format_exc())) LOG.error(msg) # NOTE(flaper87): Don't propagate all exceptions # but the ones allowed by the user. module = cls.__module__ if module not in CONF.allowed_rpc_exception_modules: cls = exception.RPCError val = six.text_type(exception.RPCError(cls=cls, val=val)) cls_path = "%s.%s" % (cls.__module__, cls.__name__) result = {"_error": {"cls": cls_path, "val": val}} results.append(result) return results
def create(self, req, body): """Creates a new server for a given user.""" if not self.is_valid_body(body, 'server'): raise exc.HTTPBadRequest(_("The request body is invalid")) context = req.environ['nova.context'] server_dict = body['server'] password = self._get_server_admin_password(server_dict) if 'name' not in server_dict: msg = _("Server name is not defined") raise exc.HTTPBadRequest(explanation=msg) name = server_dict['name'] self._validate_server_name(name) name = name.strip() # Arguments to be passed to instance create function create_kwargs = {} # Query extensions which want to manipulate the keyword # arguments. # NOTE(cyeoh): This is the hook that extensions use # to replace the extension specific code below. # When the extensions are ported this will also result # in some convenience function from this class being # moved to the extension if list(self.create_extension_manager): self.create_extension_manager.map(self._create_extension_point, server_dict, create_kwargs) image_uuid = self._image_from_req_data(server_dict, create_kwargs) # NOTE(cyeoh): Although an extension can set # return_reservation_id in order to request that a reservation # id be returned to the client instead of the newly created # instance information we do not want to pass this parameter # to the compute create call which always returns both. We use # this flag after the instance create call to determine what # to return to the client return_reservation_id = create_kwargs.pop('return_reservation_id', False) requested_networks = None # TODO(cyeoh): bp v3-api-core-as-extensions # Replace with an extension point when the os-networks # extension is ported. Currently reworked # to take into account is_neutron #if (self.ext_mgr.is_loaded('os-networks') # or utils.is_neutron()): # requested_networks = server_dict.get('networks') if utils.is_neutron(): requested_networks = server_dict.get('networks') if requested_networks is not None: requested_networks = self._get_requested_networks( requested_networks) try: flavor_id = self._flavor_id_from_req_data(body) except ValueError as error: msg = _("Invalid flavor_ref provided.") raise exc.HTTPBadRequest(explanation=msg) try: inst_type = flavors.get_flavor_by_flavor_id(flavor_id, ctxt=context, read_deleted="no") (instances, resv_id) = self.compute_api.create( context, inst_type, image_uuid, display_name=name, display_description=name, metadata=server_dict.get('metadata', {}), admin_password=password, requested_networks=requested_networks, **create_kwargs) except (exception.QuotaError, exception.PortLimitExceeded) as error: raise exc.HTTPRequestEntityTooLarge( explanation=error.format_message(), headers={'Retry-After': 0}) except exception.InvalidMetadataSize as error: raise exc.HTTPRequestEntityTooLarge( explanation=error.format_message()) except exception.ImageNotFound as error: msg = _("Can not find requested image") raise exc.HTTPBadRequest(explanation=msg) except exception.FlavorNotFound as error: msg = _("Invalid flavor_ref provided.") raise exc.HTTPBadRequest(explanation=msg) except exception.KeypairNotFound as error: msg = _("Invalid key_name provided.") raise exc.HTTPBadRequest(explanation=msg) except exception.ConfigDriveInvalidValue: msg = _("Invalid config_drive provided.") raise exc.HTTPBadRequest(explanation=msg) except messaging.RemoteError as err: msg = "%(err_type)s: %(err_msg)s" % { 'err_type': err.exc_type, 'err_msg': err.value } raise exc.HTTPBadRequest(explanation=msg) except UnicodeDecodeError as error: msg = "UnicodeError: %s" % unicode(error) raise exc.HTTPBadRequest(explanation=msg) except (exception.ImageNotActive, exception.FlavorDiskTooSmall, exception.FlavorMemoryTooSmall, exception.InvalidMetadata, exception.InvalidRequest, exception.MultiplePortsNotApplicable, exception.InstanceUserDataMalformed, exception.PortNotFound, exception.FixedIpAlreadyInUse, exception.SecurityGroupNotFound, exception.PortRequiresFixedIP, exception.NetworkRequiresSubnet, exception.NetworkNotFound) as error: raise exc.HTTPBadRequest(explanation=error.format_message()) except (exception.PortInUse, exception.NoUniqueMatch) as error: raise exc.HTTPConflict(explanation=error.format_message()) # If the caller wanted a reservation_id, return it if return_reservation_id: return wsgi.ResponseObject( {'servers_reservation': { 'reservation_id': resv_id }}) req.cache_db_instances(instances) server = self._view_builder.create(req, instances[0]) if CONF.enable_instance_password: server['server']['admin_password'] = password robj = wsgi.ResponseObject(server) return self._add_location(robj)
def _extract_tags_param(self, tags): try: return param_utils.extract_tags(tags) except ValueError as e: raise exc.HTTPBadRequest(six.text_type(e))
def bad(e): return exc.HTTPBadRequest(explanation=e)
def _get_servers(self, req, is_detail): """Returns a list of servers, based on any search options specified.""" search_opts = {} search_opts.update(req.GET) context = req.environ['nova.context'] remove_invalid_options(context, search_opts, self._get_server_search_options()) # Verify search by 'status' contains a valid status. # Convert it to filter by vm_state or task_state for compute_api. search_opts.pop('status', None) if 'status' in req.GET.keys(): statuses = req.GET.getall('status') states = common.task_and_vm_state_from_status(statuses) vm_state, task_state = states if not vm_state and not task_state: return {'servers': []} search_opts['vm_state'] = vm_state # When we search by vm state, task state will return 'default'. # So we don't need task_state search_opt. if 'default' not in task_state: search_opts['task_state'] = task_state if 'changes-since' in search_opts: try: parsed = timeutils.parse_isotime(search_opts['changes-since']) except ValueError: msg = _('Invalid changes-since value') raise exc.HTTPBadRequest(explanation=msg) search_opts['changes-since'] = parsed # By default, compute's get_all() will return deleted instances. # If an admin hasn't specified a 'deleted' search option, we need # to filter out deleted instances by setting the filter ourselves. # ... Unless 'changes-since' is specified, because 'changes-since' # should return recently deleted images according to the API spec. if 'deleted' not in search_opts: if 'changes-since' not in search_opts: # No 'changes-since', so we only want non-deleted servers search_opts['deleted'] = False else: # Convert deleted filter value to a valid boolean. # Return non-deleted servers if an invalid value # is passed with deleted filter. search_opts['deleted'] = strutils.bool_from_string( search_opts['deleted'], default=False) if search_opts.get("vm_state") == ['deleted']: if context.is_admin: search_opts['deleted'] = True else: msg = _("Only administrators may list deleted instances") raise exc.HTTPForbidden(explanation=msg) all_tenants = common.is_all_tenants(search_opts) # use the boolean from here on out so remove the entry from search_opts # if it's present search_opts.pop('all_tenants', None) elevated = None if all_tenants: policy.enforce(context, 'compute:get_all_tenants', { 'project_id': context.project_id, 'user_id': context.user_id }) elevated = context.elevated() else: if context.project_id: search_opts['project_id'] = context.project_id else: search_opts['user_id'] = context.user_id limit, marker = common.get_limit_and_marker(req) # Sorting by multiple keys and directions is conditionally enabled sort_keys, sort_dirs = None, None if self.ext_mgr.is_loaded('os-server-sort-keys'): sort_keys, sort_dirs = common.get_sort_params(req.params) expected_attrs = None if is_detail: # merge our expected attrs with what the view builder needs for # showing details expected_attrs = self._view_builder.get_show_expected_attrs( expected_attrs) try: instance_list = self.compute_api.get_all( elevated or context, search_opts=search_opts, limit=limit, marker=marker, want_objects=True, expected_attrs=expected_attrs, sort_keys=sort_keys, sort_dirs=sort_dirs) except exception.MarkerNotFound: msg = _('marker [%s] not found') % marker raise exc.HTTPBadRequest(explanation=msg) except exception.FlavorNotFound: LOG.debug("Flavor '%s' could not be found", search_opts['flavor']) instance_list = objects.InstanceList() if is_detail: instance_list.fill_faults() response = self._view_builder.detail(req, instance_list) else: response = self._view_builder.index(req, instance_list) req.cache_db_instances(instance_list) return response
def _validate_volume_id(self, volume_id): if not uuidutils.is_uuid_like(volume_id): msg = _("Bad volumeId format: volumeId is " "not in proper format (%s)") % volume_id raise exc.HTTPBadRequest(explanation=msg)
def _validate_user_data(self, user_data): if user_data and self._decode_base64(user_data) is None: expl = _('Userdata content cannot be decoded') raise exc.HTTPBadRequest(explanation=expl) return user_data
def _get_servers(self, req, is_detail): """Returns a list of servers, based on any search options specified.""" search_opts = {} search_opts.update(req.GET) context = req.environ['nova.context'] remove_invalid_options(context, search_opts, self._get_server_search_options()) # Verify search by 'status' contains a valid status. # Convert it to filter by vm_state or task_state for compute_api. search_opts.pop('status', None) if 'status' in req.GET.keys(): statuses = req.GET.getall('status') states = common.task_and_vm_state_from_status(statuses) vm_state, task_state = states if not vm_state and not task_state: return {'servers': []} search_opts['vm_state'] = vm_state # When we search by vm state, task state will return 'default'. # So we don't need task_state search_opt. if 'default' not in task_state: search_opts['task_state'] = task_state if 'changes-since' in search_opts: try: parsed = timeutils.parse_isotime(search_opts['changes-since']) except ValueError: msg = _('Invalid changes-since value') raise exc.HTTPBadRequest(explanation=msg) search_opts['changes-since'] = parsed # By default, compute's get_all() will return deleted instances. # If an admin hasn't specified a 'deleted' search option, we need # to filter out deleted instances by setting the filter ourselves. # ... Unless 'changes-since' is specified, because 'changes-since' # should return recently deleted images according to the API spec. if 'deleted' not in search_opts: if 'changes-since' not in search_opts: # No 'changes-since', so we only want non-deleted servers search_opts['deleted'] = False if search_opts.get("vm_state") == ['deleted']: if context.is_admin: search_opts['deleted'] = True else: msg = _("Only administrators may list deleted instances") raise exc.HTTPForbidden(explanation=msg) # If tenant_id is passed as a search parameter this should # imply that all_tenants is also enabled unless explicitly # disabled. Note that the tenant_id parameter is filtered out # by remove_invalid_options above unless the requestor is an # admin. if 'tenant_id' in search_opts and 'all_tenants' not in search_opts: # We do not need to add the all_tenants flag if the tenant # id associated with the token is the tenant id # specified. This is done so a request that does not need # the all_tenants flag does not fail because of lack of # policy permission for compute:get_all_tenants when it # doesn't actually need it. if context.project_id != search_opts.get('tenant_id'): search_opts['all_tenants'] = 1 # If all tenants is passed with 0 or false as the value # then remove it from the search options. Nothing passed as # the value for all_tenants is considered to enable the feature all_tenants = search_opts.get('all_tenants') if all_tenants: try: if not strutils.bool_from_string(all_tenants, True): del search_opts['all_tenants'] except ValueError as err: raise exception.InvalidInput(six.text_type(err)) if 'all_tenants' in search_opts: policy.enforce(context, 'compute:get_all_tenants', {'project_id': context.project_id, 'user_id': context.user_id}) del search_opts['all_tenants'] else: if context.project_id: search_opts['project_id'] = context.project_id else: search_opts['user_id'] = context.user_id limit, marker = common.get_limit_and_marker(req) sort_keys, sort_dirs = common.get_sort_params(req.params) try: instance_list = self.compute_api.get_all(context, search_opts=search_opts, limit=limit, marker=marker, want_objects=True, expected_attrs=['pci_devices'], sort_keys=sort_keys, sort_dirs=sort_dirs) except exception.MarkerNotFound: msg = _('marker [%s] not found') % marker raise exc.HTTPBadRequest(explanation=msg) except exception.FlavorNotFound: LOG.debug("Flavor '%s' could not be found ", search_opts['flavor']) instance_list = objects.InstanceList() if is_detail: instance_list.fill_faults() response = self._view_builder.detail(req, instance_list) else: response = self._view_builder.index(req, instance_list) req.cache_db_instances(instance_list) return response
def _image_ref_from_req_data(self, data): try: return six.text_type(data['server']['imageRef']) except (TypeError, KeyError): msg = _("Missing imageRef attribute") raise exc.HTTPBadRequest(explanation=msg)
def _migrate_live(self, req, id, body): """Permit admins to (live) migrate a server to a new host.""" context = req.environ["nova.context"] context.can(ms_policies.POLICY_ROOT % 'migrate_live') host = body["os-migrateLive"]["host"] block_migration = body["os-migrateLive"]["block_migration"] force = None async_ = api_version_request.is_supported(req, min_version='2.34') if api_version_request.is_supported(req, min_version='2.30'): force = self._get_force_param_for_live_migration(body, host) if api_version_request.is_supported(req, min_version='2.25'): if block_migration == 'auto': block_migration = None else: block_migration = strutils.bool_from_string(block_migration, strict=True) disk_over_commit = None else: disk_over_commit = body["os-migrateLive"]["disk_over_commit"] block_migration = strutils.bool_from_string(block_migration, strict=True) disk_over_commit = strutils.bool_from_string(disk_over_commit, strict=True) # NOTE(stephenfin): we need 'numa_topology' because of the # 'LiveMigrationTask._check_instance_has_no_numa' check in the # conductor instance = common.get_instance(self.compute_api, context, id, expected_attrs=['numa_topology']) # We could potentially move this check to conductor and avoid the # extra API call to neutron when we support move operations with ports # having resource requests. if (common.instance_has_port_with_resource_request( instance.uuid, self.network_api) and not common.supports_port_resource_request_during_move(req)): msg = _("The os-migrateLive action on a server with ports having " "resource requests, like a port with a QoS minimum " "bandwidth policy, is not supported with this " "microversion") raise exc.HTTPBadRequest(explanation=msg) try: self.compute_api.live_migrate(context, instance, block_migration, disk_over_commit, host, force, async_) except (exception.NoValidHost, exception.ComputeServiceUnavailable, exception.InvalidHypervisorType, exception.InvalidCPUInfo, exception.UnableToMigrateToSelf, exception.DestinationHypervisorTooOld, exception.InvalidLocalStorage, exception.InvalidSharedStorage, exception.HypervisorUnavailable, exception.MigrationPreCheckError) as ex: if async_: with excutils.save_and_reraise_exception(): LOG.error( "Unexpected exception received from " "conductor during pre-live-migration checks " "'%(ex)s'", {'ex': ex}) else: raise exc.HTTPBadRequest(explanation=ex.format_message()) except exception.OperationNotSupportedForSEV as e: raise exc.HTTPConflict(explanation=e.format_message()) except exception.InstanceIsLocked as e: raise exc.HTTPConflict(explanation=e.format_message()) except exception.ComputeHostNotFound as e: raise exc.HTTPBadRequest(explanation=e.format_message()) except exception.InstanceInvalidState as state_error: common.raise_http_conflict_for_instance_invalid_state( state_error, 'os-migrateLive', id)