def delete(self, req, replica_id, id): try: self._replica_tasks_execution_api.delete( req.environ['coriolis.context'], replica_id, id) raise exc.HTTPNoContent() except exception.NotFound as ex: raise exc.HTTPNotFound(explanation=ex.msg)
def delete(self, req, replica_id, id): context = req.environ["coriolis.context"] context.can( schedules_policies.get_replica_schedules_policy_label("delete")) self._schedule_api.delete(context, replica_id, id) raise exc.HTTPNoContent()
def delete(self, req, application_id, id): try: self._session_api.delete( req.environ['vdibroker.context'], id) raise exc.HTTPNoContent() except exception.NotFound as ex: raise exc.HTTPNotFound(explanation=ex.msg)
def update(self, req, id): """ Updates the server name or password """ if len(req.body) == 0: raise exc.HTTPUnprocessableEntity() inst_dict = self._deserialize(req.body, req.get_content_type()) if not inst_dict: return faults.Fault(exc.HTTPUnprocessableEntity()) ctxt = req.environ['nova.context'] update_dict = {} if 'name' in inst_dict['server']: name = inst_dict['server']['name'] self._validate_server_name(name) update_dict['display_name'] = name.strip() self._parse_update(ctxt, id, inst_dict, update_dict) try: self.compute_api.update(ctxt, id, **update_dict) except exception.NotFound: return faults.Fault(exc.HTTPNotFound()) return exc.HTTPNoContent()
def update(self, req, id, body): """ Updates the server name or password """ if len(req.body) == 0: raise exc.HTTPUnprocessableEntity() if not body: return faults.Fault(exc.HTTPUnprocessableEntity()) ctxt = req.environ['nova.context'] update_dict = {} if 'name' in body['server']: name = body['server']['name'] self.helper._validate_server_name(name) update_dict['display_name'] = name.strip() if 'description' in body['server']: description = body['server']['description'] update_dict['display_description'] = description.strip() self._parse_update(ctxt, id, body, update_dict) try: self.compute_api.update(ctxt, id, **update_dict) except exception.NotFound: return faults.Fault(exc.HTTPNotFound()) return exc.HTTPNoContent()
def delete(self, req, identity): """Delete the specified stack.""" self.rpc_client.delete_stack(req.context, identity, cast=False) raise exc.HTTPNoContent()
def delete(self, req, id): try: self._migration_api.delete(req.environ['MigrationTool.context'], id) raise exc.HTTPNoContent() except exception.NotFound as ex: raise exc.HTTPNotFound(explanation=ex.msg)
def delete(self, req, cluster_id): action = self.rpc_client.cluster_delete(req.context, cluster_id, cast=False) if action is not None: raise exc.HTTPNoContent() raise exc.HTTPInternalServerError(_('Failed deleting cluster.'))
def delete(self, req, id): context = req.environ["coriolis.context"] context.can(replica_policies.get_replicas_policy_label("delete")) try: self._replica_api.delete(context, id) raise exc.HTTPNoContent() except exception.NotFound as ex: raise exc.HTTPNotFound(explanation=ex.msg)
def delete(self, req, config_id): """Delete an existing software config.""" res = self.rpc_client.delete_software_config(req.context, config_id) if res is not None: raise exc.HTTPBadRequest(res['Error']) raise exc.HTTPNoContent()
def _cancel(self, req, id, body): try: self._migration_api.cancel(req.environ['coriolis.context'], id) raise exc.HTTPNoContent() except exception.NotFound as ex: raise exc.HTTPNotFound(explanation=ex.msg) except exception.InvalidParameterValue as ex: raise exc.HTTPNotFound(explanation=ex.msg)
def delete(self, req, identity): """Delete the specified stack.""" res = self.rpc_client.delete_stack(req.context, identity, cast=False) if res is not None: raise exc.HTTPBadRequest(res['Error']) raise exc.HTTPNoContent()
def delete(self, req, minion_pool_id, id): context = req.environ["coriolis.context"] context.can( pool_execution_policies.get_minion_pool_executions_policy_label( "delete")) try: self._pool_tasks_execution_api.delete(context, minion_pool_id, id) raise exc.HTTPNoContent() except exception.NotFound as ex: raise exc.HTTPNotFound(explanation=ex.msg)
def delete(self, req, deployment_id): """ Delete an existing software deployment """ res = self.rpc_client.delete_software_deployment( req.context, deployment_id) if res is not None: raise exc.HTTPBadRequest(res['Error']) raise exc.HTTPNoContent()
def _cancel(self, req, replica_id, id, body): try: force = (body["cancel"] or {}).get("force", False) self._replica_tasks_execution_api.cancel( req.environ['coriolis.context'], replica_id, id, force) raise exc.HTTPNoContent() except exception.NotFound as ex: raise exc.HTTPNotFound(explanation=ex.msg) except exception.InvalidParameterValue as ex: raise exc.HTTPNotFound(explanation=ex.msg)
def _cancel(self, req, id, body): context = req.environ['coriolis.context'] context.can(migration_policies.get_migrations_policy_label("cancel")) try: force = (body["cancel"] or {}).get("force", False) self._migration_api.cancel(context, id, force) raise exc.HTTPNoContent() except exception.NotFound as ex: raise exc.HTTPNotFound(explanation=ex.msg) except exception.InvalidParameterValue as ex: raise exc.HTTPNotFound(explanation=ex.msg)
def update(self, req, server_id, id, body): context = req.environ["nova.context"] authorize(context, action='update') self._check_instance_in_valid_state(context, server_id, 'update tag') try: jsonschema.validate(id, schema.tag) except jsonschema.ValidationError as e: msg = (_("Tag '%(tag)s' is invalid. It must be a string without " "characters '/' and ','. Validation error message: " "%(err)s") % { 'tag': id, 'err': e.message }) raise exc.HTTPBadRequest(explanation=msg) try: tags = objects.TagList.get_by_resource_id(context, server_id) except exception.InstanceNotFound as e: raise exc.HTTPNotFound(explanation=e.format_message()) if len(tags) >= objects.instance.MAX_TAG_COUNT: msg = (_("The number of tags exceeded the per-server limit %d") % objects.instance.MAX_TAG_COUNT) raise exc.HTTPBadRequest(explanation=msg) if len(id) > objects.tag.MAX_TAG_LENGTH: msg = (_("Tag '%(tag)s' is too long. Maximum length of a tag " "is %(length)d") % { 'tag': id, 'length': objects.tag.MAX_TAG_LENGTH }) raise exc.HTTPBadRequest(explanation=msg) if id in _get_tags_names(tags): # NOTE(snikitin): server already has specified tag return exc.HTTPNoContent() tag = objects.Tag(context=context, resource_id=server_id, tag=id) try: tag.create() except exception.InstanceNotFound as e: raise exc.HTTPNotFound(explanation=e.format_message()) response = exc.HTTPCreated() response.headers['Location'] = self._view_builder.get_location( req, server_id, id) return response
def delete(self, req, identity): """ Delete the specified stack """ try: res = self.engine.delete_stack(req.context, identity, cast=False) except rpc_common.RemoteError as ex: return util.remote_error(ex) if res is not None: raise exc.HTTPBadRequest(res['Error']) raise exc.HTTPNoContent()
def device(self, request, controller, user_id, handle): try: if request.method == 'GET': return controller.get_descriptor(user_id, handle) elif request.method == 'POST': props = json.loads(request.body) controller.set_props(handle, props) return controller.get_descriptor(user_id, handle) elif request.method == 'DELETE': controller.unregister(handle) return exc.HTTPNoContent() else: raise exc.HTTPMethodNotAllowed except ValueError as e: raise exc.HTTPNotFound(e.message)
def user(self, request, controller, user_id): if request.path_info_peek(): page = request.path_info_pop() if page == 'register': return self.register(request, controller, user_id) elif page == 'authenticate': return self.authenticate(request, controller, user_id) else: return self.device(request, controller, user_id, page) if request.method == 'GET': return controller.get_descriptors(user_id) elif request.method == 'DELETE': controller.delete_user(user_id) return exc.HTTPNoContent() else: raise exc.HTTPMethodNotAllowed
def delete_member(self, req, image_id, member): """ Removes a membership from the image. """ if req.context.read_only: raise exc.HTTPForbidden() elif req.context.owner is None: raise exc.HTTPUnauthorized(_("No authenticated user")) # Make sure the image exists try: image = db_api.image_get(req.context, image_id) except exception.NotFound: raise exc.HTTPNotFound() except exception.NotAuthorized: # If it's private and doesn't belong to them, don't let on # that it exists msg = _("Access by %(user)s to image %(id)s " "denied") % ({ 'user': req.context.user, 'id': image_id }) logger.info(msg) raise exc.HTTPNotFound() # Can they manipulate the membership? if not req.context.is_image_sharable(image): raise exc.HTTPForbidden(_("No permission to share that image")) # Look up an existing membership try: session = db_api.get_session() member_ref = db_api.image_member_find(req.context, image_id, member, session=session) db_api.image_member_delete(req.context, member_ref, session=session) except exception.NotFound: pass # Make an appropriate result return exc.HTTPNoContent()
class Controller(wsgi.Controller): """ The Server API base controller class for the OpenStack API """ _view_builder_class = views_servers.ViewBuilder @staticmethod def _add_location(robj): # Just in case... if 'server' not in robj.obj: return robj link = filter(lambda l: l['rel'] == 'self', robj.obj['server']['links']) if link: robj['Location'] = link[0]['href'].encode('utf-8') # Convenience return return robj def __init__(self, **kwargs): super(Controller, self).__init__(**kwargs) self.compute_api = compute.API() @wsgi.serializers(xml=MinimalServersTemplate) def index(self, req): """ Returns a list of server names and ids for a given user """ try: servers = self._get_servers(req, is_detail=False) except exception.Invalid as err: raise exc.HTTPBadRequest(explanation=str(err)) except exception.NotFound: raise exc.HTTPNotFound() return servers @wsgi.serializers(xml=ServersTemplate) def detail(self, req): """ Returns a list of server details for a given user """ try: servers = self._get_servers(req, is_detail=True) except exception.Invalid as err: raise exc.HTTPBadRequest(explanation=str(err)) except exception.NotFound as err: raise exc.HTTPNotFound() return servers def _get_block_device_mapping(self, data): """Get block_device_mapping from 'server' dictionary. Overridden by volumes controller. """ return None def _add_instance_faults(self, ctxt, instances): faults = self.compute_api.get_instance_faults(ctxt, instances) if faults is not None: for instance in instances: faults_list = faults.get(instance['uuid'], []) try: instance['fault'] = faults_list[0] except IndexError: pass return instances def _get_servers(self, req, is_detail): """Returns a list of servers, taking into account 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 for compute_api. status = search_opts.pop('status', None) if status is not None: state = common.vm_state_from_status(status) if state is None: msg = _('Invalid server status: %(status)s') % locals() raise exc.HTTPBadRequest(explanation=msg) search_opts['vm_state'] = state if 'changes-since' in search_opts: try: parsed = utils.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 # NOTE(dprince) This prevents computes' get_all() from returning # instances from multiple tenants when an admin accounts is used. # By default non-admin accounts are always limited to project/user # both here and in the compute API. if not context.is_admin or (context.is_admin and 'all_tenants' not in search_opts): if context.project_id: search_opts['project_id'] = context.project_id else: search_opts['user_id'] = context.user_id instance_list = self.compute_api.get_all(context, search_opts=search_opts) limited_list = self._limit_items(instance_list, req) if is_detail: self._add_instance_faults(context, limited_list) return self._view_builder.detail(req, limited_list) else: return self._view_builder.index(req, limited_list) def _get_server(self, context, instance_uuid): """Utility function for looking up an instance by uuid""" try: return self.compute_api.get(context, instance_uuid) except exception.NotFound: raise exc.HTTPNotFound() def _handle_quota_error(self, error): """ Reraise quota errors as api-specific http exceptions """ code_mappings = { "OnsetFileLimitExceeded": _("Personality file limit exceeded"), "OnsetFilePathLimitExceeded": _("Personality file path too long"), "OnsetFileContentLimitExceeded": _("Personality file content too long"), # NOTE(bcwaldon): expose the message generated below in order # to better explain how the quota was exceeded "InstanceLimitExceeded": error.message, } code = error.kwargs['code'] expl = code_mappings.get(code, error.message) % error.kwargs raise exc.HTTPRequestEntityTooLarge(explanation=expl, headers={'Retry-After': 0}) def _validate_server_name(self, value): if not isinstance(value, basestring): msg = _("Server name is not a string or unicode") raise exc.HTTPBadRequest(explanation=msg) if value.strip() == '': msg = _("Server name is an empty string") raise exc.HTTPBadRequest(explanation=msg) if not len(value) < 256: msg = _("Server name must be less than 256 characters.") raise exc.HTTPBadRequest(explanation=msg) def _get_injected_files(self, personality): """ Create a list of injected files from the personality attribute At this time, injected_files must be formatted as a list of (file_path, file_content) pairs for compatibility with the underlying compute service. """ injected_files = [] for item in personality: try: path = item['path'] contents = item['contents'] except KeyError as key: expl = _('Bad personality format: missing %s') % key raise exc.HTTPBadRequest(explanation=expl) except TypeError: expl = _('Bad personality format') raise exc.HTTPBadRequest(explanation=expl) try: contents = base64.b64decode(contents) except TypeError: expl = _('Personality content for %s cannot be decoded') % path raise exc.HTTPBadRequest(explanation=expl) injected_files.append((path, contents)) return injected_files def _get_requested_networks(self, requested_networks): """ Create a list of requested networks from the networks attribute """ networks = [] for network in requested_networks: try: network_uuid = network['uuid'] if not utils.is_uuid_like(network_uuid): msg = _("Bad networks format: network uuid is not in" " proper format (%s)") % network_uuid raise exc.HTTPBadRequest(explanation=msg) #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_ipv4(address): msg = _("Invalid fixed IP address (%s)") % address raise exc.HTTPBadRequest(explanation=msg) # 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 _validate_user_data(self, user_data): """Check if the user_data is encoded properly""" if not user_data: return try: user_data = base64.b64decode(user_data) except TypeError: expl = _('Userdata content cannot be decoded') raise exc.HTTPBadRequest(explanation=expl) def _validate_access_ipv4(self, address): try: socket.inet_aton(address) except socket.error: expl = _('accessIPv4 is not proper IPv4 format') raise exc.HTTPBadRequest(explanation=expl) def _validate_access_ipv6(self, address): try: socket.inet_pton(socket.AF_INET6, address) except socket.error: expl = _('accessIPv6 is not proper IPv6 format') raise exc.HTTPBadRequest(explanation=expl) @wsgi.serializers(xml=ServerTemplate) def show(self, req, id): """ Returns server details by server id """ try: context = req.environ['nova.context'] instance = self.compute_api.get(context, id) self._add_instance_faults(context, [instance]) return self._view_builder.show(req, instance) except exception.NotFound: raise exc.HTTPNotFound() @wsgi.response(202) @wsgi.serializers(xml=FullServerTemplate) @wsgi.deserializers(xml=CreateDeserializer) def create(self, req, body): """ Creates a new server for a given user """ if not body: raise exc.HTTPUnprocessableEntity() if not 'server' in body: raise exc.HTTPUnprocessableEntity() body['server']['key_name'] = self._get_key_name(req, body) context = req.environ['nova.context'] server_dict = body['server'] password = self._get_server_admin_password(server_dict) if not 'name' 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_href = self._image_ref_from_req_data(body) image_href = self._image_uuid_from_href(image_href) personality = server_dict.get('personality') config_drive = server_dict.get('config_drive') injected_files = [] if personality: injected_files = self._get_injected_files(personality) sg_names = [] security_groups = server_dict.get('security_groups') if security_groups is not None: sg_names = [sg['name'] for sg in security_groups if sg.get('name')] if not sg_names: sg_names.append('default') sg_names = list(set(sg_names)) requested_networks = server_dict.get('networks') if requested_networks is not None: requested_networks = self._get_requested_networks( requested_networks) (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) try: flavor_id = self._flavor_id_from_req_data(body) except ValueError as error: msg = _("Invalid flavorRef provided.") raise exc.HTTPBadRequest(explanation=msg) # optional openstack extensions: key_name = server_dict.get('key_name') user_data = server_dict.get('user_data') self._validate_user_data(user_data) availability_zone = server_dict.get('availability_zone') name = server_dict['name'] self._validate_server_name(name) name = name.strip() block_device_mapping = self._get_block_device_mapping(server_dict) ret_resv_id = server_dict.get('return_reservation_id', False) min_count = server_dict.get('min_count') max_count = server_dict.get('max_count') # min_count and max_count are optional. If they exist, they come # in as strings. We want to default 'min_count' to 1, and default # 'max_count' to be 'min_count'. min_count = int(min_count) if min_count else 1 max_count = int(max_count) if max_count else min_count if min_count > max_count: min_count = max_count auto_disk_config = server_dict.get('auto_disk_config') scheduler_hints = server_dict.get('scheduler_hints', {}) try: _get_inst_type = instance_types.get_instance_type_by_flavor_id inst_type = _get_inst_type(flavor_id) (instances, resv_id) = self.compute_api.create( context, inst_type, image_href, 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) except exception.QuotaError as error: self._handle_quota_error(error) except exception.InstanceTypeMemoryTooSmall as error: raise exc.HTTPBadRequest(explanation=unicode(error)) except exception.InstanceTypeDiskTooSmall as error: raise exc.HTTPBadRequest(explanation=unicode(error)) 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.SecurityGroupNotFound as error: raise exc.HTTPBadRequest(explanation=unicode(error)) except rpc_common.RemoteError as err: msg = "%(err_type)s: %(err_msg)s" % { 'err_type': err.exc_type, 'err_msg': err.value } raise exc.HTTPBadRequest(explanation=msg) # Let the caller deal with unhandled exceptions. # If the caller wanted a reservation_id, return it if ret_resv_id: return {'reservation_id': resv_id} server = self._view_builder.create(req, instances[0]) if '_is_precooked' in server['server'].keys(): del server['server']['_is_precooked'] else: if FLAGS.enable_instance_password: server['server']['adminPass'] = password robj = wsgi.ResponseObject(server) return self._add_location(robj) def _delete(self, context, id): instance = self._get_server(context, id) if FLAGS.reclaim_instance_interval: self.compute_api.soft_delete(context, instance) else: self.compute_api.delete(context, instance) @wsgi.serializers(xml=ServerTemplate) def update(self, req, id, body): """Update server then pass on to version-specific controller""" if len(req.body) == 0: raise exc.HTTPUnprocessableEntity() if not body: raise exc.HTTPUnprocessableEntity() ctxt = req.environ['nova.context'] update_dict = {} if 'name' in body['server']: name = body['server']['name'] self._validate_server_name(name) update_dict['display_name'] = name.strip() if 'accessIPv4' in body['server']: access_ipv4 = body['server']['accessIPv4'] self._validate_access_ipv4(access_ipv4) update_dict['access_ip_v4'] = access_ipv4.strip() if 'accessIPv6' in body['server']: access_ipv6 = body['server']['accessIPv6'] self._validate_access_ipv6(access_ipv6) update_dict['access_ip_v6'] = access_ipv6.strip() if 'auto_disk_config' in body['server']: auto_disk_config = utils.bool_from_str( body['server']['auto_disk_config']) update_dict['auto_disk_config'] = auto_disk_config try: instance = self.compute_api.get(ctxt, id) self.compute_api.update(ctxt, instance, **update_dict) except exception.NotFound: raise exc.HTTPNotFound() instance.update(update_dict) self._add_instance_faults(ctxt, [instance]) return self._view_builder.show(req, instance) @wsgi.response(202) @wsgi.serializers(xml=FullServerTemplate) @wsgi.deserializers(xml=ActionDeserializer) @wsgi.action('confirmResize') def _action_confirm_resize(self, req, id, body): context = req.environ['nova.context'] instance = self._get_server(context, id) try: self.compute_api.confirm_resize(context, instance) except exception.MigrationNotFound: msg = _("Instance has not been resized.") raise exc.HTTPBadRequest(explanation=msg) except exception.InstanceInvalidState as state_error: common.raise_http_conflict_for_instance_invalid_state( state_error, 'confirmResize') except Exception, e: LOG.exception(_("Error in confirm-resize %s"), e) raise exc.HTTPBadRequest() return exc.HTTPNoContent()
def _update(self, context, req, id, inst_dict): if 'adminPass' in inst_dict['server']: self.compute_api.set_admin_password( context, id, inst_dict['server']['adminPass']) return exc.HTTPNoContent()
class Controller(object): """ The Server API base controller class for the OpenStack API """ def __init__(self): self.compute_api = compute.API() self.helper = helper.CreateInstanceHelper(self) def index(self, req): """ Returns a list of server names and ids for a given user """ try: servers = self._get_servers(req, is_detail=False) except exception.Invalid as err: return exc.HTTPBadRequest(explanation=str(err)) except exception.NotFound: return exc.HTTPNotFound() return servers def detail(self, req): """ Returns a list of server details for a given user """ try: servers = self._get_servers(req, is_detail=True) except exception.Invalid as err: return exc.HTTPBadRequest(explanation=str(err)) except exception.NotFound as err: return exc.HTTPNotFound() return servers def _build_view(self, req, instance, is_detail=False): raise NotImplementedError() def _limit_items(self, items, req): raise NotImplementedError() def _action_rebuild(self, info, request, instance_id): raise NotImplementedError() def _get_servers(self, req, is_detail): """Returns a list of servers, taking into account any search options specified. """ search_opts = {} search_opts.update(req.str_GET) context = req.environ['nova.context'] remove_invalid_options(context, search_opts, self._get_server_search_options()) # Convert recurse_zones into a boolean search_opts['recurse_zones'] = utils.bool_from_str( search_opts.get('recurse_zones', False)) # If search by 'status', we need to convert it to 'vm_state' # to pass on to child zones. if 'status' in search_opts: status = search_opts['status'] state = common.vm_state_from_status(status) if state is None: reason = _('Invalid server status: %(status)s') % locals() raise exception.InvalidInput(reason=reason) search_opts['vm_state'] = state if 'changes-since' in search_opts: try: parsed = utils.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 instance_list = self.compute_api.get_all(context, search_opts=search_opts) limited_list = self._limit_items(instance_list, req) servers = [ self._build_view(req, inst, is_detail)['server'] for inst in limited_list ] return dict(servers=servers) @scheduler_api.redirect_handler def show(self, req, id): """ Returns server details by server id """ try: instance = self.compute_api.routing_get( req.environ['nova.context'], id) return self._build_view(req, instance, is_detail=True) except exception.NotFound: raise exc.HTTPNotFound() def _get_key_name(self, req, body): """ Get default keypair if not set """ raise NotImplementedError() def create(self, req, body): """ Creates a new server for a given user """ if 'server' in body: body['server']['key_name'] = self._get_key_name(req, body) extra_values = None extra_values, instances = self.helper.create_instance( req, body, self.compute_api.create) # We can only return 1 instance via the API, if we happen to # build more than one... instances is a list, so we'll just # use the first one.. inst = instances[0] for key in ['instance_type', 'image_ref']: inst[key] = extra_values[key] server = self._build_view(req, inst, is_detail=True) server['server']['adminPass'] = extra_values['password'] return server @scheduler_api.redirect_handler def update(self, req, id, body): """Update server then pass on to version-specific controller""" if len(req.body) == 0: raise exc.HTTPUnprocessableEntity() if not body: raise exc.HTTPUnprocessableEntity() ctxt = req.environ['nova.context'] update_dict = {} if 'name' in body['server']: name = body['server']['name'] self.helper._validate_server_name(name) update_dict['display_name'] = name.strip() if 'accessIPv4' in body['server']: access_ipv4 = body['server']['accessIPv4'] update_dict['access_ip_v4'] = access_ipv4.strip() if 'accessIPv6' in body['server']: access_ipv6 = body['server']['accessIPv6'] update_dict['access_ip_v6'] = access_ipv6.strip() try: self.compute_api.update(ctxt, id, **update_dict) except exception.NotFound: raise exc.HTTPNotFound() return self._update(ctxt, req, id, body) def _update(self, context, req, id, inst_dict): return exc.HTTPNotImplemented() @scheduler_api.redirect_handler def action(self, req, id, body): """Multi-purpose method used to take actions on a server""" self.actions = { 'changePassword': self._action_change_password, 'reboot': self._action_reboot, 'resize': self._action_resize, 'confirmResize': self._action_confirm_resize, 'revertResize': self._action_revert_resize, 'rebuild': self._action_rebuild, 'createImage': self._action_create_image, } if FLAGS.allow_admin_api: admin_actions = { 'createBackup': self._action_create_backup, } self.actions.update(admin_actions) for key in body: if key in self.actions: return self.actions[key](body, req, id) else: msg = _("There is no such server action: %s") % (key, ) raise exc.HTTPBadRequest(explanation=msg) msg = _("Invalid request body") raise exc.HTTPBadRequest(explanation=msg) def _action_create_backup(self, input_dict, req, instance_id): """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. """ entity = input_dict["createBackup"] 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 webob.exc.HTTPBadRequest(explanation=msg) except TypeError: msg = _("Malformed createBackup entity") raise webob.exc.HTTPBadRequest(explanation=msg) try: rotation = int(rotation) except ValueError: msg = _("createBackup attribute 'rotation' must be an integer") raise webob.exc.HTTPBadRequest(explanation=msg) # preserve link to server in image properties server_ref = os.path.join(req.application_url, 'servers', str(instance_id)) props = {'instance_ref': server_ref} metadata = entity.get('metadata', {}) context = req.environ["nova.context"] common.check_img_metadata_quota_limit(context, metadata) try: props.update(metadata) except ValueError: msg = _("Invalid metadata") raise webob.exc.HTTPBadRequest(explanation=msg) image = self.compute_api.backup(context, instance_id, image_name, backup_type, rotation, extra_properties=props) # 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 @common.check_snapshots_enabled def _action_create_image(self, input_dict, req, id): return exc.HTTPNotImplemented() def _action_change_password(self, input_dict, req, id): return exc.HTTPNotImplemented() def _action_confirm_resize(self, input_dict, req, id): try: self.compute_api.confirm_resize(req.environ['nova.context'], id) except Exception, e: LOG.exception(_("Error in confirm-resize %s"), e) raise exc.HTTPBadRequest() return exc.HTTPNoContent()
def delete(self, req, policy_id): body = {'identity': policy_id} obj = util.parse_request('PolicyDeleteRequest', req, body) self.rpc_client.call2(req.context, 'policy_delete2', obj) raise exc.HTTPNoContent()
def delete_snapshot(self, req, identity, snapshot_id): self.rpc_client.delete_snapshot(req.context, identity, snapshot_id) raise exc.HTTPNoContent()
class Controller(wsgi.Controller): """ The Server API base controller class for the OpenStack API """ _view_builder_class = views_servers.ViewBuilder def __init__(self, **kwargs): super(Controller, self).__init__(**kwargs) self.compute_api = compute.API() self.network_api = network.API() def index(self, req): """ Returns a list of server names and ids for a given user """ try: servers = self._get_servers(req, is_detail=False) except exception.Invalid as err: raise exc.HTTPBadRequest(explanation=str(err)) except exception.NotFound: raise exc.HTTPNotFound() return servers def detail(self, req): """ Returns a list of server details for a given user """ try: servers = self._get_servers(req, is_detail=True) except exception.Invalid as err: raise exc.HTTPBadRequest(explanation=str(err)) except exception.NotFound as err: raise exc.HTTPNotFound() return servers def _get_block_device_mapping(self, data): """Get block_device_mapping from 'server' dictionary. Overridden by volumes controller. """ return None def _add_instance_faults(self, ctxt, instances): faults = self.compute_api.get_instance_faults(ctxt, instances) if faults is not None: for instance in instances: faults_list = faults.get(instance['uuid'], []) try: instance['fault'] = faults_list[0] except IndexError: pass return instances def _get_servers(self, req, is_detail): """Returns a list of servers, taking into account any search options specified. """ search_opts = {} search_opts.update(req.str_GET) context = req.environ['nova.context'] remove_invalid_options(context, search_opts, self._get_server_search_options()) # Convert local_zone_only into a boolean search_opts['local_zone_only'] = utils.bool_from_str( search_opts.get('local_zone_only', False)) # If search by 'status', we need to convert it to 'vm_state' # to pass on to child zones. if 'status' in search_opts: status = search_opts['status'] state = common.vm_state_from_status(status) if state is None: reason = _('Invalid server status: %(status)s') % locals() raise exception.InvalidInput(reason=reason) search_opts['vm_state'] = state if 'changes-since' in search_opts: try: parsed = utils.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 instance_list = self.compute_api.get_all(context, search_opts=search_opts) limited_list = self._limit_items(instance_list, req) if is_detail: self._add_instance_faults(context, limited_list) return self._view_builder.detail(req, limited_list) else: return self._view_builder.index(req, limited_list) def _get_server(self, context, instance_uuid): """Utility function for looking up an instance by uuid""" try: return self.compute_api.routing_get(context, instance_uuid) except exception.NotFound: raise exc.HTTPNotFound() def _handle_quota_error(self, error): """ Reraise quota errors as api-specific http exceptions """ code_mappings = { "OnsetFileLimitExceeded": _("Personality file limit exceeded"), "OnsetFilePathLimitExceeded": _("Personality file path too long"), "OnsetFileContentLimitExceeded": _("Personality file content too long"), # NOTE(bcwaldon): expose the message generated below in order # to better explain how the quota was exceeded "InstanceLimitExceeded": error.message, } expl = code_mappings.get(error.code) if expl: raise exc.HTTPRequestEntityTooLarge(explanation=expl, headers={'Retry-After': 0}) # if the original error is okay, just reraise it raise error def _validate_server_name(self, value): if not isinstance(value, basestring): msg = _("Server name is not a string or unicode") raise exc.HTTPBadRequest(explanation=msg) if value.strip() == '': msg = _("Server name is an empty string") raise exc.HTTPBadRequest(explanation=msg) def _get_injected_files(self, personality): """ Create a list of injected files from the personality attribute At this time, injected_files must be formatted as a list of (file_path, file_content) pairs for compatibility with the underlying compute service. """ injected_files = [] for item in personality: try: path = item['path'] contents = item['contents'] except KeyError as key: expl = _('Bad personality format: missing %s') % key raise exc.HTTPBadRequest(explanation=expl) except TypeError: expl = _('Bad personality format') raise exc.HTTPBadRequest(explanation=expl) try: contents = base64.b64decode(contents) except TypeError: expl = _('Personality content for %s cannot be decoded') % path raise exc.HTTPBadRequest(explanation=expl) injected_files.append((path, contents)) return injected_files def _get_requested_networks(self, requested_networks): """ Create a list of requested networks from the networks attribute """ networks = [] for network in requested_networks: try: network_uuid = network['uuid'] if not utils.is_uuid_like(network_uuid): msg = _("Bad networks format: network uuid is not in" " proper format (%s)") % network_uuid raise exc.HTTPBadRequest(explanation=msg) #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_ipv4(address): msg = _("Invalid fixed IP address (%s)") % address raise exc.HTTPBadRequest(explanation=msg) # 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 _validate_user_data(self, user_data): """Check if the user_data is encoded properly""" if not user_data: return try: user_data = base64.b64decode(user_data) except TypeError: expl = _('Userdata content cannot be decoded') raise exc.HTTPBadRequest(explanation=expl) @exception.novaclient_converter @scheduler_api.redirect_handler def show(self, req, id): """ Returns server details by server id """ try: context = req.environ['nova.context'] instance = self.compute_api.routing_get(context, id) self._add_instance_faults(context, [instance]) return self._view_builder.show(req, instance) except exception.NotFound: raise exc.HTTPNotFound() def create(self, req, body): """ Creates a new server for a given user """ if not body: raise exc.HTTPUnprocessableEntity() if not 'server' in body: raise exc.HTTPUnprocessableEntity() body['server']['key_name'] = self._get_key_name(req, body) context = req.environ['nova.context'] server_dict = body['server'] password = self._get_server_admin_password(server_dict) if not 'name' 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_href = self._image_ref_from_req_data(body) # If the image href was generated by nova api, strip image_href # down to an id and use the default glance connection params if str(image_href).startswith(req.application_url): image_href = image_href.split('/').pop() personality = server_dict.get('personality') config_drive = server_dict.get('config_drive') injected_files = [] if personality: injected_files = self._get_injected_files(personality) sg_names = [] security_groups = server_dict.get('security_groups') if security_groups is not None: sg_names = [sg['name'] for sg in security_groups if sg.get('name')] if not sg_names: sg_names.append('default') sg_names = list(set(sg_names)) 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) zone_blob = server_dict.get('blob') # optional openstack extensions: key_name = server_dict.get('key_name') user_data = server_dict.get('user_data') self._validate_user_data(user_data) availability_zone = server_dict.get('availability_zone') name = server_dict['name'] self._validate_server_name(name) name = name.strip() block_device_mapping = self._get_block_device_mapping(server_dict) # Only allow admins to specify their own reservation_ids # This is really meant to allow zones to work. reservation_id = server_dict.get('reservation_id') if all([reservation_id is not None, reservation_id != '', not context.is_admin]): reservation_id = None ret_resv_id = server_dict.get('return_reservation_id', False) min_count = server_dict.get('min_count') max_count = server_dict.get('max_count') # min_count and max_count are optional. If they exist, they come # in as strings. We want to default 'min_count' to 1, and default # 'max_count' to be 'min_count'. min_count = int(min_count) if min_count else 1 max_count = int(max_count) if max_count else min_count if min_count > max_count: min_count = max_count auto_disk_config = server_dict.get('auto_disk_config') try: inst_type = \ instance_types.get_instance_type_by_flavor_id(flavor_id) (instances, resv_id) = self.compute_api.create(context, inst_type, image_href, display_name=name, display_description=name, key_name=key_name, metadata=server_dict.get('metadata', {}), access_ip_v4=server_dict.get('accessIPv4'), access_ip_v6=server_dict.get('accessIPv6'), injected_files=injected_files, admin_password=password, zone_blob=zone_blob, reservation_id=reservation_id, 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) except exception.QuotaError as error: self._handle_quota_error(error) except exception.InstanceTypeMemoryTooSmall as error: raise exc.HTTPBadRequest(explanation=unicode(error)) except exception.InstanceTypeDiskTooSmall as error: raise exc.HTTPBadRequest(explanation=unicode(error)) 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.SecurityGroupNotFound as error: raise exc.HTTPBadRequest(explanation=unicode(error)) except rpc_common.RemoteError as err: msg = "%(err_type)s: %(err_msg)s" % \ {'err_type': err.exc_type, 'err_msg': err.value} raise exc.HTTPBadRequest(explanation=msg) # Let the caller deal with unhandled exceptions. # If the caller wanted a reservation_id, return it if ret_resv_id: return {'reservation_id': resv_id} server = self._view_builder.create(req, instances[0]) if '_is_precooked' in server['server'].keys(): del server['server']['_is_precooked'] else: server['server']['adminPass'] = password return server def _delete(self, context, id): instance = self._get_server(context, id) if FLAGS.reclaim_instance_interval: self.compute_api.soft_delete(context, instance) else: self.compute_api.delete(context, instance) @scheduler_api.redirect_handler def update(self, req, id, body): """Update server then pass on to version-specific controller""" if len(req.body) == 0: raise exc.HTTPUnprocessableEntity() if not body: raise exc.HTTPUnprocessableEntity() ctxt = req.environ['nova.context'] update_dict = {} if 'name' in body['server']: name = body['server']['name'] self._validate_server_name(name) update_dict['display_name'] = name.strip() if 'accessIPv4' in body['server']: access_ipv4 = body['server']['accessIPv4'] update_dict['access_ip_v4'] = access_ipv4.strip() if 'accessIPv6' in body['server']: access_ipv6 = body['server']['accessIPv6'] update_dict['access_ip_v6'] = access_ipv6.strip() if 'auto_disk_config' in body['server']: auto_disk_config = utils.bool_from_str( body['server']['auto_disk_config']) update_dict['auto_disk_config'] = auto_disk_config instance = self.compute_api.routing_get(ctxt, id) try: self.compute_api.update(ctxt, instance, **update_dict) except exception.NotFound: raise exc.HTTPNotFound() instance.update(update_dict) self._add_instance_faults(ctxt, [instance]) return self._view_builder.show(req, instance) @exception.novaclient_converter @scheduler_api.redirect_handler def action(self, req, id, body): """Multi-purpose method used to take actions on a server""" _actions = { 'changePassword': self._action_change_password, 'reboot': self._action_reboot, 'resize': self._action_resize, 'confirmResize': self._action_confirm_resize, 'revertResize': self._action_revert_resize, 'rebuild': self._action_rebuild, 'createImage': self._action_create_image, } for key in body: if key in _actions: return _actions[key](body, req, id) else: msg = _("There is no such server action: %s") % (key,) raise exc.HTTPBadRequest(explanation=msg) msg = _("Invalid request body") raise exc.HTTPBadRequest(explanation=msg) def _action_confirm_resize(self, input_dict, req, id): context = req.environ['nova.context'] instance = self._get_server(context, id) try: self.compute_api.confirm_resize(context, instance) except exception.MigrationNotFound: msg = _("Instance has not been resized.") raise exc.HTTPBadRequest(explanation=msg) except Exception, e: LOG.exception(_("Error in confirm-resize %s"), e) raise exc.HTTPBadRequest() return exc.HTTPNoContent()
def notify(self, req, receiver_id, body=None): obj = util.parse_request('ReceiverNotifyRequest', req, {'identity': receiver_id}) self.rpc_client.call(req.context, 'receiver_notify', obj) raise exc.HTTPNoContent()
def delete(self, req, receiver_id): obj = util.parse_request('ReceiverDeleteRequest', req, {'identity': receiver_id}) self.rpc_client.call(req.context, 'receiver_delete', obj) raise exc.HTTPNoContent()
def no_content(): return exc.HTTPNoContent()