diff options
| author | Anthony Young <sleepsonthefloor@gmail.com> | 2011-08-22 20:05:08 -0700 |
|---|---|---|
| committer | Anthony Young <sleepsonthefloor@gmail.com> | 2011-08-22 20:05:08 -0700 |
| commit | 2f304ecb74cced6d57dc4590f5bf41b7df88a504 (patch) | |
| tree | b6a6adbbcdb6e342a21112e5521f708cd0e3d8c7 | |
| parent | af39051bd033e9e4017fec0fe1647aef582bc38e (diff) | |
| parent | c2fb9485f956482a5e6d628bb80e86d3e8d90d3a (diff) | |
| download | nova-2f304ecb74cced6d57dc4590f5bf41b7df88a504.tar.gz nova-2f304ecb74cced6d57dc4590f5bf41b7df88a504.tar.xz nova-2f304ecb74cced6d57dc4590f5bf41b7df88a504.zip | |
merge trunk, resolve conflicts, fix tests
69 files changed, 3633 insertions, 1130 deletions
diff --git a/bin/nova-manage b/bin/nova-manage index 8e6419c0b..1b29d7196 100755 --- a/bin/nova-manage +++ b/bin/nova-manage @@ -611,6 +611,8 @@ class FixedIpCommands(object): try: fixed_ip = db.fixed_ip_get_by_address(ctxt, address) + if fixed_ip is None: + raise exception.NotFound('Could not find address') db.fixed_ip_update(ctxt, fixed_ip['address'], {'reserved': reserved}) except exception.NotFound as ex: @@ -763,23 +765,26 @@ class NetworkCommands(object): def list(self): """List all created networks""" - print "%-18s\t%-15s\t%-15s\t%-15s\t%-15s\t%-15s\t%-15s" % ( - _('IPv4'), - _('IPv6'), - _('start address'), - _('DNS1'), - _('DNS2'), - _('VlanID'), - 'project') + _fmt = "%-5s\t%-18s\t%-15s\t%-15s\t%-15s\t%-15s\t%-15s\t%-15s\t%-15s" + print _fmt % (_('id'), + _('IPv4'), + _('IPv6'), + _('start address'), + _('DNS1'), + _('DNS2'), + _('VlanID'), + _('project'), + _("uuid")) for network in db.network_get_all(context.get_admin_context()): - print "%-18s\t%-15s\t%-15s\t%-15s\t%-15s\t%-15s\t%-15s" % ( - network.cidr, - network.cidr_v6, - network.dhcp_start, - network.dns1, - network.dns2, - network.vlan, - network.project_id) + print _fmt % (network.id, + network.cidr, + network.cidr_v6, + network.dhcp_start, + network.dns1, + network.dns2, + network.vlan, + network.project_id, + network.uuid) @args('--network', dest="fixed_range", metavar='<x.x.x.x/yy>', help='Network to delete') diff --git a/etc/nova/api-paste.ini b/etc/nova/api-paste.ini index abe8c20c4..b540509a2 100644 --- a/etc/nova/api-paste.ini +++ b/etc/nova/api-paste.ini @@ -20,7 +20,8 @@ use = egg:Paste#urlmap [pipeline:ec2cloud] pipeline = logrequest authenticate cloudrequest authorizer ec2executor -#pipeline = logrequest ec2lockout authenticate cloudrequest authorizer ec2executor +# NOTE(vish): use the following pipeline for keystone +# pipeline = logrequest totoken authtoken keystonecontext cloudrequest authorizer ec2executor [pipeline:ec2admin] pipeline = logrequest authenticate adminrequest authorizer ec2executor @@ -37,6 +38,9 @@ paste.filter_factory = nova.api.ec2:RequestLogging.factory [filter:ec2lockout] paste.filter_factory = nova.api.ec2:Lockout.factory +[filter:totoken] +paste.filter_factory = nova.api.ec2:ToToken.factory + [filter:authenticate] paste.filter_factory = nova.api.ec2:Authenticate.factory @@ -72,9 +76,13 @@ use = egg:Paste#urlmap [pipeline:openstackapi10] pipeline = faultwrap auth ratelimit osapiapp10 +# NOTE(vish): use the following pipeline for keystone +#pipeline = faultwrap authtoken keystonecontext ratelimit osapiapp10 [pipeline:openstackapi11] pipeline = faultwrap auth ratelimit extensions osapiapp11 +# NOTE(vish): use the following pipeline for keystone +# pipeline = faultwrap authtoken keystonecontext ratelimit extensions osapiapp11 [filter:faultwrap] paste.filter_factory = nova.api.openstack:FaultWrapper.factory @@ -99,3 +107,22 @@ pipeline = faultwrap osversionapp [app:osversionapp] paste.app_factory = nova.api.openstack.versions:Versions.factory + +########## +# Shared # +########## + +[filter:keystonecontext] +paste.filter_factory = nova.api.auth:KeystoneContext.factory + +[filter:authtoken] +paste.filter_factory = keystone.middleware.auth_token:filter_factory +service_protocol = http +service_host = 127.0.0.1 +service_port = 808 +auth_host = 127.0.0.1 +auth_port = 5001 +auth_protocol = http +auth_uri = http://127.0.0.1:5000/ +admin_token = 999888777666 + diff --git a/nova/api/auth.py b/nova/api/auth.py new file mode 100644 index 000000000..cd3e3e8a0 --- /dev/null +++ b/nova/api/auth.py @@ -0,0 +1,75 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright (c) 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. +""" +Common Auth Middleware. + +""" + +import webob.dec +import webob.exc + +from nova import context +from nova import flags +from nova import wsgi + + +FLAGS = flags.FLAGS +flags.DEFINE_boolean('use_forwarded_for', False, + 'Treat X-Forwarded-For as the canonical remote address. ' + 'Only enable this if you have a sanitizing proxy.') + + +class InjectContext(wsgi.Middleware): + """Add a 'nova.context' to WSGI environ.""" + + def __init__(self, context, *args, **kwargs): + self.context = context + super(InjectContext, self).__init__(*args, **kwargs) + + @webob.dec.wsgify(RequestClass=wsgi.Request) + def __call__(self, req): + req.environ['nova.context'] = self.context + return self.application + + +class KeystoneContext(wsgi.Middleware): + """Make a request context from keystone headers""" + + @webob.dec.wsgify(RequestClass=wsgi.Request) + def __call__(self, req): + try: + user_id = req.headers['X_USER'] + except KeyError: + return webob.exc.HTTPUnauthorized() + # get the roles + roles = [r.strip() for r in req.headers.get('X_ROLE', '').split(',')] + project_id = req.headers['X_TENANT'] + # Get the auth token + auth_token = req.headers.get('X_AUTH_TOKEN', + req.headers.get('X_STORAGE_TOKEN')) + + # Build a context, including the auth_token... + remote_address = req.remote_addr + if FLAGS.use_forwarded_for: + remote_address = req.headers.get('X-Forwarded-For', remote_address) + ctx = context.RequestContext(user_id, + project_id, + roles=roles, + auth_token=auth_token, + remote_address=remote_address) + + req.environ['nova.context'] = ctx + return self.application diff --git a/nova/api/ec2/__init__.py b/nova/api/ec2/__init__.py index 96df97393..17969099d 100644 --- a/nova/api/ec2/__init__.py +++ b/nova/api/ec2/__init__.py @@ -20,6 +20,7 @@ Starting point for routing EC2 requests. """ +import httplib2 import webob import webob.dec import webob.exc @@ -37,15 +38,16 @@ from nova.auth import manager FLAGS = flags.FLAGS LOG = logging.getLogger("nova.api") -flags.DEFINE_boolean('use_forwarded_for', False, - 'Treat X-Forwarded-For as the canonical remote address. ' - 'Only enable this if you have a sanitizing proxy.') flags.DEFINE_integer('lockout_attempts', 5, 'Number of failed auths before lockout.') flags.DEFINE_integer('lockout_minutes', 15, 'Number of minutes to lockout if triggered.') flags.DEFINE_integer('lockout_window', 15, 'Number of minutes for lockout window.') +flags.DEFINE_string('keystone_ec2_url', + 'http://localhost:5000/v2.0/ec2tokens', + 'URL to get token from ec2 request.') +flags.DECLARE('use_forwarded_for', 'nova.api.auth') class RequestLogging(wsgi.Middleware): @@ -138,6 +140,49 @@ class Lockout(wsgi.Middleware): return res +class ToToken(wsgi.Middleware): + """Authenticate an EC2 request with keystone and convert to token.""" + + @webob.dec.wsgify(RequestClass=wsgi.Request) + def __call__(self, req): + # Read request signature and access id. + try: + signature = req.params['Signature'] + access = req.params['AWSAccessKeyId'] + except KeyError: + raise webob.exc.HTTPBadRequest() + + # Make a copy of args for authentication and signature verification. + auth_params = dict(req.params) + # Not part of authentication args + auth_params.pop('Signature') + + # Authenticate the request. + client = httplib2.Http() + creds = {'ec2Credentials': {'access': access, + 'signature': signature, + 'host': req.host, + 'verb': req.method, + 'path': req.path, + 'params': auth_params, + }} + headers = {'Content-Type': 'application/json'}, + resp, content = client.request(FLAGS.keystone_ec2_url, + 'POST', + headers=headers, + body=utils.dumps(creds)) + # NOTE(vish): We could save a call to keystone by + # having keystone return token, tenant, + # user, and roles from this call. + result = utils.loads(content) + # TODO(vish): check for errors + token_id = result['auth']['token']['id'] + + # Authenticated! + req.headers['X-Auth-Token'] = token_id + return self.application + + class Authenticate(wsgi.Middleware): """Authenticate an EC2 request and add 'nova.context' to WSGI environ.""" @@ -147,7 +192,7 @@ class Authenticate(wsgi.Middleware): try: signature = req.params['Signature'] access = req.params['AWSAccessKeyId'] - except KeyError, e: + except KeyError: raise webob.exc.HTTPBadRequest() # Make a copy of args for authentication and signature verification. diff --git a/nova/api/ec2/metadatarequesthandler.py b/nova/api/ec2/metadatarequesthandler.py index 1dc275c90..0198bf490 100644 --- a/nova/api/ec2/metadatarequesthandler.py +++ b/nova/api/ec2/metadatarequesthandler.py @@ -30,6 +30,7 @@ from nova.api.ec2 import cloud LOG = logging.getLogger('nova.api.ec2.metadata') FLAGS = flags.FLAGS +flags.DECLARE('use_forwarded_for', 'nova.api.auth') class MetadataRequestHandler(wsgi.Application): 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/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/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/contrib/virtual_interfaces.py b/nova/api/openstack/contrib/virtual_interfaces.py new file mode 100644 index 000000000..dab61efc8 --- /dev/null +++ b/nova/api/openstack/contrib/virtual_interfaces.py @@ -0,0 +1,108 @@ +# Copyright (C) 2011 Midokura KK +# All Rights Reserved. +# +# 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. + +"""The virtual interfaces extension.""" + +from webob import exc +import webob + +from nova import compute +from nova import exception +from nova import log as logging +from nova.api.openstack import common +from nova.api.openstack import extensions +from nova.api.openstack import faults +from nova.api.openstack import wsgi + + +LOG = logging.getLogger("nova.api.virtual_interfaces") + + +def _translate_vif_summary_view(_context, vif): + """Maps keys for VIF summary view.""" + d = {} + d['id'] = vif['uuid'] + d['mac_address'] = vif['address'] + return d + + +def _get_metadata(): + metadata = { + "attributes": { + 'virtual_interface': ["id", "mac_address"]}} + return metadata + + +class ServerVirtualInterfaceController(object): + """The instance VIF API controller for the Openstack API. + """ + + def __init__(self): + self.compute_api = compute.API() + super(ServerVirtualInterfaceController, self).__init__() + + def _items(self, req, server_id, entity_maker): + """Returns a list of VIFs, transformed through entity_maker.""" + context = req.environ['nova.context'] + + try: + instance = self.compute_api.get(context, server_id) + except exception.NotFound: + return faults.Fault(exc.HTTPNotFound()) + + vifs = instance['virtual_interfaces'] + limited_list = common.limited(vifs, req) + res = [entity_maker(context, vif) for vif in limited_list] + return {'virtual_interfaces': res} + + def index(self, req, server_id): + """Returns the list of VIFs for a given instance.""" + return self._items(req, server_id, + entity_maker=_translate_vif_summary_view) + + +class Virtual_interfaces(extensions.ExtensionDescriptor): + + def get_name(self): + return "VirtualInterfaces" + + def get_alias(self): + return "virtual_interfaces" + + def get_description(self): + return "Virtual interface support" + + def get_namespace(self): + return "http://docs.openstack.org/ext/virtual_interfaces/api/v1.1" + + def get_updated(self): + return "2011-08-17T00:00:00+00:00" + + def get_resources(self): + resources = [] + + metadata = _get_metadata() + body_serializers = { + 'application/xml': wsgi.XMLDictSerializer(metadata=metadata, + xmlns=wsgi.XMLNS_V11)} + serializer = wsgi.ResponseSerializer(body_serializers, None) + res = extensions.ResourceExtension( + 'os-virtual-interfaces', + controller=ServerVirtualInterfaceController(), + parent=dict(member_name='server', collection_name='servers'), + 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 b0cdd87ea..76b903599 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -29,7 +29,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 @@ -111,6 +111,20 @@ class CreateInstanceHelper(object): if personality: injected_files = self._get_injected_files(personality) + sg_names = [] + security_groups = server_dict.get('security_groups') + if security_groups is not None: + sg_names = [sg['name'] for sg in security_groups if sg.get('name')] + if not sg_names: + sg_names.append('default') + + sg_names = list(set(sg_names)) + + requested_networks = server_dict.get('networks') + if requested_networks is not None: + requested_networks = self._get_requested_networks( + requested_networks) + try: flavor_id = self.controller._flavor_id_from_req_data(body) except ValueError as error: @@ -159,12 +173,16 @@ 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)) except quota.QuotaError as error: @@ -175,6 +193,12 @@ 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} + raise exc.HTTPBadRequest(explanation=msg) # Let the caller deal with unhandled exceptions. def _handle_quota_error(self, error): @@ -303,6 +327,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): """ @@ -453,7 +517,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) @@ -466,6 +531,14 @@ 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 + return server def _extract_personality(self, server_node): @@ -482,3 +555,35 @@ class ServerXMLDeserializerV11(wsgi.MetadataXMLDeserializer): return personality 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") + 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..203728f48 --- /dev/null +++ b/nova/api/openstack/schemas/v1.1/server.rng @@ -0,0 +1,53 @@ +<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="userId"> <text/> </attribute> + <attribute name="tenantId"> <text/> </attribute> + <attribute name="description"> <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 7faeb7278..8a358b532 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -168,7 +168,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() @@ -186,6 +186,13 @@ class Controller(object): if 'description' in body['server']: description = body['server']['description'] update_dict['display_description'] = description.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) @@ -651,14 +658,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) @@ -849,6 +858,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 37f48b3b2..0bef58edc 100644 --- a/nova/api/openstack/views/servers.py +++ b/nova/api/openstack/views/servers.py @@ -131,11 +131,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) @@ -146,6 +147,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): @@ -205,9 +210,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: diff --git a/nova/compute/api.py b/nova/compute/api.py index b3932c644..7de91584f 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -146,6 +146,16 @@ class API(base.Base): LOG.warn(msg) raise quota.QuotaError(msg, "MetadataLimitExceeded") + def _check_requested_networks(self, context, requested_networks): + """ Check if the networks requested belongs to the project + and the fixed IP address for each network provided is within + same the network block + """ + if requested_networks is None: + return + + self.network_api.validate_networks(context, requested_networks) + def _check_create_parameters(self, context, instance_type, image_href, kernel_id=None, ramdisk_id=None, min_count=None, max_count=None, @@ -153,7 +163,8 @@ class API(base.Base): key_name=None, key_data=None, security_group='default', availability_zone=None, user_data=None, metadata=None, injected_files=None, admin_password=None, zone_blob=None, - reservation_id=None): + reservation_id=None, access_ip_v4=None, access_ip_v6=None, + requested_networks=None): """Verify all the input parameters regardless of the provisioning strategy being performed.""" @@ -182,6 +193,7 @@ class API(base.Base): self._check_metadata_properties_quota(context, metadata) self._check_injected_file_quota(context, injected_files) + self._check_requested_networks(context, requested_networks) (image_service, image_id) = nova.image.get_image_service(image_href) image = image_service.show(context, image_id) @@ -247,6 +259,8 @@ class API(base.Base): 'key_data': key_data, 'locked': False, 'metadata': metadata, + 'access_ip_v4': access_ip_v4, + 'access_ip_v6': access_ip_v6, 'availability_zone': availability_zone, 'os_type': os_type, 'architecture': architecture, @@ -398,9 +412,9 @@ class API(base.Base): def _ask_scheduler_to_create_instance(self, context, base_options, instance_type, zone_blob, availability_zone, injected_files, - admin_password, - image, - instance_id=None, num_instances=1): + admin_password, image, + instance_id=None, num_instances=1, + requested_networks=None): """Send the run_instance request to the schedulers for processing.""" pid = context.project_id uid = context.user_id @@ -411,12 +425,11 @@ class API(base.Base): LOG.debug(_("Casting to scheduler for %(pid)s/%(uid)s's" " (all-at-once)") % locals()) - filter_class = 'nova.scheduler.host_filter.InstanceTypeFilter' request_spec = { 'image': image, 'instance_properties': base_options, 'instance_type': instance_type, - 'filter': filter_class, + 'filter': None, 'blob': zone_blob, 'num_instances': num_instances, } @@ -429,7 +442,8 @@ class API(base.Base): "request_spec": request_spec, "availability_zone": availability_zone, "admin_password": admin_password, - "injected_files": injected_files}}) + "injected_files": injected_files, + "requested_networks": requested_networks}}) def create_all_at_once(self, context, instance_type, image_href, kernel_id=None, ramdisk_id=None, @@ -438,7 +452,9 @@ class API(base.Base): key_name=None, key_data=None, security_group='default', availability_zone=None, user_data=None, metadata=None, injected_files=None, admin_password=None, zone_blob=None, - reservation_id=None, block_device_mapping=None): + reservation_id=None, block_device_mapping=None, + access_ip_v4=None, access_ip_v6=None, + requested_networks=None): """Provision the instances by passing the whole request to the Scheduler for execution. Returns a Reservation ID related to the creation of all of these instances.""" @@ -454,14 +470,15 @@ class API(base.Base): key_name, key_data, security_group, availability_zone, user_data, metadata, injected_files, admin_password, zone_blob, - reservation_id) + reservation_id, access_ip_v4, access_ip_v6, + requested_networks) self._ask_scheduler_to_create_instance(context, base_options, instance_type, zone_blob, availability_zone, injected_files, - admin_password, - image, - num_instances=num_instances) + admin_password, image, + num_instances=num_instances, + requested_networks=requested_networks) return base_options['reservation_id'] @@ -472,7 +489,9 @@ class API(base.Base): key_name=None, key_data=None, security_group='default', availability_zone=None, user_data=None, metadata=None, injected_files=None, admin_password=None, zone_blob=None, - reservation_id=None, block_device_mapping=None): + reservation_id=None, block_device_mapping=None, + access_ip_v4=None, access_ip_v6=None, + requested_networks=None): """ Provision the instances by sending off a series of single instance requests to the Schedulers. This is fine for trival @@ -496,7 +515,8 @@ class API(base.Base): key_name, key_data, security_group, availability_zone, user_data, metadata, injected_files, admin_password, zone_blob, - reservation_id) + reservation_id, access_ip_v4, access_ip_v6, + requested_networks) block_device_mapping = block_device_mapping or [] instances = [] @@ -510,11 +530,11 @@ class API(base.Base): instance_id = instance['id'] self._ask_scheduler_to_create_instance(context, base_options, - instance_type, zone_blob, - availability_zone, injected_files, - admin_password, - image, - instance_id=instance_id) + instance_type, zone_blob, + availability_zone, injected_files, + admin_password, image, + instance_id=instance_id, + requested_networks=requested_networks) return [dict(x.iteritems()) for x in instances] @@ -614,6 +634,78 @@ class API(base.Base): self.db.queue_get_for(context, FLAGS.compute_topic, host), {'method': 'refresh_provider_fw_rules', 'args': {}}) + def _is_security_group_associated_with_server(self, security_group, + instance_id): + """Check if the security group is already associated + with the instance. If Yes, return True. + """ + + if not security_group: + return False + + instances = security_group.get('instances') + if not instances: + return False + + inst_id = None + for inst_id in (instance['id'] for instance in instances \ + if instance_id == instance['id']): + return True + + return False + + def add_security_group(self, context, instance_id, security_group_name): + """Add security group to the instance""" + security_group = db.security_group_get_by_name(context, + context.project_id, + security_group_name) + # check if the server exists + inst = db.instance_get(context, instance_id) + #check if the security group is associated with the server + if self._is_security_group_associated_with_server(security_group, + instance_id): + raise exception.SecurityGroupExistsForInstance( + security_group_id=security_group['id'], + instance_id=instance_id) + + #check if the instance is in running state + if inst['state'] != power_state.RUNNING: + raise exception.InstanceNotRunning(instance_id=instance_id) + + db.instance_add_security_group(context.elevated(), + instance_id, + security_group['id']) + rpc.cast(context, + db.queue_get_for(context, FLAGS.compute_topic, inst['host']), + {"method": "refresh_security_group_rules", + "args": {"security_group_id": security_group['id']}}) + + def remove_security_group(self, context, instance_id, security_group_name): + """Remove the security group associated with the instance""" + security_group = db.security_group_get_by_name(context, + context.project_id, + security_group_name) + # check if the server exists + inst = db.instance_get(context, instance_id) + #check if the security group is associated with the server + if not self._is_security_group_associated_with_server(security_group, + instance_id): + raise exception.SecurityGroupNotExistsForInstance( + security_group_id=security_group['id'], + instance_id=instance_id) + + #check if the instance is in running state + if inst['state'] != power_state.RUNNING: + raise exception.InstanceNotRunning(instance_id=instance_id) + + db.instance_remove_security_group(context.elevated(), + instance_id, + security_group['id']) + rpc.cast(context, + db.queue_get_for(context, FLAGS.compute_topic, inst['host']), + {"method": "refresh_security_group_rules", + "args": {"security_group_id": security_group['id']}}) + @scheduler_api.reroute_compute("update") def update(self, context, instance_id, **kwargs): """Updates the instance in the datastore. diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 091b3b6b2..c207eccbb 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -382,6 +382,8 @@ class ComputeManager(manager.SchedulerDependentManager): context = context.elevated() instance = self.db.instance_get(context, instance_id) + requested_networks = kwargs.get('requested_networks', None) + if instance['name'] in self.driver.list_instances(): raise exception.Error(_("Instance has already been created")) @@ -411,7 +413,8 @@ class ComputeManager(manager.SchedulerDependentManager): # will eventually also need to save the address here. if not FLAGS.stub_network: network_info = self.network_api.allocate_for_instance(context, - instance, vpn=is_vpn) + instance, vpn=is_vpn, + requested_networks=requested_networks) LOG.debug(_("instance network_info: |%s|"), network_info) else: # TODO(tr3buchet) not really sure how this should be handled. diff --git a/nova/db/api.py b/nova/db/api.py index b9ea8757c..2d854f24c 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -323,13 +323,13 @@ def migration_get_by_instance_and_status(context, instance_uuid, status): #################### -def fixed_ip_associate(context, address, instance_id): +def fixed_ip_associate(context, address, instance_id, network_id=None): """Associate fixed ip to instance. Raises if fixed ip is not available. """ - return IMPL.fixed_ip_associate(context, address, instance_id) + return IMPL.fixed_ip_associate(context, address, instance_id, network_id) def fixed_ip_associate_pool(context, network_id, instance_id=None, host=None): @@ -396,7 +396,6 @@ def fixed_ip_update(context, address, values): """Create a fixed ip from the values dictionary.""" return IMPL.fixed_ip_update(context, address, values) - #################### @@ -570,6 +569,12 @@ def instance_add_security_group(context, instance_id, security_group_id): security_group_id) +def instance_remove_security_group(context, instance_id, security_group_id): + """Disassociate the given security group from the given instance.""" + return IMPL.instance_remove_security_group(context, instance_id, + security_group_id) + + def instance_action_create(context, values): """Create an instance action from the values dictionary.""" return IMPL.instance_action_create(context, values) @@ -680,7 +685,14 @@ def network_get_all(context): return IMPL.network_get_all(context) +def network_get_all_by_uuids(context, network_uuids, project_id=None): + """Return networks by ids.""" + return IMPL.network_get_all_by_uuids(context, network_uuids, project_id) + + # pylint: disable=C0103 + + def network_get_associated_fixed_ips(context, network_id): """Get all network's ips that have been associated.""" return IMPL.network_get_associated_fixed_ips(context, network_id) diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index fe80056ab..04b5405f6 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -652,23 +652,36 @@ def floating_ip_update(context, address, values): ################### -@require_context -def fixed_ip_associate(context, address, instance_id): +@require_admin_context +def fixed_ip_associate(context, address, instance_id, network_id=None): session = get_session() with session.begin(): - instance = instance_get(context, instance_id, session=session) + network_or_none = or_(models.FixedIp.network_id == network_id, + models.FixedIp.network_id == None) fixed_ip_ref = session.query(models.FixedIp).\ - filter_by(address=address).\ + filter(network_or_none).\ + filter_by(reserved=False).\ filter_by(deleted=False).\ - filter_by(instance=None).\ + filter_by(address=address).\ with_lockmode('update').\ first() # NOTE(vish): if with_lockmode isn't supported, as in sqlite, # then this has concurrency issues - if not fixed_ip_ref: - raise exception.NoMoreFixedIps() - fixed_ip_ref.instance = instance + if fixed_ip_ref is None: + raise exception.FixedIpNotFoundForNetwork(address=address, + network_id=network_id) + if fixed_ip_ref.instance is not None: + raise exception.FixedIpAlreadyInUse(address=address) + + if not fixed_ip_ref.network: + fixed_ip_ref.network = network_get(context, + network_id, + session=session) + fixed_ip_ref.instance = instance_get(context, + instance_id, + session=session) session.add(fixed_ip_ref) + return fixed_ip_ref['address'] @require_admin_context @@ -1502,6 +1515,19 @@ def instance_add_security_group(context, instance_id, security_group_id): @require_context +def instance_remove_security_group(context, instance_id, security_group_id): + """Disassociate the given security group from the given instance""" + session = get_session() + + session.query(models.SecurityGroupInstanceAssociation).\ + filter_by(instance_id=instance_id).\ + filter_by(security_group_id=security_group_id).\ + update({'deleted': True, + 'deleted_at': utils.utcnow(), + 'updated_at': literal_column('updated_at')}) + + +@require_context def instance_action_create(context, values): """Create an instance action from the values dictionary.""" action_ref = models.InstanceActions() @@ -1742,6 +1768,40 @@ def network_get_all(context): return result +@require_admin_context +def network_get_all_by_uuids(context, network_uuids, project_id=None): + session = get_session() + project_or_none = or_(models.Network.project_id == project_id, + models.Network.project_id == None) + result = session.query(models.Network).\ + filter(models.Network.uuid.in_(network_uuids)).\ + filter(project_or_none).\ + filter_by(deleted=False).all() + if not result: + raise exception.NoNetworksFound() + + #check if host is set to all of the networks + # returned in the result + for network in result: + if network['host'] is None: + raise exception.NetworkHostNotSet(network_id=network['id']) + + #check if the result contains all the networks + #we are looking for + for network_uuid in network_uuids: + found = False + for network in result: + if network['uuid'] == network_uuid: + found = True + break + if not found: + if project_id: + raise exception.NetworkNotFoundForProject(network_uuid=uuid, + project_id=context.project_id) + raise exception.NetworkNotFound(network_id=network_uuid) + + return result + # NOTE(vish): pylint complains because of the long method name, but # it fits with the names of the rest of the methods # pylint: disable=C0103 @@ -2437,6 +2497,7 @@ def security_group_get(context, security_group_id, session=None): filter_by(deleted=can_read_deleted(context),).\ filter_by(id=security_group_id).\ options(joinedload_all('rules')).\ + options(joinedload_all('instances')).\ first() else: result = session.query(models.SecurityGroup).\ @@ -2444,6 +2505,7 @@ def security_group_get(context, security_group_id, session=None): filter_by(id=security_group_id).\ filter_by(project_id=context.project_id).\ options(joinedload_all('rules')).\ + options(joinedload_all('instances')).\ first() if not result: raise exception.SecurityGroupNotFound( diff --git a/nova/db/sqlalchemy/migrate_repo/versions/038_add_uuid_to_virtual_interfaces.py b/nova/db/sqlalchemy/migrate_repo/versions/038_add_uuid_to_virtual_interfaces.py new file mode 100644 index 000000000..0f542cbec --- /dev/null +++ b/nova/db/sqlalchemy/migrate_repo/versions/038_add_uuid_to_virtual_interfaces.py @@ -0,0 +1,44 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright (C) 2011 Midokura KK +# +# 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 sqlalchemy import Column, Integer, MetaData, String, Table + +from nova import utils + + +meta = MetaData() + +virtual_interfaces = Table("virtual_interfaces", meta, + Column("id", Integer(), primary_key=True, + nullable=False)) +uuid_column = Column("uuid", String(36)) + + +def upgrade(migrate_engine): + meta.bind = migrate_engine + virtual_interfaces.create_column(uuid_column) + + rows = migrate_engine.execute(virtual_interfaces.select()) + for row in rows: + vif_uuid = str(utils.gen_uuid()) + migrate_engine.execute(virtual_interfaces.update()\ + .where(virtual_interfaces.c.id == row[0])\ + .values(uuid=vif_uuid)) + + +def downgrade(migrate_engine): + meta.bind = migrate_engine + virtual_interfaces.drop_column(uuid_column) diff --git a/nova/db/sqlalchemy/migrate_repo/versions/039_add_instances_accessip.py b/nova/db/sqlalchemy/migrate_repo/versions/039_add_instances_accessip.py new file mode 100644 index 000000000..39f0dd6ce --- /dev/null +++ b/nova/db/sqlalchemy/migrate_repo/versions/039_add_instances_accessip.py @@ -0,0 +1,48 @@ +# 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 sqlalchemy import Column, Integer, MetaData, Table, String + +meta = MetaData() + +accessIPv4 = Column( + 'access_ip_v4', + String(length=255, convert_unicode=False, assert_unicode=None, + unicode_error=None, _warn_on_bytestring=False), + nullable=True) + +accessIPv6 = Column( + 'access_ip_v6', + String(length=255, convert_unicode=False, assert_unicode=None, + unicode_error=None, _warn_on_bytestring=False), + nullable=True) + +instances = Table('instances', meta, + Column('id', Integer(), primary_key=True, nullable=False), + ) + + +def upgrade(migrate_engine): + # Upgrade operations go here. Don't create your own engine; + # bind migrate_engine to your metadata + meta.bind = migrate_engine + instances.create_column(accessIPv4) + instances.create_column(accessIPv6) + + +def downgrade(migrate_engine): + # Operations to reverse the above upgrade go here. + meta.bind = migrate_engine + instances.drop_column('access_ip_v4') + instances.drop_column('access_ip_v6') diff --git a/nova/db/sqlalchemy/migrate_repo/versions/040_add_uuid_to_networks.py b/nova/db/sqlalchemy/migrate_repo/versions/040_add_uuid_to_networks.py new file mode 100644 index 000000000..38c543d51 --- /dev/null +++ b/nova/db/sqlalchemy/migrate_repo/versions/040_add_uuid_to_networks.py @@ -0,0 +1,43 @@ +# 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 sqlalchemy import Column, Integer, MetaData, String, Table + +from nova import utils + + +meta = MetaData() + +networks = Table("networks", meta, + Column("id", Integer(), primary_key=True, nullable=False)) +uuid_column = Column("uuid", String(36)) + + +def upgrade(migrate_engine): + meta.bind = migrate_engine + networks.create_column(uuid_column) + + rows = migrate_engine.execute(networks.select()) + for row in rows: + networks_uuid = str(utils.gen_uuid()) + migrate_engine.execute(networks.update()\ + .where(networks.c.id == row[0])\ + .values(uuid=networks_uuid)) + + +def downgrade(migrate_engine): + meta.bind = migrate_engine + networks.drop_column(uuid_column) diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index a8e9c36db..19dc3302e 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -231,6 +231,11 @@ class Instance(BASE, NovaBase): root_device_name = Column(String(255)) + # User editable field meant to represent what ip should be used + # to connect to the instance + access_ip_v4 = Column(String(255)) + access_ip_v6 = Column(String(255)) + # TODO(vish): see Ewan's email about state improvements, probably # should be in a driver base class or some such # vmstate_state = running, halted, suspended, paused @@ -556,6 +561,7 @@ class Network(BASE, NovaBase): project_id = Column(String(255)) host = Column(String(255)) # , ForeignKey('hosts.id')) + uuid = Column(String(36)) class VirtualInterface(BASE, NovaBase): @@ -570,6 +576,8 @@ class VirtualInterface(BASE, NovaBase): instance_id = Column(Integer, ForeignKey('instances.id'), nullable=False) instance = relationship(Instance, backref=backref('virtual_interfaces')) + uuid = Column(String(36)) + @property def fixed_ipv6(self): cidr_v6 = self.network.cidr_v6 diff --git a/nova/exception.py b/nova/exception.py index b09d50797..66740019b 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -423,6 +423,15 @@ class NoNetworksFound(NotFound): message = _("No networks defined.") +class NetworkNotFoundForProject(NotFound): + message = _("Either Network uuid %(network_uuid)s is not present or " + "is not assigned to the project %(project_id)s.") + + +class NetworkHostNotSet(NovaException): + message = _("Host is not set to the network (%(network_id)s).") + + class DatastoreNotFound(NotFound): message = _("Could not find the datastore reference(s) which the VM uses.") @@ -456,6 +465,19 @@ class FixedIpNotFoundForHost(FixedIpNotFound): message = _("Host %(host)s has zero fixed ips.") +class FixedIpNotFoundForNetwork(FixedIpNotFound): + message = _("Fixed IP address (%(address)s) does not exist in " + "network (%(network_uuid)s).") + + +class FixedIpAlreadyInUse(NovaException): + message = _("Fixed IP address %(address)s is already in use.") + + +class FixedIpInvalid(Invalid): + message = _("Fixed IP address %(address)s is invalid.") + + class NoMoreFixedIps(Error): message = _("Zero fixed ips available.") @@ -541,6 +563,16 @@ class SecurityGroupNotFoundForRule(SecurityGroupNotFound): message = _("Security group with rule %(rule_id)s not found.") +class SecurityGroupExistsForInstance(Invalid): + message = _("Security group %(security_group_id)s is already associated" + " with the instance %(instance_id)s") + + +class SecurityGroupNotExistsForInstance(Invalid): + message = _("Security group %(security_group_id)s is not associated with" + " the instance %(instance_id)s") + + class MigrationNotFound(NotFound): message = _("Migration %(migration_id)s could not be found.") diff --git a/nova/network/api.py b/nova/network/api.py index 247768722..d04474df3 100644 --- a/nova/network/api.py +++ b/nova/network/api.py @@ -195,3 +195,12 @@ class API(base.Base): return rpc.call(context, FLAGS.network_topic, {'method': 'get_instance_nw_info', 'args': args}) + + def validate_networks(self, context, requested_networks): + """validate the networks passed at the time of creating + the server + """ + args = {'networks': requested_networks} + return rpc.call(context, FLAGS.network_topic, + {'method': 'validate_networks', + 'args': args}) diff --git a/nova/network/manager.py b/nova/network/manager.py index 08439b004..aa2a3700c 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -131,7 +131,15 @@ class RPCAllocateFixedIP(object): green_pool = greenpool.GreenPool() vpn = kwargs.pop('vpn') + requested_networks = kwargs.pop('requested_networks') + for network in networks: + address = None + if requested_networks is not None: + for address in (fixed_ip for (uuid, fixed_ip) in \ + requested_networks if network['uuid'] == uuid): + break + # NOTE(vish): if we are not multi_host pass to the network host if not network['multi_host']: host = network['host'] @@ -148,6 +156,7 @@ class RPCAllocateFixedIP(object): args = {} args['instance_id'] = instance_id args['network_id'] = network['id'] + args['address'] = address args['vpn'] = vpn green_pool.spawn_n(rpc.call, context, topic, @@ -155,7 +164,8 @@ class RPCAllocateFixedIP(object): 'args': args}) else: # i am the correct host, run here - self.allocate_fixed_ip(context, instance_id, network, vpn=vpn) + self.allocate_fixed_ip(context, instance_id, network, + vpn=vpn, address=address) # wait for all of the allocates (if any) to finish green_pool.waitall() @@ -199,6 +209,7 @@ class FloatingIP(object): """ instance_id = kwargs.get('instance_id') project_id = kwargs.get('project_id') + requested_networks = kwargs.get('requested_networks') LOG.debug(_("floating IP allocation for instance |%s|"), instance_id, context=context) # call the next inherited class's allocate_for_instance() @@ -380,16 +391,21 @@ class NetworkManager(manager.SchedulerDependentManager): self.compute_api.trigger_security_group_members_refresh(admin_context, group_ids) - def _get_networks_for_instance(self, context, instance_id, project_id): + def _get_networks_for_instance(self, context, instance_id, project_id, + requested_networks=None): """Determine & return which networks an instance should connect to.""" # TODO(tr3buchet) maybe this needs to be updated in the future if # there is a better way to determine which networks # a non-vlan instance should connect to - try: - networks = self.db.network_get_all(context) - except exception.NoNetworksFound: - return [] - + if requested_networks is not None and len(requested_networks) != 0: + network_uuids = [uuid for (uuid, fixed_ip) in requested_networks] + networks = self.db.network_get_all_by_uuids(context, + network_uuids) + else: + try: + networks = self.db.network_get_all(context) + except exception.NoNetworksFound: + return [] # return only networks which are not vlan networks return [network for network in networks if not network['vlan']] @@ -403,16 +419,18 @@ class NetworkManager(manager.SchedulerDependentManager): host = kwargs.pop('host') project_id = kwargs.pop('project_id') type_id = kwargs.pop('instance_type_id') + requested_networks = kwargs.get('requested_networks') vpn = kwargs.pop('vpn') admin_context = context.elevated() LOG.debug(_("network allocations for instance %s"), instance_id, context=context) - networks = self._get_networks_for_instance(admin_context, instance_id, - project_id) - LOG.warn(networks) + networks = self._get_networks_for_instance(admin_context, + instance_id, project_id, + requested_networks=requested_networks) self._allocate_mac_addresses(context, instance_id, networks) - self._allocate_fixed_ips(admin_context, instance_id, host, networks, - vpn=vpn) + self._allocate_fixed_ips(admin_context, instance_id, + host, networks, vpn=vpn, + requested_networks=requested_networks) return self.get_instance_nw_info(context, instance_id, type_id, host) def deallocate_for_instance(self, context, **kwargs): @@ -500,6 +518,7 @@ class NetworkManager(manager.SchedulerDependentManager): 'dhcp_server': dhcp_server, 'broadcast': network['broadcast'], 'mac': vif['address'], + 'vif_uuid': vif['uuid'], 'rxtx_cap': flavor['rxtx_cap'], 'dns': [], 'ips': [ip_dict(ip) for ip in network_IPs], @@ -524,7 +543,8 @@ class NetworkManager(manager.SchedulerDependentManager): for network in networks: vif = {'address': self.generate_mac_address(), 'instance_id': instance_id, - 'network_id': network['id']} + 'network_id': network['id'], + 'uuid': str(utils.gen_uuid())} # try FLAG times to create a vif record with a unique mac_address for i in range(FLAGS.create_unique_mac_address_attempts): try: @@ -568,9 +588,15 @@ class NetworkManager(manager.SchedulerDependentManager): # network_get_by_compute_host address = None if network['cidr']: - address = self.db.fixed_ip_associate_pool(context.elevated(), - network['id'], - instance_id) + address = kwargs.get('address', None) + if address: + address = self.db.fixed_ip_associate(context, + address, instance_id, + network['id']) + else: + address = self.db.fixed_ip_associate_pool(context.elevated(), + network['id'], + instance_id) self._do_trigger_security_group_members_refresh_for_instance( instance_id) get_vif = self.db.virtual_interface_get_by_instance_and_network @@ -796,6 +822,35 @@ class NetworkManager(manager.SchedulerDependentManager): """Sets up network on this host.""" raise NotImplementedError() + def validate_networks(self, context, networks): + """check if the networks exists and host + is set to each network. + """ + if networks is None or len(networks) == 0: + return + + network_uuids = [uuid for (uuid, fixed_ip) in networks] + + self._get_networks_by_uuids(context, network_uuids) + + for network_uuid, address in networks: + # check if the fixed IP address is valid and + # it actually belongs to the network + if address is not None: + if not utils.is_valid_ipv4(address): + raise exception.FixedIpInvalid(address=address) + + fixed_ip_ref = self.db.fixed_ip_get_by_address(context, + address) + if fixed_ip_ref['network']['uuid'] != network_uuid: + raise exception.FixedIpNotFoundForNetwork(address=address, + network_uuid=network_uuid) + if fixed_ip_ref['instance'] is not None: + raise exception.FixedIpAlreadyInUse(address=address) + + def _get_networks_by_uuids(self, context, network_uuids): + return self.db.network_get_all_by_uuids(context, network_uuids) + class FlatManager(NetworkManager): """Basic network where no vlans are used. @@ -830,8 +885,16 @@ class FlatManager(NetworkManager): def _allocate_fixed_ips(self, context, instance_id, host, networks, **kwargs): """Calls allocate_fixed_ip once for each network.""" + requested_networks = kwargs.pop('requested_networks') for network in networks: - self.allocate_fixed_ip(context, instance_id, network) + address = None + if requested_networks is not None: + for address in (fixed_ip for (uuid, fixed_ip) in \ + requested_networks if network['uuid'] == uuid): + break + + self.allocate_fixed_ip(context, instance_id, + network, address=address) def deallocate_fixed_ip(self, context, address, **kwargs): """Returns a fixed ip to the pool.""" @@ -925,9 +988,15 @@ class VlanManager(RPCAllocateFixedIP, FloatingIP, NetworkManager): address, instance_id) else: - address = self.db.fixed_ip_associate_pool(context, - network['id'], - instance_id) + address = kwargs.get('address', None) + if address: + address = self.db.fixed_ip_associate(context, address, + instance_id, + network['id']) + else: + address = self.db.fixed_ip_associate_pool(context, + network['id'], + instance_id) self._do_trigger_security_group_members_refresh_for_instance( instance_id) vif = self.db.virtual_interface_get_by_instance_and_network(context, @@ -943,10 +1012,18 @@ class VlanManager(RPCAllocateFixedIP, FloatingIP, NetworkManager): """Force adds another network to a project.""" self.db.network_associate(context, project_id, force=True) - def _get_networks_for_instance(self, context, instance_id, project_id): + def _get_networks_for_instance(self, context, instance_id, project_id, + requested_networks=None): """Determine which networks an instance should connect to.""" # get networks associated with project - return self.db.project_get_networks(context, project_id) + if requested_networks is not None and len(requested_networks) != 0: + network_uuids = [uuid for (uuid, fixed_ip) in requested_networks] + networks = self.db.network_get_all_by_uuids(context, + network_uuids, + project_id) + else: + networks = self.db.project_get_networks(context, project_id) + return networks def create_networks(self, context, **kwargs): """Create networks based on parameters.""" @@ -995,6 +1072,10 @@ class VlanManager(RPCAllocateFixedIP, FloatingIP, NetworkManager): self.db.network_update(context, network_ref['id'], {'gateway_v6': gateway}) + def _get_networks_by_uuids(self, context, network_uuids): + return self.db.network_get_all_by_uuids(context, network_uuids, + context.project_id) + @property def _bottom_reserved_ips(self): """Number of reserved ips at the bottom of the range.""" diff --git a/nova/scheduler/abstract_scheduler.py b/nova/scheduler/abstract_scheduler.py index 77db67773..e8c343a4b 100644 --- a/nova/scheduler/abstract_scheduler.py +++ b/nova/scheduler/abstract_scheduler.py @@ -62,12 +62,13 @@ class AbstractScheduler(driver.Scheduler): host = build_plan_item['hostname'] base_options = request_spec['instance_properties'] image = request_spec['image'] + instance_type = request_spec.get('instance_type') # TODO(sandy): I guess someone needs to add block_device_mapping # support at some point? Also, OS API has no concept of security # groups. instance = compute_api.API().create_db_entry_for_new_instance(context, - image, base_options, None, []) + instance_type, image, base_options, None, []) instance_id = instance['id'] kwargs['instance_id'] = instance_id diff --git a/nova/scheduler/host_filter.py b/nova/scheduler/host_filter.py index 4bc5158cc..826a99b0a 100644 --- a/nova/scheduler/host_filter.py +++ b/nova/scheduler/host_filter.py @@ -58,8 +58,6 @@ def choose_host_filter(filter_name=None): if not filter_name: filter_name = FLAGS.default_host_filter for filter_class in _get_filters(): - host_match = "%s.%s" % (filter_class.__module__, filter_class.__name__) - if (host_match.startswith("nova.scheduler.filters") and - (host_match.split(".")[-1] == filter_name)): + if filter_class.__name__ == filter_name: return filter_class() raise exception.SchedulerHostFilterNotFound(filter_name=filter_name) diff --git a/nova/tests/api/openstack/__init__.py b/nova/tests/api/openstack/__init__.py index 458434a81..7d44489a1 100644 --- a/nova/tests/api/openstack/__init__.py +++ b/nova/tests/api/openstack/__init__.py @@ -44,14 +44,15 @@ class RateLimitingMiddlewareTest(test.TestCase): action = middleware.get_action_name(req) self.assertEqual(action, action_name) - verify('PUT', '/servers/4', 'PUT') - verify('DELETE', '/servers/4', 'DELETE') - verify('POST', '/images/4', 'POST') - verify('POST', '/servers/4', 'POST servers') - verify('GET', '/foo?a=4&changes-since=never&b=5', 'GET changes-since') - verify('GET', '/foo?a=4&monkeys-since=never&b=5', None) - verify('GET', '/servers/4', None) - verify('HEAD', '/servers/4', None) + verify('PUT', '/fake/servers/4', 'PUT') + verify('DELETE', '/fake/servers/4', 'DELETE') + verify('POST', '/fake/images/4', 'POST') + verify('POST', '/fake/servers/4', 'POST servers') + verify('GET', '/fake/foo?a=4&changes-since=never&b=5', + 'GET changes-since') + verify('GET', '/fake/foo?a=4&monkeys-since=never&b=5', None) + verify('GET', '/fake/servers/4', None) + verify('HEAD', '/fake/servers/4', None) def exhaust(self, middleware, method, url, username, times): req = Request.blank(url, dict(REQUEST_METHOD=method), @@ -67,13 +68,13 @@ class RateLimitingMiddlewareTest(test.TestCase): def test_single_action(self): middleware = RateLimitingMiddleware(simple_wsgi) - self.exhaust(middleware, 'DELETE', '/servers/4', 'usr1', 100) - self.exhaust(middleware, 'DELETE', '/servers/4', 'usr2', 100) + self.exhaust(middleware, 'DELETE', '/fake/servers/4', 'usr1', 100) + self.exhaust(middleware, 'DELETE', '/fake/servers/4', 'usr2', 100) def test_POST_servers_action_implies_POST_action(self): middleware = RateLimitingMiddleware(simple_wsgi) - self.exhaust(middleware, 'POST', '/servers/4', 'usr1', 10) - self.exhaust(middleware, 'POST', '/images/4', 'usr2', 10) + self.exhaust(middleware, 'POST', '/fake/servers/4', 'usr1', 10) + self.exhaust(middleware, 'POST', '/fake/images/4', 'usr2', 10) self.assertTrue(set(middleware.limiter._levels) == \ set(['usr1:POST', 'usr1:POST servers', 'usr2:POST'])) @@ -81,11 +82,11 @@ class RateLimitingMiddlewareTest(test.TestCase): middleware = RateLimitingMiddleware(simple_wsgi) # Use up all of our "POST" allowance for the minute, 5 times for i in range(5): - self.exhaust(middleware, 'POST', '/servers/4', 'usr1', 10) + self.exhaust(middleware, 'POST', '/fake/servers/4', 'usr1', 10) # Reset the 'POST' action counter. del middleware.limiter._levels['usr1:POST'] # All 50 daily "POST servers" actions should be all used up - self.exhaust(middleware, 'POST', '/servers/4', 'usr1', 0) + self.exhaust(middleware, 'POST', '/fake/servers/4', 'usr1', 0) def test_proxy_ctor_works(self): middleware = RateLimitingMiddleware(simple_wsgi) diff --git a/nova/tests/api/openstack/contrib/test_createserverext.py b/nova/tests/api/openstack/contrib/test_createserverext.py new file mode 100644 index 000000000..e5eed14fe --- /dev/null +++ b/nova/tests/api/openstack/contrib/test_createserverext.py @@ -0,0 +1,306 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010-2011 OpenStack LLC. +# All Rights Reserved. +# +# 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. + +import base64 +import json +import unittest +from xml.dom import minidom + +import stubout +import webob + +from nova import exception +from nova import flags +from nova import test +from nova import utils +import nova.api.openstack +from nova.api.openstack import servers +from nova.api.openstack.contrib import createserverext +import nova.compute.api + +import nova.scheduler.api +import nova.image.fake +import nova.rpc +from nova.tests.api.openstack import fakes + + +FLAGS = flags.FLAGS +FLAGS.verbose = True + +FAKE_UUID = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa' + +FAKE_NETWORKS = [('aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', '10.0.1.12'), + ('bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb', '10.0.2.12')] + +DUPLICATE_NETWORKS = [('aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', '10.0.1.12'), + ('aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', '10.0.1.12')] + +INVALID_NETWORKS = [('invalid', 'invalid-ip-address')] + + +class CreateserverextTest(test.TestCase): + + def setUp(self): + super(CreateserverextTest, self).setUp() + self.stubs = stubout.StubOutForTesting() + fakes.FakeAuthManager.auth_data = {} + fakes.FakeAuthDatabase.data = {} + fakes.stub_out_auth(self.stubs) + fakes.stub_out_image_service(self.stubs) + fakes.stub_out_key_pair_funcs(self.stubs) + self.allow_admin = FLAGS.allow_admin_api + + def tearDown(self): + self.stubs.UnsetAll() + FLAGS.allow_admin_api = self.allow_admin + super(CreateserverextTest, self).tearDown() + + def _setup_mock_compute_api(self): + + class MockComputeAPI(nova.compute.API): + + def __init__(self): + self.injected_files = None + self.networks = None + + def create(self, *args, **kwargs): + if 'injected_files' in kwargs: + self.injected_files = kwargs['injected_files'] + else: + self.injected_files = None + + if 'requested_networks' in kwargs: + self.networks = kwargs['requested_networks'] + else: + self.networks = None + return [{'id': '1234', 'display_name': 'fakeinstance', + 'uuid': FAKE_UUID, + 'created_at': "", + 'updated_at': ""}] + + def set_admin_password(self, *args, **kwargs): + pass + + def make_stub_method(canned_return): + def stub_method(*args, **kwargs): + return canned_return + return stub_method + + compute_api = MockComputeAPI() + self.stubs.Set(nova.compute, 'API', make_stub_method(compute_api)) + self.stubs.Set( + nova.api.openstack.create_instance_helper.CreateInstanceHelper, + '_get_kernel_ramdisk_from_image', make_stub_method((1, 1))) + return compute_api + + def _create_networks_request_dict(self, networks): + server = {} + server['name'] = 'new-server-test' + server['imageRef'] = 1 + server['flavorRef'] = 1 + if networks is not None: + network_list = [] + for uuid, fixed_ip in networks: + network_list.append({'uuid': uuid, 'fixed_ip': fixed_ip}) + server['networks'] = network_list + return {'server': server} + + def _get_create_request_json(self, body_dict): + req = webob.Request.blank('/v1.1/123/os-create-server-ext') + req.headers['Content-Type'] = 'application/json' + req.method = 'POST' + req.body = json.dumps(body_dict) + return req + + def _run_create_instance_with_mock_compute_api(self, request): + compute_api = self._setup_mock_compute_api() + response = request.get_response(fakes.wsgi_app()) + return compute_api, response + + def _format_xml_request_body(self, body_dict): + server = body_dict['server'] + body_parts = [] + body_parts.extend([ + '<?xml version="1.0" encoding="UTF-8"?>', + '<server xmlns="http://docs.rackspacecloud.com/servers/api/v1.1"', + ' name="%s" imageRef="%s" flavorRef="%s">' % ( + server['name'], server['imageRef'], server['flavorRef'])]) + if 'metadata' in server: + metadata = server['metadata'] + body_parts.append('<metadata>') + for item in metadata.iteritems(): + body_parts.append('<meta key="%s">%s</meta>' % item) + body_parts.append('</metadata>') + if 'personality' in server: + personalities = server['personality'] + body_parts.append('<personality>') + for file in personalities: + item = (file['path'], file['contents']) + body_parts.append('<file path="%s">%s</file>' % item) + body_parts.append('</personality>') + if 'networks' in server: + networks = server['networks'] + body_parts.append('<networks>') + for network in networks: + item = (network['uuid'], network['fixed_ip']) + body_parts.append('<network uuid="%s" fixed_ip="%s"></network>' + % item) + body_parts.append('</networks>') + body_parts.append('</server>') + return ''.join(body_parts) + + def _get_create_request_xml(self, body_dict): + req = webob.Request.blank('/v1.1/123/os-create-server-ext') + req.content_type = 'application/xml' + req.accept = 'application/xml' + req.method = 'POST' + req.body = self._format_xml_request_body(body_dict) + return req + + def _create_instance_with_networks_json(self, networks): + body_dict = self._create_networks_request_dict(networks) + request = self._get_create_request_json(body_dict) + compute_api, response = \ + self._run_create_instance_with_mock_compute_api(request) + return request, response, compute_api.networks + + def _create_instance_with_networks_xml(self, networks): + body_dict = self._create_networks_request_dict(networks) + request = self._get_create_request_xml(body_dict) + compute_api, response = \ + self._run_create_instance_with_mock_compute_api(request) + return request, response, compute_api.networks + + def test_create_instance_with_no_networks(self): + request, response, networks = \ + self._create_instance_with_networks_json(networks=None) + self.assertEquals(response.status_int, 202) + self.assertEquals(networks, None) + + def test_create_instance_with_no_networks_xml(self): + request, response, networks = \ + self._create_instance_with_networks_xml(networks=None) + self.assertEquals(response.status_int, 202) + self.assertEquals(networks, None) + + def test_create_instance_with_one_network(self): + request, response, networks = \ + self._create_instance_with_networks_json([FAKE_NETWORKS[0]]) + self.assertEquals(response.status_int, 202) + self.assertEquals(networks, [FAKE_NETWORKS[0]]) + + def test_create_instance_with_one_network_xml(self): + request, response, networks = \ + self._create_instance_with_networks_xml([FAKE_NETWORKS[0]]) + self.assertEquals(response.status_int, 202) + self.assertEquals(networks, [FAKE_NETWORKS[0]]) + + def test_create_instance_with_two_networks(self): + request, response, networks = \ + self._create_instance_with_networks_json(FAKE_NETWORKS) + self.assertEquals(response.status_int, 202) + self.assertEquals(networks, FAKE_NETWORKS) + + def test_create_instance_with_two_networks_xml(self): + request, response, networks = \ + self._create_instance_with_networks_xml(FAKE_NETWORKS) + self.assertEquals(response.status_int, 202) + self.assertEquals(networks, FAKE_NETWORKS) + + def test_create_instance_with_duplicate_networks(self): + request, response, networks = \ + self._create_instance_with_networks_json(DUPLICATE_NETWORKS) + self.assertEquals(response.status_int, 400) + self.assertEquals(networks, None) + + def test_create_instance_with_duplicate_networks_xml(self): + request, response, networks = \ + self._create_instance_with_networks_xml(DUPLICATE_NETWORKS) + self.assertEquals(response.status_int, 400) + self.assertEquals(networks, None) + + def test_create_instance_with_network_no_id(self): + body_dict = self._create_networks_request_dict([FAKE_NETWORKS[0]]) + del body_dict['server']['networks'][0]['uuid'] + request = self._get_create_request_json(body_dict) + compute_api, response = \ + self._run_create_instance_with_mock_compute_api(request) + self.assertEquals(response.status_int, 400) + self.assertEquals(compute_api.networks, None) + + def test_create_instance_with_network_no_id_xml(self): + body_dict = self._create_networks_request_dict([FAKE_NETWORKS[0]]) + request = self._get_create_request_xml(body_dict) + uuid = ' uuid="aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"' + request.body = request.body.replace(uuid, '') + compute_api, response = \ + self._run_create_instance_with_mock_compute_api(request) + self.assertEquals(response.status_int, 400) + self.assertEquals(compute_api.networks, None) + + def test_create_instance_with_network_invalid_id(self): + request, response, networks = \ + self._create_instance_with_networks_json(INVALID_NETWORKS) + self.assertEquals(response.status_int, 400) + self.assertEquals(networks, None) + + def test_create_instance_with_network_invalid_id_xml(self): + request, response, networks = \ + self._create_instance_with_networks_xml(INVALID_NETWORKS) + self.assertEquals(response.status_int, 400) + self.assertEquals(networks, None) + + def test_create_instance_with_network_empty_fixed_ip(self): + networks = [('1', '')] + request, response, networks = \ + self._create_instance_with_networks_json(networks) + self.assertEquals(response.status_int, 400) + self.assertEquals(networks, None) + + def test_create_instance_with_network_non_string_fixed_ip(self): + networks = [('1', 12345)] + request, response, networks = \ + self._create_instance_with_networks_json(networks) + self.assertEquals(response.status_int, 400) + self.assertEquals(networks, None) + + def test_create_instance_with_network_empty_fixed_ip_xml(self): + networks = [('1', '')] + request, response, networks = \ + self._create_instance_with_networks_xml(networks) + self.assertEquals(response.status_int, 400) + self.assertEquals(networks, None) + + def test_create_instance_with_network_no_fixed_ip(self): + body_dict = self._create_networks_request_dict([FAKE_NETWORKS[0]]) + del body_dict['server']['networks'][0]['fixed_ip'] + request = self._get_create_request_json(body_dict) + compute_api, response = \ + self._run_create_instance_with_mock_compute_api(request) + self.assertEquals(response.status_int, 202) + self.assertEquals(compute_api.networks, + [('aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', None)]) + + def test_create_instance_with_network_no_fixed_ip_xml(self): + body_dict = self._create_networks_request_dict([FAKE_NETWORKS[0]]) + request = self._get_create_request_xml(body_dict) + request.body = request.body.replace(' fixed_ip="10.0.1.12"', '') + compute_api, response = \ + self._run_create_instance_with_mock_compute_api(request) + self.assertEquals(response.status_int, 202) + self.assertEquals(compute_api.networks, + [('aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', None)]) diff --git a/nova/tests/api/openstack/contrib/test_floating_ips.py b/nova/tests/api/openstack/contrib/test_floating_ips.py index 704d06582..568faf867 100644 --- a/nova/tests/api/openstack/contrib/test_floating_ips.py +++ b/nova/tests/api/openstack/contrib/test_floating_ips.py @@ -17,6 +17,7 @@ import json import stubout import webob +from nova import compute from nova import context from nova import db from nova import test @@ -30,6 +31,11 @@ from nova.api.openstack.contrib.floating_ips import _translate_floating_ip_view def network_api_get_floating_ip(self, context, id): return {'id': 1, 'address': '10.10.10.10', + 'fixed_ip': None} + + +def network_api_get_floating_ip_by_ip(self, context, address): + return {'id': 1, 'address': '10.10.10.10', 'fixed_ip': {'address': '11.0.0.1'}} @@ -50,7 +56,7 @@ def network_api_release(self, context, address): pass -def network_api_associate(self, context, floating_ip, fixed_ip): +def compute_api_associate(self, context, instance_id, floating_ip): pass @@ -78,14 +84,16 @@ class FloatingIpTest(test.TestCase): fakes.stub_out_rate_limiting(self.stubs) self.stubs.Set(network.api.API, "get_floating_ip", network_api_get_floating_ip) + self.stubs.Set(network.api.API, "get_floating_ip_by_ip", + network_api_get_floating_ip) self.stubs.Set(network.api.API, "list_floating_ips", network_api_list_floating_ips) self.stubs.Set(network.api.API, "allocate_floating_ip", network_api_allocate) self.stubs.Set(network.api.API, "release_floating_ip", network_api_release) - self.stubs.Set(network.api.API, "associate_floating_ip", - network_api_associate) + self.stubs.Set(compute.api.API, "associate_floating_ip", + compute_api_associate) self.stubs.Set(network.api.API, "disassociate_floating_ip", network_api_disassociate) self.context = context.get_admin_context() @@ -112,7 +120,7 @@ class FloatingIpTest(test.TestCase): self.assertTrue('floating_ip' in view) def test_floating_ips_list(self): - req = webob.Request.blank('/v1.1/os-floating-ips') + req = webob.Request.blank('/v1.1/123/os-floating-ips') res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 200) res_dict = json.loads(res.body) @@ -127,65 +135,91 @@ class FloatingIpTest(test.TestCase): self.assertEqual(res_dict, response) def test_floating_ip_show(self): - req = webob.Request.blank('/v1.1/os-floating-ips/1') + req = webob.Request.blank('/v1.1/123/os-floating-ips/1') res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 200) res_dict = json.loads(res.body) self.assertEqual(res_dict['floating_ip']['id'], 1) self.assertEqual(res_dict['floating_ip']['ip'], '10.10.10.10') - self.assertEqual(res_dict['floating_ip']['fixed_ip'], '11.0.0.1') self.assertEqual(res_dict['floating_ip']['instance_id'], None) def test_floating_ip_allocate(self): - req = webob.Request.blank('/v1.1/os-floating-ips') + req = webob.Request.blank('/v1.1/123/os-floating-ips') req.method = 'POST' req.headers['Content-Type'] = 'application/json' res = req.get_response(fakes.wsgi_app()) - print res self.assertEqual(res.status_int, 200) - ip = json.loads(res.body)['allocated'] + ip = json.loads(res.body)['floating_ip'] + expected = { "id": 1, - "floating_ip": '10.10.10.10'} + "instance_id": None, + "ip": "10.10.10.10", + "fixed_ip": None} self.assertEqual(ip, expected) def test_floating_ip_release(self): - req = webob.Request.blank('/v1.1/os-floating-ips/1') + req = webob.Request.blank('/v1.1/123/os-floating-ips/1') req.method = 'DELETE' res = req.get_response(fakes.wsgi_app()) - self.assertEqual(res.status_int, 200) - actual = json.loads(res.body)['released'] - expected = { - "id": 1, - "floating_ip": '10.10.10.10'} - self.assertEqual(actual, expected) + self.assertEqual(res.status_int, 202) - def test_floating_ip_associate(self): - body = dict(associate_address=dict(fixed_ip='1.2.3.4')) - req = webob.Request.blank('/v1.1/os-floating-ips/1/associate') - req.method = 'POST' + def test_add_floating_ip_to_instance(self): + body = dict(addFloatingIp=dict(address='11.0.0.1')) + req = webob.Request.blank('/v1.1/123/servers/test_inst/action') + req.method = "POST" req.body = json.dumps(body) req.headers["content-type"] = "application/json" - res = req.get_response(fakes.wsgi_app()) - self.assertEqual(res.status_int, 200) - actual = json.loads(res.body)['associated'] - expected = { - "floating_ip_id": '1', - "floating_ip": "10.10.10.10", - "fixed_ip": "1.2.3.4"} - self.assertEqual(actual, expected) - - def test_floating_ip_disassociate(self): - body = dict() - req = webob.Request.blank('/v1.1/os-floating-ips/1/disassociate') - req.method = 'POST' + resp = req.get_response(fakes.wsgi_app()) + self.assertEqual(resp.status_int, 202) + + def test_remove_floating_ip_from_instance(self): + body = dict(removeFloatingIp=dict(address='11.0.0.1')) + req = webob.Request.blank('/v1.1/123/servers/test_inst/action') + req.method = "POST" req.body = json.dumps(body) - req.headers['Content-Type'] = 'application/json' - res = req.get_response(fakes.wsgi_app()) - self.assertEqual(res.status_int, 200) - ip = json.loads(res.body)['disassociated'] - expected = { - "floating_ip": '10.10.10.10', - "fixed_ip": '11.0.0.1'} - self.assertEqual(ip, expected) + req.headers["content-type"] = "application/json" + + resp = req.get_response(fakes.wsgi_app()) + self.assertEqual(resp.status_int, 202) + + def test_bad_address_param_in_remove_floating_ip(self): + body = dict(removeFloatingIp=dict(badparam='11.0.0.1')) + req = webob.Request.blank('/v1.1/123/servers/test_inst/action') + req.method = "POST" + req.body = json.dumps(body) + req.headers["content-type"] = "application/json" + + resp = req.get_response(fakes.wsgi_app()) + self.assertEqual(resp.status_int, 400) + + def test_missing_dict_param_in_remove_floating_ip(self): + body = dict(removeFloatingIp='11.0.0.1') + req = webob.Request.blank('/v1.1/123/servers/test_inst/action') + req.method = "POST" + req.body = json.dumps(body) + req.headers["content-type"] = "application/json" + + resp = req.get_response(fakes.wsgi_app()) + self.assertEqual(resp.status_int, 400) + + def test_bad_address_param_in_add_floating_ip(self): + body = dict(addFloatingIp=dict(badparam='11.0.0.1')) + req = webob.Request.blank('/v1.1/123/servers/test_inst/action') + req.method = "POST" + req.body = json.dumps(body) + req.headers["content-type"] = "application/json" + + resp = req.get_response(fakes.wsgi_app()) + self.assertEqual(resp.status_int, 400) + + def test_missing_dict_param_in_add_floating_ip(self): + body = dict(addFloatingIp='11.0.0.1') + req = webob.Request.blank('/v1.1/123/servers/test_inst/action') + req.method = "POST" + req.body = json.dumps(body) + req.headers["content-type"] = "application/json" + + resp = req.get_response(fakes.wsgi_app()) + self.assertEqual(resp.status_int, 400) diff --git a/nova/tests/api/openstack/contrib/test_keypairs.py b/nova/tests/api/openstack/contrib/test_keypairs.py index eb3bc7af0..92e401aac 100644 --- a/nova/tests/api/openstack/contrib/test_keypairs.py +++ b/nova/tests/api/openstack/contrib/test_keypairs.py @@ -58,7 +58,7 @@ class KeypairsTest(test.TestCase): self.context = context.get_admin_context() def test_keypair_list(self): - req = webob.Request.blank('/v1.1/os-keypairs') + req = webob.Request.blank('/v1.1/123/os-keypairs') res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 200) res_dict = json.loads(res.body) @@ -67,7 +67,7 @@ class KeypairsTest(test.TestCase): def test_keypair_create(self): body = {'keypair': {'name': 'create_test'}} - req = webob.Request.blank('/v1.1/os-keypairs') + req = webob.Request.blank('/v1.1/123/os-keypairs') req.method = 'POST' req.body = json.dumps(body) req.headers['Content-Type'] = 'application/json' @@ -93,7 +93,7 @@ class KeypairsTest(test.TestCase): }, } - req = webob.Request.blank('/v1.1/os-keypairs') + req = webob.Request.blank('/v1.1/123/os-keypairs') req.method = 'POST' req.body = json.dumps(body) req.headers['Content-Type'] = 'application/json' @@ -105,7 +105,7 @@ class KeypairsTest(test.TestCase): self.assertFalse('private_key' in res_dict['keypair']) def test_keypair_delete(self): - req = webob.Request.blank('/v1.1/os-keypairs/FAKE') + req = webob.Request.blank('/v1.1/123/os-keypairs/FAKE') req.method = 'DELETE' req.headers['Content-Type'] = 'application/json' res = req.get_response(fakes.wsgi_app()) diff --git a/nova/tests/api/openstack/contrib/test_multinic_xs.py b/nova/tests/api/openstack/contrib/test_multinic_xs.py index ac28f6be6..cecc4af4f 100644 --- a/nova/tests/api/openstack/contrib/test_multinic_xs.py +++ b/nova/tests/api/openstack/contrib/test_multinic_xs.py @@ -55,7 +55,7 @@ class FixedIpTest(test.TestCase): last_add_fixed_ip = (None, None) body = dict(addFixedIp=dict(networkId='test_net')) - req = webob.Request.blank('/v1.1/servers/test_inst/action') + req = webob.Request.blank('/v1.1/123/servers/test_inst/action') req.method = 'POST' req.body = json.dumps(body) req.headers['content-type'] = 'application/json' @@ -69,7 +69,7 @@ class FixedIpTest(test.TestCase): last_add_fixed_ip = (None, None) body = dict(addFixedIp=dict()) - req = webob.Request.blank('/v1.1/servers/test_inst/action') + req = webob.Request.blank('/v1.1/123/servers/test_inst/action') req.method = 'POST' req.body = json.dumps(body) req.headers['content-type'] = 'application/json' @@ -83,7 +83,7 @@ class FixedIpTest(test.TestCase): last_remove_fixed_ip = (None, None) body = dict(removeFixedIp=dict(address='10.10.10.1')) - req = webob.Request.blank('/v1.1/servers/test_inst/action') + req = webob.Request.blank('/v1.1/123/servers/test_inst/action') req.method = 'POST' req.body = json.dumps(body) req.headers['content-type'] = 'application/json' @@ -97,7 +97,7 @@ class FixedIpTest(test.TestCase): last_remove_fixed_ip = (None, None) body = dict(removeFixedIp=dict()) - req = webob.Request.blank('/v1.1/servers/test_inst/action') + req = webob.Request.blank('/v1.1/123/servers/test_inst/action') req.method = 'POST' req.body = json.dumps(body) req.headers['content-type'] = 'application/json' diff --git a/nova/tests/api/openstack/contrib/test_quotas.py b/nova/tests/api/openstack/contrib/test_quotas.py index f6a25385f..7faef08b2 100644 --- a/nova/tests/api/openstack/contrib/test_quotas.py +++ b/nova/tests/api/openstack/contrib/test_quotas.py @@ -78,7 +78,8 @@ class QuotaSetsTest(test.TestCase): self.assertEqual(qs['injected_file_content_bytes'], 10240) def test_quotas_defaults(self): - req = webob.Request.blank('/v1.1/os-quota-sets/fake_tenant/defaults') + uri = '/v1.1/fake_tenant/os-quota-sets/fake_tenant/defaults' + req = webob.Request.blank(uri) req.method = 'GET' req.headers['Content-Type'] = 'application/json' res = req.get_response(fakes.wsgi_app()) @@ -99,7 +100,7 @@ class QuotaSetsTest(test.TestCase): self.assertEqual(json.loads(res.body), expected) def test_quotas_show_as_admin(self): - req = webob.Request.blank('/v1.1/os-quota-sets/1234') + req = webob.Request.blank('/v1.1/1234/os-quota-sets/1234') req.method = 'GET' req.headers['Content-Type'] = 'application/json' res = req.get_response(fakes.wsgi_app( @@ -109,7 +110,7 @@ class QuotaSetsTest(test.TestCase): self.assertEqual(json.loads(res.body), quota_set('1234')) def test_quotas_show_as_unauthorized_user(self): - req = webob.Request.blank('/v1.1/os-quota-sets/1234') + req = webob.Request.blank('/v1.1/fake/os-quota-sets/1234') req.method = 'GET' req.headers['Content-Type'] = 'application/json' res = req.get_response(fakes.wsgi_app( @@ -124,7 +125,7 @@ class QuotaSetsTest(test.TestCase): 'metadata_items': 128, 'injected_files': 5, 'injected_file_content_bytes': 10240}} - req = webob.Request.blank('/v1.1/os-quota-sets/update_me') + req = webob.Request.blank('/v1.1/1234/os-quota-sets/update_me') req.method = 'PUT' req.body = json.dumps(updated_quota_set) req.headers['Content-Type'] = 'application/json' @@ -141,7 +142,7 @@ class QuotaSetsTest(test.TestCase): 'metadata_items': 128, 'injected_files': 5, 'injected_file_content_bytes': 10240}} - req = webob.Request.blank('/v1.1/os-quota-sets/update_me') + req = webob.Request.blank('/v1.1/1234/os-quota-sets/update_me') req.method = 'PUT' req.body = json.dumps(updated_quota_set) req.headers['Content-Type'] = 'application/json' diff --git a/nova/tests/api/openstack/contrib/test_rescue.py b/nova/tests/api/openstack/contrib/test_rescue.py index fc8e4be4e..f8126d461 100644 --- a/nova/tests/api/openstack/contrib/test_rescue.py +++ b/nova/tests/api/openstack/contrib/test_rescue.py @@ -36,7 +36,7 @@ class RescueTest(test.TestCase): def test_rescue(self): body = dict(rescue=None) - req = webob.Request.blank('/v1.1/servers/test_inst/action') + req = webob.Request.blank('/v1.1/123/servers/test_inst/action') req.method = "POST" req.body = json.dumps(body) req.headers["content-type"] = "application/json" @@ -46,7 +46,7 @@ class RescueTest(test.TestCase): def test_unrescue(self): body = dict(unrescue=None) - req = webob.Request.blank('/v1.1/servers/test_inst/action') + req = webob.Request.blank('/v1.1/123/servers/test_inst/action') req.method = "POST" req.body = json.dumps(body) req.headers["content-type"] = "application/json" diff --git a/nova/tests/api/openstack/contrib/test_security_groups.py b/nova/tests/api/openstack/contrib/test_security_groups.py index 4317880ca..bc1536911 100644 --- a/nova/tests/api/openstack/contrib/test_security_groups.py +++ b/nova/tests/api/openstack/contrib/test_security_groups.py @@ -15,17 +15,20 @@ # under the License. import json +import mox +import nova import unittest import webob from xml.dom import minidom +from nova import exception from nova import test from nova.api.openstack.contrib import security_groups from nova.tests.api.openstack import fakes def _get_create_request_json(body_dict): - req = webob.Request.blank('/v1.1/os-security-groups') + req = webob.Request.blank('/v1.1/123/os-security-groups') req.headers['Content-Type'] = 'application/json' req.method = 'POST' req.body = json.dumps(body_dict) @@ -51,6 +54,28 @@ def _create_security_group_request_dict(security_group): return {'security_group': sg} +def return_server(context, server_id): + return {'id': server_id, 'state': 0x01, 'host': "localhost"} + + +def return_non_running_server(context, server_id): + return {'id': server_id, 'state': 0x02, + 'host': "localhost"} + + +def return_security_group(context, project_id, group_name): + return {'id': 1, 'name': group_name, "instances": [ + {'id': 1}]} + + +def return_security_group_without_instances(context, project_id, group_name): + return {'id': 1, 'name': group_name} + + +def return_server_nonexistant(context, server_id): + raise exception.InstanceNotFound(instance_id=server_id) + + class TestSecurityGroups(test.TestCase): def setUp(self): super(TestSecurityGroups, self).setUp() @@ -84,7 +109,7 @@ class TestSecurityGroups(test.TestCase): return ''.join(body_parts) def _get_create_request_xml(self, body_dict): - req = webob.Request.blank('/v1.1/os-security-groups') + req = webob.Request.blank('/v1.1/123/os-security-groups') req.headers['Content-Type'] = 'application/xml' req.content_type = 'application/xml' req.accept = 'application/xml' @@ -99,7 +124,7 @@ class TestSecurityGroups(test.TestCase): return response def _delete_security_group(self, id): - request = webob.Request.blank('/v1.1/os-security-groups/%s' + request = webob.Request.blank('/v1.1/123/os-security-groups/%s' % id) request.method = 'DELETE' response = request.get_response(fakes.wsgi_app()) @@ -238,7 +263,7 @@ class TestSecurityGroups(test.TestCase): security_group['description'] = "group-description" response = _create_security_group_json(security_group) - req = webob.Request.blank('/v1.1/os-security-groups') + req = webob.Request.blank('/v1.1/123/os-security-groups') req.headers['Content-Type'] = 'application/json' req.method = 'GET' response = req.get_response(fakes.wsgi_app()) @@ -247,7 +272,7 @@ class TestSecurityGroups(test.TestCase): expected = {'security_groups': [ {'id': 1, 'name':"default", - 'tenant_id': "fake", + 'tenant_id': "123", "description":"default", "rules": [] }, @@ -257,7 +282,7 @@ class TestSecurityGroups(test.TestCase): { 'id': 2, 'name': "test", - 'tenant_id': "fake", + 'tenant_id': "123", "description": "group-description", "rules": [] } @@ -272,7 +297,7 @@ class TestSecurityGroups(test.TestCase): response = _create_security_group_json(security_group) res_dict = json.loads(response.body) - req = webob.Request.blank('/v1.1/os-security-groups/%s' % + req = webob.Request.blank('/v1.1/123/os-security-groups/%s' % res_dict['security_group']['id']) req.headers['Content-Type'] = 'application/json' req.method = 'GET' @@ -283,23 +308,22 @@ class TestSecurityGroups(test.TestCase): 'security_group': { 'id': 2, 'name': "test", - 'tenant_id': "fake", + 'tenant_id': "123", 'description': "group-description", 'rules': [] } } - self.assertEquals(response.status_int, 200) self.assertEquals(res_dict, expected) def test_get_security_group_by_invalid_id(self): - req = webob.Request.blank('/v1.1/os-security-groups/invalid') + req = webob.Request.blank('/v1.1/123/os-security-groups/invalid') req.headers['Content-Type'] = 'application/json' req.method = 'GET' response = req.get_response(fakes.wsgi_app()) self.assertEquals(response.status_int, 400) def test_get_security_group_by_non_existing_id(self): - req = webob.Request.blank('/v1.1/os-security-groups/111111111') + req = webob.Request.blank('/v1.1/123/os-security-groups/111111111') req.headers['Content-Type'] = 'application/json' req.method = 'GET' response = req.get_response(fakes.wsgi_app()) @@ -325,6 +349,252 @@ class TestSecurityGroups(test.TestCase): response = self._delete_security_group(11111111) self.assertEquals(response.status_int, 404) + def test_associate_by_non_existing_security_group_name(self): + body = dict(addSecurityGroup=dict(name='non-existing')) + req = webob.Request.blank('/v1.1/123/servers/1/action') + req.headers['Content-Type'] = 'application/json' + req.method = 'POST' + req.body = json.dumps(body) + response = req.get_response(fakes.wsgi_app()) + self.assertEquals(response.status_int, 404) + + def test_associate_by_invalid_server_id(self): + body = dict(addSecurityGroup=dict(name='test')) + self.stubs.Set(nova.db, 'security_group_get_by_name', + return_security_group) + req = webob.Request.blank('/v1.1/123/servers/invalid/action') + req.headers['Content-Type'] = 'application/json' + req.method = 'POST' + req.body = json.dumps(body) + response = req.get_response(fakes.wsgi_app()) + self.assertEquals(response.status_int, 400) + + def test_associate_without_body(self): + req = webob.Request.blank('/v1.1/123/servers/1/action') + body = dict(addSecurityGroup=None) + self.stubs.Set(nova.db, 'instance_get', return_server) + req.headers['Content-Type'] = 'application/json' + req.method = 'POST' + req.body = json.dumps(body) + response = req.get_response(fakes.wsgi_app()) + self.assertEquals(response.status_int, 400) + + def test_associate_no_security_group_name(self): + req = webob.Request.blank('/v1.1/123/servers/1/action') + body = dict(addSecurityGroup=dict()) + self.stubs.Set(nova.db, 'instance_get', return_server) + req.headers['Content-Type'] = 'application/json' + req.method = 'POST' + req.body = json.dumps(body) + response = req.get_response(fakes.wsgi_app()) + self.assertEquals(response.status_int, 400) + + def test_associate_security_group_name_with_whitespaces(self): + req = webob.Request.blank('/v1.1/123/servers/1/action') + body = dict(addSecurityGroup=dict(name=" ")) + self.stubs.Set(nova.db, 'instance_get', return_server) + req.headers['Content-Type'] = 'application/json' + req.method = 'POST' + req.body = json.dumps(body) + response = req.get_response(fakes.wsgi_app()) + self.assertEquals(response.status_int, 400) + + def test_associate_non_existing_instance(self): + self.stubs.Set(nova.db, 'instance_get', return_server_nonexistant) + body = dict(addSecurityGroup=dict(name="test")) + self.stubs.Set(nova.db, 'security_group_get_by_name', + return_security_group) + req = webob.Request.blank('/v1.1/123/servers/10000/action') + req.headers['Content-Type'] = 'application/json' + req.method = 'POST' + req.body = json.dumps(body) + response = req.get_response(fakes.wsgi_app()) + self.assertEquals(response.status_int, 404) + + def test_associate_non_running_instance(self): + self.stubs.Set(nova.db, 'instance_get', return_non_running_server) + self.stubs.Set(nova.db, 'security_group_get_by_name', + return_security_group_without_instances) + body = dict(addSecurityGroup=dict(name="test")) + req = webob.Request.blank('/v1.1/123/servers/1/action') + req.headers['Content-Type'] = 'application/json' + req.method = 'POST' + req.body = json.dumps(body) + response = req.get_response(fakes.wsgi_app()) + self.assertEquals(response.status_int, 400) + + def test_associate_already_associated_security_group_to_instance(self): + self.stubs.Set(nova.db, 'instance_get', return_server) + self.stubs.Set(nova.db, 'security_group_get_by_name', + return_security_group) + body = dict(addSecurityGroup=dict(name="test")) + req = webob.Request.blank('/v1.1/123/servers/1/action') + req.headers['Content-Type'] = 'application/json' + req.method = 'POST' + req.body = json.dumps(body) + response = req.get_response(fakes.wsgi_app()) + self.assertEquals(response.status_int, 400) + + def test_associate(self): + self.stubs.Set(nova.db, 'instance_get', return_server) + self.mox.StubOutWithMock(nova.db, 'instance_add_security_group') + nova.db.instance_add_security_group(mox.IgnoreArg(), + mox.IgnoreArg(), + mox.IgnoreArg()) + self.stubs.Set(nova.db, 'security_group_get_by_name', + return_security_group_without_instances) + self.mox.ReplayAll() + + body = dict(addSecurityGroup=dict(name="test")) + req = webob.Request.blank('/v1.1/123/servers/1/action') + req.headers['Content-Type'] = 'application/json' + req.method = 'POST' + req.body = json.dumps(body) + response = req.get_response(fakes.wsgi_app()) + self.assertEquals(response.status_int, 202) + + def test_associate_xml(self): + self.stubs.Set(nova.db, 'instance_get', return_server) + self.mox.StubOutWithMock(nova.db, 'instance_add_security_group') + nova.db.instance_add_security_group(mox.IgnoreArg(), + mox.IgnoreArg(), + mox.IgnoreArg()) + self.stubs.Set(nova.db, 'security_group_get_by_name', + return_security_group_without_instances) + self.mox.ReplayAll() + + req = webob.Request.blank('/v1.1/123/servers/1/action') + req.headers['Content-Type'] = 'application/xml' + req.method = 'POST' + req.body = """<addSecurityGroup> + <name>test</name> + </addSecurityGroup>""" + response = req.get_response(fakes.wsgi_app()) + self.assertEquals(response.status_int, 202) + + def test_disassociate_by_non_existing_security_group_name(self): + body = dict(removeSecurityGroup=dict(name='non-existing')) + req = webob.Request.blank('/v1.1/123/servers/1/action') + req.headers['Content-Type'] = 'application/json' + req.method = 'POST' + req.body = json.dumps(body) + response = req.get_response(fakes.wsgi_app()) + self.assertEquals(response.status_int, 404) + + def test_disassociate_by_invalid_server_id(self): + body = dict(removeSecurityGroup=dict(name='test')) + self.stubs.Set(nova.db, 'security_group_get_by_name', + return_security_group) + req = webob.Request.blank('/v1.1/123/servers/invalid/action') + req.headers['Content-Type'] = 'application/json' + req.method = 'POST' + req.body = json.dumps(body) + response = req.get_response(fakes.wsgi_app()) + self.assertEquals(response.status_int, 400) + + def test_disassociate_without_body(self): + req = webob.Request.blank('/v1.1/123/servers/1/action') + body = dict(removeSecurityGroup=None) + self.stubs.Set(nova.db, 'instance_get', return_server) + req.headers['Content-Type'] = 'application/json' + req.method = 'POST' + req.body = json.dumps(body) + response = req.get_response(fakes.wsgi_app()) + self.assertEquals(response.status_int, 400) + + def test_disassociate_no_security_group_name(self): + req = webob.Request.blank('/v1.1/123/servers/1/action') + body = dict(removeSecurityGroup=dict()) + self.stubs.Set(nova.db, 'instance_get', return_server) + req.headers['Content-Type'] = 'application/json' + req.method = 'POST' + req.body = json.dumps(body) + response = req.get_response(fakes.wsgi_app()) + self.assertEquals(response.status_int, 400) + + def test_disassociate_security_group_name_with_whitespaces(self): + req = webob.Request.blank('/v1.1/123/servers/1/action') + body = dict(removeSecurityGroup=dict(name=" ")) + self.stubs.Set(nova.db, 'instance_get', return_server) + req.headers['Content-Type'] = 'application/json' + req.method = 'POST' + req.body = json.dumps(body) + response = req.get_response(fakes.wsgi_app()) + self.assertEquals(response.status_int, 400) + + def test_disassociate_non_existing_instance(self): + self.stubs.Set(nova.db, 'instance_get', return_server_nonexistant) + body = dict(removeSecurityGroup=dict(name="test")) + self.stubs.Set(nova.db, 'security_group_get_by_name', + return_security_group) + req = webob.Request.blank('/v1.1/123/servers/10000/action') + req.headers['Content-Type'] = 'application/json' + req.method = 'POST' + req.body = json.dumps(body) + response = req.get_response(fakes.wsgi_app()) + self.assertEquals(response.status_int, 404) + + def test_disassociate_non_running_instance(self): + self.stubs.Set(nova.db, 'instance_get', return_non_running_server) + self.stubs.Set(nova.db, 'security_group_get_by_name', + return_security_group) + body = dict(removeSecurityGroup=dict(name="test")) + req = webob.Request.blank('/v1.1/123/servers/1/action') + req.headers['Content-Type'] = 'application/json' + req.method = 'POST' + req.body = json.dumps(body) + response = req.get_response(fakes.wsgi_app()) + self.assertEquals(response.status_int, 400) + + def test_disassociate_already_associated_security_group_to_instance(self): + self.stubs.Set(nova.db, 'instance_get', return_server) + self.stubs.Set(nova.db, 'security_group_get_by_name', + return_security_group_without_instances) + body = dict(removeSecurityGroup=dict(name="test")) + req = webob.Request.blank('/v1.1/123/servers/1/action') + req.headers['Content-Type'] = 'application/json' + req.method = 'POST' + req.body = json.dumps(body) + response = req.get_response(fakes.wsgi_app()) + self.assertEquals(response.status_int, 400) + + def test_disassociate(self): + self.stubs.Set(nova.db, 'instance_get', return_server) + self.mox.StubOutWithMock(nova.db, 'instance_remove_security_group') + nova.db.instance_remove_security_group(mox.IgnoreArg(), + mox.IgnoreArg(), + mox.IgnoreArg()) + self.stubs.Set(nova.db, 'security_group_get_by_name', + return_security_group) + self.mox.ReplayAll() + + body = dict(removeSecurityGroup=dict(name="test")) + req = webob.Request.blank('/v1.1/123/servers/1/action') + req.headers['Content-Type'] = 'application/json' + req.method = 'POST' + req.body = json.dumps(body) + response = req.get_response(fakes.wsgi_app()) + self.assertEquals(response.status_int, 202) + + def test_disassociate_xml(self): + self.stubs.Set(nova.db, 'instance_get', return_server) + self.mox.StubOutWithMock(nova.db, 'instance_remove_security_group') + nova.db.instance_remove_security_group(mox.IgnoreArg(), + mox.IgnoreArg(), + mox.IgnoreArg()) + self.stubs.Set(nova.db, 'security_group_get_by_name', + return_security_group) + self.mox.ReplayAll() + + req = webob.Request.blank('/v1.1/123/servers/1/action') + req.headers['Content-Type'] = 'application/xml' + req.method = 'POST' + req.body = """<removeSecurityGroup> + <name>test</name> + </removeSecurityGroup>""" + response = req.get_response(fakes.wsgi_app()) + self.assertEquals(response.status_int, 202) + class TestSecurityGroupRules(test.TestCase): def setUp(self): @@ -354,7 +624,7 @@ class TestSecurityGroupRules(test.TestCase): super(TestSecurityGroupRules, self).tearDown() def _create_security_group_rule_json(self, rules): - request = webob.Request.blank('/v1.1/os-security-group-rules') + request = webob.Request.blank('/v1.1/123/os-security-group-rules') request.headers['Content-Type'] = 'application/json' request.method = 'POST' request.body = json.dumps(rules) @@ -362,7 +632,7 @@ class TestSecurityGroupRules(test.TestCase): return response def _delete_security_group_rule(self, id): - request = webob.Request.blank('/v1.1/os-security-group-rules/%s' + request = webob.Request.blank('/v1.1/123/os-security-group-rules/%s' % id) request.method = 'DELETE' response = request.get_response(fakes.wsgi_app()) @@ -420,7 +690,7 @@ class TestSecurityGroupRules(test.TestCase): self.assertEquals(response.status_int, 400) def test_create_with_no_body_json(self): - request = webob.Request.blank('/v1.1/os-security-group-rules') + request = webob.Request.blank('/v1.1/123/os-security-group-rules') request.headers['Content-Type'] = 'application/json' request.method = 'POST' request.body = json.dumps(None) @@ -428,7 +698,7 @@ class TestSecurityGroupRules(test.TestCase): self.assertEquals(response.status_int, 422) def test_create_with_no_security_group_rule_in_body_json(self): - request = webob.Request.blank('/v1.1/os-security-group-rules') + request = webob.Request.blank('/v1.1/123/os-security-group-rules') request.headers['Content-Type'] = 'application/json' request.method = 'POST' body_dict = {'test': "test"} diff --git a/nova/tests/api/openstack/contrib/test_virtual_interfaces.py b/nova/tests/api/openstack/contrib/test_virtual_interfaces.py new file mode 100644 index 000000000..1db253b35 --- /dev/null +++ b/nova/tests/api/openstack/contrib/test_virtual_interfaces.py @@ -0,0 +1,55 @@ +# Copyright (C) 2011 Midokura KK +# All Rights Reserved. +# +# 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. + +import json +import stubout +import webob + +from nova import test +from nova import compute +from nova.tests.api.openstack import fakes +from nova.api.openstack.contrib.virtual_interfaces import \ + ServerVirtualInterfaceController + + +def compute_api_get(self, context, server_id): + return {'virtual_interfaces': [ + {'uuid': '00000000-0000-0000-0000-00000000000000000', + 'address': '00-00-00-00-00-00'}, + {'uuid': '11111111-1111-1111-1111-11111111111111111', + 'address': '11-11-11-11-11-11'}]} + + +class ServerVirtualInterfaceTest(test.TestCase): + + def setUp(self): + super(ServerVirtualInterfaceTest, self).setUp() + self.controller = ServerVirtualInterfaceController() + self.stubs.Set(compute.api.API, "get", compute_api_get) + + def tearDown(self): + super(ServerVirtualInterfaceTest, self).tearDown() + + def test_get_virtual_interfaces_list(self): + req = webob.Request.blank('/v1.1/123/servers/1/os-virtual-interfaces') + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 200) + res_dict = json.loads(res.body) + response = {'virtual_interfaces': [ + {'id': '00000000-0000-0000-0000-00000000000000000', + 'mac_address': '00-00-00-00-00-00'}, + {'id': '11111111-1111-1111-1111-11111111111111111', + 'mac_address': '11-11-11-11-11-11'}]} + self.assertEqual(res_dict, response) diff --git a/nova/tests/api/openstack/extensions/foxinsocks.py b/nova/tests/api/openstack/extensions/foxinsocks.py index 03aad007a..2d8313cf6 100644 --- a/nova/tests/api/openstack/extensions/foxinsocks.py +++ b/nova/tests/api/openstack/extensions/foxinsocks.py @@ -72,8 +72,9 @@ class Foxinsocks(object): res.body = json.dumps(data) return res - req_ext1 = extensions.RequestExtension('GET', '/v1.1/flavors/:(id)', - _goose_handler) + req_ext1 = extensions.RequestExtension('GET', + '/v1.1/:(project_id)/flavors/:(id)', + _goose_handler) request_exts.append(req_ext1) def _bands_handler(req, res): @@ -84,8 +85,9 @@ class Foxinsocks(object): res.body = json.dumps(data) return res - req_ext2 = extensions.RequestExtension('GET', '/v1.1/flavors/:(id)', - _bands_handler) + req_ext2 = extensions.RequestExtension('GET', + '/v1.1/:(project_id)/flavors/:(id)', + _bands_handler) request_exts.append(req_ext2) return request_exts diff --git a/nova/tests/api/openstack/fakes.py b/nova/tests/api/openstack/fakes.py index d11fbf788..a095dd90a 100644 --- a/nova/tests/api/openstack/fakes.py +++ b/nova/tests/api/openstack/fakes.py @@ -32,6 +32,7 @@ from nova import utils from nova import wsgi import nova.api.openstack.auth from nova.api import openstack +from nova.api import auth as api_auth from nova.api.openstack import auth from nova.api.openstack import extensions from nova.api.openstack import versions @@ -83,9 +84,9 @@ def wsgi_app(inner_app10=None, inner_app11=None, fake_auth=True, ctxt = fake_auth_context else: ctxt = context.RequestContext('fake', 'fake') - api10 = openstack.FaultWrapper(wsgi.InjectContext(ctxt, + api10 = openstack.FaultWrapper(api_auth.InjectContext(ctxt, limits.RateLimitingMiddleware(inner_app10))) - api11 = openstack.FaultWrapper(wsgi.InjectContext(ctxt, + api11 = openstack.FaultWrapper(api_auth.InjectContext(ctxt, limits.RateLimitingMiddleware( extensions.ExtensionMiddleware(inner_app11)))) else: diff --git a/nova/tests/api/openstack/test_auth.py b/nova/tests/api/openstack/test_auth.py index 306ae1aa0..7fd3935a2 100644 --- a/nova/tests/api/openstack/test_auth.py +++ b/nova/tests/api/openstack/test_auth.py @@ -53,6 +53,7 @@ class Test(test.TestCase): req = webob.Request.blank('/v1.0/') req.headers['X-Auth-User'] = 'user1' req.headers['X-Auth-Key'] = 'user1_key' + req.headers['X-Auth-Project-Id'] = 'user1_project' result = req.get_response(fakes.wsgi_app(fake_auth=False)) self.assertEqual(result.status, '204 No Content') self.assertEqual(len(result.headers['X-Auth-Token']), 40) @@ -73,14 +74,14 @@ class Test(test.TestCase): self.assertEqual(result.status, '204 No Content') self.assertEqual(len(result.headers['X-Auth-Token']), 40) self.assertEqual(result.headers['X-Server-Management-Url'], - "http://foo/v1.0/") + "http://foo/v1.0") self.assertEqual(result.headers['X-CDN-Management-Url'], "") self.assertEqual(result.headers['X-Storage-Url'], "") token = result.headers['X-Auth-Token'] self.stubs.Set(nova.api.openstack, 'APIRouterV10', fakes.FakeRouter) - req = webob.Request.blank('/v1.0/fake') + req = webob.Request.blank('/v1.0/user1_project') req.headers['X-Auth-Token'] = token result = req.get_response(fakes.wsgi_app(fake_auth=False)) self.assertEqual(result.status, '200 OK') @@ -125,7 +126,7 @@ class Test(test.TestCase): token = result.headers['X-Auth-Token'] self.stubs.Set(nova.api.openstack, 'APIRouterV10', fakes.FakeRouter) - req = webob.Request.blank('/v1.0/fake') + req = webob.Request.blank('/v1.0/') req.headers['X-Auth-Token'] = token req.headers['X-Auth-Project-Id'] = 'user2_project' result = req.get_response(fakes.wsgi_app(fake_auth=False)) @@ -136,6 +137,7 @@ class Test(test.TestCase): req = webob.Request.blank('/v1.0/') req.headers['X-Auth-User'] = 'unknown_user' req.headers['X-Auth-Key'] = 'unknown_user_key' + req.headers['X-Auth-Project-Id'] = 'user_project' result = req.get_response(fakes.wsgi_app(fake_auth=False)) self.assertEqual(result.status, '401 Unauthorized') diff --git a/nova/tests/api/openstack/test_extensions.py b/nova/tests/api/openstack/test_extensions.py index 34a4b3f89..9f923852d 100644 --- a/nova/tests/api/openstack/test_extensions.py +++ b/nova/tests/api/openstack/test_extensions.py @@ -85,6 +85,7 @@ class ExtensionControllerTest(test.TestCase): ext_path = os.path.join(os.path.dirname(__file__), "extensions") self.flags(osapi_extensions_path=ext_path) self.ext_list = [ + "Createserverext", "FlavorExtraSpecs", "Floating_ips", "Fox In Socks", @@ -92,16 +93,17 @@ class ExtensionControllerTest(test.TestCase): "Keypairs", "Multinic", "Quotas", + "Rescue", "SecurityGroups", + "VirtualInterfaces", "Volumes", - "Rescue", ] self.ext_list.sort() def test_list_extensions_json(self): app = openstack.APIRouterV11() ext_midware = extensions.ExtensionMiddleware(app) - request = webob.Request.blank("/extensions") + request = webob.Request.blank("/123/extensions") response = request.get_response(ext_midware) self.assertEqual(200, response.status_int) @@ -127,7 +129,7 @@ class ExtensionControllerTest(test.TestCase): def test_get_extension_json(self): app = openstack.APIRouterV11() ext_midware = extensions.ExtensionMiddleware(app) - request = webob.Request.blank("/extensions/FOXNSOX") + request = webob.Request.blank("/123/extensions/FOXNSOX") response = request.get_response(ext_midware) self.assertEqual(200, response.status_int) @@ -143,7 +145,7 @@ class ExtensionControllerTest(test.TestCase): def test_list_extensions_xml(self): app = openstack.APIRouterV11() ext_midware = extensions.ExtensionMiddleware(app) - request = webob.Request.blank("/extensions") + request = webob.Request.blank("/123/extensions") request.accept = "application/xml" response = request.get_response(ext_midware) self.assertEqual(200, response.status_int) @@ -170,7 +172,7 @@ class ExtensionControllerTest(test.TestCase): def test_get_extension_xml(self): app = openstack.APIRouterV11() ext_midware = extensions.ExtensionMiddleware(app) - request = webob.Request.blank("/extensions/FOXNSOX") + request = webob.Request.blank("/123/extensions/FOXNSOX") request.accept = "application/xml" response = request.get_response(ext_midware) self.assertEqual(200, response.status_int) @@ -211,7 +213,7 @@ class ResourceExtensionTest(test.TestCase): manager = StubExtensionManager(res_ext) app = openstack.APIRouterV11() ext_midware = extensions.ExtensionMiddleware(app, manager) - request = webob.Request.blank("/tweedles") + request = webob.Request.blank("/123/tweedles") response = request.get_response(ext_midware) self.assertEqual(200, response.status_int) self.assertEqual(response_body, response.body) @@ -222,7 +224,7 @@ class ResourceExtensionTest(test.TestCase): manager = StubExtensionManager(res_ext) app = openstack.APIRouterV11() ext_midware = extensions.ExtensionMiddleware(app, manager) - request = webob.Request.blank("/tweedles") + request = webob.Request.blank("/123/tweedles") response = request.get_response(ext_midware) self.assertEqual(200, response.status_int) self.assertEqual(response_body, response.body) @@ -246,7 +248,7 @@ class ExtensionManagerTest(test.TestCase): def test_get_resources(self): app = openstack.APIRouterV11() ext_midware = extensions.ExtensionMiddleware(app) - request = webob.Request.blank("/foxnsocks") + request = webob.Request.blank("/123/foxnsocks") response = request.get_response(ext_midware) self.assertEqual(200, response.status_int) self.assertEqual(response_body, response.body) @@ -279,23 +281,26 @@ class ActionExtensionTest(test.TestCase): def test_extended_action(self): body = dict(add_tweedle=dict(name="test")) - response = self._send_server_action_request("/servers/1/action", body) + url = "/123/servers/1/action" + response = self._send_server_action_request(url, body) self.assertEqual(200, response.status_int) self.assertEqual("Tweedle Beetle Added.", response.body) body = dict(delete_tweedle=dict(name="test")) - response = self._send_server_action_request("/servers/1/action", body) + response = self._send_server_action_request(url, body) self.assertEqual(200, response.status_int) self.assertEqual("Tweedle Beetle Deleted.", response.body) def test_invalid_action_body(self): body = dict(blah=dict(name="test")) # Doesn't exist - response = self._send_server_action_request("/servers/1/action", body) + url = "/123/servers/1/action" + response = self._send_server_action_request(url, body) self.assertEqual(400, response.status_int) def test_invalid_action(self): body = dict(blah=dict(name="test")) - response = self._send_server_action_request("/fdsa/1/action", body) + url = "/123/fdsa/1/action" + response = self._send_server_action_request(url, body) self.assertEqual(404, response.status_int) @@ -316,13 +321,13 @@ class RequestExtensionTest(test.TestCase): return res req_ext = extensions.RequestExtension('GET', - '/v1.1/flavors/:(id)', + '/v1.1/123/flavors/:(id)', _req_handler) manager = StubExtensionManager(None, None, req_ext) app = fakes.wsgi_app() ext_midware = extensions.ExtensionMiddleware(app, manager) - request = webob.Request.blank("/v1.1/flavors/1?chewing=bluegoo") + request = webob.Request.blank("/v1.1/123/flavors/1?chewing=bluegoo") request.environ['api.version'] = '1.1' response = request.get_response(ext_midware) self.assertEqual(200, response.status_int) @@ -333,7 +338,7 @@ class RequestExtensionTest(test.TestCase): app = fakes.wsgi_app() ext_midware = extensions.ExtensionMiddleware(app) - request = webob.Request.blank("/v1.1/flavors/1?chewing=newblue") + request = webob.Request.blank("/v1.1/123/flavors/1?chewing=newblue") request.environ['api.version'] = '1.1' response = request.get_response(ext_midware) self.assertEqual(200, response.status_int) diff --git a/nova/tests/api/openstack/test_flavors.py b/nova/tests/api/openstack/test_flavors.py index d0fe72001..812bece42 100644 --- a/nova/tests/api/openstack/test_flavors.py +++ b/nova/tests/api/openstack/test_flavors.py @@ -138,7 +138,7 @@ class FlavorsTest(test.TestCase): self.assertEqual(res.status_int, 404) def test_get_flavor_by_id_v1_1(self): - req = webob.Request.blank('/v1.1/flavors/12') + req = webob.Request.blank('/v1.1/fake/flavors/12') req.environ['api.version'] = '1.1' res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 200) @@ -152,11 +152,11 @@ class FlavorsTest(test.TestCase): "links": [ { "rel": "self", - "href": "http://localhost/v1.1/flavors/12", + "href": "http://localhost/v1.1/fake/flavors/12", }, { "rel": "bookmark", - "href": "http://localhost/flavors/12", + "href": "http://localhost/fake/flavors/12", }, ], }, @@ -164,7 +164,7 @@ class FlavorsTest(test.TestCase): self.assertEqual(flavor, expected) def test_get_flavor_list_v1_1(self): - req = webob.Request.blank('/v1.1/flavors') + req = webob.Request.blank('/v1.1/fake/flavors') req.environ['api.version'] = '1.1' res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 200) @@ -177,11 +177,11 @@ class FlavorsTest(test.TestCase): "links": [ { "rel": "self", - "href": "http://localhost/v1.1/flavors/1", + "href": "http://localhost/v1.1/fake/flavors/1", }, { "rel": "bookmark", - "href": "http://localhost/flavors/1", + "href": "http://localhost/fake/flavors/1", }, ], }, @@ -191,11 +191,11 @@ class FlavorsTest(test.TestCase): "links": [ { "rel": "self", - "href": "http://localhost/v1.1/flavors/2", + "href": "http://localhost/v1.1/fake/flavors/2", }, { "rel": "bookmark", - "href": "http://localhost/flavors/2", + "href": "http://localhost/fake/flavors/2", }, ], }, @@ -204,7 +204,7 @@ class FlavorsTest(test.TestCase): self.assertEqual(flavor, expected) def test_get_flavor_list_detail_v1_1(self): - req = webob.Request.blank('/v1.1/flavors/detail') + req = webob.Request.blank('/v1.1/fake/flavors/detail') req.environ['api.version'] = '1.1' res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 200) @@ -219,11 +219,11 @@ class FlavorsTest(test.TestCase): "links": [ { "rel": "self", - "href": "http://localhost/v1.1/flavors/1", + "href": "http://localhost/v1.1/fake/flavors/1", }, { "rel": "bookmark", - "href": "http://localhost/flavors/1", + "href": "http://localhost/fake/flavors/1", }, ], }, @@ -235,11 +235,11 @@ class FlavorsTest(test.TestCase): "links": [ { "rel": "self", - "href": "http://localhost/v1.1/flavors/2", + "href": "http://localhost/v1.1/fake/flavors/2", }, { "rel": "bookmark", - "href": "http://localhost/flavors/2", + "href": "http://localhost/fake/flavors/2", }, ], }, @@ -252,7 +252,7 @@ class FlavorsTest(test.TestCase): return {} self.stubs.Set(nova.db.api, "instance_type_get_all", _return_empty) - req = webob.Request.blank('/v1.1/flavors') + req = webob.Request.blank('/v1.1/fake/flavors') res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 200) flavors = json.loads(res.body)["flavors"] @@ -274,11 +274,11 @@ class FlavorsXMLSerializationTest(test.TestCase): "links": [ { "rel": "self", - "href": "http://localhost/v1.1/flavors/12", + "href": "http://localhost/v1.1/fake/flavors/12", }, { "rel": "bookmark", - "href": "http://localhost/flavors/12", + "href": "http://localhost/fake/flavors/12", }, ], }, @@ -294,8 +294,10 @@ class FlavorsXMLSerializationTest(test.TestCase): name="asdf" ram="256" disk="10"> - <atom:link href="http://localhost/v1.1/flavors/12" rel="self"/> - <atom:link href="http://localhost/flavors/12" rel="bookmark"/> + <atom:link href="http://localhost/v1.1/fake/flavors/12" + rel="self"/> + <atom:link href="http://localhost/fake/flavors/12" + rel="bookmark"/> </flavor> """.replace(" ", "")) @@ -313,11 +315,11 @@ class FlavorsXMLSerializationTest(test.TestCase): "links": [ { "rel": "self", - "href": "http://localhost/v1.1/flavors/12", + "href": "http://localhost/v1.1/fake/flavors/12", }, { "rel": "bookmark", - "href": "http://localhost/flavors/12", + "href": "http://localhost/fake/flavors/12", }, ], }, @@ -333,8 +335,10 @@ class FlavorsXMLSerializationTest(test.TestCase): name="asdf" ram="256" disk="10"> - <atom:link href="http://localhost/v1.1/flavors/12" rel="self"/> - <atom:link href="http://localhost/flavors/12" rel="bookmark"/> + <atom:link href="http://localhost/v1.1/fake/flavors/12" + rel="self"/> + <atom:link href="http://localhost/fake/flavors/12" + rel="bookmark"/> </flavor> """.replace(" ", "")) @@ -353,11 +357,11 @@ class FlavorsXMLSerializationTest(test.TestCase): "links": [ { "rel": "self", - "href": "http://localhost/v1.1/flavors/23", + "href": "http://localhost/v1.1/fake/flavors/23", }, { "rel": "bookmark", - "href": "http://localhost/flavors/23", + "href": "http://localhost/fake/flavors/23", }, ], }, { @@ -368,11 +372,11 @@ class FlavorsXMLSerializationTest(test.TestCase): "links": [ { "rel": "self", - "href": "http://localhost/v1.1/flavors/13", + "href": "http://localhost/v1.1/fake/flavors/13", }, { "rel": "bookmark", - "href": "http://localhost/flavors/13", + "href": "http://localhost/fake/flavors/13", }, ], }, @@ -389,15 +393,19 @@ class FlavorsXMLSerializationTest(test.TestCase): name="flavor 23" ram="512" disk="20"> - <atom:link href="http://localhost/v1.1/flavors/23" rel="self"/> - <atom:link href="http://localhost/flavors/23" rel="bookmark"/> + <atom:link href="http://localhost/v1.1/fake/flavors/23" + rel="self"/> + <atom:link href="http://localhost/fake/flavors/23" + rel="bookmark"/> </flavor> <flavor id="13" name="flavor 13" ram="256" disk="10"> - <atom:link href="http://localhost/v1.1/flavors/13" rel="self"/> - <atom:link href="http://localhost/flavors/13" rel="bookmark"/> + <atom:link href="http://localhost/v1.1/fake/flavors/13" + rel="self"/> + <atom:link href="http://localhost/fake/flavors/13" + rel="bookmark"/> </flavor> </flavors> """.replace(" ", "") % locals()) @@ -417,11 +425,11 @@ class FlavorsXMLSerializationTest(test.TestCase): "links": [ { "rel": "self", - "href": "http://localhost/v1.1/flavors/23", + "href": "http://localhost/v1.1/fake/flavors/23", }, { "rel": "bookmark", - "href": "http://localhost/flavors/23", + "href": "http://localhost/fake/flavors/23", }, ], }, { @@ -432,11 +440,11 @@ class FlavorsXMLSerializationTest(test.TestCase): "links": [ { "rel": "self", - "href": "http://localhost/v1.1/flavors/13", + "href": "http://localhost/v1.1/fake/flavors/13", }, { "rel": "bookmark", - "href": "http://localhost/flavors/13", + "href": "http://localhost/fake/flavors/13", }, ], }, @@ -450,12 +458,16 @@ class FlavorsXMLSerializationTest(test.TestCase): <flavors xmlns="http://docs.openstack.org/compute/api/v1.1" xmlns:atom="http://www.w3.org/2005/Atom"> <flavor id="23" name="flavor 23"> - <atom:link href="http://localhost/v1.1/flavors/23" rel="self"/> - <atom:link href="http://localhost/flavors/23" rel="bookmark"/> + <atom:link href="http://localhost/v1.1/fake/flavors/23" + rel="self"/> + <atom:link href="http://localhost/fake/flavors/23" + rel="bookmark"/> </flavor> <flavor id="13" name="flavor 13"> - <atom:link href="http://localhost/v1.1/flavors/13" rel="self"/> - <atom:link href="http://localhost/flavors/13" rel="bookmark"/> + <atom:link href="http://localhost/v1.1/fake/flavors/13" + rel="self"/> + <atom:link href="http://localhost/fake/flavors/13" + rel="bookmark"/> </flavor> </flavors> """.replace(" ", "") % locals()) diff --git a/nova/tests/api/openstack/test_flavors_extra_specs.py b/nova/tests/api/openstack/test_flavors_extra_specs.py index ccd1b0d9f..f382d06e9 100644 --- a/nova/tests/api/openstack/test_flavors_extra_specs.py +++ b/nova/tests/api/openstack/test_flavors_extra_specs.py @@ -63,7 +63,7 @@ class FlavorsExtraSpecsTest(test.TestCase): def test_index(self): self.stubs.Set(nova.db.api, 'instance_type_extra_specs_get', return_flavor_extra_specs) - request = webob.Request.blank('/v1.1/flavors/1/os-extra_specs') + request = webob.Request.blank('/v1.1/123/flavors/1/os-extra_specs') res = request.get_response(fakes.wsgi_app()) self.assertEqual(200, res.status_int) res_dict = json.loads(res.body) @@ -73,7 +73,7 @@ class FlavorsExtraSpecsTest(test.TestCase): def test_index_no_data(self): self.stubs.Set(nova.db.api, 'instance_type_extra_specs_get', return_empty_flavor_extra_specs) - req = webob.Request.blank('/v1.1/flavors/1/os-extra_specs') + req = webob.Request.blank('/v1.1/123/flavors/1/os-extra_specs') res = req.get_response(fakes.wsgi_app()) res_dict = json.loads(res.body) self.assertEqual(200, res.status_int) @@ -83,7 +83,7 @@ class FlavorsExtraSpecsTest(test.TestCase): def test_show(self): self.stubs.Set(nova.db.api, 'instance_type_extra_specs_get', return_flavor_extra_specs) - req = webob.Request.blank('/v1.1/flavors/1/os-extra_specs/key5') + req = webob.Request.blank('/v1.1/123/flavors/1/os-extra_specs/key5') res = req.get_response(fakes.wsgi_app()) self.assertEqual(200, res.status_int) res_dict = json.loads(res.body) @@ -93,7 +93,7 @@ class FlavorsExtraSpecsTest(test.TestCase): def test_show_spec_not_found(self): self.stubs.Set(nova.db.api, 'instance_type_extra_specs_get', return_empty_flavor_extra_specs) - req = webob.Request.blank('/v1.1/flavors/1/os-extra_specs/key6') + req = webob.Request.blank('/v1.1/123/flavors/1/os-extra_specs/key6') res = req.get_response(fakes.wsgi_app()) res_dict = json.loads(res.body) self.assertEqual(404, res.status_int) @@ -101,7 +101,7 @@ class FlavorsExtraSpecsTest(test.TestCase): def test_delete(self): self.stubs.Set(nova.db.api, 'instance_type_extra_specs_delete', delete_flavor_extra_specs) - req = webob.Request.blank('/v1.1/flavors/1/os-extra_specs/key5') + req = webob.Request.blank('/v1.1/123/flavors/1/os-extra_specs/key5') req.method = 'DELETE' res = req.get_response(fakes.wsgi_app()) self.assertEqual(200, res.status_int) @@ -110,7 +110,7 @@ class FlavorsExtraSpecsTest(test.TestCase): self.stubs.Set(nova.db.api, 'instance_type_extra_specs_update_or_create', return_create_flavor_extra_specs) - req = webob.Request.blank('/v1.1/flavors/1/os-extra_specs') + req = webob.Request.blank('/v1.1/123/flavors/1/os-extra_specs') req.method = 'POST' req.body = '{"extra_specs": {"key1": "value1"}}' req.headers["content-type"] = "application/json" @@ -124,7 +124,7 @@ class FlavorsExtraSpecsTest(test.TestCase): self.stubs.Set(nova.db.api, 'instance_type_extra_specs_update_or_create', return_create_flavor_extra_specs) - req = webob.Request.blank('/v1.1/flavors/1/os-extra_specs') + req = webob.Request.blank('/v1.1/123/flavors/1/os-extra_specs') req.method = 'POST' req.headers["content-type"] = "application/json" res = req.get_response(fakes.wsgi_app()) @@ -134,7 +134,7 @@ class FlavorsExtraSpecsTest(test.TestCase): self.stubs.Set(nova.db.api, 'instance_type_extra_specs_update_or_create', return_create_flavor_extra_specs) - req = webob.Request.blank('/v1.1/flavors/1/os-extra_specs/key1') + req = webob.Request.blank('/v1.1/123/flavors/1/os-extra_specs/key1') req.method = 'PUT' req.body = '{"key1": "value1"}' req.headers["content-type"] = "application/json" @@ -148,7 +148,7 @@ class FlavorsExtraSpecsTest(test.TestCase): self.stubs.Set(nova.db.api, 'instance_type_extra_specs_update_or_create', return_create_flavor_extra_specs) - req = webob.Request.blank('/v1.1/flavors/1/os-extra_specs/key1') + req = webob.Request.blank('/v1.1/123/flavors/1/os-extra_specs/key1') req.method = 'PUT' req.headers["content-type"] = "application/json" res = req.get_response(fakes.wsgi_app()) @@ -158,7 +158,7 @@ class FlavorsExtraSpecsTest(test.TestCase): self.stubs.Set(nova.db.api, 'instance_type_extra_specs_update_or_create', return_create_flavor_extra_specs) - req = webob.Request.blank('/v1.1/flavors/1/os-extra_specs/key1') + req = webob.Request.blank('/v1.1/123/flavors/1/os-extra_specs/key1') req.method = 'PUT' req.body = '{"key1": "value1", "key2": "value2"}' req.headers["content-type"] = "application/json" @@ -169,7 +169,7 @@ class FlavorsExtraSpecsTest(test.TestCase): self.stubs.Set(nova.db.api, 'instance_type_extra_specs_update_or_create', return_create_flavor_extra_specs) - req = webob.Request.blank('/v1.1/flavors/1/os-extra_specs/bad') + req = webob.Request.blank('/v1.1/123/flavors/1/os-extra_specs/bad') req.method = 'PUT' req.body = '{"key1": "value1"}' req.headers["content-type"] = "application/json" diff --git a/nova/tests/api/openstack/test_image_metadata.py b/nova/tests/api/openstack/test_image_metadata.py index 21743eeef..fe42e35e5 100644 --- a/nova/tests/api/openstack/test_image_metadata.py +++ b/nova/tests/api/openstack/test_image_metadata.py @@ -90,7 +90,7 @@ class ImageMetaDataTest(test.TestCase): fakes.stub_out_glance(self.stubs, self.IMAGE_FIXTURES) def test_index(self): - req = webob.Request.blank('/v1.1/images/1/metadata') + req = webob.Request.blank('/v1.1/123/images/1/metadata') res = req.get_response(fakes.wsgi_app()) res_dict = json.loads(res.body) self.assertEqual(200, res.status_int) @@ -100,7 +100,7 @@ class ImageMetaDataTest(test.TestCase): self.assertEqual(value, res_dict['metadata'][key]) def test_show(self): - req = webob.Request.blank('/v1.1/images/1/metadata/key1') + req = webob.Request.blank('/v1.1/fake/images/1/metadata/key1') res = req.get_response(fakes.wsgi_app()) res_dict = json.loads(res.body) self.assertEqual(200, res.status_int) @@ -109,12 +109,12 @@ class ImageMetaDataTest(test.TestCase): self.assertEqual('value1', res_dict['meta']['key1']) def test_show_not_found(self): - req = webob.Request.blank('/v1.1/images/1/metadata/key9') + req = webob.Request.blank('/v1.1/fake/images/1/metadata/key9') res = req.get_response(fakes.wsgi_app()) self.assertEqual(404, res.status_int) def test_create(self): - req = webob.Request.blank('/v1.1/images/2/metadata') + req = webob.Request.blank('/v1.1/fake/images/2/metadata') req.method = 'POST' req.body = '{"metadata": {"key9": "value9"}}' req.headers["content-type"] = "application/json" @@ -134,7 +134,7 @@ class ImageMetaDataTest(test.TestCase): self.assertEqual(expected_output, actual_output) def test_update_all(self): - req = webob.Request.blank('/v1.1/images/2/metadata') + req = webob.Request.blank('/v1.1/fake/images/1/metadata') req.method = 'PUT' req.body = '{"metadata": {"key9": "value9"}}' req.headers["content-type"] = "application/json" @@ -152,7 +152,7 @@ class ImageMetaDataTest(test.TestCase): self.assertEqual(expected_output, actual_output) def test_update_item(self): - req = webob.Request.blank('/v1.1/images/1/metadata/key1') + req = webob.Request.blank('/v1.1/fake/images/1/metadata/key1') req.method = 'PUT' req.body = '{"meta": {"key1": "zz"}}' req.headers["content-type"] = "application/json" @@ -168,7 +168,7 @@ class ImageMetaDataTest(test.TestCase): self.assertEqual(actual_output, expected_output) def test_update_item_bad_body(self): - req = webob.Request.blank('/v1.1/images/1/metadata/key1') + req = webob.Request.blank('/v1.1/fake/images/1/metadata/key1') req.method = 'PUT' req.body = '{"key1": "zz"}' req.headers["content-type"] = "application/json" @@ -176,7 +176,7 @@ class ImageMetaDataTest(test.TestCase): self.assertEqual(400, res.status_int) def test_update_item_too_many_keys(self): - req = webob.Request.blank('/v1.1/images/1/metadata/key1') + req = webob.Request.blank('/v1.1/fake/images/1/metadata/key1') req.method = 'PUT' req.body = '{"meta": {"key1": "value1", "key2": "value2"}}' req.headers["content-type"] = "application/json" @@ -184,7 +184,7 @@ class ImageMetaDataTest(test.TestCase): self.assertEqual(400, res.status_int) def test_update_item_body_uri_mismatch(self): - req = webob.Request.blank('/v1.1/images/1/metadata/bad') + req = webob.Request.blank('/v1.1/fake/images/1/metadata/bad') req.method = 'PUT' req.body = '{"meta": {"key1": "value1"}}' req.headers["content-type"] = "application/json" @@ -192,7 +192,7 @@ class ImageMetaDataTest(test.TestCase): self.assertEqual(400, res.status_int) def test_update_item_xml(self): - req = webob.Request.blank('/v1.1/images/1/metadata/key1') + req = webob.Request.blank('/v1.1/fake/images/1/metadata/key1') req.method = 'PUT' req.body = '<meta key="key1">five</meta>' req.headers["content-type"] = "application/xml" @@ -208,14 +208,14 @@ class ImageMetaDataTest(test.TestCase): self.assertEqual(actual_output, expected_output) def test_delete(self): - req = webob.Request.blank('/v1.1/images/2/metadata/key1') + req = webob.Request.blank('/v1.1/fake/images/2/metadata/key1') req.method = 'DELETE' res = req.get_response(fakes.wsgi_app()) self.assertEqual(204, res.status_int) self.assertEqual('', res.body) def test_delete_not_found(self): - req = webob.Request.blank('/v1.1/images/2/metadata/blah') + req = webob.Request.blank('/v1.1/fake/images/2/metadata/blah') req.method = 'DELETE' res = req.get_response(fakes.wsgi_app()) self.assertEqual(404, res.status_int) @@ -225,7 +225,7 @@ class ImageMetaDataTest(test.TestCase): for num in range(FLAGS.quota_metadata_items + 1): data['metadata']['key%i' % num] = "blah" json_string = str(data).replace("\'", "\"") - req = webob.Request.blank('/v1.1/images/2/metadata') + req = webob.Request.blank('/v1.1/fake/images/2/metadata') req.method = 'POST' req.body = json_string req.headers["content-type"] = "application/json" @@ -233,7 +233,7 @@ class ImageMetaDataTest(test.TestCase): self.assertEqual(413, res.status_int) def test_too_many_metadata_items_on_put(self): - req = webob.Request.blank('/v1.1/images/3/metadata/blah') + req = webob.Request.blank('/v1.1/fake/images/3/metadata/blah') req.method = 'PUT' req.body = '{"meta": {"blah": "blah"}}' req.headers["content-type"] = "application/json" diff --git a/nova/tests/api/openstack/test_images.py b/nova/tests/api/openstack/test_images.py index 383ed2e03..2a7cfc382 100644 --- a/nova/tests/api/openstack/test_images.py +++ b/nova/tests/api/openstack/test_images.py @@ -339,6 +339,11 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): self.stubs.UnsetAll() super(ImageControllerWithGlanceServiceTest, self).tearDown() + def _get_fake_context(self): + class Context(object): + project_id = 'fake' + return Context() + def _applicable_fixture(self, fixture, user_id): """Determine if this fixture is applicable for given user id.""" is_public = fixture["is_public"] @@ -386,13 +391,13 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): self.assertEqual(expected_image, actual_image) def test_get_image_v1_1(self): - request = webob.Request.blank('/v1.1/images/124') + request = webob.Request.blank('/v1.1/fake/images/124') response = request.get_response(fakes.wsgi_app()) actual_image = json.loads(response.body) - href = "http://localhost/v1.1/images/124" - bookmark = "http://localhost/images/124" + href = "http://localhost/v1.1/fake/images/124" + bookmark = "http://localhost/fake/images/124" server_href = "http://localhost/v1.1/servers/42" server_bookmark = "http://localhost/servers/42" @@ -508,7 +513,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): self.assertEqual(expected.toxml(), actual.toxml()) def test_get_image_404_v1_1_json(self): - request = webob.Request.blank('/v1.1/images/NonExistantImage') + request = webob.Request.blank('/v1.1/fake/images/NonExistantImage') response = request.get_response(fakes.wsgi_app()) self.assertEqual(404, response.status_int) @@ -524,7 +529,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): self.assertEqual(expected, actual) def test_get_image_404_v1_1_xml(self): - request = webob.Request.blank('/v1.1/images/NonExistantImage') + request = webob.Request.blank('/v1.1/fake/images/NonExistantImage') request.accept = "application/xml" response = request.get_response(fakes.wsgi_app()) self.assertEqual(404, response.status_int) @@ -545,7 +550,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): self.assertEqual(expected.toxml(), actual.toxml()) def test_get_image_index_v1_1(self): - request = webob.Request.blank('/v1.1/images') + request = webob.Request.blank('/v1.1/fake/images') response = request.get_response(fakes.wsgi_app()) response_dict = json.loads(response.body) @@ -558,8 +563,8 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): fixtures.remove(image) continue - href = "http://localhost/v1.1/images/%s" % image["id"] - bookmark = "http://localhost/images/%s" % image["id"] + href = "http://localhost/v1.1/fake/images/%s" % image["id"] + bookmark = "http://localhost/fake/images/%s" % image["id"] test_image = { "id": image["id"], "name": image["name"], @@ -637,7 +642,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): self.assertDictListMatch(expected, response_list) def test_get_image_details_v1_1(self): - request = webob.Request.blank('/v1.1/images/detail') + request = webob.Request.blank('/v1.1/fake/images/detail') response = request.get_response(fakes.wsgi_app()) response_dict = json.loads(response.body) @@ -655,11 +660,11 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): 'progress': 100, "links": [{ "rel": "self", - "href": "http://localhost/v1.1/images/123", + "href": "http://localhost/v1.1/fake/images/123", }, { "rel": "bookmark", - "href": "http://localhost/images/123", + "href": "http://localhost/fake/images/123", }], }, { @@ -686,11 +691,11 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): }, "links": [{ "rel": "self", - "href": "http://localhost/v1.1/images/124", + "href": "http://localhost/v1.1/fake/images/124", }, { "rel": "bookmark", - "href": "http://localhost/images/124", + "href": "http://localhost/fake/images/124", }], }, { @@ -717,11 +722,11 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): }, "links": [{ "rel": "self", - "href": "http://localhost/v1.1/images/125", + "href": "http://localhost/v1.1/fake/images/125", }, { "rel": "bookmark", - "href": "http://localhost/images/125", + "href": "http://localhost/fake/images/125", }], }, { @@ -748,11 +753,11 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): }, "links": [{ "rel": "self", - "href": "http://localhost/v1.1/images/126", + "href": "http://localhost/v1.1/fake/images/126", }, { "rel": "bookmark", - "href": "http://localhost/images/126", + "href": "http://localhost/fake/images/126", }], }, { @@ -779,11 +784,11 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): }, "links": [{ "rel": "self", - "href": "http://localhost/v1.1/images/127", + "href": "http://localhost/v1.1/fake/images/127", }, { "rel": "bookmark", - "href": "http://localhost/images/127", + "href": "http://localhost/fake/images/127", }], }, { @@ -796,11 +801,11 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): 'progress': 100, "links": [{ "rel": "self", - "href": "http://localhost/v1.1/images/129", + "href": "http://localhost/v1.1/fake/images/129", }, { "rel": "bookmark", - "href": "http://localhost/images/129", + "href": "http://localhost/fake/images/129", }], }, ] @@ -809,7 +814,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): def test_image_filter_with_name(self): image_service = self.mox.CreateMockAnything() - context = object() + context = self._get_fake_context() filters = {'name': 'testname'} image_service.index(context, filters=filters).AndReturn([]) self.mox.ReplayAll() @@ -821,7 +826,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): def test_image_filter_with_status(self): image_service = self.mox.CreateMockAnything() - context = object() + context = self._get_fake_context() filters = {'status': 'ACTIVE'} image_service.index(context, filters=filters).AndReturn([]) self.mox.ReplayAll() @@ -833,7 +838,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): def test_image_filter_with_property(self): image_service = self.mox.CreateMockAnything() - context = object() + context = self._get_fake_context() filters = {'property-test': '3'} image_service.index(context, filters=filters).AndReturn([]) self.mox.ReplayAll() @@ -845,7 +850,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): def test_image_filter_server(self): image_service = self.mox.CreateMockAnything() - context = object() + context = self._get_fake_context() # 'server' should be converted to 'property-instance_ref' filters = {'property-instance_ref': 'http://localhost:8774/servers/12'} image_service.index(context, filters=filters).AndReturn([]) @@ -859,7 +864,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): def test_image_filter_changes_since(self): image_service = self.mox.CreateMockAnything() - context = object() + context = self._get_fake_context() filters = {'changes-since': '2011-01-24T17:08Z'} image_service.index(context, filters=filters).AndReturn([]) self.mox.ReplayAll() @@ -872,7 +877,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): def test_image_filter_with_type(self): image_service = self.mox.CreateMockAnything() - context = object() + context = self._get_fake_context() filters = {'property-image_type': 'BASE'} image_service.index(context, filters=filters).AndReturn([]) self.mox.ReplayAll() @@ -884,7 +889,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): def test_image_filter_not_supported(self): image_service = self.mox.CreateMockAnything() - context = object() + context = self._get_fake_context() filters = {'status': 'ACTIVE'} image_service.detail(context, filters=filters).AndReturn([]) self.mox.ReplayAll() @@ -897,7 +902,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): def test_image_no_filters(self): image_service = self.mox.CreateMockAnything() - context = object() + context = self._get_fake_context() filters = {} image_service.index( context, filters=filters).AndReturn([]) @@ -911,11 +916,11 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): def test_image_detail_filter_with_name(self): image_service = self.mox.CreateMockAnything() - context = object() + context = self._get_fake_context() filters = {'name': 'testname'} image_service.detail(context, filters=filters).AndReturn([]) self.mox.ReplayAll() - request = webob.Request.blank('/v1.1/images/detail?name=testname') + request = webob.Request.blank('/v1.1/fake/images/detail?name=testname') request.environ['nova.context'] = context controller = images.ControllerV11(image_service=image_service) controller.detail(request) @@ -923,11 +928,11 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): def test_image_detail_filter_with_status(self): image_service = self.mox.CreateMockAnything() - context = object() + context = self._get_fake_context() filters = {'status': 'ACTIVE'} image_service.detail(context, filters=filters).AndReturn([]) self.mox.ReplayAll() - request = webob.Request.blank('/v1.1/images/detail?status=ACTIVE') + request = webob.Request.blank('/v1.1/fake/images/detail?status=ACTIVE') request.environ['nova.context'] = context controller = images.ControllerV11(image_service=image_service) controller.detail(request) @@ -935,11 +940,12 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): def test_image_detail_filter_with_property(self): image_service = self.mox.CreateMockAnything() - context = object() + context = self._get_fake_context() filters = {'property-test': '3'} image_service.detail(context, filters=filters).AndReturn([]) self.mox.ReplayAll() - request = webob.Request.blank('/v1.1/images/detail?property-test=3') + request = webob.Request.blank( + '/v1.1/fake/images/detail?property-test=3') request.environ['nova.context'] = context controller = images.ControllerV11(image_service=image_service) controller.detail(request) @@ -947,12 +953,12 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): def test_image_detail_filter_server(self): image_service = self.mox.CreateMockAnything() - context = object() + context = self._get_fake_context() # 'server' should be converted to 'property-instance_ref' filters = {'property-instance_ref': 'http://localhost:8774/servers/12'} image_service.index(context, filters=filters).AndReturn([]) self.mox.ReplayAll() - request = webob.Request.blank('/v1.1/images/detail?server=' + request = webob.Request.blank('/v1.1/fake/images/detail?server=' 'http://localhost:8774/servers/12') request.environ['nova.context'] = context controller = images.ControllerV11(image_service=image_service) @@ -961,11 +967,11 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): def test_image_detail_filter_changes_since(self): image_service = self.mox.CreateMockAnything() - context = object() + context = self._get_fake_context() filters = {'changes-since': '2011-01-24T17:08Z'} image_service.index(context, filters=filters).AndReturn([]) self.mox.ReplayAll() - request = webob.Request.blank('/v1.1/images/detail?changes-since=' + request = webob.Request.blank('/v1.1/fake/images/detail?changes-since=' '2011-01-24T17:08Z') request.environ['nova.context'] = context controller = images.ControllerV11(image_service=image_service) @@ -974,11 +980,11 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): def test_image_detail_filter_with_type(self): image_service = self.mox.CreateMockAnything() - context = object() + context = self._get_fake_context() filters = {'property-image_type': 'BASE'} image_service.index(context, filters=filters).AndReturn([]) self.mox.ReplayAll() - request = webob.Request.blank('/v1.1/images/detail?type=BASE') + request = webob.Request.blank('/v1.1/fake/images/detail?type=BASE') request.environ['nova.context'] = context controller = images.ControllerV11(image_service=image_service) controller.index(request) @@ -986,11 +992,11 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): def test_image_detail_filter_not_supported(self): image_service = self.mox.CreateMockAnything() - context = object() + context = self._get_fake_context() filters = {'status': 'ACTIVE'} image_service.detail(context, filters=filters).AndReturn([]) self.mox.ReplayAll() - request = webob.Request.blank('/v1.1/images/detail?status=ACTIVE&' + request = webob.Request.blank('/v1.1/fake/images/detail?status=ACTIVE&' 'UNSUPPORTEDFILTER=testname') request.environ['nova.context'] = context controller = images.ControllerV11(image_service=image_service) @@ -999,11 +1005,11 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): def test_image_detail_no_filters(self): image_service = self.mox.CreateMockAnything() - context = object() + context = self._get_fake_context() filters = {} image_service.detail(context, filters=filters).AndReturn([]) self.mox.ReplayAll() - request = webob.Request.blank('/v1.1/images/detail') + request = webob.Request.blank('/v1.1/fake/images/detail') request.environ['nova.context'] = context controller = images.ControllerV11(image_service=image_service) controller.detail(request) @@ -1123,8 +1129,8 @@ class ImageXMLSerializationTest(test.TestCase): TIMESTAMP = "2010-10-11T10:30:22Z" SERVER_HREF = 'http://localhost/v1.1/servers/123' SERVER_BOOKMARK = 'http://localhost/servers/123' - IMAGE_HREF = 'http://localhost/v1.1/images/%s' - IMAGE_BOOKMARK = 'http://localhost/images/%s' + IMAGE_HREF = 'http://localhost/v1.1/fake/images/%s' + IMAGE_BOOKMARK = 'http://localhost/fake/images/%s' def test_show(self): serializer = images.ImageXMLSerializer() diff --git a/nova/tests/api/openstack/test_server_actions.py b/nova/tests/api/openstack/test_server_actions.py index 80a27e30f..90fe2f0b3 100644 --- a/nova/tests/api/openstack/test_server_actions.py +++ b/nova/tests/api/openstack/test_server_actions.py @@ -489,7 +489,7 @@ class ServerActionsTestV11(test.TestCase): def test_server_bad_body(self): body = {} - req = webob.Request.blank('/v1.1/servers/1/action') + req = webob.Request.blank('/v1.1/fake/servers/1/action') req.method = 'POST' req.content_type = 'application/json' req.body = json.dumps(body) @@ -498,7 +498,7 @@ class ServerActionsTestV11(test.TestCase): def test_server_unknown_action(self): body = {'sockTheFox': {'fakekey': '1234'}} - req = webob.Request.blank('/v1.1/servers/1/action') + req = webob.Request.blank('/v1.1/fake/servers/1/action') req.method = 'POST' req.content_type = 'application/json' req.body = json.dumps(body) @@ -509,7 +509,7 @@ class ServerActionsTestV11(test.TestCase): mock_method = MockSetAdminPassword() self.stubs.Set(nova.compute.api.API, 'set_admin_password', mock_method) body = {'changePassword': {'adminPass': '1234pass'}} - req = webob.Request.blank('/v1.1/servers/1/action') + req = webob.Request.blank('/v1.1/fake/servers/1/action') req.method = 'POST' req.content_type = 'application/json' req.body = json.dumps(body) @@ -521,7 +521,7 @@ class ServerActionsTestV11(test.TestCase): def test_server_change_password_xml(self): mock_method = MockSetAdminPassword() self.stubs.Set(nova.compute.api.API, 'set_admin_password', mock_method) - req = webob.Request.blank('/v1.1/servers/1/action') + req = webob.Request.blank('/v1.1/fake/servers/1/action') req.method = 'POST' req.content_type = "application/xml" req.body = """<?xml version="1.0" encoding="UTF-8"?> @@ -535,7 +535,7 @@ class ServerActionsTestV11(test.TestCase): def test_server_change_password_not_a_string(self): body = {'changePassword': {'adminPass': 1234}} - req = webob.Request.blank('/v1.1/servers/1/action') + req = webob.Request.blank('/v1.1/fake/servers/1/action') req.method = 'POST' req.content_type = 'application/json' req.body = json.dumps(body) @@ -544,7 +544,7 @@ class ServerActionsTestV11(test.TestCase): def test_server_change_password_bad_request(self): body = {'changePassword': {'pass': '12345'}} - req = webob.Request.blank('/v1.1/servers/1/action') + req = webob.Request.blank('/v1.1/fake/servers/1/action') req.method = 'POST' req.content_type = 'application/json' req.body = json.dumps(body) @@ -553,7 +553,7 @@ class ServerActionsTestV11(test.TestCase): def test_server_change_password_empty_string(self): body = {'changePassword': {'adminPass': ''}} - req = webob.Request.blank('/v1.1/servers/1/action') + req = webob.Request.blank('/v1.1/fake/servers/1/action') req.method = 'POST' req.content_type = 'application/json' req.body = json.dumps(body) @@ -562,7 +562,7 @@ class ServerActionsTestV11(test.TestCase): def test_server_change_password_none(self): body = {'changePassword': {'adminPass': None}} - req = webob.Request.blank('/v1.1/servers/1/action') + req = webob.Request.blank('/v1.1/fake/servers/1/action') req.method = 'POST' req.content_type = 'application/json' req.body = json.dumps(body) @@ -571,7 +571,7 @@ class ServerActionsTestV11(test.TestCase): def test_server_reboot_hard(self): body = dict(reboot=dict(type="HARD")) - req = webob.Request.blank('/v1.1/servers/1/action') + req = webob.Request.blank('/v1.1/fake/servers/1/action') req.method = 'POST' req.content_type = 'application/json' req.body = json.dumps(body) @@ -580,7 +580,7 @@ class ServerActionsTestV11(test.TestCase): def test_server_reboot_soft(self): body = dict(reboot=dict(type="SOFT")) - req = webob.Request.blank('/v1.1/servers/1/action') + req = webob.Request.blank('/v1.1/fake/servers/1/action') req.method = 'POST' req.content_type = 'application/json' req.body = json.dumps(body) @@ -589,7 +589,7 @@ class ServerActionsTestV11(test.TestCase): def test_server_reboot_incorrect_type(self): body = dict(reboot=dict(type="NOT_A_TYPE")) - req = webob.Request.blank('/v1.1/servers/1/action') + req = webob.Request.blank('/v1.1/fake/servers/1/action') req.method = 'POST' req.content_type = 'application/json' req.body = json.dumps(body) @@ -598,7 +598,7 @@ class ServerActionsTestV11(test.TestCase): def test_server_reboot_missing_type(self): body = dict(reboot=dict()) - req = webob.Request.blank('/v1.1/servers/1/action') + req = webob.Request.blank('/v1.1/fake/servers/1/action') req.method = 'POST' req.content_type = 'application/json' req.body = json.dumps(body) @@ -612,7 +612,7 @@ class ServerActionsTestV11(test.TestCase): }, } - req = webob.Request.blank('/v1.1/servers/1/action') + req = webob.Request.blank('/v1.1/fake/servers/1/action') req.method = 'POST' req.content_type = 'application/json' req.body = json.dumps(body) @@ -633,7 +633,7 @@ class ServerActionsTestV11(test.TestCase): self.stubs.Set(nova.db, 'instance_get_by_uuid', return_server_with_uuid_and_power_state(state)) - req = webob.Request.blank('/v1.1/servers/1/action') + req = webob.Request.blank('/v1.1/fake/servers/1/action') req.method = 'POST' req.content_type = 'application/json' req.body = json.dumps(body) @@ -651,7 +651,7 @@ class ServerActionsTestV11(test.TestCase): }, } - req = webob.Request.blank('/v1.1/servers/1/action') + req = webob.Request.blank('/v1.1/fake/servers/1/action') req.method = 'POST' req.content_type = 'application/json' req.body = json.dumps(body) @@ -667,7 +667,7 @@ class ServerActionsTestV11(test.TestCase): }, } - req = webob.Request.blank('/v1.1/servers/1/action') + req = webob.Request.blank('/v1.1/fake/servers/1/action') req.method = 'POST' req.content_type = 'application/json' req.body = json.dumps(body) @@ -682,7 +682,7 @@ class ServerActionsTestV11(test.TestCase): }, } - req = webob.Request.blank('/v1.1/servers/1/action') + req = webob.Request.blank('/v1.1/fake/servers/1/action') req.method = 'POST' req.content_type = 'application/json' req.body = json.dumps(body) @@ -701,7 +701,7 @@ class ServerActionsTestV11(test.TestCase): }, } - req = webob.Request.blank('/v1.1/servers/1/action') + req = webob.Request.blank('/v1.1/fake/servers/1/action') req.method = 'POST' req.content_type = 'application/json' req.body = json.dumps(body) @@ -720,7 +720,7 @@ class ServerActionsTestV11(test.TestCase): }, } - req = webob.Request.blank('/v1.1/servers/1/action') + req = webob.Request.blank('/v1.1/fake/servers/1/action') req.method = 'POST' req.content_type = 'application/json' req.body = json.dumps(body) @@ -730,7 +730,7 @@ class ServerActionsTestV11(test.TestCase): def test_resize_server(self): - req = webob.Request.blank('/v1.1/servers/1/action') + req = webob.Request.blank('/v1.1/fake/servers/1/action') req.content_type = 'application/json' req.method = 'POST' body_dict = dict(resize=dict(flavorRef="http://localhost/3")) @@ -748,7 +748,7 @@ class ServerActionsTestV11(test.TestCase): self.assertEqual(self.resize_called, True) def test_resize_server_no_flavor(self): - req = webob.Request.blank('/v1.1/servers/1/action') + req = webob.Request.blank('/v1.1/fake/servers/1/action') req.content_type = 'application/json' req.method = 'POST' body_dict = dict(resize=dict()) @@ -758,7 +758,7 @@ class ServerActionsTestV11(test.TestCase): self.assertEqual(res.status_int, 400) def test_resize_server_no_flavor_ref(self): - req = webob.Request.blank('/v1.1/servers/1/action') + req = webob.Request.blank('/v1.1/fake/servers/1/action') req.content_type = 'application/json' req.method = 'POST' body_dict = dict(resize=dict(flavorRef=None)) @@ -768,7 +768,7 @@ class ServerActionsTestV11(test.TestCase): self.assertEqual(res.status_int, 400) def test_confirm_resize_server(self): - req = webob.Request.blank('/v1.1/servers/1/action') + req = webob.Request.blank('/v1.1/fake/servers/1/action') req.content_type = 'application/json' req.method = 'POST' body_dict = dict(confirmResize=None) @@ -786,7 +786,7 @@ class ServerActionsTestV11(test.TestCase): self.assertEqual(self.confirm_resize_called, True) def test_revert_resize_server(self): - req = webob.Request.blank('/v1.1/servers/1/action') + req = webob.Request.blank('/v1.1/fake/servers/1/action') req.content_type = 'application/json' req.method = 'POST' body_dict = dict(revertResize=None) @@ -809,7 +809,7 @@ class ServerActionsTestV11(test.TestCase): 'name': 'Snapshot 1', }, } - req = webob.Request.blank('/v1.1/servers/1/action') + req = webob.Request.blank('/v1.1/fake/servers/1/action') req.method = 'POST' req.body = json.dumps(body) req.headers["content-type"] = "application/json" @@ -828,7 +828,7 @@ class ServerActionsTestV11(test.TestCase): 'name': 'Snapshot 1', }, } - req = webob.Request.blank('/v1.1/servers/1/action') + req = webob.Request.blank('/v1.1/fake/servers/1/action') req.method = 'POST' req.body = json.dumps(body) req.headers["content-type"] = "application/json" @@ -842,7 +842,7 @@ class ServerActionsTestV11(test.TestCase): 'metadata': {'key': 'asdf'}, }, } - req = webob.Request.blank('/v1.1/servers/1/action') + req = webob.Request.blank('/v1.1/fake/servers/1/action') req.method = 'POST' req.body = json.dumps(body) req.headers["content-type"] = "application/json" @@ -860,7 +860,7 @@ class ServerActionsTestV11(test.TestCase): } for num in range(FLAGS.quota_metadata_items + 1): body['createImage']['metadata']['foo%i' % num] = "bar" - req = webob.Request.blank('/v1.1/servers/1/action') + req = webob.Request.blank('/v1.1/fake/servers/1/action') req.method = 'POST' req.body = json.dumps(body) req.headers["content-type"] = "application/json" @@ -871,7 +871,7 @@ class ServerActionsTestV11(test.TestCase): body = { 'createImage': {}, } - req = webob.Request.blank('/v1.1/servers/1/action') + req = webob.Request.blank('/v1.1/fake/servers/1/action') req.method = 'POST' req.body = json.dumps(body) req.headers["content-type"] = "application/json" @@ -885,7 +885,7 @@ class ServerActionsTestV11(test.TestCase): 'metadata': 'henry', }, } - req = webob.Request.blank('/v1.1/servers/1/action') + req = webob.Request.blank('/v1.1/fake/servers/1/action') req.method = 'POST' req.body = json.dumps(body) req.headers["content-type"] = "application/json" @@ -904,7 +904,7 @@ class ServerActionsTestV11(test.TestCase): }, } - req = webob.Request.blank('/v1.1/servers/1/action') + req = webob.Request.blank('/v1.1/fake/servers/1/action') req.method = 'POST' req.body = json.dumps(body) req.headers["content-type"] = "application/json" diff --git a/nova/tests/api/openstack/test_server_metadata.py b/nova/tests/api/openstack/test_server_metadata.py index 8512bd518..296bbd9dc 100644 --- a/nova/tests/api/openstack/test_server_metadata.py +++ b/nova/tests/api/openstack/test_server_metadata.py @@ -83,7 +83,7 @@ class ServerMetaDataTest(test.TestCase): def test_index(self): self.stubs.Set(nova.db.api, 'instance_metadata_get', return_server_metadata) - req = webob.Request.blank('/v1.1/servers/1/metadata') + req = webob.Request.blank('/v1.1/fake/servers/1/metadata') res = req.get_response(fakes.wsgi_app()) self.assertEqual(200, res.status_int) res_dict = json.loads(res.body) @@ -100,7 +100,7 @@ class ServerMetaDataTest(test.TestCase): def test_index_xml(self): self.stubs.Set(nova.db.api, 'instance_metadata_get', return_server_metadata) - request = webob.Request.blank("/v1.1/servers/1/metadata") + request = webob.Request.blank("/v1.1/fake/servers/1/metadata") request.accept = "application/xml" response = request.get_response(fakes.wsgi_app()) self.assertEqual(200, response.status_int) @@ -120,14 +120,14 @@ class ServerMetaDataTest(test.TestCase): def test_index_nonexistant_server(self): self.stubs.Set(nova.db.api, 'instance_get', return_server_nonexistant) - req = webob.Request.blank('/v1.1/servers/1/metadata') + req = webob.Request.blank('/v1.1/fake/servers/1/metadata') res = req.get_response(fakes.wsgi_app()) self.assertEqual(404, res.status_int) def test_index_no_data(self): self.stubs.Set(nova.db.api, 'instance_metadata_get', return_empty_server_metadata) - req = webob.Request.blank('/v1.1/servers/1/metadata') + req = webob.Request.blank('/v1.1/fake/servers/1/metadata') res = req.get_response(fakes.wsgi_app()) self.assertEqual(200, res.status_int) res_dict = json.loads(res.body) @@ -137,7 +137,7 @@ class ServerMetaDataTest(test.TestCase): def test_show(self): self.stubs.Set(nova.db.api, 'instance_metadata_get', return_server_metadata) - req = webob.Request.blank('/v1.1/servers/1/metadata/key2') + req = webob.Request.blank('/v1.1/fake/servers/1/metadata/key2') res = req.get_response(fakes.wsgi_app()) res_dict = json.loads(res.body) self.assertEqual(200, res.status_int) @@ -147,7 +147,7 @@ class ServerMetaDataTest(test.TestCase): def test_show_xml(self): self.stubs.Set(nova.db.api, 'instance_metadata_get', return_server_metadata) - request = webob.Request.blank("/v1.1/servers/1/metadata/key2") + request = webob.Request.blank("/v1.1/fake/servers/1/metadata/key2") request.accept = "application/xml" response = request.get_response(fakes.wsgi_app()) self.assertEqual(200, response.status_int) @@ -164,14 +164,14 @@ class ServerMetaDataTest(test.TestCase): def test_show_nonexistant_server(self): self.stubs.Set(nova.db.api, 'instance_get', return_server_nonexistant) - req = webob.Request.blank('/v1.1/servers/1/metadata/key2') + req = webob.Request.blank('/v1.1/fake/servers/1/metadata/key2') res = req.get_response(fakes.wsgi_app()) self.assertEqual(404, res.status_int) def test_show_meta_not_found(self): self.stubs.Set(nova.db.api, 'instance_metadata_get', return_empty_server_metadata) - req = webob.Request.blank('/v1.1/servers/1/metadata/key6') + req = webob.Request.blank('/v1.1/fake/servers/1/metadata/key6') res = req.get_response(fakes.wsgi_app()) self.assertEqual(404, res.status_int) @@ -180,7 +180,7 @@ class ServerMetaDataTest(test.TestCase): return_server_metadata) self.stubs.Set(nova.db.api, 'instance_metadata_delete', delete_server_metadata) - req = webob.Request.blank('/v1.1/servers/1/metadata/key2') + req = webob.Request.blank('/v1.1/fake/servers/1/metadata/key2') req.method = 'DELETE' res = req.get_response(fakes.wsgi_app()) self.assertEqual(204, res.status_int) @@ -188,7 +188,7 @@ class ServerMetaDataTest(test.TestCase): def test_delete_nonexistant_server(self): self.stubs.Set(nova.db.api, 'instance_get', return_server_nonexistant) - req = webob.Request.blank('/v1.1/servers/1/metadata/key1') + req = webob.Request.blank('/v1.1/fake/servers/1/metadata/key1') req.method = 'DELETE' res = req.get_response(fakes.wsgi_app()) self.assertEqual(404, res.status_int) @@ -196,7 +196,7 @@ class ServerMetaDataTest(test.TestCase): def test_delete_meta_not_found(self): self.stubs.Set(nova.db.api, 'instance_metadata_get', return_empty_server_metadata) - req = webob.Request.blank('/v1.1/servers/1/metadata/key6') + req = webob.Request.blank('/v1.1/fake/servers/1/metadata/key6') req.method = 'DELETE' res = req.get_response(fakes.wsgi_app()) self.assertEqual(404, res.status_int) @@ -206,7 +206,7 @@ class ServerMetaDataTest(test.TestCase): return_server_metadata) self.stubs.Set(nova.db.api, 'instance_metadata_update', return_create_instance_metadata) - req = webob.Request.blank('/v1.1/servers/1/metadata') + req = webob.Request.blank('/v1.1/fake/servers/1/metadata') req.method = 'POST' req.content_type = "application/json" input = {"metadata": {"key9": "value9"}} @@ -227,7 +227,7 @@ class ServerMetaDataTest(test.TestCase): return_server_metadata) self.stubs.Set(nova.db.api, "instance_metadata_update", return_create_instance_metadata) - req = webob.Request.blank("/v1.1/servers/1/metadata") + req = webob.Request.blank("/v1.1/fake/servers/1/metadata") req.method = "POST" req.content_type = "application/xml" req.accept = "application/xml" @@ -258,7 +258,7 @@ class ServerMetaDataTest(test.TestCase): def test_create_empty_body(self): self.stubs.Set(nova.db.api, 'instance_metadata_update', return_create_instance_metadata) - req = webob.Request.blank('/v1.1/servers/1/metadata') + req = webob.Request.blank('/v1.1/fake/servers/1/metadata') req.method = 'POST' req.headers["content-type"] = "application/json" res = req.get_response(fakes.wsgi_app()) @@ -266,7 +266,7 @@ class ServerMetaDataTest(test.TestCase): def test_create_nonexistant_server(self): self.stubs.Set(nova.db.api, 'instance_get', return_server_nonexistant) - req = webob.Request.blank('/v1.1/servers/100/metadata') + req = webob.Request.blank('/v1.1/fake/servers/100/metadata') req.method = 'POST' req.body = '{"metadata": {"key1": "value1"}}' req.headers["content-type"] = "application/json" @@ -276,7 +276,7 @@ class ServerMetaDataTest(test.TestCase): def test_update_all(self): self.stubs.Set(nova.db.api, 'instance_metadata_update', return_create_instance_metadata) - req = webob.Request.blank('/v1.1/servers/1/metadata') + req = webob.Request.blank('/v1.1/fake/servers/1/metadata') req.method = 'PUT' req.content_type = "application/json" expected = { @@ -294,7 +294,7 @@ class ServerMetaDataTest(test.TestCase): def test_update_all_empty_container(self): self.stubs.Set(nova.db.api, 'instance_metadata_update', return_create_instance_metadata) - req = webob.Request.blank('/v1.1/servers/1/metadata') + req = webob.Request.blank('/v1.1/fake/servers/1/metadata') req.method = 'PUT' req.content_type = "application/json" expected = {'metadata': {}} @@ -307,7 +307,7 @@ class ServerMetaDataTest(test.TestCase): def test_update_all_malformed_container(self): self.stubs.Set(nova.db.api, 'instance_metadata_update', return_create_instance_metadata) - req = webob.Request.blank('/v1.1/servers/1/metadata') + req = webob.Request.blank('/v1.1/fake/servers/1/metadata') req.method = 'PUT' req.content_type = "application/json" expected = {'meta': {}} @@ -318,7 +318,7 @@ class ServerMetaDataTest(test.TestCase): def test_update_all_malformed_data(self): self.stubs.Set(nova.db.api, 'instance_metadata_update', return_create_instance_metadata) - req = webob.Request.blank('/v1.1/servers/1/metadata') + req = webob.Request.blank('/v1.1/fake/servers/1/metadata') req.method = 'PUT' req.content_type = "application/json" expected = {'metadata': ['asdf']} @@ -328,7 +328,7 @@ class ServerMetaDataTest(test.TestCase): def test_update_all_nonexistant_server(self): self.stubs.Set(nova.db.api, 'instance_get', return_server_nonexistant) - req = webob.Request.blank('/v1.1/servers/100/metadata') + req = webob.Request.blank('/v1.1/fake/servers/100/metadata') req.method = 'PUT' req.content_type = "application/json" req.body = json.dumps({'metadata': {'key10': 'value10'}}) @@ -338,7 +338,7 @@ class ServerMetaDataTest(test.TestCase): def test_update_item(self): self.stubs.Set(nova.db.api, 'instance_metadata_update', return_create_instance_metadata) - req = webob.Request.blank('/v1.1/servers/1/metadata/key1') + req = webob.Request.blank('/v1.1/fake/servers/1/metadata/key1') req.method = 'PUT' req.body = '{"meta": {"key1": "value1"}}' req.headers["content-type"] = "application/json" @@ -352,7 +352,7 @@ class ServerMetaDataTest(test.TestCase): def test_update_item_xml(self): self.stubs.Set(nova.db.api, 'instance_metadata_update', return_create_instance_metadata) - req = webob.Request.blank('/v1.1/servers/1/metadata/key9') + req = webob.Request.blank('/v1.1/fake/servers/1/metadata/key9') req.method = 'PUT' req.accept = "application/json" req.content_type = "application/xml" @@ -369,7 +369,7 @@ class ServerMetaDataTest(test.TestCase): def test_update_item_nonexistant_server(self): self.stubs.Set(nova.db.api, 'instance_get', return_server_nonexistant) - req = webob.Request.blank('/v1.1/servers/asdf/metadata/key1') + req = webob.Request.blank('/v1.1/fake/servers/asdf/metadata/key1') req.method = 'PUT' req.body = '{"meta":{"key1": "value1"}}' req.headers["content-type"] = "application/json" @@ -379,7 +379,7 @@ class ServerMetaDataTest(test.TestCase): def test_update_item_empty_body(self): self.stubs.Set(nova.db.api, 'instance_metadata_update', return_create_instance_metadata) - req = webob.Request.blank('/v1.1/servers/1/metadata/key1') + req = webob.Request.blank('/v1.1/fake/servers/1/metadata/key1') req.method = 'PUT' req.headers["content-type"] = "application/json" res = req.get_response(fakes.wsgi_app()) @@ -388,7 +388,7 @@ class ServerMetaDataTest(test.TestCase): def test_update_item_too_many_keys(self): self.stubs.Set(nova.db.api, 'instance_metadata_update', return_create_instance_metadata) - req = webob.Request.blank('/v1.1/servers/1/metadata/key1') + req = webob.Request.blank('/v1.1/fake/servers/1/metadata/key1') req.method = 'PUT' req.body = '{"meta": {"key1": "value1", "key2": "value2"}}' req.headers["content-type"] = "application/json" @@ -398,7 +398,7 @@ class ServerMetaDataTest(test.TestCase): def test_update_item_body_uri_mismatch(self): self.stubs.Set(nova.db.api, 'instance_metadata_update', return_create_instance_metadata) - req = webob.Request.blank('/v1.1/servers/1/metadata/bad') + req = webob.Request.blank('/v1.1/fake/servers/1/metadata/bad') req.method = 'PUT' req.body = '{"meta": {"key1": "value1"}}' req.headers["content-type"] = "application/json" @@ -412,7 +412,7 @@ class ServerMetaDataTest(test.TestCase): for num in range(FLAGS.quota_metadata_items + 1): data['metadata']['key%i' % num] = "blah" json_string = str(data).replace("\'", "\"") - req = webob.Request.blank('/v1.1/servers/1/metadata') + req = webob.Request.blank('/v1.1/fake/servers/1/metadata') req.method = 'POST' req.body = json_string req.headers["content-type"] = "application/json" @@ -422,7 +422,7 @@ class ServerMetaDataTest(test.TestCase): def test_too_many_metadata_items_on_update_item(self): self.stubs.Set(nova.db.api, 'instance_metadata_update', return_create_instance_metadata_max) - req = webob.Request.blank('/v1.1/servers/1/metadata/key1') + req = webob.Request.blank('/v1.1/fake/servers/1/metadata/key1') req.method = 'PUT' req.body = '{"meta": {"a new key": "a new value"}}' req.headers["content-type"] = "application/json" diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 2f849f07a..114fc4ea2 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -19,6 +19,7 @@ import base64 import datetime import json import unittest +from lxml import etree from xml.dom import minidom import webob @@ -32,6 +33,7 @@ import nova.api.openstack from nova.api.openstack import create_instance_helper from nova.api.openstack import servers from nova.api.openstack import wsgi +from nova.api.openstack import xmlutil import nova.compute.api from nova.compute import instance_types from nova.compute import power_state @@ -46,6 +48,8 @@ from nova.tests.api.openstack import fakes FAKE_UUID = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa' +NS = "{http://docs.openstack.org/compute/api/v1.1}" +ATOMNS = "{http://www.w3.org/2005/Atom}" def fake_gen_uuid(): @@ -146,7 +150,8 @@ def stub_instance(id, user_id='fake', project_id='fake', private_address=None, public_addresses=None, host=None, power_state=0, reservation_id="", uuid=FAKE_UUID, image_ref="10", flavor_id="1", interfaces=None, name=None, - description='fakedescription'): + description='fakedescription', + access_ipv4=None, access_ipv6=None): metadata = [] metadata.append(InstanceMetadata(key='seq', value=id)) @@ -198,6 +203,8 @@ def stub_instance(id, user_id='fake', project_id='fake', private_address=None, "display_description": description, "locked": False, "metadata": metadata, + "access_ip_v4": access_ipv4, + "access_ip_v6": access_ipv6, "uuid": uuid, "virtual_interfaces": interfaces} @@ -298,10 +305,10 @@ class ServersTest(test.TestCase): self.assertEqual(res_dict['server']['name'], 'server1') def test_get_server_by_id_v1_1(self): - image_bookmark = "http://localhost/images/10" - flavor_ref = "http://localhost/v1.1/flavors/1" + image_bookmark = "http://localhost/fake/images/10" + flavor_ref = "http://localhost/v1.1/fake/flavors/1" flavor_id = "1" - flavor_bookmark = "http://localhost/flavors/1" + flavor_bookmark = "http://localhost/fake/flavors/1" public_ip = '192.168.0.3' private_ip = '172.19.0.1' @@ -323,7 +330,7 @@ class ServersTest(test.TestCase): interfaces=interfaces) self.stubs.Set(nova.db.api, 'instance_get', new_return_server) - req = webob.Request.blank('/v1.1/servers/1') + req = webob.Request.blank('/v1.1/fake/servers/1') res = req.get_response(fakes.wsgi_app()) res_dict = json.loads(res.body) expected_server = { @@ -338,6 +345,8 @@ class ServersTest(test.TestCase): "name": "server1", "description": "fakedescription", "status": "BUILD", + "accessIPv4": "", + "accessIPv6": "", "hostId": '', "image": { "id": "10", @@ -378,11 +387,11 @@ class ServersTest(test.TestCase): { "rel": "self", #FIXME(wwolf) Do we want the links to be id or uuid? - "href": "http://localhost/v1.1/servers/1", + "href": "http://localhost/v1.1/fake/servers/1", }, { "rel": "bookmark", - "href": "http://localhost/servers/1", + "href": "http://localhost/fake/servers/1", }, ], } @@ -391,12 +400,12 @@ class ServersTest(test.TestCase): self.assertDictMatch(res_dict, expected_server) def test_get_server_by_id_v1_1_xml(self): - image_bookmark = "http://localhost/images/10" - flavor_ref = "http://localhost/v1.1/flavors/1" + image_bookmark = "http://localhost/fake/images/10" + flavor_ref = "http://localhost/v1.1/fake/flavors/1" flavor_id = "1" - flavor_bookmark = "http://localhost/flavors/1" - server_href = "http://localhost/v1.1/servers/1" - server_bookmark = "http://localhost/servers/1" + flavor_bookmark = "http://localhost/fake/flavors/1" + server_href = "http://localhost/v1.1/fake/servers/1" + server_bookmark = "http://localhost/fake/servers/1" public_ip = '192.168.0.3' private_ip = '172.19.0.1' @@ -418,7 +427,7 @@ class ServersTest(test.TestCase): interfaces=interfaces) self.stubs.Set(nova.db.api, 'instance_get', new_return_server) - req = webob.Request.blank('/v1.1/servers/1') + req = webob.Request.blank('/v1.1/fake/servers/1') req.headers['Accept'] = 'application/xml' res = req.get_response(fakes.wsgi_app()) actual = minidom.parseString(res.body.replace(' ', '')) @@ -438,6 +447,8 @@ class ServersTest(test.TestCase): created="%(expected_created)s" hostId="" status="BUILD" + accessIPv4="" + accessIPv6="" progress="0"> <atom:link href="%(server_href)s" rel="self"/> <atom:link href="%(server_bookmark)s" rel="bookmark"/> @@ -466,10 +477,10 @@ class ServersTest(test.TestCase): self.assertEqual(expected.toxml(), actual.toxml()) def test_get_server_with_active_status_by_id_v1_1(self): - image_bookmark = "http://localhost/images/10" - flavor_ref = "http://localhost/v1.1/flavors/1" + image_bookmark = "http://localhost/fake/images/10" + flavor_ref = "http://localhost/v1.1/fake/flavors/1" flavor_id = "1" - flavor_bookmark = "http://localhost/flavors/1" + flavor_bookmark = "http://localhost/fake/flavors/1" private_ip = "192.168.0.3" public_ip = "1.2.3.4" @@ -491,7 +502,7 @@ class ServersTest(test.TestCase): interfaces=interfaces, power_state=1) self.stubs.Set(nova.db.api, 'instance_get', new_return_server) - req = webob.Request.blank('/v1.1/servers/1') + req = webob.Request.blank('/v1.1/fake/servers/1') res = req.get_response(fakes.wsgi_app()) res_dict = json.loads(res.body) expected_server = { @@ -506,6 +517,8 @@ class ServersTest(test.TestCase): "name": "server1", "description": "fakedescription", "status": "ACTIVE", + "accessIPv4": "", + "accessIPv6": "", "hostId": '', "image": { "id": "10", @@ -545,11 +558,11 @@ class ServersTest(test.TestCase): "links": [ { "rel": "self", - "href": "http://localhost/v1.1/servers/1", + "href": "http://localhost/v1.1/fake/servers/1", }, { "rel": "bookmark", - "href": "http://localhost/servers/1", + "href": "http://localhost/fake/servers/1", }, ], } @@ -559,10 +572,10 @@ class ServersTest(test.TestCase): def test_get_server_with_id_image_ref_by_id_v1_1(self): image_ref = "10" - image_bookmark = "http://localhost/images/10" - flavor_ref = "http://localhost/v1.1/flavors/1" + image_bookmark = "http://localhost/fake/images/10" + flavor_ref = "http://localhost/v1.1/fake/flavors/1" flavor_id = "1" - flavor_bookmark = "http://localhost/flavors/1" + flavor_bookmark = "http://localhost/fake/flavors/1" private_ip = "192.168.0.3" public_ip = "1.2.3.4" @@ -585,7 +598,7 @@ class ServersTest(test.TestCase): flavor_id=flavor_id) self.stubs.Set(nova.db.api, 'instance_get', new_return_server) - req = webob.Request.blank('/v1.1/servers/1') + req = webob.Request.blank('/v1.1/fake/servers/1') res = req.get_response(fakes.wsgi_app()) res_dict = json.loads(res.body) expected_server = { @@ -600,6 +613,8 @@ class ServersTest(test.TestCase): "name": "server1", "description": "fakedescription", "status": "ACTIVE", + "accessIPv4": "", + "accessIPv6": "", "hostId": '', "image": { "id": "10", @@ -639,11 +654,11 @@ class ServersTest(test.TestCase): "links": [ { "rel": "self", - "href": "http://localhost/v1.1/servers/1", + "href": "http://localhost/v1.1/fake/servers/1", }, { "rel": "bookmark", - "href": "http://localhost/servers/1", + "href": "http://localhost/fake/servers/1", }, ], } @@ -788,7 +803,7 @@ class ServersTest(test.TestCase): interfaces=interfaces) self.stubs.Set(nova.db.api, 'instance_get', new_return_server) - req = webob.Request.blank('/v1.1/servers/1') + req = webob.Request.blank('/v1.1/fake/servers/1') res = req.get_response(fakes.wsgi_app()) res_dict = json.loads(res.body) @@ -832,7 +847,7 @@ class ServersTest(test.TestCase): interfaces=interfaces) self.stubs.Set(nova.db.api, 'instance_get', new_return_server) - req = webob.Request.blank('/v1.1/servers/1') + req = webob.Request.blank('/v1.1/fake/servers/1') res = req.get_response(fakes.wsgi_app()) res_dict = json.loads(res.body) @@ -882,7 +897,7 @@ class ServersTest(test.TestCase): 'virtual_interface_get_by_instance', _return_vifs) - req = webob.Request.blank('/v1.1/servers/1/ips') + req = webob.Request.blank('/v1.1/fake/servers/1/ips') res = req.get_response(fakes.wsgi_app()) res_dict = json.loads(res.body) @@ -932,7 +947,7 @@ class ServersTest(test.TestCase): 'virtual_interface_get_by_instance', _return_vifs) - req = webob.Request.blank('/v1.1/servers/1/ips/network_2') + req = webob.Request.blank('/v1.1/fake/servers/1/ips/network_2') res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 200) res_dict = json.loads(res.body) @@ -952,7 +967,7 @@ class ServersTest(test.TestCase): 'virtual_interface_get_by_instance', _return_vifs) - req = webob.Request.blank('/v1.1/servers/1/ips/network_0') + req = webob.Request.blank('/v1.1/fake/servers/1/ips/network_0') res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 404) @@ -962,7 +977,7 @@ class ServersTest(test.TestCase): 'virtual_interface_get_by_instance', _return_vifs) - req = webob.Request.blank('/v1.1/servers/600/ips') + req = webob.Request.blank('/v1.1/fake/servers/600/ips') res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 404) @@ -1031,7 +1046,7 @@ class ServersTest(test.TestCase): i += 1 def test_get_server_list_v1_1(self): - req = webob.Request.blank('/v1.1/servers') + req = webob.Request.blank('/v1.1/fake/servers') res = req.get_response(fakes.wsgi_app()) res_dict = json.loads(res.body) @@ -1044,11 +1059,11 @@ class ServersTest(test.TestCase): expected_links = [ { "rel": "self", - "href": "http://localhost/v1.1/servers/%s" % s['id'], + "href": "http://localhost/v1.1/fake/servers/%s" % s['id'], }, { "rel": "bookmark", - "href": "http://localhost/servers/%s" % s['id'], + "href": "http://localhost/fake/servers/%s" % s['id'], }, ] @@ -1095,19 +1110,19 @@ class ServersTest(test.TestCase): self.assertTrue(res.body.find('offset param') > -1) def test_get_servers_with_marker(self): - req = webob.Request.blank('/v1.1/servers?marker=2') + req = webob.Request.blank('/v1.1/fake/servers?marker=2') res = req.get_response(fakes.wsgi_app()) servers = json.loads(res.body)['servers'] self.assertEqual([s['name'] for s in servers], ["server3", "server4"]) def test_get_servers_with_limit_and_marker(self): - req = webob.Request.blank('/v1.1/servers?limit=2&marker=1') + req = webob.Request.blank('/v1.1/fake/servers?limit=2&marker=1') res = req.get_response(fakes.wsgi_app()) servers = json.loads(res.body)['servers'] self.assertEqual([s['name'] for s in servers], ['server2', 'server3']) def test_get_servers_with_bad_marker(self): - req = webob.Request.blank('/v1.1/servers?limit=2&marker=asdf') + req = webob.Request.blank('/v1.1/fake/servers?limit=2&marker=asdf') res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 400) self.assertTrue(res.body.find('marker param') > -1) @@ -1133,7 +1148,7 @@ class ServersTest(test.TestCase): self.stubs.Set(nova.compute.API, 'get_all', fake_get_all) - req = webob.Request.blank('/v1.1/servers?unknownoption=whee') + req = webob.Request.blank('/v1.1/fake/servers?unknownoption=whee') res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 200) servers = json.loads(res.body)['servers'] @@ -1150,7 +1165,7 @@ class ServersTest(test.TestCase): self.stubs.Set(nova.compute.API, 'get_all', fake_get_all) self.flags(allow_admin_api=False) - req = webob.Request.blank('/v1.1/servers?image=12345') + req = webob.Request.blank('/v1.1/fake/servers?image=12345') res = req.get_response(fakes.wsgi_app()) # The following assert will fail if either of the asserts in # fake_get_all() fail @@ -1169,7 +1184,7 @@ class ServersTest(test.TestCase): self.stubs.Set(nova.compute.API, 'get_all', fake_get_all) self.flags(allow_admin_api=True) - req = webob.Request.blank('/v1.1/servers?tenant_id=faketenant') + req = webob.Request.blank('/v1.1/fake/servers?tenant_id=faketenant') # Use admin context context = nova.context.RequestContext('testuser', 'testproject', is_admin=True) @@ -1189,7 +1204,7 @@ class ServersTest(test.TestCase): self.stubs.Set(nova.compute.API, 'get_all', fake_get_all) self.flags(allow_admin_api=False) - req = webob.Request.blank('/v1.1/servers?flavor=12345') + req = webob.Request.blank('/v1.1/fake/servers?flavor=12345') res = req.get_response(fakes.wsgi_app()) # The following assert will fail if either of the asserts in # fake_get_all() fail @@ -1209,7 +1224,7 @@ class ServersTest(test.TestCase): self.stubs.Set(nova.compute.API, 'get_all', fake_get_all) self.flags(allow_admin_api=False) - req = webob.Request.blank('/v1.1/servers?status=active') + req = webob.Request.blank('/v1.1/fake/servers?status=active') res = req.get_response(fakes.wsgi_app()) # The following assert will fail if either of the asserts in # fake_get_all() fail @@ -1223,7 +1238,7 @@ class ServersTest(test.TestCase): self.flags(allow_admin_api=False) - req = webob.Request.blank('/v1.1/servers?status=running') + req = webob.Request.blank('/v1.1/fake/servers?status=running') res = req.get_response(fakes.wsgi_app()) # The following assert will fail if either of the asserts in # fake_get_all() fail @@ -1240,7 +1255,7 @@ class ServersTest(test.TestCase): self.stubs.Set(nova.compute.API, 'get_all', fake_get_all) self.flags(allow_admin_api=False) - req = webob.Request.blank('/v1.1/servers?name=whee.*') + req = webob.Request.blank('/v1.1/fake/servers?name=whee.*') res = req.get_response(fakes.wsgi_app()) # The following assert will fail if either of the asserts in # fake_get_all() fail @@ -1271,7 +1286,7 @@ class ServersTest(test.TestCase): self.stubs.Set(nova.compute.API, 'get_all', fake_get_all) query_str = "name=foo&ip=10.*&status=active&unknown_option=meow" - req = webob.Request.blank('/v1.1/servers?%s' % query_str) + req = webob.Request.blank('/v1.1/fake/servers?%s' % query_str) # Request admin context context = nova.context.RequestContext('testuser', 'testproject', is_admin=True) @@ -1305,7 +1320,7 @@ class ServersTest(test.TestCase): self.stubs.Set(nova.compute.API, 'get_all', fake_get_all) query_str = "name=foo&ip=10.*&status=active&unknown_option=meow" - req = webob.Request.blank('/v1.1/servers?%s' % query_str) + req = webob.Request.blank('/v1.1/fake/servers?%s' % query_str) # Request admin context context = nova.context.RequestContext('testuser', 'testproject', is_admin=False) @@ -1338,7 +1353,7 @@ class ServersTest(test.TestCase): self.stubs.Set(nova.compute.API, 'get_all', fake_get_all) query_str = "name=foo&ip=10.*&status=active&unknown_option=meow" - req = webob.Request.blank('/v1.1/servers?%s' % query_str) + req = webob.Request.blank('/v1.1/fake/servers?%s' % query_str) # Request admin context context = nova.context.RequestContext('testuser', 'testproject', is_admin=True) @@ -1364,7 +1379,7 @@ class ServersTest(test.TestCase): self.stubs.Set(nova.compute.API, 'get_all', fake_get_all) - req = webob.Request.blank('/v1.1/servers?ip=10\..*') + req = webob.Request.blank('/v1.1/fake/servers?ip=10\..*') # Request admin context context = nova.context.RequestContext('testuser', 'testproject', is_admin=True) @@ -1390,7 +1405,7 @@ class ServersTest(test.TestCase): self.stubs.Set(nova.compute.API, 'get_all', fake_get_all) - req = webob.Request.blank('/v1.1/servers?ip6=ffff.*') + req = webob.Request.blank('/v1.1/fake/servers?ip6=ffff.*') # Request admin context context = nova.context.RequestContext('testuser', 'testproject', is_admin=True) @@ -1411,6 +1426,8 @@ class ServersTest(test.TestCase): 'display_name': 'server_test', 'uuid': FAKE_UUID, 'instance_type': dict(inst_type), + 'access_ip_v4': '1.2.3.4', + 'access_ip_v6': 'fead::1234', 'image_ref': image_ref, 'display_description': 'fakedescription', 'user_id': 'fake', @@ -1614,18 +1631,82 @@ class ServersTest(test.TestCase): res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 400) + def test_create_instance_with_access_ip_v1_1(self): + self._setup_for_create_instance() + + # proper local hrefs must start with 'http://localhost/v1.1/' + image_href = 'http://localhost/v1.1/123/images/2' + flavor_ref = 'http://localhost/123/flavors/3' + access_ipv4 = '1.2.3.4' + access_ipv6 = 'fead::1234' + expected_flavor = { + "id": "3", + "links": [ + { + "rel": "bookmark", + "href": 'http://localhost/123/flavors/3', + }, + ], + } + expected_image = { + "id": "2", + "links": [ + { + "rel": "bookmark", + "href": 'http://localhost/123/images/2', + }, + ], + } + body = { + 'server': { + 'name': 'server_test', + 'imageRef': image_href, + 'flavorRef': flavor_ref, + 'accessIPv4': access_ipv4, + 'accessIPv6': access_ipv6, + 'metadata': { + 'hello': 'world', + 'open': 'stack', + }, + 'personality': [ + { + "path": "/etc/banner.txt", + "contents": "MQ==", + }, + ], + }, + } + + req = webob.Request.blank('/v1.1/123/servers') + req.method = 'POST' + req.body = json.dumps(body) + req.headers["content-type"] = "application/json" + + res = req.get_response(fakes.wsgi_app()) + + self.assertEqual(res.status_int, 202) + server = json.loads(res.body)['server'] + self.assertEqual(16, len(server['adminPass'])) + self.assertEqual(1, server['id']) + self.assertEqual(0, server['progress']) + self.assertEqual('server_test', server['name']) + self.assertEqual(expected_flavor, server['flavor']) + self.assertEqual(expected_image, server['image']) + self.assertEqual(access_ipv4, server['accessIPv4']) + self.assertEqual(access_ipv6, server['accessIPv6']) + def test_create_instance_v1_1(self): self._setup_for_create_instance() # proper local hrefs must start with 'http://localhost/v1.1/' image_href = 'http://localhost/v1.1/images/2' - flavor_ref = 'http://localhost/flavors/3' + flavor_ref = 'http://localhost/123/flavors/3' expected_flavor = { "id": "3", "links": [ { "rel": "bookmark", - "href": 'http://localhost/flavors/3', + "href": 'http://localhost/fake/flavors/3', }, ], } @@ -1634,7 +1715,7 @@ class ServersTest(test.TestCase): "links": [ { "rel": "bookmark", - "href": 'http://localhost/images/2', + "href": 'http://localhost/fake/images/2', }, ], } @@ -1656,7 +1737,7 @@ class ServersTest(test.TestCase): }, } - req = webob.Request.blank('/v1.1/servers') + req = webob.Request.blank('/v1.1/fake/servers') req.method = 'POST' req.body = json.dumps(body) req.headers["content-type"] = "application/json" @@ -1671,6 +1752,8 @@ class ServersTest(test.TestCase): self.assertEqual('server_test', server['name']) self.assertEqual(expected_flavor, server['flavor']) self.assertEqual(expected_image, server['image']) + self.assertEqual('1.2.3.4', server['accessIPv4']) + self.assertEqual('fead::1234', server['accessIPv6']) def test_create_instance_v1_1_invalid_flavor_href(self): self._setup_for_create_instance() @@ -1681,7 +1764,7 @@ class ServersTest(test.TestCase): name='server_test', imageRef=image_href, flavorRef=flavor_ref, metadata={'hello': 'world', 'open': 'stack'}, personality={})) - req = webob.Request.blank('/v1.1/servers') + req = webob.Request.blank('/v1.1/fake/servers') req.method = 'POST' req.body = json.dumps(body) req.headers["content-type"] = "application/json" @@ -1691,13 +1774,13 @@ class ServersTest(test.TestCase): def test_create_instance_v1_1_invalid_flavor_id_int(self): self._setup_for_create_instance() - image_href = 'http://localhost/v1.1/images/2' + image_href = 'http://localhost/v1.1/123/images/2' flavor_ref = -1 body = dict(server=dict( name='server_test', imageRef=image_href, flavorRef=flavor_ref, metadata={'hello': 'world', 'open': 'stack'}, personality={})) - req = webob.Request.blank('/v1.1/servers') + req = webob.Request.blank('/v1.1/123/servers') req.method = 'POST' req.body = json.dumps(body) req.headers["content-type"] = "application/json" @@ -1713,7 +1796,7 @@ class ServersTest(test.TestCase): name='server_test', imageRef=image_href, flavorRef=flavor_ref, metadata={'hello': 'world', 'open': 'stack'}, personality={})) - req = webob.Request.blank('/v1.1/servers') + req = webob.Request.blank('/v1.1/fake/servers') req.method = 'POST' req.body = json.dumps(body) req.headers["content-type"] = "application/json" @@ -1729,7 +1812,7 @@ class ServersTest(test.TestCase): name='server_test', imageRef=image_href, flavorRef=flavor_ref, metadata={'hello': 'world', 'open': 'stack'}, personality={})) - req = webob.Request.blank('/v1.1/servers') + req = webob.Request.blank('/v1.1/fake/servers') req.method = 'POST' req.body = json.dumps(body) req.headers["content-type"] = "application/json" @@ -1746,7 +1829,7 @@ class ServersTest(test.TestCase): "links": [ { "rel": "bookmark", - "href": 'http://localhost/flavors/3', + "href": 'http://localhost/fake/flavors/3', }, ], } @@ -1755,7 +1838,7 @@ class ServersTest(test.TestCase): "links": [ { "rel": "bookmark", - "href": 'http://localhost/images/2', + "href": 'http://localhost/fake/images/2', }, ], } @@ -1767,7 +1850,7 @@ class ServersTest(test.TestCase): }, } - req = webob.Request.blank('/v1.1/servers') + req = webob.Request.blank('/v1.1/fake/servers') req.method = 'POST' req.body = json.dumps(body) req.headers["content-type"] = "application/json" @@ -1814,7 +1897,7 @@ class ServersTest(test.TestCase): }, } - req = webob.Request.blank('/v1.1/servers') + req = webob.Request.blank('/v1.1/fake/servers') req.method = 'POST' req.body = json.dumps(body) req.headers['content-type'] = "application/json" @@ -1835,13 +1918,36 @@ class ServersTest(test.TestCase): }, } - req = webob.Request.blank('/v1.1/servers') + req = webob.Request.blank('/v1.1/fake/servers') req.method = 'POST' req.body = json.dumps(body) req.headers['content-type'] = "application/json" res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 400) + def test_create_instance_whitespace_name(self): + self._setup_for_create_instance() + + body = { + 'server': { + 'name': ' ', + 'imageId': 3, + 'flavorId': 1, + 'metadata': { + 'hello': 'world', + 'open': 'stack', + }, + 'personality': {}, + }, + } + + req = webob.Request.blank('/v1.0/servers') + req.method = 'POST' + req.body = json.dumps(body) + req.headers["content-type"] = "application/json" + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 400) + def test_update_server_no_body(self): req = webob.Request.blank('/v1.0/servers/1') req.method = 'PUT' @@ -1909,15 +2015,37 @@ class ServersTest(test.TestCase): self.assertEqual(mock_method.password, 'bacon') def test_update_server_no_body_v1_1(self): - req = webob.Request.blank('/v1.0/servers/1') + req = webob.Request.blank('/v1.1/fake/servers/1') req.method = 'PUT' res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 400) + def test_update_server_all_attributes_v1_1(self): + self.stubs.Set(nova.db.api, 'instance_get', + return_server_with_attributes(name='server_test', + access_ipv4='0.0.0.0', + access_ipv6='beef::0123')) + req = webob.Request.blank('/v1.1/123/servers/1') + req.method = 'PUT' + req.content_type = 'application/json' + body = {'server': { + 'name': 'server_test', + 'accessIPv4': '0.0.0.0', + 'accessIPv6': 'beef::0123', + }} + req.body = json.dumps(body) + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 200) + res_dict = json.loads(res.body) + self.assertEqual(res_dict['server']['id'], 1) + self.assertEqual(res_dict['server']['name'], 'server_test') + self.assertEqual(res_dict['server']['accessIPv4'], '0.0.0.0') + self.assertEqual(res_dict['server']['accessIPv6'], 'beef::0123') + def test_update_server_name_v1_1(self): self.stubs.Set(nova.db.api, 'instance_get', return_server_with_attributes(name='server_test')) - req = webob.Request.blank('/v1.1/servers/1') + req = webob.Request.blank('/v1.1/fake/servers/1') req.method = 'PUT' req.content_type = 'application/json' req.body = json.dumps({'server': {'name': 'server_test'}}) @@ -1945,7 +2073,7 @@ class ServersTest(test.TestCase): self.stubs.Set(nova.db.api, 'instance_update', server_update) - req = webob.Request.blank('/v1.1/servers/1') + req = webob.Request.blank('/v1.1/fake/servers/1') req.method = 'PUT' req.content_type = 'application/json' req.body = json.dumps({'server': {'description': DESC}}) @@ -1955,6 +2083,32 @@ class ServersTest(test.TestCase): self.assertEqual(res_dict['server']['id'], 1) self.assertEqual(res_dict['server']['description'], DESC) + def test_update_server_access_ipv4_v1_1(self): + self.stubs.Set(nova.db.api, 'instance_get', + return_server_with_attributes(access_ipv4='0.0.0.0')) + req = webob.Request.blank('/v1.1/123/servers/1') + req.method = 'PUT' + req.content_type = 'application/json' + req.body = json.dumps({'server': {'accessIPv4': '0.0.0.0'}}) + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 200) + res_dict = json.loads(res.body) + self.assertEqual(res_dict['server']['id'], 1) + self.assertEqual(res_dict['server']['accessIPv4'], '0.0.0.0') + + def test_update_server_access_ipv6_v1_1(self): + self.stubs.Set(nova.db.api, 'instance_get', + return_server_with_attributes(access_ipv6='beef::0123')) + req = webob.Request.blank('/v1.1/123/servers/1') + req.method = 'PUT' + req.content_type = 'application/json' + req.body = json.dumps({'server': {'accessIPv6': 'beef::0123'}}) + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 200) + res_dict = json.loads(res.body) + self.assertEqual(res_dict['server']['id'], 1) + self.assertEqual(res_dict['server']['accessIPv6'], 'beef::0123') + def test_update_server_adminPass_ignored_v1_1(self): inst_dict = dict(name='server_test', adminPass='bacon') self.body = json.dumps(dict(server=inst_dict)) @@ -1968,7 +2122,7 @@ class ServersTest(test.TestCase): self.stubs.Set(nova.db.api, 'instance_get', return_server_with_attributes(name='server_test')) - req = webob.Request.blank('/v1.1/servers/1') + req = webob.Request.blank('/v1.1/fake/servers/1') req.method = 'PUT' req.content_type = "application/json" req.body = self.body @@ -2001,7 +2155,7 @@ class ServersTest(test.TestCase): self.assertEqual(res.status_int, 501) def test_server_backup_schedule_deprecated_v1_1(self): - req = webob.Request.blank('/v1.1/servers/1/backup_schedule') + req = webob.Request.blank('/v1.1/fake/servers/1/backup_schedule') res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 404) @@ -2041,7 +2195,7 @@ class ServersTest(test.TestCase): "links": [ { "rel": "bookmark", - "href": 'http://localhost/flavors/1', + "href": 'http://localhost/fake/flavors/1', }, ], } @@ -2050,11 +2204,11 @@ class ServersTest(test.TestCase): "links": [ { "rel": "bookmark", - "href": 'http://localhost/images/10', + "href": 'http://localhost/fake/images/10', }, ], } - req = webob.Request.blank('/v1.1/servers/detail') + req = webob.Request.blank('/v1.1/fake/servers/detail') res = req.get_response(fakes.wsgi_app()) res_dict = json.loads(res.body) @@ -2213,7 +2367,7 @@ class ServersTest(test.TestCase): self.assertEqual(res.status_int, 422) def test_delete_server_instance_v1_1(self): - req = webob.Request.blank('/v1.1/servers/1') + req = webob.Request.blank('/v1.1/fake/servers/1') req.method = 'DELETE' self.server_delete_called = False @@ -2554,6 +2708,62 @@ class TestServerCreateRequestXMLDeserializerV11(test.TestCase): } self.assertEquals(request['body'], expected) + def test_access_ipv4(self): + serial_request = """ +<server xmlns="http://docs.openstack.org/compute/api/v1.1" + name="new-server-test" + imageRef="1" + flavorRef="2" + accessIPv4="1.2.3.4"/>""" + request = self.deserializer.deserialize(serial_request, 'create') + expected = { + "server": { + "name": "new-server-test", + "imageRef": "1", + "flavorRef": "2", + "accessIPv4": "1.2.3.4", + }, + } + self.assertEquals(request['body'], expected) + + def test_access_ipv6(self): + serial_request = """ +<server xmlns="http://docs.openstack.org/compute/api/v1.1" + name="new-server-test" + imageRef="1" + flavorRef="2" + accessIPv6="fead::1234"/>""" + request = self.deserializer.deserialize(serial_request, 'create') + expected = { + "server": { + "name": "new-server-test", + "imageRef": "1", + "flavorRef": "2", + "accessIPv6": "fead::1234", + }, + } + self.assertEquals(request['body'], expected) + + def test_access_ip(self): + serial_request = """ +<server xmlns="http://docs.openstack.org/compute/api/v1.1" + name="new-server-test" + imageRef="1" + flavorRef="2" + accessIPv4="1.2.3.4" + accessIPv6="fead::1234"/>""" + request = self.deserializer.deserialize(serial_request, 'create') + expected = { + "server": { + "name": "new-server-test", + "imageRef": "1", + "flavorRef": "2", + "accessIPv4": "1.2.3.4", + "accessIPv6": "fead::1234", + }, + } + self.assertEquals(request['body'], expected) + def test_admin_pass(self): serial_request = """ <server xmlns="http://docs.openstack.org/compute/api/v1.1" @@ -2705,6 +2915,164 @@ class TestServerCreateRequestXMLDeserializerV11(test.TestCase): } self.assertEquals(request['body'], expected) + def test_request_with_empty_networks(self): + serial_request = """ +<server xmlns="http://docs.openstack.org/compute/api/v1.1" + name="new-server-test" imageRef="1" flavorRef="1"> + <networks/> +</server>""" + request = self.deserializer.deserialize(serial_request, 'create') + expected = {"server": { + "name": "new-server-test", + "imageRef": "1", + "flavorRef": "1", + "networks": [], + }} + self.assertEquals(request['body'], expected) + + def test_request_with_one_network(self): + serial_request = """ +<server xmlns="http://docs.openstack.org/compute/api/v1.1" + name="new-server-test" imageRef="1" flavorRef="1"> + <networks> + <network uuid="1" fixed_ip="10.0.1.12"/> + </networks> +</server>""" + request = self.deserializer.deserialize(serial_request, 'create') + expected = {"server": { + "name": "new-server-test", + "imageRef": "1", + "flavorRef": "1", + "networks": [{"uuid": "1", "fixed_ip": "10.0.1.12"}], + }} + self.assertEquals(request['body'], expected) + + def test_request_with_two_networks(self): + serial_request = """ +<server xmlns="http://docs.openstack.org/compute/api/v1.1" + name="new-server-test" imageRef="1" flavorRef="1"> + <networks> + <network uuid="1" fixed_ip="10.0.1.12"/> + <network uuid="2" fixed_ip="10.0.2.12"/> + </networks> +</server>""" + request = self.deserializer.deserialize(serial_request, 'create') + expected = {"server": { + "name": "new-server-test", + "imageRef": "1", + "flavorRef": "1", + "networks": [{"uuid": "1", "fixed_ip": "10.0.1.12"}, + {"uuid": "2", "fixed_ip": "10.0.2.12"}], + }} + self.assertEquals(request['body'], expected) + + def test_request_with_second_network_node_ignored(self): + serial_request = """ +<server xmlns="http://docs.openstack.org/compute/api/v1.1" + name="new-server-test" imageRef="1" flavorRef="1"> + <networks> + <network uuid="1" fixed_ip="10.0.1.12"/> + </networks> + <networks> + <network uuid="2" fixed_ip="10.0.2.12"/> + </networks> +</server>""" + request = self.deserializer.deserialize(serial_request, 'create') + expected = {"server": { + "name": "new-server-test", + "imageRef": "1", + "flavorRef": "1", + "networks": [{"uuid": "1", "fixed_ip": "10.0.1.12"}], + }} + self.assertEquals(request['body'], expected) + + def test_request_with_one_network_missing_id(self): + serial_request = """ +<server xmlns="http://docs.openstack.org/compute/api/v1.1" + name="new-server-test" imageRef="1" flavorRef="1"> + <networks> + <network fixed_ip="10.0.1.12"/> + </networks> +</server>""" + request = self.deserializer.deserialize(serial_request, 'create') + expected = {"server": { + "name": "new-server-test", + "imageRef": "1", + "flavorRef": "1", + "networks": [{"fixed_ip": "10.0.1.12"}], + }} + self.assertEquals(request['body'], expected) + + def test_request_with_one_network_missing_fixed_ip(self): + serial_request = """ +<server xmlns="http://docs.openstack.org/compute/api/v1.1" + name="new-server-test" imageRef="1" flavorRef="1"> + <networks> + <network uuid="1"/> + </networks> +</server>""" + request = self.deserializer.deserialize(serial_request, 'create') + expected = {"server": { + "name": "new-server-test", + "imageRef": "1", + "flavorRef": "1", + "networks": [{"uuid": "1"}], + }} + self.assertEquals(request['body'], expected) + + def test_request_with_one_network_empty_id(self): + serial_request = """ + <server xmlns="http://docs.openstack.org/compute/api/v1.1" + name="new-server-test" imageRef="1" flavorRef="1"> + <networks> + <network uuid="" fixed_ip="10.0.1.12"/> + </networks> + </server>""" + request = self.deserializer.deserialize(serial_request, 'create') + expected = {"server": { + "name": "new-server-test", + "imageRef": "1", + "flavorRef": "1", + "networks": [{"uuid": "", "fixed_ip": "10.0.1.12"}], + }} + self.assertEquals(request['body'], expected) + + def test_request_with_one_network_empty_fixed_ip(self): + serial_request = """ + <server xmlns="http://docs.openstack.org/compute/api/v1.1" + name="new-server-test" imageRef="1" flavorRef="1"> + <networks> + <network uuid="1" fixed_ip=""/> + </networks> + </server>""" + request = self.deserializer.deserialize(serial_request, 'create') + expected = {"server": { + "name": "new-server-test", + "imageRef": "1", + "flavorRef": "1", + "networks": [{"uuid": "1", "fixed_ip": ""}], + }} + self.assertEquals(request['body'], expected) + + def test_request_with_networks_duplicate_ids(self): + serial_request = """ + <server xmlns="http://docs.openstack.org/compute/api/v1.1" + name="new-server-test" imageRef="1" flavorRef="1"> + <networks> + <network uuid="1" fixed_ip="10.0.1.12"/> + <network uuid="1" fixed_ip="10.0.2.12"/> + </networks> + </server>""" + request = self.deserializer.deserialize(serial_request, 'create') + expected = {"server": { + "name": "new-server-test", + "imageRef": "1", + "flavorRef": "1", + "networks": [{"uuid": "1", "fixed_ip": "10.0.1.12"}, + {"uuid": "1", "fixed_ip": "10.0.2.12"}], + }} + self.assertEquals(request['body'], expected) + class TestAddressesXMLSerialization(test.TestCase): @@ -2775,12 +3143,14 @@ class TestServerInstanceCreation(test.TestCase): def __init__(self): self.injected_files = None + self.networks = None def create(self, *args, **kwargs): if 'injected_files' in kwargs: self.injected_files = kwargs['injected_files'] else: self.injected_files = None + return [{'id': '1234', 'display_name': 'fakeinstance', 'user_id': 'fake', 'project_id': 'fake', @@ -3105,24 +3475,28 @@ class ServersViewBuilderV11Test(test.TestCase): "display_description": "fakedescription", "locked": False, "metadata": [], + "accessIPv4": "1.2.3.4", + "accessIPv6": "fead::1234", #"address": , #"floating_ips": [{"address":ip} for ip in public_addresses]} "uuid": "deadbeef-feed-edee-beef-d0ea7beefedd"} return instance - def _get_view_builder(self): + def _get_view_builder(self, project_id=""): base_url = "http://localhost/v1.1" views = nova.api.openstack.views address_builder = views.addresses.ViewBuilderV11() - flavor_builder = views.flavors.ViewBuilderV11(base_url) - image_builder = views.images.ViewBuilderV11(base_url) + flavor_builder = views.flavors.ViewBuilderV11(base_url, project_id) + image_builder = views.images.ViewBuilderV11(base_url, project_id) view_builder = nova.api.openstack.views.servers.ViewBuilderV11( address_builder, flavor_builder, image_builder, - base_url) + base_url, + project_id, + ) return view_builder def test_build_server(self): @@ -3147,6 +3521,29 @@ class ServersViewBuilderV11Test(test.TestCase): output = self.view_builder.build(self.instance, False) self.assertDictMatch(output, expected_server) + def test_build_server_with_project_id(self): + expected_server = { + "server": { + "id": 1, + "uuid": self.instance['uuid'], + "name": "test_server", + "links": [ + { + "rel": "self", + "href": "http://localhost/v1.1/fake/servers/1", + }, + { + "rel": "bookmark", + "href": "http://localhost/fake/servers/1", + }, + ], + } + } + + view_builder = self._get_view_builder(project_id='fake') + output = view_builder.build(self.instance, False) + self.assertDictMatch(output, expected_server) + def test_build_server_detail(self): image_bookmark = "http://localhost/images/5" flavor_bookmark = "http://localhost/flavors/1" @@ -3162,6 +3559,8 @@ class ServersViewBuilderV11Test(test.TestCase): "name": "test_server", "description": "fakedescription", "status": "BUILD", + "accessIPv4": "", + "accessIPv6": "", "hostId": '', "image": { "id": "5", @@ -3216,6 +3615,8 @@ class ServersViewBuilderV11Test(test.TestCase): "name": "test_server", "description": "fakedescription", "status": "ACTIVE", + "accessIPv4": "", + "accessIPv6": "", "hostId": '', "image": { "id": "5", @@ -3253,6 +3654,120 @@ class ServersViewBuilderV11Test(test.TestCase): output = self.view_builder.build(self.instance, True) self.assertDictMatch(output, expected_server) + def test_build_server_detail_with_accessipv4(self): + + self.instance['access_ip_v4'] = '1.2.3.4' + + image_bookmark = "http://localhost/images/5" + flavor_bookmark = "http://localhost/flavors/1" + expected_server = { + "server": { + "id": 1, + "uuid": self.instance['uuid'], + "user_id": "fake", + "tenant_id": "fake", + "updated": "2010-11-11T11:00:00Z", + "created": "2010-10-10T12:00:00Z", + "progress": 0, + "name": "test_server", + "description": "fakedescription", + "status": "BUILD", + "hostId": '', + "image": { + "id": "5", + "links": [ + { + "rel": "bookmark", + "href": image_bookmark, + }, + ], + }, + "flavor": { + "id": "1", + "links": [ + { + "rel": "bookmark", + "href": flavor_bookmark, + }, + ], + }, + "addresses": {}, + "metadata": {}, + "accessIPv4": "1.2.3.4", + "accessIPv6": "", + "links": [ + { + "rel": "self", + "href": "http://localhost/v1.1/servers/1", + }, + { + "rel": "bookmark", + "href": "http://localhost/servers/1", + }, + ], + } + } + + output = self.view_builder.build(self.instance, True) + self.assertDictMatch(output, expected_server) + + def test_build_server_detail_with_accessipv6(self): + + self.instance['access_ip_v6'] = 'fead::1234' + + image_bookmark = "http://localhost/images/5" + flavor_bookmark = "http://localhost/flavors/1" + expected_server = { + "server": { + "id": 1, + "uuid": self.instance['uuid'], + "user_id": "fake", + "tenant_id": "fake", + "updated": "2010-11-11T11:00:00Z", + "created": "2010-10-10T12:00:00Z", + "progress": 0, + "name": "test_server", + "description": "fakedescription", + "status": "BUILD", + "hostId": '', + "image": { + "id": "5", + "links": [ + { + "rel": "bookmark", + "href": image_bookmark, + }, + ], + }, + "flavor": { + "id": "1", + "links": [ + { + "rel": "bookmark", + "href": flavor_bookmark, + }, + ], + }, + "addresses": {}, + "metadata": {}, + "accessIPv4": "", + "accessIPv6": "fead::1234", + "links": [ + { + "rel": "self", + "href": "http://localhost/v1.1/servers/1", + }, + { + "rel": "bookmark", + "href": "http://localhost/servers/1", + }, + ], + } + } + + output = self.view_builder.build(self.instance, True) + self.assertDictMatch(output, expected_server) + def test_build_server_detail_with_metadata(self): metadata = [] @@ -3274,6 +3789,8 @@ class ServersViewBuilderV11Test(test.TestCase): "name": "test_server", "description": "fakedescription", "status": "BUILD", + "accessIPv4": "", + "accessIPv6": "", "hostId": '', "image": { "id": "5", @@ -3343,6 +3860,8 @@ class ServerXMLSerializationTest(test.TestCase): "description": "fakedescription", "status": "BUILD", "hostId": 'e4d909c290d0fb1ca068ffaddf22cbd0', + "accessIPv4": "1.2.3.4", + "accessIPv6": "fead::1234", "image": { "id": "5", "links": [ @@ -3401,7 +3920,9 @@ class ServerXMLSerializationTest(test.TestCase): } output = serializer.serialize(fixture, 'show') - actual = minidom.parseString(output.replace(" ", "")) + print output + root = etree.XML(output) + xmlutil.validate_schema(root, 'server') expected_server_href = self.SERVER_HREF expected_server_bookmark = self.SERVER_BOOKMARK @@ -3409,50 +3930,57 @@ class ServerXMLSerializationTest(test.TestCase): expected_flavor_bookmark = self.FLAVOR_BOOKMARK expected_now = self.TIMESTAMP expected_uuid = FAKE_UUID - expected = minidom.parseString(""" - <server id="1" - uuid="%(expected_uuid)s" - userId="fake" - tenantId="fake" - xmlns="http://docs.openstack.org/compute/api/v1.1" - xmlns:atom="http://www.w3.org/2005/Atom" - name="test_server" - description="fakedescription" - updated="%(expected_now)s" - created="%(expected_now)s" - hostId="e4d909c290d0fb1ca068ffaddf22cbd0" - status="BUILD" - progress="0"> - <atom:link href="%(expected_server_href)s" rel="self"/> - <atom:link href="%(expected_server_bookmark)s" rel="bookmark"/> - <image id="5"> - <atom:link rel="bookmark" href="%(expected_image_bookmark)s"/> - </image> - <flavor id="1"> - <atom:link rel="bookmark" href="%(expected_flavor_bookmark)s"/> - </flavor> - <metadata> - <meta key="Open"> - Stack - </meta> - <meta key="Number"> - 1 - </meta> - </metadata> - <addresses> - <network id="network_one"> - <ip version="4" addr="67.23.10.138"/> - <ip version="6" addr="::babe:67.23.10.138"/> - </network> - <network id="network_two"> - <ip version="4" addr="67.23.10.139"/> - <ip version="6" addr="::babe:67.23.10.139"/> - </network> - </addresses> - </server> - """.replace(" ", "") % (locals())) - - self.assertEqual(expected.toxml(), actual.toxml()) + server_dict = fixture['server'] + + for key in ['name', 'id', 'uuid', 'created', 'accessIPv4', + 'updated', 'progress', 'status', 'hostId', + 'accessIPv6']: + self.assertEqual(root.get(key), str(server_dict[key])) + + link_nodes = root.findall('{0}link'.format(ATOMNS)) + self.assertEqual(len(link_nodes), 2) + for i, link in enumerate(server_dict['links']): + for key, value in link.items(): + self.assertEqual(link_nodes[i].get(key), value) + + metadata_root = root.find('{0}metadata'.format(NS)) + metadata_elems = metadata_root.findall('{0}meta'.format(NS)) + self.assertEqual(len(metadata_elems), 2) + for i, metadata_elem in enumerate(metadata_elems): + (meta_key, meta_value) = server_dict['metadata'].items()[i] + self.assertEqual(str(metadata_elem.get('key')), str(meta_key)) + self.assertEqual(str(metadata_elem.text).strip(), str(meta_value)) + + image_root = root.find('{0}image'.format(NS)) + self.assertEqual(image_root.get('id'), server_dict['image']['id']) + link_nodes = image_root.findall('{0}link'.format(ATOMNS)) + self.assertEqual(len(link_nodes), 1) + for i, link in enumerate(server_dict['image']['links']): + for key, value in link.items(): + self.assertEqual(link_nodes[i].get(key), value) + + flavor_root = root.find('{0}flavor'.format(NS)) + self.assertEqual(flavor_root.get('id'), server_dict['flavor']['id']) + link_nodes = flavor_root.findall('{0}link'.format(ATOMNS)) + self.assertEqual(len(link_nodes), 1) + for i, link in enumerate(server_dict['flavor']['links']): + for key, value in link.items(): + self.assertEqual(link_nodes[i].get(key), value) + + addresses_root = root.find('{0}addresses'.format(NS)) + addresses_dict = server_dict['addresses'] + network_elems = addresses_root.findall('{0}network'.format(NS)) + self.assertEqual(len(network_elems), 2) + for i, network_elem in enumerate(network_elems): + network = addresses_dict.items()[i] + self.assertEqual(str(network_elem.get('id')), str(network[0])) + ip_elems = network_elem.findall('{0}ip'.format(NS)) + for z, ip_elem in enumerate(ip_elems): + ip = network[1][z] + self.assertEqual(str(ip_elem.get('version')), + str(ip['version'])) + self.assertEqual(str(ip_elem.get('addr')), + str(ip['addr'])) def test_create(self): serializer = servers.ServerXMLSerializer() @@ -3469,6 +3997,8 @@ class ServerXMLSerializationTest(test.TestCase): "name": "test_server", "description": "fakedescription", "status": "BUILD", + "accessIPv4": "1.2.3.4", + "accessIPv6": "fead::1234", "hostId": "e4d909c290d0fb1ca068ffaddf22cbd0", "adminPass": "test_password", "image": { @@ -3529,7 +4059,9 @@ class ServerXMLSerializationTest(test.TestCase): } output = serializer.serialize(fixture, 'create') - actual = minidom.parseString(output.replace(" ", "")) + print output + root = etree.XML(output) + xmlutil.validate_schema(root, 'server') expected_server_href = self.SERVER_HREF expected_server_bookmark = self.SERVER_BOOKMARK @@ -3537,51 +4069,57 @@ class ServerXMLSerializationTest(test.TestCase): expected_flavor_bookmark = self.FLAVOR_BOOKMARK expected_now = self.TIMESTAMP expected_uuid = FAKE_UUID - expected = minidom.parseString(""" - <server id="1" - uuid="%(expected_uuid)s" - userId="fake" - tenantId="fake" - xmlns="http://docs.openstack.org/compute/api/v1.1" - xmlns:atom="http://www.w3.org/2005/Atom" - name="test_server" - description="fakedescription" - updated="%(expected_now)s" - created="%(expected_now)s" - hostId="e4d909c290d0fb1ca068ffaddf22cbd0" - status="BUILD" - adminPass="test_password" - progress="0"> - <atom:link href="%(expected_server_href)s" rel="self"/> - <atom:link href="%(expected_server_bookmark)s" rel="bookmark"/> - <image id="5"> - <atom:link rel="bookmark" href="%(expected_image_bookmark)s"/> - </image> - <flavor id="1"> - <atom:link rel="bookmark" href="%(expected_flavor_bookmark)s"/> - </flavor> - <metadata> - <meta key="Open"> - Stack - </meta> - <meta key="Number"> - 1 - </meta> - </metadata> - <addresses> - <network id="network_one"> - <ip version="4" addr="67.23.10.138"/> - <ip version="6" addr="::babe:67.23.10.138"/> - </network> - <network id="network_two"> - <ip version="4" addr="67.23.10.139"/> - <ip version="6" addr="::babe:67.23.10.139"/> - </network> - </addresses> - </server> - """.replace(" ", "") % (locals())) - - self.assertEqual(expected.toxml(), actual.toxml()) + server_dict = fixture['server'] + + for key in ['name', 'id', 'uuid', 'created', 'accessIPv4', + 'updated', 'progress', 'status', 'hostId', + 'accessIPv6', 'adminPass']: + self.assertEqual(root.get(key), str(server_dict[key])) + + link_nodes = root.findall('{0}link'.format(ATOMNS)) + self.assertEqual(len(link_nodes), 2) + for i, link in enumerate(server_dict['links']): + for key, value in link.items(): + self.assertEqual(link_nodes[i].get(key), value) + + metadata_root = root.find('{0}metadata'.format(NS)) + metadata_elems = metadata_root.findall('{0}meta'.format(NS)) + self.assertEqual(len(metadata_elems), 2) + for i, metadata_elem in enumerate(metadata_elems): + (meta_key, meta_value) = server_dict['metadata'].items()[i] + self.assertEqual(str(metadata_elem.get('key')), str(meta_key)) + self.assertEqual(str(metadata_elem.text).strip(), str(meta_value)) + + image_root = root.find('{0}image'.format(NS)) + self.assertEqual(image_root.get('id'), server_dict['image']['id']) + link_nodes = image_root.findall('{0}link'.format(ATOMNS)) + self.assertEqual(len(link_nodes), 1) + for i, link in enumerate(server_dict['image']['links']): + for key, value in link.items(): + self.assertEqual(link_nodes[i].get(key), value) + + flavor_root = root.find('{0}flavor'.format(NS)) + self.assertEqual(flavor_root.get('id'), server_dict['flavor']['id']) + link_nodes = flavor_root.findall('{0}link'.format(ATOMNS)) + self.assertEqual(len(link_nodes), 1) + for i, link in enumerate(server_dict['flavor']['links']): + for key, value in link.items(): + self.assertEqual(link_nodes[i].get(key), value) + + addresses_root = root.find('{0}addresses'.format(NS)) + addresses_dict = server_dict['addresses'] + network_elems = addresses_root.findall('{0}network'.format(NS)) + self.assertEqual(len(network_elems), 2) + for i, network_elem in enumerate(network_elems): + network = addresses_dict.items()[i] + self.assertEqual(str(network_elem.get('id')), str(network[0])) + ip_elems = network_elem.findall('{0}ip'.format(NS)) + for z, ip_elem in enumerate(ip_elems): + ip = network[1][z] + self.assertEqual(str(ip_elem.get('version')), + str(ip['version'])) + self.assertEqual(str(ip_elem.get('addr')), + str(ip['addr'])) def test_index(self): serializer = servers.ServerXMLSerializer() @@ -3622,23 +4160,21 @@ class ServerXMLSerializationTest(test.TestCase): ]} output = serializer.serialize(fixture, 'index') - actual = minidom.parseString(output.replace(" ", "")) - - expected = minidom.parseString(""" - <servers xmlns="http://docs.openstack.org/compute/api/v1.1" - xmlns:atom="http://www.w3.org/2005/Atom"> - <server id="1" name="test_server"> - <atom:link href="%(expected_server_href)s" rel="self"/> - <atom:link href="%(expected_server_bookmark)s" rel="bookmark"/> - </server> - <server id="2" name="test_server_2"> - <atom:link href="%(expected_server_href_2)s" rel="self"/> - <atom:link href="%(expected_server_bookmark_2)s" rel="bookmark"/> - </server> - </servers> - """.replace(" ", "") % (locals())) - - self.assertEqual(expected.toxml(), actual.toxml()) + print output + root = etree.XML(output) + xmlutil.validate_schema(root, 'servers_index') + server_elems = root.findall('{0}server'.format(NS)) + self.assertEqual(len(server_elems), 2) + for i, server_elem in enumerate(server_elems): + server_dict = fixture['servers'][i] + for key in ['name', 'id']: + self.assertEqual(server_elem.get(key), str(server_dict[key])) + + link_nodes = server_elem.findall('{0}link'.format(ATOMNS)) + self.assertEqual(len(link_nodes), 2) + for i, link in enumerate(server_dict['links']): + for key, value in link.items(): + self.assertEqual(link_nodes[i].get(key), value) def test_detail(self): serializer = servers.ServerXMLSerializer() @@ -3664,6 +4200,8 @@ class ServerXMLSerializationTest(test.TestCase): "name": "test_server", "description": "fakedescription", "status": "BUILD", + "accessIPv4": "1.2.3.4", + "accessIPv6": "fead::1234", "hostId": 'e4d909c290d0fb1ca068ffaddf22cbd0', "image": { "id": "5", @@ -3720,6 +4258,8 @@ class ServerXMLSerializationTest(test.TestCase): "name": "test_server_2", "description": "fakedescription", "status": "ACTIVE", + "accessIPv4": "1.2.3.4", + "accessIPv6": "fead::1234", "hostId": 'e4d909c290d0fb1ca068ffaddf22cbd0', "image": { "id": "5", @@ -3768,77 +4308,63 @@ class ServerXMLSerializationTest(test.TestCase): ]} output = serializer.serialize(fixture, 'detail') - actual = minidom.parseString(output.replace(" ", "")) - - expected = minidom.parseString(""" - <servers xmlns="http://docs.openstack.org/compute/api/v1.1" - xmlns:atom="http://www.w3.org/2005/Atom"> - <server id="1" - uuid="%(expected_uuid)s" - userId="fake" - tenantId="fake" - name="test_server" - description="fakedescription" - updated="%(expected_now)s" - created="%(expected_now)s" - hostId="e4d909c290d0fb1ca068ffaddf22cbd0" - status="BUILD" - progress="0"> - <atom:link href="%(expected_server_href)s" rel="self"/> - <atom:link href="%(expected_server_bookmark)s" rel="bookmark"/> - <image id="5"> - <atom:link rel="bookmark" href="%(expected_image_bookmark)s"/> - </image> - <flavor id="1"> - <atom:link rel="bookmark" href="%(expected_flavor_bookmark)s"/> - </flavor> - <metadata> - <meta key="Number"> - 1 - </meta> - </metadata> - <addresses> - <network id="network_one"> - <ip version="4" addr="67.23.10.138"/> - <ip version="6" addr="::babe:67.23.10.138"/> - </network> - </addresses> - </server> - <server id="2" - uuid="%(expected_uuid)s" - userId="fake" - tenantId="fake" - name="test_server_2" - description="fakedescription" - updated="%(expected_now)s" - created="%(expected_now)s" - hostId="e4d909c290d0fb1ca068ffaddf22cbd0" - status="ACTIVE" - progress="100"> - <atom:link href="%(expected_server_href_2)s" rel="self"/> - <atom:link href="%(expected_server_bookmark_2)s" rel="bookmark"/> - <image id="5"> - <atom:link rel="bookmark" href="%(expected_image_bookmark)s"/> - </image> - <flavor id="1"> - <atom:link rel="bookmark" href="%(expected_flavor_bookmark)s"/> - </flavor> - <metadata> - <meta key="Number"> - 2 - </meta> - </metadata> - <addresses> - <network id="network_one"> - <ip version="4" addr="67.23.10.138"/> - <ip version="6" addr="::babe:67.23.10.138"/> - </network> - </addresses> - </server> - </servers> - """.replace(" ", "") % (locals())) - - self.assertEqual(expected.toxml(), actual.toxml()) + print output + root = etree.XML(output) + xmlutil.validate_schema(root, 'servers') + server_elems = root.findall('{0}server'.format(NS)) + self.assertEqual(len(server_elems), 2) + for i, server_elem in enumerate(server_elems): + server_dict = fixture['servers'][i] + + for key in ['name', 'id', 'uuid', 'created', 'accessIPv4', + 'updated', 'progress', 'status', 'hostId', + 'accessIPv6']: + self.assertEqual(server_elem.get(key), str(server_dict[key])) + + link_nodes = server_elem.findall('{0}link'.format(ATOMNS)) + self.assertEqual(len(link_nodes), 2) + for i, link in enumerate(server_dict['links']): + for key, value in link.items(): + self.assertEqual(link_nodes[i].get(key), value) + + metadata_root = server_elem.find('{0}metadata'.format(NS)) + metadata_elems = metadata_root.findall('{0}meta'.format(NS)) + for i, metadata_elem in enumerate(metadata_elems): + (meta_key, meta_value) = server_dict['metadata'].items()[i] + self.assertEqual(str(metadata_elem.get('key')), str(meta_key)) + self.assertEqual(str(metadata_elem.text).strip(), + str(meta_value)) + + image_root = server_elem.find('{0}image'.format(NS)) + self.assertEqual(image_root.get('id'), server_dict['image']['id']) + link_nodes = image_root.findall('{0}link'.format(ATOMNS)) + self.assertEqual(len(link_nodes), 1) + for i, link in enumerate(server_dict['image']['links']): + for key, value in link.items(): + self.assertEqual(link_nodes[i].get(key), value) + + flavor_root = server_elem.find('{0}flavor'.format(NS)) + self.assertEqual(flavor_root.get('id'), + server_dict['flavor']['id']) + link_nodes = flavor_root.findall('{0}link'.format(ATOMNS)) + self.assertEqual(len(link_nodes), 1) + for i, link in enumerate(server_dict['flavor']['links']): + for key, value in link.items(): + self.assertEqual(link_nodes[i].get(key), value) + + addresses_root = server_elem.find('{0}addresses'.format(NS)) + addresses_dict = server_dict['addresses'] + network_elems = addresses_root.findall('{0}network'.format(NS)) + for i, network_elem in enumerate(network_elems): + network = addresses_dict.items()[i] + self.assertEqual(str(network_elem.get('id')), str(network[0])) + ip_elems = network_elem.findall('{0}ip'.format(NS)) + for z, ip_elem in enumerate(ip_elems): + ip = network[1][z] + self.assertEqual(str(ip_elem.get('version')), + str(ip['version'])) + self.assertEqual(str(ip_elem.get('addr')), + str(ip['addr'])) def test_update(self): serializer = servers.ServerXMLSerializer() @@ -3856,6 +4382,8 @@ class ServerXMLSerializationTest(test.TestCase): "description": "fakedescription", "status": "BUILD", "hostId": 'e4d909c290d0fb1ca068ffaddf22cbd0', + "accessIPv4": "1.2.3.4", + "accessIPv6": "fead::1234", "image": { "id": "5", "links": [ @@ -3914,7 +4442,9 @@ class ServerXMLSerializationTest(test.TestCase): } output = serializer.serialize(fixture, 'update') - actual = minidom.parseString(output.replace(" ", "")) + print output + root = etree.XML(output) + xmlutil.validate_schema(root, 'server') expected_server_href = self.SERVER_HREF expected_server_bookmark = self.SERVER_BOOKMARK @@ -3922,47 +4452,54 @@ class ServerXMLSerializationTest(test.TestCase): expected_flavor_bookmark = self.FLAVOR_BOOKMARK expected_now = self.TIMESTAMP expected_uuid = FAKE_UUID - expected = minidom.parseString(""" - <server id="1" - uuid="%(expected_uuid)s" - userId="fake" - tenantId="fake" - xmlns="http://docs.openstack.org/compute/api/v1.1" - xmlns:atom="http://www.w3.org/2005/Atom" - name="test_server" - description="fakedescription" - updated="%(expected_now)s" - created="%(expected_now)s" - hostId="e4d909c290d0fb1ca068ffaddf22cbd0" - status="BUILD" - progress="0"> - <atom:link href="%(expected_server_href)s" rel="self"/> - <atom:link href="%(expected_server_bookmark)s" rel="bookmark"/> - <image id="5"> - <atom:link rel="bookmark" href="%(expected_image_bookmark)s"/> - </image> - <flavor id="1"> - <atom:link rel="bookmark" href="%(expected_flavor_bookmark)s"/> - </flavor> - <metadata> - <meta key="Open"> - Stack - </meta> - <meta key="Number"> - 1 - </meta> - </metadata> - <addresses> - <network id="network_one"> - <ip version="4" addr="67.23.10.138"/> - <ip version="6" addr="::babe:67.23.10.138"/> - </network> - <network id="network_two"> - <ip version="4" addr="67.23.10.139"/> - <ip version="6" addr="::babe:67.23.10.139"/> - </network> - </addresses> - </server> - """.replace(" ", "") % (locals())) - - self.assertEqual(expected.toxml(), actual.toxml()) + server_dict = fixture['server'] + + for key in ['name', 'id', 'uuid', 'created', 'accessIPv4', + 'updated', 'progress', 'status', 'hostId', + 'accessIPv6']: + self.assertEqual(root.get(key), str(server_dict[key])) + + link_nodes = root.findall('{0}link'.format(ATOMNS)) + self.assertEqual(len(link_nodes), 2) + for i, link in enumerate(server_dict['links']): + for key, value in link.items(): + self.assertEqual(link_nodes[i].get(key), value) + + metadata_root = root.find('{0}metadata'.format(NS)) + metadata_elems = metadata_root.findall('{0}meta'.format(NS)) + self.assertEqual(len(metadata_elems), 2) + for i, metadata_elem in enumerate(metadata_elems): + (meta_key, meta_value) = server_dict['metadata'].items()[i] + self.assertEqual(str(metadata_elem.get('key')), str(meta_key)) + self.assertEqual(str(metadata_elem.text).strip(), str(meta_value)) + + image_root = root.find('{0}image'.format(NS)) + self.assertEqual(image_root.get('id'), server_dict['image']['id']) + link_nodes = image_root.findall('{0}link'.format(ATOMNS)) + self.assertEqual(len(link_nodes), 1) + for i, link in enumerate(server_dict['image']['links']): + for key, value in link.items(): + self.assertEqual(link_nodes[i].get(key), value) + + flavor_root = root.find('{0}flavor'.format(NS)) + self.assertEqual(flavor_root.get('id'), server_dict['flavor']['id']) + link_nodes = flavor_root.findall('{0}link'.format(ATOMNS)) + self.assertEqual(len(link_nodes), 1) + for i, link in enumerate(server_dict['flavor']['links']): + for key, value in link.items(): + self.assertEqual(link_nodes[i].get(key), value) + + addresses_root = root.find('{0}addresses'.format(NS)) + addresses_dict = server_dict['addresses'] + network_elems = addresses_root.findall('{0}network'.format(NS)) + self.assertEqual(len(network_elems), 2) + for i, network_elem in enumerate(network_elems): + network = addresses_dict.items()[i] + self.assertEqual(str(network_elem.get('id')), str(network[0])) + ip_elems = network_elem.findall('{0}ip'.format(NS)) + for z, ip_elem in enumerate(ip_elems): + ip = network[1][z] + self.assertEqual(str(ip_elem.get('version')), + str(ip['version'])) + self.assertEqual(str(ip_elem.get('addr')), + str(ip['addr'])) diff --git a/nova/tests/integrated/api/client.py b/nova/tests/integrated/api/client.py index 035a35aab..67c35fe6b 100644 --- a/nova/tests/integrated/api/client.py +++ b/nova/tests/integrated/api/client.py @@ -48,6 +48,14 @@ class OpenStackApiAuthenticationException(OpenStackApiException): response) +class OpenStackApiAuthorizationException(OpenStackApiException): + def __init__(self, response=None, message=None): + if not message: + message = _("Authorization error") + super(OpenStackApiAuthorizationException, self).__init__(message, + response) + + class OpenStackApiNotFoundException(OpenStackApiException): def __init__(self, response=None, message=None): if not message: @@ -69,6 +77,8 @@ class TestOpenStackClient(object): self.auth_user = auth_user self.auth_key = auth_key self.auth_uri = auth_uri + # default project_id + self.project_id = 'openstack' def request(self, url, method='GET', body=None, headers=None): _headers = {'Content-Type': 'application/json'} @@ -105,7 +115,8 @@ class TestOpenStackClient(object): auth_uri = self.auth_uri headers = {'X-Auth-User': self.auth_user, - 'X-Auth-Key': self.auth_key} + 'X-Auth-Key': self.auth_key, + 'X-Auth-Project-Id': self.project_id} response = self.request(auth_uri, headers=headers) @@ -127,7 +138,8 @@ class TestOpenStackClient(object): # NOTE(justinsb): httplib 'helpfully' converts headers to lower case base_uri = auth_result['x-server-management-url'] - full_uri = base_uri + relative_uri + + full_uri = '%s/%s' % (base_uri, relative_uri) headers = kwargs.setdefault('headers', {}) headers['X-Auth-Token'] = auth_result['x-auth-token'] @@ -141,6 +153,8 @@ class TestOpenStackClient(object): if not http_status in check_response_status: if http_status == 404: raise OpenStackApiNotFoundException(response=response) + elif http_status == 401: + raise OpenStackApiAuthorizationException(response=response) else: raise OpenStackApiException( message=_("Unexpected status code"), @@ -256,7 +270,8 @@ class TestOpenStackClient(object): def post_server_volume(self, server_id, volume_attachment): return self.api_post('/servers/%s/os-volume_attachments' % - (server_id), volume_attachment)['volumeAttachment'] + (server_id), volume_attachment + )['volumeAttachment'] def delete_server_volume(self, server_id, attachment_id): return self.api_delete('/servers/%s/os-volume_attachments/%s' % diff --git a/nova/tests/integrated/test_login.py b/nova/tests/integrated/test_login.py index 06359a52f..9d1925bc0 100644 --- a/nova/tests/integrated/test_login.py +++ b/nova/tests/integrated/test_login.py @@ -59,6 +59,12 @@ class LoginTest(integrated_helpers._IntegratedTestBase): self.assertRaises(client.OpenStackApiAuthenticationException, bad_credentials_api.get_flavors) + def test_good_login_bad_project(self): + """Test that I get a 401 with valid user/pass but bad project""" + self.api.project_id = 'openstackBAD' + + self.assertRaises(client.OpenStackApiAuthorizationException, + self.api.get_flavors) if __name__ == "__main__": unittest.main() diff --git a/nova/tests/test_api.py b/nova/tests/test_api.py index 2011ae756..526d1c490 100644 --- a/nova/tests/test_api.py +++ b/nova/tests/test_api.py @@ -32,6 +32,7 @@ from nova import context from nova import exception from nova import test from nova import wsgi +from nova.api import auth from nova.api import ec2 from nova.api.ec2 import apirequest from nova.api.ec2 import cloud @@ -199,7 +200,7 @@ class ApiEc2TestCase(test.TestCase): # NOTE(vish): skipping the Authorizer roles = ['sysadmin', 'netadmin'] ctxt = context.RequestContext('fake', 'fake', roles=roles) - self.app = wsgi.InjectContext(ctxt, + self.app = auth.InjectContext(ctxt, ec2.Requestify(ec2.Authorizer(ec2.Executor()), 'nova.api.ec2.cloud.CloudController')) diff --git a/nova/tests/test_compute.py b/nova/tests/test_compute.py index 4f5d36f14..993a87f23 100644 --- a/nova/tests/test_compute.py +++ b/nova/tests/test_compute.py @@ -347,7 +347,7 @@ class ComputeTestCase(test.TestCase): self.assertEquals(msg['priority'], 'INFO') self.assertEquals(msg['event_type'], 'compute.instance.create') payload = msg['payload'] - self.assertEquals(payload['tenant_id'], self.project_id) + self.assertEquals(payload['project_id'], self.project_id) self.assertEquals(payload['user_id'], self.user_id) self.assertEquals(payload['instance_id'], instance_id) self.assertEquals(payload['instance_type'], 'm1.tiny') @@ -371,7 +371,7 @@ class ComputeTestCase(test.TestCase): self.assertEquals(msg['priority'], 'INFO') self.assertEquals(msg['event_type'], 'compute.instance.delete') payload = msg['payload'] - self.assertEquals(payload['tenant_id'], self.project_id) + self.assertEquals(payload['project_id'], self.project_id) self.assertEquals(payload['user_id'], self.user_id) self.assertEquals(payload['instance_id'], instance_id) self.assertEquals(payload['instance_type'], 'm1.tiny') @@ -454,7 +454,7 @@ class ComputeTestCase(test.TestCase): self.assertEquals(msg['priority'], 'INFO') self.assertEquals(msg['event_type'], 'compute.instance.resize.prep') payload = msg['payload'] - self.assertEquals(payload['tenant_id'], self.project_id) + self.assertEquals(payload['project_id'], self.project_id) self.assertEquals(payload['user_id'], self.user_id) self.assertEquals(payload['instance_id'], instance_id) self.assertEquals(payload['instance_type'], 'm1.tiny') diff --git a/nova/tests/test_network.py b/nova/tests/test_network.py index 0ead680ee..0b8539442 100644 --- a/nova/tests/test_network.py +++ b/nova/tests/test_network.py @@ -15,6 +15,7 @@ # License for the specific language governing permissions and limitations # under the License. +from nova import context from nova import db from nova import exception from nova import log as logging @@ -41,6 +42,7 @@ class FakeModel(dict): networks = [{'id': 0, + 'uuid': "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", 'label': 'test0', 'injected': False, 'multi_host': False, @@ -60,6 +62,7 @@ networks = [{'id': 0, 'project_id': 'fake_project', 'vpn_public_address': '192.168.0.2'}, {'id': 1, + 'uuid': "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb", 'label': 'test1', 'injected': False, 'multi_host': False, @@ -108,11 +111,14 @@ floating_ip_fields = {'id': 0, vifs = [{'id': 0, 'address': 'DE:AD:BE:EF:00:00', + 'uuid': '00000000-0000-0000-0000-0000000000000000', 'network_id': 0, 'network': FakeModel(**networks[0]), 'instance_id': 0}, {'id': 1, 'address': 'DE:AD:BE:EF:00:01', + 'uuid': '00000000-0000-0000-0000-0000000000000001', + 'network_id': 0, 'network_id': 1, 'network': FakeModel(**networks[1]), 'instance_id': 0}] @@ -123,6 +129,8 @@ class FlatNetworkTestCase(test.TestCase): super(FlatNetworkTestCase, self).setUp() self.network = network_manager.FlatManager(host=HOST) self.network.db = db + self.context = context.RequestContext('testuser', 'testproject', + is_admin=False) def test_get_instance_nw_info(self): self.mox.StubOutWithMock(db, 'fixed_ip_get_by_instance') @@ -163,6 +171,8 @@ class FlatNetworkTestCase(test.TestCase): 'ips': 'DONTCARE', 'label': 'test%s' % i, 'mac': 'DE:AD:BE:EF:00:0%s' % i, + 'vif_uuid': ('00000000-0000-0000-0000-000000000000000%s' % + i), 'rxtx_cap': 'DONTCARE', 'should_create_vlan': False, 'should_create_bridge': False} @@ -178,12 +188,73 @@ class FlatNetworkTestCase(test.TestCase): 'netmask': '255.255.255.0'}] self.assertDictListMatch(nw[1]['ips'], check) + def test_validate_networks(self): + self.mox.StubOutWithMock(db, 'network_get_all_by_uuids') + self.mox.StubOutWithMock(db, "fixed_ip_get_by_address") + + requested_networks = [("bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb", + "192.168.1.100")] + db.network_get_all_by_uuids(mox.IgnoreArg(), + mox.IgnoreArg()).AndReturn(networks) + + fixed_ips[1]['network'] = FakeModel(**networks[1]) + fixed_ips[1]['instance'] = None + db.fixed_ip_get_by_address(mox.IgnoreArg(), + mox.IgnoreArg()).AndReturn(fixed_ips[1]) + + self.mox.ReplayAll() + self.network.validate_networks(self.context, requested_networks) + + def test_validate_networks_none_requested_networks(self): + self.network.validate_networks(self.context, None) + + def test_validate_networks_empty_requested_networks(self): + requested_networks = [] + self.mox.ReplayAll() + + self.network.validate_networks(self.context, requested_networks) + + def test_validate_networks_invalid_fixed_ip(self): + self.mox.StubOutWithMock(db, 'network_get_all_by_uuids') + requested_networks = [(1, "192.168.0.100.1")] + db.network_get_all_by_uuids(mox.IgnoreArg(), + mox.IgnoreArg()).AndReturn(networks) + self.mox.ReplayAll() + + self.assertRaises(exception.FixedIpInvalid, + self.network.validate_networks, None, + requested_networks) + + def test_validate_networks_empty_fixed_ip(self): + self.mox.StubOutWithMock(db, 'network_get_all_by_uuids') + + requested_networks = [(1, "")] + db.network_get_all_by_uuids(mox.IgnoreArg(), + mox.IgnoreArg()).AndReturn(networks) + self.mox.ReplayAll() + + self.assertRaises(exception.FixedIpInvalid, + self.network.validate_networks, + None, requested_networks) + + def test_validate_networks_none_fixed_ip(self): + self.mox.StubOutWithMock(db, 'network_get_all_by_uuids') + + requested_networks = [(1, None)] + db.network_get_all_by_uuids(mox.IgnoreArg(), + mox.IgnoreArg()).AndReturn(networks) + self.mox.ReplayAll() + + self.network.validate_networks(None, requested_networks) + class VlanNetworkTestCase(test.TestCase): def setUp(self): super(VlanNetworkTestCase, self).setUp() self.network = network_manager.VlanManager(host=HOST) self.network.db = db + self.context = context.RequestContext('testuser', 'testproject', + is_admin=False) def test_vpn_allocate_fixed_ip(self): self.mox.StubOutWithMock(db, 'fixed_ip_associate') @@ -227,7 +298,7 @@ class VlanNetworkTestCase(test.TestCase): network = dict(networks[0]) network['vpn_private_address'] = '192.168.0.2' - self.network.allocate_fixed_ip(None, 0, network) + self.network.allocate_fixed_ip(self.context, 0, network) def test_create_networks_too_big(self): self.assertRaises(ValueError, self.network.create_networks, None, @@ -238,6 +309,68 @@ class VlanNetworkTestCase(test.TestCase): num_networks=100, vlan_start=1, cidr='192.168.0.1/24', network_size=100) + def test_validate_networks(self): + self.mox.StubOutWithMock(db, 'network_get_all_by_uuids') + self.mox.StubOutWithMock(db, "fixed_ip_get_by_address") + + requested_networks = [("bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb", + "192.168.1.100")] + db.network_get_all_by_uuids(mox.IgnoreArg(), + mox.IgnoreArg(), + mox.IgnoreArg()).AndReturn(networks) + + fixed_ips[1]['network'] = FakeModel(**networks[1]) + fixed_ips[1]['instance'] = None + db.fixed_ip_get_by_address(mox.IgnoreArg(), + mox.IgnoreArg()).AndReturn(fixed_ips[1]) + + self.mox.ReplayAll() + self.network.validate_networks(self.context, requested_networks) + + def test_validate_networks_none_requested_networks(self): + self.network.validate_networks(self.context, None) + + def test_validate_networks_empty_requested_networks(self): + requested_networks = [] + self.mox.ReplayAll() + + self.network.validate_networks(self.context, requested_networks) + + def test_validate_networks_invalid_fixed_ip(self): + self.mox.StubOutWithMock(db, 'network_get_all_by_uuids') + requested_networks = [(1, "192.168.0.100.1")] + db.network_get_all_by_uuids(mox.IgnoreArg(), + mox.IgnoreArg(), + mox.IgnoreArg()).AndReturn(networks) + self.mox.ReplayAll() + + self.assertRaises(exception.FixedIpInvalid, + self.network.validate_networks, self.context, + requested_networks) + + def test_validate_networks_empty_fixed_ip(self): + self.mox.StubOutWithMock(db, 'network_get_all_by_uuids') + + requested_networks = [(1, "")] + db.network_get_all_by_uuids(mox.IgnoreArg(), + mox.IgnoreArg(), + mox.IgnoreArg()).AndReturn(networks) + self.mox.ReplayAll() + + self.assertRaises(exception.FixedIpInvalid, + self.network.validate_networks, + self.context, requested_networks) + + def test_validate_networks_none_fixed_ip(self): + self.mox.StubOutWithMock(db, 'network_get_all_by_uuids') + + requested_networks = [(1, None)] + db.network_get_all_by_uuids(mox.IgnoreArg(), + mox.IgnoreArg(), + mox.IgnoreArg()).AndReturn(networks) + self.mox.ReplayAll() + self.network.validate_networks(self.context, requested_networks) + class CommonNetworkTestCase(test.TestCase): diff --git a/nova/tests/test_nova_manage.py b/nova/tests/test_nova_manage.py index 9c6563f14..f5ea68a03 100644 --- a/nova/tests/test_nova_manage.py +++ b/nova/tests/test_nova_manage.py @@ -28,55 +28,45 @@ sys.dont_write_bytecode = True import imp nova_manage = imp.load_source('nova_manage.py', NOVA_MANAGE_PATH) sys.dont_write_bytecode = False +import mox +import stubout -import netaddr from nova import context from nova import db -from nova import flags +from nova import exception from nova import test - -FLAGS = flags.FLAGS +from nova.tests.db import fakes as db_fakes class FixedIpCommandsTestCase(test.TestCase): def setUp(self): super(FixedIpCommandsTestCase, self).setUp() - cidr = '10.0.0.0/24' - net = netaddr.IPNetwork(cidr) - net_info = {'bridge': 'fakebr', - 'bridge_interface': 'fakeeth', - 'dns': FLAGS.flat_network_dns, - 'cidr': cidr, - 'netmask': str(net.netmask), - 'gateway': str(net[1]), - 'broadcast': str(net.broadcast), - 'dhcp_start': str(net[2])} - self.network = db.network_create_safe(context.get_admin_context(), - net_info) - num_ips = len(net) - for index in range(num_ips): - address = str(net[index]) - reserved = (index == 1 or index == 2) - db.fixed_ip_create(context.get_admin_context(), - {'network_id': self.network['id'], - 'address': address, - 'reserved': reserved}) + self.stubs = stubout.StubOutForTesting() + db_fakes.stub_out_db_network_api(self.stubs) self.commands = nova_manage.FixedIpCommands() def tearDown(self): - db.network_delete_safe(context.get_admin_context(), self.network['id']) super(FixedIpCommandsTestCase, self).tearDown() + self.stubs.UnsetAll() def test_reserve(self): - self.commands.reserve('10.0.0.100') + self.commands.reserve('192.168.0.100') address = db.fixed_ip_get_by_address(context.get_admin_context(), - '10.0.0.100') + '192.168.0.100') self.assertEqual(address['reserved'], True) + def test_reserve_nonexistent_address(self): + self.assertRaises(SystemExit, + self.commands.reserve, + '55.55.55.55') + def test_unreserve(self): - db.fixed_ip_update(context.get_admin_context(), '10.0.0.100', - {'reserved': True}) - self.commands.unreserve('10.0.0.100') + self.commands.unreserve('192.168.0.100') address = db.fixed_ip_get_by_address(context.get_admin_context(), - '10.0.0.100') + '192.168.0.100') self.assertEqual(address['reserved'], False) + + def test_unreserve_nonexistent_address(self): + self.assertRaises(SystemExit, + self.commands.unreserve, + '55.55.55.55') diff --git a/nova/tests/test_utils.py b/nova/tests/test_utils.py index ec5098a37..28e366a8e 100644 --- a/nova/tests/test_utils.py +++ b/nova/tests/test_utils.py @@ -384,3 +384,13 @@ class ToPrimitiveTestCase(test.TestCase): def test_typeerror(self): x = bytearray # Class, not instance self.assertEquals(utils.to_primitive(x), u"<type 'bytearray'>") + + def test_nasties(self): + def foo(): + pass + x = [datetime, foo, dir] + ret = utils.to_primitive(x) + self.assertEquals(len(ret), 3) + self.assertTrue(ret[0].startswith(u"<module 'datetime' from ")) + self.assertTrue(ret[1].startswith(u'<function foo at 0x')) + self.assertEquals(ret[2], u'<built-in function dir>') diff --git a/nova/utils.py b/nova/utils.py index 54126f644..fc4bbd53b 100644 --- a/nova/utils.py +++ b/nova/utils.py @@ -295,7 +295,7 @@ EASIER_PASSWORD_SYMBOLS = ('23456789' # Removed: 0, 1 def usage_from_instance(instance_ref, **kw): usage_info = dict( - tenant_id=instance_ref['project_id'], + project_id=instance_ref['project_id'], user_id=instance_ref['user_id'], instance_id=instance_ref['id'], instance_type=instance_ref['instance_type']['name'], @@ -547,11 +547,17 @@ def to_primitive(value, convert_instances=False, level=0): Therefore, convert_instances=True is lossy ... be aware. """ - if inspect.isclass(value): - return unicode(value) + nasty = [inspect.ismodule, inspect.isclass, inspect.ismethod, + inspect.isfunction, inspect.isgeneratorfunction, + inspect.isgenerator, inspect.istraceback, inspect.isframe, + inspect.iscode, inspect.isbuiltin, inspect.isroutine, + inspect.isabstract] + for test in nasty: + if test(value): + return unicode(value) if level > 3: - return [] + return '?' # The try block may not be necessary after the class check above, # but just in case ... @@ -838,3 +844,19 @@ def bool_from_str(val): return True if int(val) else False except ValueError: return val.lower() == 'true' + + +def is_valid_ipv4(address): + """valid the address strictly as per format xxx.xxx.xxx.xxx. + where xxx is a value between 0 and 255. + """ + parts = address.split(".") + if len(parts) != 4: + return False + for item in parts: + try: + if not 0 <= int(item) <= 255: + return False + except ValueError: + return False + return True diff --git a/nova/virt/driver.py b/nova/virt/driver.py index 20af2666d..93290aba7 100644 --- a/nova/virt/driver.py +++ b/nova/virt/driver.py @@ -62,11 +62,41 @@ def block_device_info_get_mapping(block_device_info): class ComputeDriver(object): """Base class for compute drivers. - Lots of documentation is currently on fake.py. + The interface to this class talks in terms of 'instances' (Amazon EC2 and + internal Nova terminology), by which we mean 'running virtual machine' + (XenAPI terminology) or domain (Xen or libvirt terminology). + + An instance has an ID, which is the identifier chosen by Nova to represent + the instance further up the stack. This is unfortunately also called a + 'name' elsewhere. As far as this layer is concerned, 'instance ID' and + 'instance name' are synonyms. + + Note that the instance ID or name is not human-readable or + customer-controlled -- it's an internal ID chosen by Nova. At the + nova.virt layer, instances do not have human-readable names at all -- such + things are only known higher up the stack. + + Most virtualization platforms will also have their own identity schemes, + to uniquely identify a VM or domain. These IDs must stay internal to the + platform-specific layer, and never escape the connection interface. The + platform-specific layer is responsible for keeping track of which instance + ID maps to which platform-specific ID, and vice versa. + + In contrast, the list_disks and list_interfaces calls may return + platform-specific IDs. These identify a specific virtual disk or specific + virtual network interface, and these IDs are opaque to the rest of Nova. + + Some methods here take an instance of nova.compute.service.Instance. This + is the datastructure used by nova.compute to store details regarding an + instance, and pass them into this layer. This layer is responsible for + translating that generic datastructure into terms that are specific to the + virtualization platform. + """ def init_host(self, host): - """Adopt existing VM's running here""" + """Initialize anything that is necessary for the driver to function, + including catching up with currently running VM's on the given host.""" # TODO(Vek): Need to pass context in for access to auth_token raise NotImplementedError() @@ -74,6 +104,7 @@ class ComputeDriver(object): """Get the current status of an instance, by name (not ID!) Returns a dict containing: + :state: the running state, one of the power_state codes :max_mem: (int) the maximum memory in KBytes allowed :mem: (int) the memory in KBytes used by the domain @@ -84,6 +115,10 @@ class ComputeDriver(object): raise NotImplementedError() def list_instances(self): + """ + Return the names of all the instances known to the virtualization + layer, as a list. + """ # TODO(Vek): Need to pass context in for access to auth_token raise NotImplementedError() @@ -94,28 +129,53 @@ class ComputeDriver(object): def spawn(self, context, instance, network_info=None, block_device_info=None): - """Launch a VM for the specified instance""" + """ + Create a new instance/VM/domain on the virtualization platform. + + Once this successfully completes, the instance should be + running (power_state.RUNNING). + + If this fails, any partial instance should be completely + cleaned up, and the virtualization platform should be in the state + that it was before this call began. + + :param context: security context + :param instance: Instance of {nova.compute.service.Instance}. + This function should use the data there to guide + the creation of the new instance. + :param network_info: + :py:meth:`~nova.network.manager.NetworkManager.get_instance_nw_info` + :param block_device_info: + """ raise NotImplementedError() def destroy(self, instance, network_info, cleanup=True): """Destroy (shutdown and delete) the specified instance. The given parameter is an instance of nova.compute.service.Instance, - and so the instance is being specified as instance.name. - - The work will be done asynchronously. This function returns a - task that allows the caller to detect when it is complete. If the instance is not found (for example if networking failed), this function should still succeed. It's probably a good idea to log a warning in that case. + :param instance: Instance of {nova.compute.service.Instance} and so + the instance is being specified as instance.name. + :param network_info: + :py:meth:`~nova.network.manager.NetworkManager.get_instance_nw_info` + :param cleanup: + """ # TODO(Vek): Need to pass context in for access to auth_token raise NotImplementedError() def reboot(self, instance, network_info): - """Reboot specified VM""" + """Reboot the specified instance. + + :param instance: Instance of {nova.compute.service.Instance} and so + the instance is being specified as instance.name. + :param network_info: + :py:meth:`~nova.network.manager.NetworkManager.get_instance_nw_info` + """ # TODO(Vek): Need to pass context in for access to auth_token raise NotImplementedError() @@ -140,31 +200,60 @@ class ComputeDriver(object): raise NotImplementedError() def get_host_ip_addr(self): + """ + Retrieves the IP address of the dom0 + """ # TODO(Vek): Need to pass context in for access to auth_token raise NotImplementedError() def attach_volume(self, context, instance_id, volume_id, mountpoint): + """Attach the disk at device_path to the instance at mountpoint""" raise NotImplementedError() def detach_volume(self, context, instance_id, volume_id): + """Detach the disk attached to the instance at mountpoint""" raise NotImplementedError() - def compare_cpu(self, context, cpu_info): + def compare_cpu(self, cpu_info): + """Compares given cpu info against host + + Before attempting to migrate a VM to this host, + compare_cpu is called to ensure that the VM will + actually run here. + + :param cpu_info: (str) JSON structure describing the source CPU. + :returns: None if migration is acceptable + :raises: :py:class:`~nova.exception.InvalidCPUInfo` if migration + is not acceptable. + """ raise NotImplementedError() def migrate_disk_and_power_off(self, instance, dest): - """Transfers the VHD of a running instance to another host, then shuts - off the instance copies over the COW disk""" + """ + Transfers the disk of a running instance in multiple phases, turning + off the instance before the end. + """ # TODO(Vek): Need to pass context in for access to auth_token raise NotImplementedError() def snapshot(self, context, instance, image_id): - """Create snapshot from a running VM instance.""" + """ + Snapshots the specified instance. + + The given parameter is an instance of nova.compute.service.Instance, + and so the instance is being specified as instance.name. + + The second parameter is the name of the snapshot. + """ raise NotImplementedError() def finish_migration(self, context, instance, disk_info, network_info, resize_instance): - """Completes a resize, turning on the migrated instance""" + """Completes a resize, turning on the migrated instance + + :param network_info: + :py:meth:`~nova.network.manager.NetworkManager.get_instance_nw_info` + """ raise NotImplementedError() def revert_migration(self, instance): @@ -173,7 +262,7 @@ class ComputeDriver(object): raise NotImplementedError() def pause(self, instance, callback): - """Pause VM instance""" + """Pause the specified instance.""" # TODO(Vek): Need to pass context in for access to auth_token raise NotImplementedError() @@ -218,15 +307,15 @@ class ComputeDriver(object): post_method, recover_method): """Spawning live_migration operation for distributing high-load. - :params ctxt: security context - :params instance_ref: + :param ctxt: security context + :param instance_ref: nova.db.sqlalchemy.models.Instance object instance object that is migrated. - :params dest: destination host - :params post_method: + :param dest: destination host + :param post_method: post operation method. expected nova.compute.manager.post_live_migration. - :params recover_method: + :param recover_method: recovery method when any exception occurs. expected nova.compute.manager.recover_live_migration. @@ -235,15 +324,69 @@ class ComputeDriver(object): raise NotImplementedError() def refresh_security_group_rules(self, security_group_id): + """This method is called after a change to security groups. + + All security groups and their associated rules live in the datastore, + and calling this method should apply the updated rules to instances + running the specified security group. + + An error should be raised if the operation cannot complete. + + """ # TODO(Vek): Need to pass context in for access to auth_token raise NotImplementedError() def refresh_security_group_members(self, security_group_id): + """This method is called when a security group is added to an instance. + + This message is sent to the virtualization drivers on hosts that are + running an instance that belongs to a security group that has a rule + that references the security group identified by `security_group_id`. + It is the responsiblity of this method to make sure any rules + that authorize traffic flow with members of the security group are + updated and any new members can communicate, and any removed members + cannot. + + Scenario: + * we are running on host 'H0' and we have an instance 'i-0'. + * instance 'i-0' is a member of security group 'speaks-b' + * group 'speaks-b' has an ingress rule that authorizes group 'b' + * another host 'H1' runs an instance 'i-1' + * instance 'i-1' is a member of security group 'b' + + When 'i-1' launches or terminates we will recieve the message + to update members of group 'b', at which time we will make + any changes needed to the rules for instance 'i-0' to allow + or deny traffic coming from 'i-1', depending on if it is being + added or removed from the group. + + In this scenario, 'i-1' could just as easily have been running on our + host 'H0' and this method would still have been called. The point was + that this method isn't called on the host where instances of that + group are running (as is the case with + :method:`refresh_security_group_rules`) but is called where references + are made to authorizing those instances. + + An error should be raised if the operation cannot complete. + + """ # TODO(Vek): Need to pass context in for access to auth_token raise NotImplementedError() def refresh_provider_fw_rules(self, security_group_id): - """See: nova/virt/fake.py for docs.""" + """This triggers a firewall update based on database changes. + + When this is called, rules have either been added or removed from the + datastore. You can retrieve rules with + :method:`nova.db.api.provider_fw_rule_get_all`. + + Provider rules take precedence over security group rules. If an IP + would be allowed by a security group ingress rule, but blocked by + a provider rule, then packets from the IP are dropped. This includes + intra-project traffic in the case of the allow_project_net_traffic + flag for the libvirt-derived classes. + + """ # TODO(Vek): Need to pass context in for access to auth_token raise NotImplementedError() @@ -284,18 +427,38 @@ class ComputeDriver(object): raise NotImplementedError() def set_admin_password(self, context, instance_id, new_pass=None): - """Set the root/admin password for an instance on this server.""" + """ + Set the root password on the specified instance. + + The first parameter is an instance of nova.compute.service.Instance, + and so the instance is being specified as instance.name. The second + parameter is the value of the new password. + """ raise NotImplementedError() def inject_file(self, instance, b64_path, b64_contents): - """Create a file on the VM instance. The file path and contents - should be base64-encoded. + """ + Writes a file on the specified instance. + + The first parameter is an instance of nova.compute.service.Instance, + and so the instance is being specified as instance.name. The second + parameter is the base64-encoded path to which the file is to be + written on the instance; the third is the contents of the file, also + base64-encoded. """ # TODO(Vek): Need to pass context in for access to auth_token raise NotImplementedError() def agent_update(self, instance, url, md5hash): - """Update agent on the VM instance.""" + """ + Update agent on the specified instance. + + The first parameter is an instance of nova.compute.service.Instance, + and so the instance is being specified as instance.name. The second + parameter is the URL of the agent to be fetched and updated on the + instance; the third is the md5 hash of the file for verification + purposes. + """ # TODO(Vek): Need to pass context in for access to auth_token raise NotImplementedError() @@ -322,3 +485,83 @@ class ComputeDriver(object): """Plugs in VIFs to networks.""" # TODO(Vek): Need to pass context in for access to auth_token raise NotImplementedError() + + def update_host_status(self): + """Refresh host stats""" + raise NotImplementedError() + + def get_host_stats(self, refresh=False): + """Return currently known host stats""" + raise NotImplementedError() + + def list_disks(self, instance_name): + """ + Return the IDs of all the virtual disks attached to the specified + instance, as a list. These IDs are opaque to the caller (they are + only useful for giving back to this layer as a parameter to + disk_stats). These IDs only need to be unique for a given instance. + + Note that this function takes an instance ID. + """ + raise NotImplementedError() + + def list_interfaces(self, instance_name): + """ + Return the IDs of all the virtual network interfaces attached to the + specified instance, as a list. These IDs are opaque to the caller + (they are only useful for giving back to this layer as a parameter to + interface_stats). These IDs only need to be unique for a given + instance. + + Note that this function takes an instance ID. + """ + raise NotImplementedError() + + def resize(self, instance, flavor): + """ + Resizes/Migrates the specified instance. + + The flavor parameter determines whether or not the instance RAM and + disk space are modified, and if so, to what size. + """ + raise NotImplementedError() + + def block_stats(self, instance_name, disk_id): + """ + Return performance counters associated with the given disk_id on the + given instance_name. These are returned as [rd_req, rd_bytes, wr_req, + wr_bytes, errs], where rd indicates read, wr indicates write, req is + the total number of I/O requests made, bytes is the total number of + bytes transferred, and errs is the number of requests held up due to a + full pipeline. + + All counters are long integers. + + This method is optional. On some platforms (e.g. XenAPI) performance + statistics can be retrieved directly in aggregate form, without Nova + having to do the aggregation. On those platforms, this method is + unused. + + Note that this function takes an instance ID. + """ + raise NotImplementedError() + + def interface_stats(self, instance_name, iface_id): + """ + Return performance counters associated with the given iface_id on the + given instance_id. These are returned as [rx_bytes, rx_packets, + rx_errs, rx_drop, tx_bytes, tx_packets, tx_errs, tx_drop], where rx + indicates receive, tx indicates transmit, bytes and packets indicate + the total number of bytes or packets transferred, and errs and dropped + is the total number of packets failed / dropped. + + All counters are long integers. + + This method is optional. On some platforms (e.g. XenAPI) performance + statistics can be retrieved directly in aggregate form, without Nova + having to do the aggregation. On those platforms, this method is + unused. + + Note that this function takes an instance ID. + """ + raise NotImplementedError() diff --git a/nova/virt/fake.py b/nova/virt/fake.py index dc0628772..13b7aeab5 100644 --- a/nova/virt/fake.py +++ b/nova/virt/fake.py @@ -48,37 +48,7 @@ class FakeInstance(object): class FakeConnection(driver.ComputeDriver): - """ - The interface to this class talks in terms of 'instances' (Amazon EC2 and - internal Nova terminology), by which we mean 'running virtual machine' - (XenAPI terminology) or domain (Xen or libvirt terminology). - - An instance has an ID, which is the identifier chosen by Nova to represent - the instance further up the stack. This is unfortunately also called a - 'name' elsewhere. As far as this layer is concerned, 'instance ID' and - 'instance name' are synonyms. - - Note that the instance ID or name is not human-readable or - customer-controlled -- it's an internal ID chosen by Nova. At the - nova.virt layer, instances do not have human-readable names at all -- such - things are only known higher up the stack. - - Most virtualization platforms will also have their own identity schemes, - to uniquely identify a VM or domain. These IDs must stay internal to the - platform-specific layer, and never escape the connection interface. The - platform-specific layer is responsible for keeping track of which instance - ID maps to which platform-specific ID, and vice versa. - - In contrast, the list_disks and list_interfaces calls may return - platform-specific IDs. These identify a specific virtual disk or specific - virtual network interface, and these IDs are opaque to the rest of Nova. - - Some methods here take an instance of nova.compute.service.Instance. This - is the datastructure used by nova.compute to store details regarding an - instance, and pass them into this layer. This layer is responsible for - translating that generic datastructure into terms that are specific to the - virtualization platform. - """ + """Fake hypervisor driver""" def __init__(self): self.instances = {} @@ -105,17 +75,9 @@ class FakeConnection(driver.ComputeDriver): return cls._instance def init_host(self, host): - """ - Initialize anything that is necessary for the driver to function, - including catching up with currently running VM's on the given host. - """ return def list_instances(self): - """ - Return the names of all the instances known to the virtualization - layer, as a list. - """ return self.instances.keys() def _map_to_instance_info(self, instance): @@ -131,167 +93,54 @@ class FakeConnection(driver.ComputeDriver): def spawn(self, context, instance, network_info=None, block_device_info=None): - """ - Create a new instance/VM/domain on the virtualization platform. - - The given parameter is an instance of nova.compute.service.Instance. - This function should use the data there to guide the creation of - the new instance. - - The work will be done asynchronously. This function returns a - task that allows the caller to detect when it is complete. - - Once this successfully completes, the instance should be - running (power_state.RUNNING). - - If this fails, any partial instance should be completely - cleaned up, and the virtualization platform should be in the state - that it was before this call began. - """ - name = instance.name state = power_state.RUNNING fake_instance = FakeInstance(name, state) self.instances[name] = fake_instance def snapshot(self, context, instance, name): - """ - Snapshots the specified instance. - - The given parameter is an instance of nova.compute.service.Instance, - and so the instance is being specified as instance.name. - - The second parameter is the name of the snapshot. - - The work will be done asynchronously. This function returns a - task that allows the caller to detect when it is complete. - """ pass def reboot(self, instance, network_info): - """ - Reboot the specified instance. - - The given parameter is an instance of nova.compute.service.Instance, - and so the instance is being specified as instance.name. - - The work will be done asynchronously. This function returns a - task that allows the caller to detect when it is complete. - """ pass def get_host_ip_addr(self): - """ - Retrieves the IP address of the dom0 - """ - pass + return '192.168.0.1' def resize(self, instance, flavor): - """ - Resizes/Migrates the specified instance. - - The flavor parameter determines whether or not the instance RAM and - disk space are modified, and if so, to what size. - - The work will be done asynchronously. This function returns a task - that allows the caller to detect when it is complete. - """ pass def set_admin_password(self, instance, new_pass): - """ - Set the root password on the specified instance. - - The first parameter is an instance of nova.compute.service.Instance, - and so the instance is being specified as instance.name. The second - parameter is the value of the new password. - - The work will be done asynchronously. This function returns a - task that allows the caller to detect when it is complete. - """ pass def inject_file(self, instance, b64_path, b64_contents): - """ - Writes a file on the specified instance. - - The first parameter is an instance of nova.compute.service.Instance, - and so the instance is being specified as instance.name. The second - parameter is the base64-encoded path to which the file is to be - written on the instance; the third is the contents of the file, also - base64-encoded. - - The work will be done asynchronously. This function returns a - task that allows the caller to detect when it is complete. - """ pass def agent_update(self, instance, url, md5hash): - """ - Update agent on the specified instance. - - The first parameter is an instance of nova.compute.service.Instance, - and so the instance is being specified as instance.name. The second - parameter is the URL of the agent to be fetched and updated on the - instance; the third is the md5 hash of the file for verification - purposes. - - The work will be done asynchronously. This function returns a - task that allows the caller to detect when it is complete. - """ pass def rescue(self, context, instance, callback, network_info): - """ - Rescue the specified instance. - """ pass def unrescue(self, instance, callback, network_info): - """ - Unrescue the specified instance. - """ pass def poll_rescued_instances(self, timeout): - """Poll for rescued instances""" pass def migrate_disk_and_power_off(self, instance, dest): - """ - Transfers the disk of a running instance in multiple phases, turning - off the instance before the end. - """ - pass - - def attach_disk(self, instance, disk_info): - """ - Attaches the disk to an instance given the metadata disk_info - """ pass def pause(self, instance, callback): - """ - Pause the specified instance. - """ pass def unpause(self, instance, callback): - """ - Unpause the specified instance. - """ pass def suspend(self, instance, callback): - """ - suspend the specified instance - """ pass def resume(self, instance, callback): - """ - resume the specified instance - """ pass def destroy(self, instance, network_info, cleanup=True): @@ -303,25 +152,12 @@ class FakeConnection(driver.ComputeDriver): (key, self.instances)) def attach_volume(self, instance_name, device_path, mountpoint): - """Attach the disk at device_path to the instance at mountpoint""" return True def detach_volume(self, instance_name, mountpoint): - """Detach the disk attached to the instance at mountpoint""" return True def get_info(self, instance_name): - """ - Get a block of information about the given instance. This is returned - as a dictionary containing 'state': The power_state of the instance, - 'max_mem': The maximum memory for the instance, in KiB, 'mem': The - current memory the instance has, in KiB, 'num_cpu': The current number - of virtual CPUs the instance has, 'cpu_time': The total CPU time used - by the instance, in nanoseconds. - - This method should raise exception.NotFound if the hypervisor has no - knowledge of the instance - """ if instance_name not in self.instances: raise exception.InstanceNotFound(instance_id=instance_name) i = self.instances[instance_name] @@ -332,69 +168,18 @@ class FakeConnection(driver.ComputeDriver): 'cpu_time': 0} def get_diagnostics(self, instance_name): - pass + return {} def list_disks(self, instance_name): - """ - Return the IDs of all the virtual disks attached to the specified - instance, as a list. These IDs are opaque to the caller (they are - only useful for giving back to this layer as a parameter to - disk_stats). These IDs only need to be unique for a given instance. - - Note that this function takes an instance ID. - """ return ['A_DISK'] def list_interfaces(self, instance_name): - """ - Return the IDs of all the virtual network interfaces attached to the - specified instance, as a list. These IDs are opaque to the caller - (they are only useful for giving back to this layer as a parameter to - interface_stats). These IDs only need to be unique for a given - instance. - - Note that this function takes an instance ID. - """ return ['A_VIF'] def block_stats(self, instance_name, disk_id): - """ - Return performance counters associated with the given disk_id on the - given instance_name. These are returned as [rd_req, rd_bytes, wr_req, - wr_bytes, errs], where rd indicates read, wr indicates write, req is - the total number of I/O requests made, bytes is the total number of - bytes transferred, and errs is the number of requests held up due to a - full pipeline. - - All counters are long integers. - - This method is optional. On some platforms (e.g. XenAPI) performance - statistics can be retrieved directly in aggregate form, without Nova - having to do the aggregation. On those platforms, this method is - unused. - - Note that this function takes an instance ID. - """ return [0L, 0L, 0L, 0L, None] def interface_stats(self, instance_name, iface_id): - """ - Return performance counters associated with the given iface_id on the - given instance_id. These are returned as [rx_bytes, rx_packets, - rx_errs, rx_drop, tx_bytes, tx_packets, tx_errs, tx_drop], where rx - indicates receive, tx indicates transmit, bytes and packets indicate - the total number of bytes or packets transferred, and errs and dropped - is the total number of packets failed / dropped. - - All counters are long integers. - - This method is optional. On some platforms (e.g. XenAPI) performance - statistics can be retrieved directly in aggregate form, without Nova - having to do the aggregation. On those platforms, this method is - unused. - - Note that this function takes an instance ID. - """ return [0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L] def get_console_output(self, instance): @@ -416,67 +201,12 @@ class FakeConnection(driver.ComputeDriver): 'password': 'fakepassword'} def refresh_security_group_rules(self, security_group_id): - """This method is called after a change to security groups. - - All security groups and their associated rules live in the datastore, - and calling this method should apply the updated rules to instances - running the specified security group. - - An error should be raised if the operation cannot complete. - - """ return True def refresh_security_group_members(self, security_group_id): - """This method is called when a security group is added to an instance. - - This message is sent to the virtualization drivers on hosts that are - running an instance that belongs to a security group that has a rule - that references the security group identified by `security_group_id`. - It is the responsiblity of this method to make sure any rules - that authorize traffic flow with members of the security group are - updated and any new members can communicate, and any removed members - cannot. - - Scenario: - * we are running on host 'H0' and we have an instance 'i-0'. - * instance 'i-0' is a member of security group 'speaks-b' - * group 'speaks-b' has an ingress rule that authorizes group 'b' - * another host 'H1' runs an instance 'i-1' - * instance 'i-1' is a member of security group 'b' - - When 'i-1' launches or terminates we will recieve the message - to update members of group 'b', at which time we will make - any changes needed to the rules for instance 'i-0' to allow - or deny traffic coming from 'i-1', depending on if it is being - added or removed from the group. - - In this scenario, 'i-1' could just as easily have been running on our - host 'H0' and this method would still have been called. The point was - that this method isn't called on the host where instances of that - group are running (as is the case with - :method:`refresh_security_group_rules`) but is called where references - are made to authorizing those instances. - - An error should be raised if the operation cannot complete. - - """ return True def refresh_provider_fw_rules(self): - """This triggers a firewall update based on database changes. - - When this is called, rules have either been added or removed from the - datastore. You can retrieve rules with - :method:`nova.db.api.provider_fw_rule_get_all`. - - Provider rules take precedence over security group rules. If an IP - would be allowed by a security group ingress rule, but blocked by - a provider rule, then packets from the IP are dropped. This includes - intra-project traffic in the case of the allow_project_net_traffic - flag for the libvirt-derived classes. - - """ pass def update_available_resource(self, ctxt, host): diff --git a/nova/virt/libvirt/vif.py b/nova/virt/libvirt/vif.py index 5a91a4e28..0b7438011 100644 --- a/nova/virt/libvirt/vif.py +++ b/nova/virt/libvirt/vif.py @@ -100,10 +100,12 @@ class LibvirtBridgeDriver(VIFDriver): class LibvirtOpenVswitchDriver(VIFDriver): """VIF driver for Open vSwitch.""" + def get_dev_name(_self, iface_id): + return "tap-" + iface_id[0:15] + def plug(self, instance, network, mapping): - vif_id = str(instance['id']) + "-" + str(network['id']) - dev = "tap-%s" % vif_id - iface_id = "nova-" + vif_id + iface_id = mapping['vif_uuid'] + dev = self.get_dev_name(iface_id) if not linux_net._device_exists(dev): utils.execute('ip', 'tuntap', 'add', dev, 'mode', 'tap', run_as_root=True) @@ -127,11 +129,10 @@ class LibvirtOpenVswitchDriver(VIFDriver): def unplug(self, instance, network, mapping): """Unplug the VIF from the network by deleting the port from the bridge.""" - vif_id = str(instance['id']) + "-" + str(network['id']) - dev = "tap-%s" % vif_id + dev = self.get_dev_name(mapping['vif_uuid']) try: utils.execute('ovs-vsctl', 'del-port', - network['bridge'], dev, run_as_root=True) + FLAGS.libvirt_ovs_bridge, dev, run_as_root=True) utils.execute('ip', 'link', 'delete', dev, run_as_root=True) except exception.ProcessExecutionError: LOG.warning(_("Failed while unplugging vif of instance '%s'"), diff --git a/nova/virt/xenapi/vif.py b/nova/virt/xenapi/vif.py index 527602243..2f25efeb2 100644 --- a/nova/virt/xenapi/vif.py +++ b/nova/virt/xenapi/vif.py @@ -128,12 +128,12 @@ class XenAPIOpenVswitchDriver(VIFDriver): vif_rec['VM'] = vm_ref vif_rec['MAC'] = network_mapping['mac'] vif_rec['MTU'] = '1500' - vif_id = "nova-" + str(instance['id']) + "-" + str(network['id']) vif_rec['qos_algorithm_type'] = "" vif_rec['qos_algorithm_params'] = {} # OVS on the hypervisor monitors this key and uses it to # set the iface-id attribute - vif_rec['other_config'] = {"nicira-iface-id": vif_id} + vif_rec['other_config'] = \ + {"nicira-iface-id": network_mapping['vif_uuid']} return vif_rec def unplug(self, instance, network, mapping): diff --git a/nova/wsgi.py b/nova/wsgi.py index f2846aa73..09b45be5a 100644 --- a/nova/wsgi.py +++ b/nova/wsgi.py @@ -271,18 +271,6 @@ class Middleware(Application): return self.process_response(response) -class InjectContext(Middleware): - """Add a 'nova.context' to WSGI environ.""" - def __init__(self, context, *args, **kwargs): - self.context = context - super(InjectContext, self).__init__(*args, **kwargs) - - @webob.dec.wsgify(RequestClass=Request) - def __call__(self, req): - req.environ['nova.context'] = self.context - return self.application - - class Debug(Middleware): """Helper class for debugging a WSGI application. |
