diff options
| author | Tushar Patil <tushar.vitthal.patil@gmail.com> | 2011-08-22 16:35:09 -0700 |
|---|---|---|
| committer | Tushar Patil <tushar.vitthal.patil@gmail.com> | 2011-08-22 16:35:09 -0700 |
| commit | 4ded14d0d8fb4ec1bbc14255e90cbaae0626997f (patch) | |
| tree | f7e8d93de30560ed16ff8811dabf3d45fec77250 /nova/api | |
| parent | 94b3055ade678a80d88b5e8ee8c3491c7bce95a2 (diff) | |
| parent | 71f039b936aabb7381b0423e743da65f1475fb35 (diff) | |
| download | nova-4ded14d0d8fb4ec1bbc14255e90cbaae0626997f.tar.gz nova-4ded14d0d8fb4ec1bbc14255e90cbaae0626997f.tar.xz nova-4ded14d0d8fb4ec1bbc14255e90cbaae0626997f.zip | |
Merged from trunk,resolved conflicts and fixed broken unit tests due to changes in the extensions which now include ProjectMapper
Diffstat (limited to 'nova/api')
| -rw-r--r-- | nova/api/openstack/__init__.py | 29 | ||||
| -rw-r--r-- | nova/api/openstack/auth.py | 66 | ||||
| -rw-r--r-- | nova/api/openstack/contrib/floating_ips.py | 114 | ||||
| -rw-r--r-- | nova/api/openstack/contrib/security_groups.py | 113 | ||||
| -rw-r--r-- | nova/api/openstack/create_instance_helper.py | 37 | ||||
| -rw-r--r-- | nova/api/openstack/extensions.py | 18 | ||||
| -rw-r--r-- | nova/api/openstack/flavors.py | 3 | ||||
| -rw-r--r-- | nova/api/openstack/images.py | 7 | ||||
| -rw-r--r-- | nova/api/openstack/schemas/v1.1/server.rng | 50 | ||||
| -rw-r--r-- | nova/api/openstack/schemas/v1.1/servers.rng | 6 | ||||
| -rw-r--r-- | nova/api/openstack/schemas/v1.1/servers_index.rng | 12 | ||||
| -rw-r--r-- | nova/api/openstack/servers.py | 22 | ||||
| -rw-r--r-- | nova/api/openstack/views/flavors.py | 15 | ||||
| -rw-r--r-- | nova/api/openstack/views/images.py | 16 | ||||
| -rw-r--r-- | nova/api/openstack/views/servers.py | 12 | ||||
| -rw-r--r-- | nova/api/openstack/wsgi.py | 4 |
16 files changed, 411 insertions, 113 deletions
diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index e0c1e9d04..3b74fefc9 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -68,6 +68,22 @@ class FaultWrapper(base_wsgi.Middleware): return faults.Fault(exc) +class ProjectMapper(routes.Mapper): + + def resource(self, member_name, collection_name, **kwargs): + if not ('parent_resource' in kwargs): + kwargs['path_prefix'] = '{project_id}/' + else: + parent_resource = kwargs['parent_resource'] + p_collection = parent_resource['collection_name'] + p_member = parent_resource['member_name'] + kwargs['path_prefix'] = '{project_id}/%s/:%s_id' % (p_collection, + p_member) + routes.Mapper.resource(self, member_name, + collection_name, + **kwargs) + + class APIRouter(base_wsgi.Router): """ Routes requests on the OpenStack API to the appropriate controller @@ -81,10 +97,13 @@ class APIRouter(base_wsgi.Router): def __init__(self, ext_mgr=None): self.server_members = {} - mapper = routes.Mapper() + mapper = self._mapper() self._setup_routes(mapper) super(APIRouter, self).__init__(mapper) + def _mapper(self): + return routes.Mapper() + def _setup_routes(self, mapper): raise NotImplementedError(_("You must implement _setup_routes.")) @@ -174,6 +193,9 @@ class APIRouterV10(APIRouter): class APIRouterV11(APIRouter): """Define routes specific to OpenStack API V1.1.""" + def _mapper(self): + return ProjectMapper() + def _setup_routes(self, mapper): self._setup_base_routes(mapper, '1.1') @@ -184,7 +206,7 @@ class APIRouterV11(APIRouter): parent_resource=dict(member_name='image', collection_name='images')) - mapper.connect("metadata", "/images/{image_id}/metadata", + mapper.connect("metadata", "/{project_id}/images/{image_id}/metadata", controller=image_metadata_controller, action='update_all', conditions={"method": ['PUT']}) @@ -196,7 +218,8 @@ class APIRouterV11(APIRouter): parent_resource=dict(member_name='server', collection_name='servers')) - mapper.connect("metadata", "/servers/{server_id}/metadata", + mapper.connect("metadata", + "/{project_id}/servers/{server_id}/metadata", controller=server_metadata_controller, action='update_all', conditions={"method": ['PUT']}) diff --git a/nova/api/openstack/auth.py b/nova/api/openstack/auth.py index d42abe1f8..b6ff1126b 100644 --- a/nova/api/openstack/auth.py +++ b/nova/api/openstack/auth.py @@ -28,6 +28,7 @@ from nova import flags from nova import log as logging from nova import utils from nova import wsgi +from nova.api.openstack import common from nova.api.openstack import faults LOG = logging.getLogger('nova.api.openstack') @@ -55,16 +56,33 @@ class AuthMiddleware(wsgi.Middleware): LOG.warn(msg % locals()) return faults.Fault(webob.exc.HTTPUnauthorized()) - try: - project_id = req.headers["X-Auth-Project-Id"] - except KeyError: - # FIXME(usrleon): It needed only for compatibility - # while osapi clients don't use this header - projects = self.auth.get_projects(user_id) - if projects: - project_id = projects[0].id - else: + # Get all valid projects for the user + projects = self.auth.get_projects(user_id) + if not projects: + return faults.Fault(webob.exc.HTTPUnauthorized()) + + project_id = "" + path_parts = req.path.split('/') + # TODO(wwolf): this v1.1 check will be temporary as + # keystone should be taking this over at some point + if len(path_parts) > 1 and path_parts[1] == 'v1.1': + project_id = path_parts[2] + # Check that the project for project_id exists, and that user + # is authorized to use it + try: + project = self.auth.get_project(project_id) + except exception.ProjectNotFound: + return faults.Fault(webob.exc.HTTPUnauthorized()) + if project_id not in [p.id for p in projects]: return faults.Fault(webob.exc.HTTPUnauthorized()) + else: + # As a fallback, set project_id from the headers, which is the v1.0 + # behavior. As a last resort, be forgiving to the user and set + # project_id based on a valid project of theirs. + try: + project_id = req.headers["X-Auth-Project-Id"] + except KeyError: + project_id = projects[0].id is_admin = self.auth.is_admin(user_id) req.environ['nova.context'] = context.RequestContext(user_id, @@ -95,12 +113,19 @@ class AuthMiddleware(wsgi.Middleware): LOG.warn(msg) return faults.Fault(webob.exc.HTTPUnauthorized(explanation=msg)) + def _get_auth_header(key): + """Ensures that the KeyError returned is meaningful.""" + try: + return req.headers[key] + except KeyError as ex: + raise KeyError(key) try: - username = req.headers['X-Auth-User'] - key = req.headers['X-Auth-Key'] + username = _get_auth_header('X-Auth-User') + key = _get_auth_header('X-Auth-Key') except KeyError as ex: - LOG.warn(_("Could not find %s in request.") % ex) - return faults.Fault(webob.exc.HTTPUnauthorized()) + msg = _("Could not find %s in request.") % ex + LOG.warn(msg) + return faults.Fault(webob.exc.HTTPUnauthorized(explanation=msg)) token, user = self._authorize_user(username, key, req) if user and token: @@ -149,6 +174,16 @@ class AuthMiddleware(wsgi.Middleware): """ ctxt = context.get_admin_context() + project_id = req.headers.get('X-Auth-Project-Id') + if project_id is None: + # If the project_id is not provided in the headers, be forgiving to + # the user and set project_id based on a valid project of theirs. + user = self.auth.get_user_from_access_key(key) + projects = self.auth.get_projects(user.id) + if not projects: + raise webob.exc.HTTPUnauthorized() + project_id = projects[0].id + try: user = self.auth.get_user_from_access_key(key) except exception.NotFound: @@ -162,7 +197,10 @@ class AuthMiddleware(wsgi.Middleware): token_dict['token_hash'] = token_hash token_dict['cdn_management_url'] = '' os_url = req.url - token_dict['server_management_url'] = os_url + token_dict['server_management_url'] = os_url.strip('/') + version = common.get_version_from_href(os_url) + if version == '1.1': + token_dict['server_management_url'] += '/' + project_id token_dict['storage_url'] = '' token_dict['user_id'] = user.id token = self.db.auth_token_create(ctxt, token_dict) diff --git a/nova/api/openstack/contrib/floating_ips.py b/nova/api/openstack/contrib/floating_ips.py index 44b35c385..40086f778 100644 --- a/nova/api/openstack/contrib/floating_ips.py +++ b/nova/api/openstack/contrib/floating_ips.py @@ -15,8 +15,9 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License -from webob import exc +import webob +from nova import compute from nova import exception from nova import log as logging from nova import network @@ -71,18 +72,22 @@ class FloatingIPController(object): try: floating_ip = self.network_api.get_floating_ip(context, id) except exception.NotFound: - return faults.Fault(exc.HTTPNotFound()) + return faults.Fault(webob.exc.HTTPNotFound()) return _translate_floating_ip_view(floating_ip) def index(self, req): context = req.environ['nova.context'] - floating_ips = self.network_api.list_floating_ips(context) + try: + # FIXME(ja) - why does self.network_api.list_floating_ips raise? + floating_ips = self.network_api.list_floating_ips(context) + except exception.FloatingIpNotFoundForProject: + floating_ips = [] return _translate_floating_ips_view(floating_ips) - def create(self, req): + def create(self, req, body=None): context = req.environ['nova.context'] try: @@ -95,63 +100,67 @@ class FloatingIPController(object): else: raise - return {'allocated': { - "id": ip['id'], - "floating_ip": ip['address']}} + return _translate_floating_ip_view(ip) def delete(self, req, id): context = req.environ['nova.context'] - ip = self.network_api.get_floating_ip(context, id) + floating_ip = self.network_api.get_floating_ip(context, id) - if 'fixed_ip' in ip: - self.disassociate(req, id) + if 'fixed_ip' in floating_ip: + self.network_api.disassociate_floating_ip(context, + floating_ip['address']) - self.network_api.release_floating_ip(context, address=ip['address']) + self.network_api.release_floating_ip(context, + address=floating_ip['address']) + return webob.exc.HTTPAccepted() - return {'released': { - "id": ip['id'], - "floating_ip": ip['address']}} + def _get_ip_by_id(self, context, value): + """Checks that value is id and then returns its address.""" + return self.network_api.get_floating_ip(context, value)['address'] - def associate(self, req, id, body): - """ /floating_ips/{id}/associate fixed ip in body """ - context = req.environ['nova.context'] - floating_ip = self._get_ip_by_id(context, id) - fixed_ip = body['associate_address']['fixed_ip'] +class Floating_ips(extensions.ExtensionDescriptor): + def __init__(self): + self.compute_api = compute.API() + self.network_api = network.API() + super(Floating_ips, self).__init__() - try: - self.network_api.associate_floating_ip(context, - floating_ip, fixed_ip) - except rpc.RemoteError: - raise - - return {'associated': - { - "floating_ip_id": id, - "floating_ip": floating_ip, - "fixed_ip": fixed_ip}} - - def disassociate(self, req, id, body=None): - """ POST /floating_ips/{id}/disassociate """ + def _add_floating_ip(self, input_dict, req, instance_id): + """Associate floating_ip to an instance.""" context = req.environ['nova.context'] - floating_ip = self.network_api.get_floating_ip(context, id) - address = floating_ip['address'] - fixed_ip = floating_ip['fixed_ip']['address'] try: - self.network_api.disassociate_floating_ip(context, address) - except rpc.RemoteError: - raise + address = input_dict['addFloatingIp']['address'] + except TypeError: + msg = _("Missing parameter dict") + raise webob.exc.HTTPBadRequest(explanation=msg) + except KeyError: + msg = _("Address not specified") + raise webob.exc.HTTPBadRequest(explanation=msg) - return {'disassociated': {'floating_ip': address, - 'fixed_ip': fixed_ip}} + self.compute_api.associate_floating_ip(context, instance_id, address) - def _get_ip_by_id(self, context, value): - """Checks that value is id and then returns its address.""" - return self.network_api.get_floating_ip(context, value)['address'] + return webob.Response(status_int=202) + def _remove_floating_ip(self, input_dict, req, instance_id): + """Dissociate floating_ip from an instance.""" + context = req.environ['nova.context'] + + try: + address = input_dict['removeFloatingIp']['address'] + except TypeError: + msg = _("Missing parameter dict") + raise webob.exc.HTTPBadRequest(explanation=msg) + except KeyError: + msg = _("Address not specified") + raise webob.exc.HTTPBadRequest(explanation=msg) + + floating_ip = self.network_api.get_floating_ip_by_ip(context, address) + if 'fixed_ip' in floating_ip: + self.network_api.disassociate_floating_ip(context, address) + + return webob.Response(status_int=202) -class Floating_ips(extensions.ExtensionDescriptor): def get_name(self): return "Floating_ips" @@ -172,9 +181,18 @@ class Floating_ips(extensions.ExtensionDescriptor): res = extensions.ResourceExtension('os-floating-ips', FloatingIPController(), - member_actions={ - 'associate': 'POST', - 'disassociate': 'POST'}) + member_actions={}) resources.append(res) return resources + + def get_actions(self): + """Return the actions the extension adds, as required by contract.""" + actions = [ + extensions.ActionExtension("servers", "addFloatingIp", + self._add_floating_ip), + extensions.ActionExtension("servers", "removeFloatingIp", + self._remove_floating_ip), + ] + + return actions diff --git a/nova/api/openstack/contrib/security_groups.py b/nova/api/openstack/contrib/security_groups.py index 6c57fbb51..1fd64f3b8 100644 --- a/nova/api/openstack/contrib/security_groups.py +++ b/nova/api/openstack/contrib/security_groups.py @@ -25,10 +25,11 @@ from nova import db from nova import exception from nova import flags from nova import log as logging +from nova import rpc from nova.api.openstack import common from nova.api.openstack import extensions from nova.api.openstack import wsgi - +from nova.compute import power_state from xml.dom import minidom @@ -73,33 +74,28 @@ class SecurityGroupController(object): context, rule)] return security_group - def show(self, req, id): - """Return data about the given security group.""" - context = req.environ['nova.context'] + def _get_security_group(self, context, id): try: id = int(id) security_group = db.security_group_get(context, id) except ValueError: - msg = _("Security group id is not integer") - return exc.HTTPBadRequest(explanation=msg) + msg = _("Security group id should be integer") + raise exc.HTTPBadRequest(explanation=msg) except exception.NotFound as exp: - return exc.HTTPNotFound(explanation=unicode(exp)) + raise exc.HTTPNotFound(explanation=unicode(exp)) + return security_group + def show(self, req, id): + """Return data about the given security group.""" + context = req.environ['nova.context'] + security_group = self._get_security_group(context, id) return {'security_group': self._format_security_group(context, security_group)} def delete(self, req, id): """Delete a security group.""" context = req.environ['nova.context'] - try: - id = int(id) - security_group = db.security_group_get(context, id) - except ValueError: - msg = _("Security group id is not integer") - return exc.HTTPBadRequest(explanation=msg) - except exception.SecurityGroupNotFound as exp: - return exc.HTTPNotFound(explanation=unicode(exp)) - + security_group = self._get_security_group(context, id) LOG.audit(_("Delete security group %s"), id, context=context) db.security_group_destroy(context, security_group.id) @@ -226,9 +222,9 @@ class SecurityGroupRulesController(SecurityGroupController): security_group_rule = db.security_group_rule_create(context, values) self.compute_api.trigger_security_group_rules_refresh(context, - security_group_id=security_group['id']) + security_group_id=security_group['id']) - return {'security_group_rule': self._format_security_group_rule( + return {"security_group_rule": self._format_security_group_rule( context, security_group_rule)} @@ -336,6 +332,11 @@ class SecurityGroupRulesController(SecurityGroupController): class Security_groups(extensions.ExtensionDescriptor): + + def __init__(self): + self.compute_api = compute.API() + super(Security_groups, self).__init__() + def get_name(self): return "SecurityGroups" @@ -351,6 +352,82 @@ class Security_groups(extensions.ExtensionDescriptor): def get_updated(self): return "2011-07-21T00:00:00+00:00" + def _addSecurityGroup(self, input_dict, req, instance_id): + context = req.environ['nova.context'] + + try: + body = input_dict['addSecurityGroup'] + group_name = body['name'] + instance_id = int(instance_id) + except ValueError: + msg = _("Server id should be integer") + raise exc.HTTPBadRequest(explanation=msg) + except TypeError: + msg = _("Missing parameter dict") + raise webob.exc.HTTPBadRequest(explanation=msg) + except KeyError: + msg = _("Security group not specified") + raise webob.exc.HTTPBadRequest(explanation=msg) + + if not group_name or group_name.strip() == '': + msg = _("Security group name cannot be empty") + raise webob.exc.HTTPBadRequest(explanation=msg) + + try: + self.compute_api.add_security_group(context, instance_id, + group_name) + except exception.SecurityGroupNotFound as exp: + return exc.HTTPNotFound(explanation=unicode(exp)) + except exception.InstanceNotFound as exp: + return exc.HTTPNotFound(explanation=unicode(exp)) + except exception.Invalid as exp: + return exc.HTTPBadRequest(explanation=unicode(exp)) + + return exc.HTTPAccepted() + + def _removeSecurityGroup(self, input_dict, req, instance_id): + context = req.environ['nova.context'] + + try: + body = input_dict['removeSecurityGroup'] + group_name = body['name'] + instance_id = int(instance_id) + except ValueError: + msg = _("Server id should be integer") + raise exc.HTTPBadRequest(explanation=msg) + except TypeError: + msg = _("Missing parameter dict") + raise webob.exc.HTTPBadRequest(explanation=msg) + except KeyError: + msg = _("Security group not specified") + raise webob.exc.HTTPBadRequest(explanation=msg) + + if not group_name or group_name.strip() == '': + msg = _("Security group name cannot be empty") + raise webob.exc.HTTPBadRequest(explanation=msg) + + try: + self.compute_api.remove_security_group(context, instance_id, + group_name) + except exception.SecurityGroupNotFound as exp: + return exc.HTTPNotFound(explanation=unicode(exp)) + except exception.InstanceNotFound as exp: + return exc.HTTPNotFound(explanation=unicode(exp)) + except exception.Invalid as exp: + return exc.HTTPBadRequest(explanation=unicode(exp)) + + return exc.HTTPAccepted() + + def get_actions(self): + """Return the actions the extensions adds""" + actions = [ + extensions.ActionExtension("servers", "addSecurityGroup", + self._addSecurityGroup), + extensions.ActionExtension("servers", "removeSecurityGroup", + self._removeSecurityGroup) + ] + return actions + def get_resources(self): resources = [] diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index 42b553674..4b4a1b0c3 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -111,8 +111,16 @@ class CreateInstanceHelper(object): if personality: injected_files = self._get_injected_files(personality) - requested_networks = server_dict.get('networks') + 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) @@ -164,6 +172,8 @@ class CreateInstanceHelper(object): key_name=key_name, key_data=key_data, 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, @@ -171,6 +181,7 @@ class CreateInstanceHelper(object): min_count=min_count, max_count=max_count, requested_networks=requested_networks, + security_group=sg_names, user_data=user_data, availability_zone=availability_zone)) except quota.QuotaError as error: @@ -181,6 +192,8 @@ class CreateInstanceHelper(object): except exception.FlavorNotFound as error: msg = _("Invalid flavorRef provided.") raise exc.HTTPBadRequest(explanation=msg) + except exception.SecurityGroupNotFound as error: + raise exc.HTTPBadRequest(explanation=unicode(error)) except RemoteError as err: msg = "%(err_type)s: %(err_msg)s" % \ {'err_type': err.exc_type, 'err_msg': err.value} @@ -503,7 +516,8 @@ class ServerXMLDeserializerV11(wsgi.MetadataXMLDeserializer): server = {} server_node = self.find_first_child_named(node, 'server') - attributes = ["name", "imageRef", "flavorRef", "adminPass"] + attributes = ["name", "imageRef", "flavorRef", "adminPass", + "accessIPv4", "accessIPv6"] for attr in attributes: if server_node.getAttribute(attr): server[attr] = server_node.getAttribute(attr) @@ -520,6 +534,10 @@ class ServerXMLDeserializerV11(wsgi.MetadataXMLDeserializer): if networks is not None: server["networks"] = networks + security_groups = self._extract_security_groups(server_node) + if security_groups is not None: + server["security_groups"] = security_groups + return server def _extract_personality(self, server_node): @@ -553,3 +571,18 @@ class ServerXMLDeserializerV11(wsgi.MetadataXMLDeserializer): return networks else: return None + + def _extract_security_groups(self, server_node): + """Marshal the security_groups attribute of a parsed request""" + node = self.find_first_child_named(server_node, "security_groups") + if node is not None: + security_groups = [] + for sg_node in self.find_children_named(node, "security_group"): + item = {} + name_node = self.find_first_child_named(sg_node, "name") + if name_node: + item["name"] = self.extract_text(name_node) + security_groups.append(item) + return security_groups + else: + return None diff --git a/nova/api/openstack/extensions.py b/nova/api/openstack/extensions.py index bb407a045..efede945f 100644 --- a/nova/api/openstack/extensions.py +++ b/nova/api/openstack/extensions.py @@ -29,6 +29,7 @@ from nova import exception from nova import flags from nova import log as logging from nova import wsgi as base_wsgi +import nova.api.openstack from nova.api.openstack import common from nova.api.openstack import faults from nova.api.openstack import wsgi @@ -220,12 +221,13 @@ class ExtensionMiddleware(base_wsgi.Middleware): for action in ext_mgr.get_actions(): if not action.collection in action_resources.keys(): resource = ActionExtensionResource(application) - mapper.connect("/%s/:(id)/action.:(format)" % + mapper.connect("/:(project_id)/%s/:(id)/action.:(format)" % action.collection, action='action', controller=resource, conditions=dict(method=['POST'])) - mapper.connect("/%s/:(id)/action" % action.collection, + mapper.connect("/:(project_id)/%s/:(id)/action" % + action.collection, action='action', controller=resource, conditions=dict(method=['POST'])) @@ -258,7 +260,7 @@ class ExtensionMiddleware(base_wsgi.Middleware): ext_mgr = ExtensionManager(FLAGS.osapi_extensions_path) self.ext_mgr = ext_mgr - mapper = routes.Mapper() + mapper = nova.api.openstack.ProjectMapper() serializer = wsgi.ResponseSerializer( {'application/xml': ExtensionsXMLSerializer()}) @@ -269,13 +271,17 @@ class ExtensionMiddleware(base_wsgi.Middleware): if resource.serializer is None: resource.serializer = serializer - mapper.resource(resource.collection, resource.collection, + kargs = dict( controller=wsgi.Resource( resource.controller, resource.deserializer, resource.serializer), collection=resource.collection_actions, - member=resource.member_actions, - parent_resource=resource.parent) + member=resource.member_actions) + + if resource.parent: + kargs['parent_resource'] = resource.parent + + mapper.resource(resource.collection, resource.collection, **kargs) # extended actions action_resources = self._action_ext_resources(application, ext_mgr, diff --git a/nova/api/openstack/flavors.py b/nova/api/openstack/flavors.py index b4bda68d4..fd36060da 100644 --- a/nova/api/openstack/flavors.py +++ b/nova/api/openstack/flavors.py @@ -72,7 +72,8 @@ class ControllerV11(Controller): def _get_view_builder(self, req): base_url = req.application_url - return views.flavors.ViewBuilderV11(base_url) + project_id = getattr(req.environ['nova.context'], 'project_id', '') + return views.flavors.ViewBuilderV11(base_url, project_id) class FlavorXMLSerializer(wsgi.XMLDictSerializer): diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index 0aabb9e56..1c8fc10c9 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -166,10 +166,11 @@ class ControllerV10(Controller): class ControllerV11(Controller): """Version 1.1 specific controller logic.""" - def get_builder(self, request): + def get_builder(self, req): """Property to get the ViewBuilder class we need to use.""" - base_url = request.application_url - return images_view.ViewBuilderV11(base_url) + base_url = req.application_url + project_id = getattr(req.environ['nova.context'], 'project_id', '') + return images_view.ViewBuilderV11(base_url, project_id) def index(self, req): """Return an index listing of images available to the request. diff --git a/nova/api/openstack/schemas/v1.1/server.rng b/nova/api/openstack/schemas/v1.1/server.rng new file mode 100644 index 000000000..dbd169a83 --- /dev/null +++ b/nova/api/openstack/schemas/v1.1/server.rng @@ -0,0 +1,50 @@ +<element name="server" ns="http://docs.openstack.org/compute/api/v1.1" + xmlns="http://relaxng.org/ns/structure/1.0"> + <attribute name="name"> <text/> </attribute> + <attribute name="id"> <text/> </attribute> + <attribute name="uuid"> <text/> </attribute> + <attribute name="updated"> <text/> </attribute> + <attribute name="created"> <text/> </attribute> + <attribute name="hostId"> <text/> </attribute> + <attribute name="accessIPv4"> <text/> </attribute> + <attribute name="accessIPv6"> <text/> </attribute> + <attribute name="status"> <text/> </attribute> + <optional> + <attribute name="progress"> <text/> </attribute> + </optional> + <optional> + <attribute name="adminPass"> <text/> </attribute> + </optional> + <zeroOrMore> + <externalRef href="../atom-link.rng"/> + </zeroOrMore> + <element name="image"> + <attribute name="id"> <text/> </attribute> + <externalRef href="../atom-link.rng"/> + </element> + <element name="flavor"> + <attribute name="id"> <text/> </attribute> + <externalRef href="../atom-link.rng"/> + </element> + <element name="metadata"> + <zeroOrMore> + <element name="meta"> + <attribute name="key"> <text/> </attribute> + <text/> + </element> + </zeroOrMore> + </element> + <element name="addresses"> + <zeroOrMore> + <element name="network"> + <attribute name="id"> <text/> </attribute> + <zeroOrMore> + <element name="ip"> + <attribute name="version"> <text/> </attribute> + <attribute name="addr"> <text/> </attribute> + </element> + </zeroOrMore> + </element> + </zeroOrMore> + </element> +</element> diff --git a/nova/api/openstack/schemas/v1.1/servers.rng b/nova/api/openstack/schemas/v1.1/servers.rng new file mode 100644 index 000000000..4e2bb8853 --- /dev/null +++ b/nova/api/openstack/schemas/v1.1/servers.rng @@ -0,0 +1,6 @@ +<element name="servers" xmlns="http://relaxng.org/ns/structure/1.0" + ns="http://docs.openstack.org/compute/api/v1.1"> + <zeroOrMore> + <externalRef href="server.rng"/> + </zeroOrMore> +</element> diff --git a/nova/api/openstack/schemas/v1.1/servers_index.rng b/nova/api/openstack/schemas/v1.1/servers_index.rng new file mode 100644 index 000000000..768f0912d --- /dev/null +++ b/nova/api/openstack/schemas/v1.1/servers_index.rng @@ -0,0 +1,12 @@ +<element name="servers" ns="http://docs.openstack.org/compute/api/v1.1" + xmlns="http://relaxng.org/ns/structure/1.0"> + <zeroOrMore> + <element name="server"> + <attribute name="name"> <text/> </attribute> + <attribute name="id"> <text/> </attribute> + <zeroOrMore> + <externalRef href="../atom-link.rng"/> + </zeroOrMore> + </element> + </zeroOrMore> +</element> diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 41e63ec3c..553357404 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -163,7 +163,7 @@ class Controller(object): @scheduler_api.redirect_handler def update(self, req, id, body): - """Update server name then pass on to version-specific controller""" + """Update server then pass on to version-specific controller""" if len(req.body) == 0: raise exc.HTTPUnprocessableEntity() @@ -178,6 +178,14 @@ class Controller(object): 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: @@ -642,14 +650,16 @@ class ControllerV11(Controller): return common.get_id_from_href(flavor_ref) def _build_view(self, req, instance, is_detail=False): + project_id = getattr(req.environ['nova.context'], 'project_id', '') base_url = req.application_url flavor_builder = nova.api.openstack.views.flavors.ViewBuilderV11( - base_url) + base_url, project_id) image_builder = nova.api.openstack.views.images.ViewBuilderV11( - base_url) + base_url, project_id) addresses_builder = nova.api.openstack.views.addresses.ViewBuilderV11() builder = nova.api.openstack.views.servers.ViewBuilderV11( - addresses_builder, flavor_builder, image_builder, base_url) + addresses_builder, flavor_builder, image_builder, + base_url, project_id) return builder.build(instance, is_detail=is_detail) @@ -837,6 +847,10 @@ class ServerXMLSerializer(wsgi.XMLDictSerializer): node.setAttribute('created', str(server['created'])) node.setAttribute('updated', str(server['updated'])) node.setAttribute('status', server['status']) + if 'accessIPv4' in server: + node.setAttribute('accessIPv4', str(server['accessIPv4'])) + if 'accessIPv6' in server: + node.setAttribute('accessIPv6', str(server['accessIPv6'])) if 'progress' in server: node.setAttribute('progress', str(server['progress'])) diff --git a/nova/api/openstack/views/flavors.py b/nova/api/openstack/views/flavors.py index 0403ece1b..aea34b424 100644 --- a/nova/api/openstack/views/flavors.py +++ b/nova/api/openstack/views/flavors.py @@ -15,6 +15,9 @@ # License for the specific language governing permissions and limitations # under the License. +import os.path + + from nova.api.openstack import common @@ -59,11 +62,12 @@ class ViewBuilder(object): class ViewBuilderV11(ViewBuilder): """Openstack API v1.1 flavors view builder.""" - def __init__(self, base_url): + def __init__(self, base_url, project_id=""): """ :param base_url: url of the root wsgi application """ self.base_url = base_url + self.project_id = project_id def _build_extra(self, flavor_obj): flavor_obj["links"] = self._build_links(flavor_obj) @@ -88,11 +92,10 @@ class ViewBuilderV11(ViewBuilder): def generate_href(self, flavor_id): """Create an url that refers to a specific flavor id.""" - return "%s/flavors/%s" % (self.base_url, flavor_id) + return os.path.join(self.base_url, self.project_id, + "flavors", str(flavor_id)) def generate_bookmark(self, flavor_id): """Create an url that refers to a specific flavor id.""" - return "%s/flavors/%s" % ( - common.remove_version_from_href(self.base_url), - flavor_id, - ) + return os.path.join(common.remove_version_from_href(self.base_url), + self.project_id, "flavors", str(flavor_id)) diff --git a/nova/api/openstack/views/images.py b/nova/api/openstack/views/images.py index 912303d14..21f1b2d3e 100644 --- a/nova/api/openstack/views/images.py +++ b/nova/api/openstack/views/images.py @@ -23,9 +23,10 @@ from nova.api.openstack import common class ViewBuilder(object): """Base class for generating responses to OpenStack API image requests.""" - def __init__(self, base_url): + def __init__(self, base_url, project_id=""): """Initialize new `ViewBuilder`.""" - self._url = base_url + self.base_url = base_url + self.project_id = project_id def _format_dates(self, image): """Update all date fields to ensure standardized formatting.""" @@ -54,7 +55,7 @@ class ViewBuilder(object): def generate_href(self, image_id): """Return an href string pointing to this object.""" - return os.path.join(self._url, "images", str(image_id)) + return os.path.join(self.base_url, "images", str(image_id)) def build(self, image_obj, detail=False): """Return a standardized image structure for display by the API.""" @@ -117,6 +118,11 @@ class ViewBuilderV11(ViewBuilder): except KeyError: return + def generate_href(self, image_id): + """Return an href string pointing to this object.""" + return os.path.join(self.base_url, self.project_id, + "images", str(image_id)) + def build(self, image_obj, detail=False): """Return a standardized image structure for display by the API.""" image = ViewBuilder.build(self, image_obj, detail) @@ -142,5 +148,5 @@ class ViewBuilderV11(ViewBuilder): def generate_bookmark(self, image_id): """Create an url that refers to a specific flavor id.""" - return os.path.join(common.remove_version_from_href(self._url), - "images", str(image_id)) + return os.path.join(common.remove_version_from_href(self.base_url), + self.project_id, "images", str(image_id)) diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py index edc328129..465287adc 100644 --- a/nova/api/openstack/views/servers.py +++ b/nova/api/openstack/views/servers.py @@ -128,11 +128,12 @@ class ViewBuilderV10(ViewBuilder): class ViewBuilderV11(ViewBuilder): """Model an Openstack API V1.0 server response.""" def __init__(self, addresses_builder, flavor_builder, image_builder, - base_url): + base_url, project_id=""): ViewBuilder.__init__(self, addresses_builder) self.flavor_builder = flavor_builder self.image_builder = image_builder self.base_url = base_url + self.project_id = project_id def _build_detail(self, inst): response = super(ViewBuilderV11, self)._build_detail(inst) @@ -143,6 +144,10 @@ class ViewBuilderV11(ViewBuilder): response['server']['progress'] = 100 elif response['server']['status'] == "BUILD": response['server']['progress'] = 0 + + response['server']['accessIPv4'] = inst.get('access_ip_v4') or "" + response['server']['accessIPv6'] = inst.get('access_ip_v6') or "" + return response def _build_image(self, response, inst): @@ -202,9 +207,10 @@ class ViewBuilderV11(ViewBuilder): def generate_href(self, server_id): """Create an url that refers to a specific server id.""" - return os.path.join(self.base_url, "servers", str(server_id)) + return os.path.join(self.base_url, self.project_id, + "servers", str(server_id)) def generate_bookmark(self, server_id): """Create an url that refers to a specific flavor id.""" return os.path.join(common.remove_version_from_href(self.base_url), - "servers", str(server_id)) + self.project_id, "servers", str(server_id)) diff --git a/nova/api/openstack/wsgi.py b/nova/api/openstack/wsgi.py index 0eb47044e..dc0f1b93e 100644 --- a/nova/api/openstack/wsgi.py +++ b/nova/api/openstack/wsgi.py @@ -486,6 +486,10 @@ class Resource(wsgi.Application): msg = _("Malformed request body") return faults.Fault(webob.exc.HTTPBadRequest(explanation=msg)) + project_id = args.pop("project_id", None) + if 'nova.context' in request.environ and project_id: + request.environ['nova.context'].project_id = project_id + try: action_result = self.dispatch(request, action, args) except webob.exc.HTTPException as ex: |
