summaryrefslogtreecommitdiffstats
path: root/nova/api
diff options
context:
space:
mode:
authorTushar Patil <tushar.vitthal.patil@gmail.com>2011-08-22 16:35:09 -0700
committerTushar Patil <tushar.vitthal.patil@gmail.com>2011-08-22 16:35:09 -0700
commit4ded14d0d8fb4ec1bbc14255e90cbaae0626997f (patch)
treef7e8d93de30560ed16ff8811dabf3d45fec77250 /nova/api
parent94b3055ade678a80d88b5e8ee8c3491c7bce95a2 (diff)
parent71f039b936aabb7381b0423e743da65f1475fb35 (diff)
downloadnova-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__.py29
-rw-r--r--nova/api/openstack/auth.py66
-rw-r--r--nova/api/openstack/contrib/floating_ips.py114
-rw-r--r--nova/api/openstack/contrib/security_groups.py113
-rw-r--r--nova/api/openstack/create_instance_helper.py37
-rw-r--r--nova/api/openstack/extensions.py18
-rw-r--r--nova/api/openstack/flavors.py3
-rw-r--r--nova/api/openstack/images.py7
-rw-r--r--nova/api/openstack/schemas/v1.1/server.rng50
-rw-r--r--nova/api/openstack/schemas/v1.1/servers.rng6
-rw-r--r--nova/api/openstack/schemas/v1.1/servers_index.rng12
-rw-r--r--nova/api/openstack/servers.py22
-rw-r--r--nova/api/openstack/views/flavors.py15
-rw-r--r--nova/api/openstack/views/images.py16
-rw-r--r--nova/api/openstack/views/servers.py12
-rw-r--r--nova/api/openstack/wsgi.py4
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: