summaryrefslogtreecommitdiffstats
path: root/nova/api
diff options
context:
space:
mode:
authorBrian Waldon <brian.waldon@rackspace.com>2011-08-23 13:08:42 -0400
committerBrian Waldon <brian.waldon@rackspace.com>2011-08-23 13:08:42 -0400
commit3a64fa3171d424d02c68ec1fbecd4087edb694a0 (patch)
tree6f09840c20cf50cbe2900e6326c8ea6ab19d4656 /nova/api
parent5ad22e341e0ad5ff62e97906edf7822ee53b4ae9 (diff)
parente23eb5aa5c9810f68f3818cd1119e4993b99a297 (diff)
downloadnova-3a64fa3171d424d02c68ec1fbecd4087edb694a0.tar.gz
nova-3a64fa3171d424d02c68ec1fbecd4087edb694a0.tar.xz
nova-3a64fa3171d424d02c68ec1fbecd4087edb694a0.zip
merging trunk
Diffstat (limited to 'nova/api')
-rw-r--r--nova/api/auth.py1
-rw-r--r--nova/api/ec2/__init__.py21
-rw-r--r--nova/api/ec2/admin.py4
-rw-r--r--nova/api/openstack/__init__.py29
-rw-r--r--nova/api/openstack/auth.py118
-rw-r--r--nova/api/openstack/contrib/createserverext.py66
-rw-r--r--nova/api/openstack/create_instance_helper.py84
-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.py17
-rw-r--r--nova/api/openstack/wsgi.py4
18 files changed, 441 insertions, 52 deletions
diff --git a/nova/api/auth.py b/nova/api/auth.py
index cd3e3e8a0..cd0d38b3f 100644
--- a/nova/api/auth.py
+++ b/nova/api/auth.py
@@ -62,6 +62,7 @@ class KeystoneContext(wsgi.Middleware):
req.headers.get('X_STORAGE_TOKEN'))
# Build a context, including the auth_token...
+ remote_address = getattr(req, 'remote_address', '127.0.0.1')
remote_address = req.remote_addr
if FLAGS.use_forwarded_for:
remote_address = req.headers.get('X-Forwarded-For', remote_address)
diff --git a/nova/api/ec2/__init__.py b/nova/api/ec2/__init__.py
index 17969099d..5430f443d 100644
--- a/nova/api/ec2/__init__.py
+++ b/nova/api/ec2/__init__.py
@@ -183,6 +183,27 @@ class ToToken(wsgi.Middleware):
return self.application
+class NoAuth(wsgi.Middleware):
+ """Add user:project as 'nova.context' to WSGI environ."""
+
+ @webob.dec.wsgify(RequestClass=wsgi.Request)
+ def __call__(self, req):
+ if 'AWSAccessKeyId' not in req.params:
+ raise webob.exc.HTTPBadRequest()
+ user_id, _sep, project_id = req.params['AWSAccessKeyId'].partition(':')
+ project_id = project_id or user_id
+ remote_address = getattr(req, 'remote_address', '127.0.0.1')
+ if FLAGS.use_forwarded_for:
+ remote_address = req.headers.get('X-Forwarded-For', remote_address)
+ ctx = context.RequestContext(user_id,
+ project_id,
+ is_admin=True,
+ remote_address=remote_address)
+
+ req.environ['nova.context'] = ctx
+ return self.application
+
+
class Authenticate(wsgi.Middleware):
"""Authenticate an EC2 request and add 'nova.context' to WSGI environ."""
diff --git a/nova/api/ec2/admin.py b/nova/api/ec2/admin.py
index df7876b9d..dfbbc0a2b 100644
--- a/nova/api/ec2/admin.py
+++ b/nova/api/ec2/admin.py
@@ -283,8 +283,10 @@ class AdminController(object):
# NOTE(vish) import delayed because of __init__.py
from nova.cloudpipe import pipelib
pipe = pipelib.CloudPipe()
+ proj = manager.AuthManager().get_project(project)
+ user_id = proj.project_manager_id
try:
- pipe.launch_vpn_instance(project)
+ pipe.launch_vpn_instance(project, user_id)
except db.NoMoreNetworks:
raise exception.ApiError("Unable to claim IP for VPN instance"
", ensure it isn't running, and try "
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..6754fea27 100644
--- a/nova/api/openstack/auth.py
+++ b/nova/api/openstack/auth.py
@@ -28,10 +28,51 @@ 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')
FLAGS = flags.FLAGS
+flags.DECLARE('use_forwarded_for', 'nova.api.auth')
+
+
+class NoAuthMiddleware(wsgi.Middleware):
+ """Return a fake token if one isn't specified."""
+
+ @webob.dec.wsgify(RequestClass=wsgi.Request)
+ def __call__(self, req):
+ if 'X-Auth-Token' not in req.headers:
+ os_url = req.url
+ version = common.get_version_from_href(os_url)
+ user_id = req.headers.get('X-Auth-User', 'admin')
+ project_id = req.headers.get('X-Auth-Project-Id', 'admin')
+ if version == '1.1':
+ os_url += '/' + project_id
+ res = webob.Response()
+ # NOTE(vish): This is expecting and returning Auth(1.1), whereas
+ # keystone uses 2.0 auth. We should probably allow
+ # 2.0 auth here as well.
+ res.headers['X-Auth-Token'] = '%s:%s' % (user_id, project_id)
+ res.headers['X-Server-Management-Url'] = os_url
+ res.headers['X-Storage-Url'] = ''
+ res.headers['X-CDN-Management-Url'] = ''
+ res.content_type = 'text/plain'
+ res.status = '204'
+ return res
+
+ token = req.headers['X-Auth-Token']
+ user_id, _sep, project_id = token.partition(':')
+ project_id = project_id or user_id
+ remote_address = getattr(req, 'remote_address', '127.0.0.1')
+ if FLAGS.use_forwarded_for:
+ remote_address = req.headers.get('X-Forwarded-For', remote_address)
+ ctx = context.RequestContext(user_id,
+ project_id,
+ is_admin=True,
+ remote_address=remote_address)
+
+ req.environ['nova.context'] = ctx
+ return self.application
class AuthMiddleware(wsgi.Middleware):
@@ -55,21 +96,44 @@ 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,
- project_id,
- is_admin)
+ remote_address = getattr(req, 'remote_address', '127.0.0.1')
+ if FLAGS.use_forwarded_for:
+ remote_address = req.headers.get('X-Forwarded-For', remote_address)
+ ctx = context.RequestContext(user_id,
+ project_id,
+ is_admin=is_admin,
+ remote_address=remote_address)
+ req.environ['nova.context'] = ctx
+
if not is_admin and not self.auth.is_project_member(user_id,
project_id):
msg = _("%(user_id)s must be an admin or a "
@@ -95,12 +159,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 +220,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 +243,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/createserverext.py b/nova/api/openstack/contrib/createserverext.py
new file mode 100644
index 000000000..ba72fdb0b
--- /dev/null
+++ b/nova/api/openstack/contrib/createserverext.py
@@ -0,0 +1,66 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2011 OpenStack LLC.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# 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 nova.api.openstack import create_instance_helper as helper
+from nova.api.openstack import extensions
+from nova.api.openstack import servers
+from nova.api.openstack import wsgi
+
+
+class Createserverext(extensions.ExtensionDescriptor):
+ """The servers create ext
+
+ Exposes addFixedIp and removeFixedIp actions on servers.
+
+ """
+ def get_name(self):
+ return "Createserverext"
+
+ def get_alias(self):
+ return "os-create-server-ext"
+
+ def get_description(self):
+ return "Extended support to the Create Server v1.1 API"
+
+ def get_namespace(self):
+ return "http://docs.openstack.org/ext/createserverext/api/v1.1"
+
+ def get_updated(self):
+ return "2011-07-19T00:00:00+00:00"
+
+ def get_resources(self):
+ resources = []
+
+ headers_serializer = servers.HeadersSerializer()
+ body_serializers = {
+ 'application/xml': servers.ServerXMLSerializer(),
+ }
+
+ body_deserializers = {
+ 'application/xml': helper.ServerXMLDeserializerV11(),
+ }
+
+ serializer = wsgi.ResponseSerializer(body_serializers,
+ headers_serializer)
+ deserializer = wsgi.RequestDeserializer(body_deserializers)
+
+ res = extensions.ResourceExtension('os-create-server-ext',
+ controller=servers.ControllerV11(),
+ deserializer=deserializer,
+ serializer=serializer)
+ resources.append(res)
+
+ return resources
diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py
index 73e48dca5..483ff4985 100644
--- a/nova/api/openstack/create_instance_helper.py
+++ b/nova/api/openstack/create_instance_helper.py
@@ -1,4 +1,5 @@
# Copyright 2011 OpenStack LLC.
+# Copyright 2011 Piston Cloud Computing, Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
@@ -29,7 +30,7 @@ from nova import utils
from nova.compute import instance_types
from nova.api.openstack import common
from nova.api.openstack import wsgi
-
+from nova.rpc.common import RemoteError
LOG = logging.getLogger('nova.api.openstack.create_instance_helper')
FLAGS = flags.FLAGS
@@ -106,6 +107,7 @@ class CreateInstanceHelper(object):
raise exc.HTTPBadRequest(explanation=msg)
personality = server_dict.get('personality')
+ config_drive = server_dict.get('config_drive')
injected_files = []
if personality:
@@ -120,6 +122,11 @@ class CreateInstanceHelper(object):
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.controller._flavor_id_from_req_data(body)
except ValueError as error:
@@ -154,6 +161,7 @@ class CreateInstanceHelper(object):
extra_values = {
'instance_type': inst_type,
'image_ref': image_href,
+ 'config_drive': config_drive,
'password': password}
return (extra_values,
@@ -167,15 +175,19 @@ 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,
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))
+ availability_zone=availability_zone,
+ config_drive=config_drive,))
except quota.QuotaError as error:
self._handle_quota_error(error)
except exception.ImageNotFound as error:
@@ -186,6 +198,10 @@ class CreateInstanceHelper(object):
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}
+ raise exc.HTTPBadRequest(explanation=msg)
# Let the caller deal with unhandled exceptions.
def _handle_quota_error(self, error):
@@ -314,6 +330,46 @@ class CreateInstanceHelper(object):
raise exc.HTTPBadRequest(explanation=msg)
return password
+ 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
+
class ServerXMLDeserializer(wsgi.XMLDeserializer):
"""
@@ -464,7 +520,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)
@@ -477,6 +534,10 @@ class ServerXMLDeserializerV11(wsgi.MetadataXMLDeserializer):
if personality is not None:
server["personality"] = personality
+ networks = self._extract_networks(server_node)
+ 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
@@ -498,6 +559,23 @@ class ServerXMLDeserializerV11(wsgi.MetadataXMLDeserializer):
else:
return None
+ def _extract_networks(self, server_node):
+ """Marshal the networks attribute of a parsed request"""
+ node = self.find_first_child_named(server_node, "networks")
+ if node is not None:
+ networks = []
+ for network_node in self.find_children_named(node,
+ "network"):
+ item = {}
+ if network_node.hasAttribute("uuid"):
+ item["uuid"] = network_node.getAttribute("uuid")
+ if network_node.hasAttribute("fixed_ip"):
+ item["fixed_ip"] = network_node.getAttribute("fixed_ip")
+ networks.append(item)
+ 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")
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..0ec98591e 100644
--- a/nova/api/openstack/views/servers.py
+++ b/nova/api/openstack/views/servers.py
@@ -1,6 +1,7 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2010-2011 OpenStack LLC.
+# Copyright 2011 Piston Cloud Computing, Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
@@ -128,11 +129,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 +145,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):
@@ -182,6 +188,7 @@ class ViewBuilderV11(ViewBuilder):
def _build_extra(self, response, inst):
self._build_links(response, inst)
response['uuid'] = inst['uuid']
+ self._build_config_drive(response, inst)
def _build_links(self, response, inst):
href = self.generate_href(inst["id"])
@@ -200,11 +207,15 @@ class ViewBuilderV11(ViewBuilder):
response["links"] = links
+ def _build_config_drive(self, response, inst):
+ response['config_drive'] = inst.get('config_drive')
+
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 3616c9ec6..8641e960a 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: