summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorVishvananda Ishaya <vishvananda@gmail.com>2011-08-26 16:16:17 -0700
committerVishvananda Ishaya <vishvananda@gmail.com>2011-08-26 16:16:17 -0700
commitaaec0f17abccf0b6d842b21d5c6e34fb972afa2c (patch)
tree0e8563dd271e6cd5d9f1f46fc4c1e0c6bf1a110b
parentde0a17310e7228aa96263243851a89fb016f9730 (diff)
parent847d6aecb64d7abece4d4f426f26a9561ffb1e51 (diff)
downloadnova-aaec0f17abccf0b6d842b21d5c6e34fb972afa2c.tar.gz
nova-aaec0f17abccf0b6d842b21d5c6e34fb972afa2c.tar.xz
nova-aaec0f17abccf0b6d842b21d5c6e34fb972afa2c.zip
merged trunk
-rw-r--r--Authors2
-rwxr-xr-xbin/nova-api1
-rwxr-xr-xbin/nova-api-ec21
-rwxr-xr-xbin/nova-api-os1
-rwxr-xr-xbin/nova-compute1
-rwxr-xr-xbin/nova-manage89
-rwxr-xr-xbin/nova-network1
-rwxr-xr-xbin/nova-objectstore1
-rwxr-xr-xbin/nova-scheduler3
-rwxr-xr-xbin/nova-volume1
-rw-r--r--etc/nova/api-paste.ini24
-rw-r--r--nova/api/auth.py1
-rw-r--r--nova/api/ec2/__init__.py21
-rw-r--r--nova/api/ec2/admin.py4
-rw-r--r--nova/api/openstack/__init__.py29
-rw-r--r--nova/api/openstack/auth.py118
-rw-r--r--nova/api/openstack/contrib/createserverext.py66
-rw-r--r--nova/api/openstack/contrib/security_groups.py113
-rw-r--r--nova/api/openstack/contrib/volumes.py36
-rw-r--r--nova/api/openstack/contrib/volumetypes.py197
-rw-r--r--nova/api/openstack/create_instance_helper.py115
-rw-r--r--nova/api/openstack/extensions.py18
-rw-r--r--nova/api/openstack/flavors.py3
-rw-r--r--nova/api/openstack/images.py7
-rw-r--r--nova/api/openstack/schemas/v1.1/server.rng50
-rw-r--r--nova/api/openstack/schemas/v1.1/servers.rng6
-rw-r--r--nova/api/openstack/schemas/v1.1/servers_index.rng12
-rw-r--r--nova/api/openstack/servers.py51
-rw-r--r--nova/api/openstack/views/addresses.py23
-rw-r--r--nova/api/openstack/views/flavors.py15
-rw-r--r--nova/api/openstack/views/images.py16
-rw-r--r--nova/api/openstack/views/servers.py17
-rw-r--r--nova/api/openstack/wsgi.py10
-rw-r--r--nova/auth/manager.py16
-rw-r--r--nova/cloudpipe/pipelib.py9
-rw-r--r--nova/compute/api.py148
-rw-r--r--nova/compute/manager.py11
-rw-r--r--nova/db/api.py94
-rw-r--r--nova/db/sqlalchemy/api.py400
-rw-r--r--nova/db/sqlalchemy/migrate_repo/versions/039_add_instances_accessip.py48
-rw-r--r--nova/db/sqlalchemy/migrate_repo/versions/040_add_uuid_to_networks.py43
-rw-r--r--nova/db/sqlalchemy/migrate_repo/versions/041_add_config_drive_to_instances.py38
-rw-r--r--nova/db/sqlalchemy/migrate_repo/versions/042_add_volume_types_and_extradata.py115
-rw-r--r--nova/db/sqlalchemy/migration.py3
-rw-r--r--nova/db/sqlalchemy/models.py53
-rw-r--r--nova/exception.py59
-rw-r--r--nova/flags.py11
-rw-r--r--nova/ipv6/account_identifier.py6
-rw-r--r--nova/ipv6/rfc2462.py4
-rw-r--r--nova/network/api.py9
-rw-r--r--nova/network/manager.py123
-rw-r--r--nova/notifier/api.py27
-rw-r--r--nova/notifier/list_notifier.py68
-rw-r--r--nova/tests/api/openstack/__init__.py29
-rw-r--r--nova/tests/api/openstack/contrib/test_createserverext.py306
-rw-r--r--nova/tests/api/openstack/contrib/test_floating_ips.py20
-rw-r--r--nova/tests/api/openstack/contrib/test_keypairs.py8
-rw-r--r--nova/tests/api/openstack/contrib/test_multinic_xs.py8
-rw-r--r--nova/tests/api/openstack/contrib/test_quotas.py11
-rw-r--r--nova/tests/api/openstack/contrib/test_rescue.py4
-rw-r--r--nova/tests/api/openstack/contrib/test_security_groups.py300
-rw-r--r--nova/tests/api/openstack/contrib/test_virtual_interfaces.py2
-rw-r--r--nova/tests/api/openstack/extensions/foxinsocks.py10
-rw-r--r--nova/tests/api/openstack/test_auth.py8
-rw-r--r--nova/tests/api/openstack/test_extensions.py33
-rw-r--r--nova/tests/api/openstack/test_flavors.py88
-rw-r--r--nova/tests/api/openstack/test_flavors_extra_specs.py22
-rw-r--r--nova/tests/api/openstack/test_image_metadata.py28
-rw-r--r--nova/tests/api/openstack/test_images.py100
-rw-r--r--nova/tests/api/openstack/test_server_actions.py209
-rw-r--r--nova/tests/api/openstack/test_server_metadata.py56
-rw-r--r--nova/tests/api/openstack/test_servers.py1406
-rw-r--r--nova/tests/api/openstack/test_volume_types.py171
-rw-r--r--nova/tests/api/openstack/test_volume_types_extra_specs.py181
-rw-r--r--nova/tests/integrated/api/client.py21
-rw-r--r--nova/tests/integrated/integrated_helpers.py109
-rw-r--r--nova/tests/integrated/test_login.py33
-rw-r--r--nova/tests/integrated/test_servers.py2
-rw-r--r--nova/tests/integrated/test_volumes.py17
-rw-r--r--nova/tests/monkey_patch_example/__init__.py33
-rw-r--r--nova/tests/monkey_patch_example/example_a.py29
-rw-r--r--nova/tests/monkey_patch_example/example_b.py30
-rw-r--r--nova/tests/notifier/__init__.py16
-rw-r--r--nova/tests/notifier/test_list_notifier.py88
-rw-r--r--nova/tests/test_auth.py1
-rw-r--r--nova/tests/test_compute.py26
-rw-r--r--nova/tests/test_instance_types.py68
-rw-r--r--nova/tests/test_ipv6.py38
-rw-r--r--nova/tests/test_network.py130
-rw-r--r--nova/tests/test_notifier.py21
-rw-r--r--nova/tests/test_nova_manage.py206
-rw-r--r--nova/tests/test_utils.py55
-rw-r--r--nova/tests/test_versions.py61
-rw-r--r--nova/tests/test_volume_types.py207
-rw-r--r--nova/tests/test_volume_types_extra_specs.py132
-rw-r--r--nova/utils.py71
-rw-r--r--nova/virt/disk.py32
-rw-r--r--nova/virt/driver.py291
-rw-r--r--nova/virt/fake.py276
-rw-r--r--nova/virt/libvirt.xml.template7
-rw-r--r--nova/virt/libvirt/connection.py72
-rw-r--r--nova/virt/xenapi/vm_utils.py15
-rw-r--r--nova/virt/xenapi/vmops.py2
-rw-r--r--nova/volume/api.py84
-rw-r--r--nova/volume/driver.py2
-rw-r--r--nova/volume/volume_types.py129
-rw-r--r--po/ast.po4
-rw-r--r--po/cs.po4
-rw-r--r--po/da.po4
-rw-r--r--po/de.po22
-rw-r--r--po/en_AU.po4
-rw-r--r--po/en_GB.po25
-rw-r--r--po/es.po69
-rw-r--r--po/fr.po52
-rw-r--r--po/it.po87
-rw-r--r--po/ja.po50
-rw-r--r--po/pt_BR.po52
-rw-r--r--po/ru.po22
-rw-r--r--po/tl.po4
-rw-r--r--po/uk.po12
-rw-r--r--po/zh_CN.po169
-rw-r--r--po/zh_TW.po15
-rwxr-xr-xrun_tests.sh2
123 files changed, 6751 insertions, 1488 deletions
diff --git a/Authors b/Authors
index 864679929..2d2cde97e 100644
--- a/Authors
+++ b/Authors
@@ -18,6 +18,7 @@ Chiradeep Vittal <chiradeep@cloud.com>
Chmouel Boudjnah <chmouel@chmouel.com>
Chris Behrens <cbehrens@codestud.com>
Christian Berendt <berendt@b1-systems.de>
+Christopher MacGown <chris@pistoncloud.com>
Chuck Short <zulcss@ubuntu.com>
Cory Wright <corywright@gmail.com>
Dan Prince <dan.prince@rackspace.com>
@@ -99,6 +100,7 @@ Scott Moser <smoser@ubuntu.com>
Soren Hansen <soren.hansen@rackspace.com>
Stephanie Reese <reese.sm@gmail.com>
Thierry Carrez <thierry@openstack.org>
+Tim Simpson <tim.simpson@rackspace.com>
Todd Willey <todd@ansolabs.com>
Trey Morris <trey.morris@rackspace.com>
Troy Toman <troy.toman@rackspace.com>
diff --git a/bin/nova-api b/bin/nova-api
index 38e2624d8..d8635978e 100755
--- a/bin/nova-api
+++ b/bin/nova-api
@@ -45,6 +45,7 @@ if __name__ == '__main__':
utils.default_flagfile()
flags.FLAGS(sys.argv)
logging.setup()
+ utils.monkey_patch()
servers = []
for api in flags.FLAGS.enabled_apis:
servers.append(service.WSGIService(api))
diff --git a/bin/nova-api-ec2 b/bin/nova-api-ec2
index df50f713d..9f82a69e4 100755
--- a/bin/nova-api-ec2
+++ b/bin/nova-api-ec2
@@ -41,6 +41,7 @@ if __name__ == '__main__':
utils.default_flagfile()
flags.FLAGS(sys.argv)
logging.setup()
+ utils.monkey_patch()
server = service.WSGIService('ec2')
service.serve(server)
service.wait()
diff --git a/bin/nova-api-os b/bin/nova-api-os
index 374e850ea..83a808987 100755
--- a/bin/nova-api-os
+++ b/bin/nova-api-os
@@ -41,6 +41,7 @@ if __name__ == '__main__':
utils.default_flagfile()
flags.FLAGS(sys.argv)
logging.setup()
+ utils.monkey_patch()
server = service.WSGIService('osapi')
service.serve(server)
service.wait()
diff --git a/bin/nova-compute b/bin/nova-compute
index 5239fae72..0c69a8129 100755
--- a/bin/nova-compute
+++ b/bin/nova-compute
@@ -43,6 +43,7 @@ if __name__ == '__main__':
utils.default_flagfile()
flags.FLAGS(sys.argv)
logging.setup()
+ utils.monkey_patch()
server = service.Service.create(binary='nova-compute')
service.serve(server)
service.wait()
diff --git a/bin/nova-manage b/bin/nova-manage
index 8e6419c0b..890cde0b8 100755
--- a/bin/nova-manage
+++ b/bin/nova-manage
@@ -134,7 +134,7 @@ class VpnCommands(object):
help='Project name')
def list(self, project=None):
"""Print a listing of the VPN data for one or all projects."""
-
+ print "WARNING: This method only works with deprecated auth"
print "%-12s\t" % 'project',
print "%-20s\t" % 'ip:port',
print "%-20s\t" % 'private_ip',
@@ -170,17 +170,22 @@ class VpnCommands(object):
def spawn(self):
"""Run all VPNs."""
+ print "WARNING: This method only works with deprecated auth"
for p in reversed(self.manager.get_projects()):
if not self._vpn_for(p.id):
print 'spawning %s' % p.id
- self.pipe.launch_vpn_instance(p.id)
+ self.pipe.launch_vpn_instance(p.id, p.project_manager_id)
time.sleep(10)
@args('--project', dest="project_id", metavar='<Project name>',
help='Project name')
- def run(self, project_id):
- """Start the VPN for a given project."""
- self.pipe.launch_vpn_instance(project_id)
+ @args('--user', dest="user_id", metavar='<user name>', help='User name')
+ def run(self, project_id, user_id):
+ """Start the VPN for a given project and user."""
+ if not user_id:
+ print "WARNING: This method only works with deprecated auth"
+ user_id = self.manager.get_project(project_id).project_manager_id
+ self.pipe.launch_vpn_instance(project_id, user_id)
@args('--project', dest="project_id", metavar='<Project name>',
help='Project name')
@@ -195,10 +200,6 @@ class VpnCommands(object):
"""
# TODO(tr3buchet): perhaps this shouldn't update all networks
# associated with a project in the future
- project = self.manager.get_project(project_id)
- if not project:
- print 'No project %s' % (project_id)
- return
admin_context = context.get_admin_context()
networks = db.project_get_networks(admin_context, project_id)
for network in networks:
@@ -611,6 +612,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 +766,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')
@@ -792,6 +798,39 @@ class NetworkCommands(object):
' before delete' % network.project_id))
db.network_delete_safe(context.get_admin_context(), network.id)
+ @args('--network', dest="fixed_range", metavar='<x.x.x.x/yy>',
+ help='Network to modify')
+ @args('--project', dest="project", metavar='<project name>',
+ help='Project name to associate')
+ @args('--host', dest="host", metavar='<host>',
+ help='Host to associate')
+ @args('--disassociate-project', action="store_true", dest='dis_project',
+ default=False, help='Disassociate Network from Project')
+ @args('--disassociate-host', action="store_true", dest='dis_host',
+ default=False, help='Disassociate Host from Project')
+ def modify(self, fixed_range, project=None, host=None,
+ dis_project=None, dis_host=None):
+ """Associate/Disassociate Network with Project and/or Host
+ arguments: network project host
+ leave any field blank to ignore it
+ """
+ admin_context = context.get_admin_context()
+ network = db.network_get_by_cidr(admin_context, fixed_range)
+ net = {}
+ #User can choose the following actions each for project and host.
+ #1) Associate (set not None value given by project/host parameter)
+ #2) Disassociate (set None by disassociate parameter)
+ #3) Keep unchanged (project/host key is not added to 'net')
+ if project:
+ net['project_id'] = project
+ elif dis_project:
+ net['project_id'] = None
+ if host:
+ net['host'] = host
+ elif dis_host:
+ net['host'] = None
+ db.network_update(admin_context, network['id'], net)
+
class VmCommands(object):
"""Class for mangaging VM instances."""
diff --git a/bin/nova-network b/bin/nova-network
index 57759d30a..0f1482515 100755
--- a/bin/nova-network
+++ b/bin/nova-network
@@ -43,6 +43,7 @@ if __name__ == '__main__':
utils.default_flagfile()
flags.FLAGS(sys.argv)
logging.setup()
+ utils.monkey_patch()
server = service.Service.create(binary='nova-network')
service.serve(server)
service.wait()
diff --git a/bin/nova-objectstore b/bin/nova-objectstore
index c7a76e120..757301c24 100755
--- a/bin/nova-objectstore
+++ b/bin/nova-objectstore
@@ -49,6 +49,7 @@ if __name__ == '__main__':
utils.default_flagfile()
FLAGS(sys.argv)
logging.setup()
+ utils.monkey_patch()
router = s3server.S3Application(FLAGS.buckets_path)
server = wsgi.Server("S3 Objectstore",
router,
diff --git a/bin/nova-scheduler b/bin/nova-scheduler
index 2e168cbc6..c1033a304 100755
--- a/bin/nova-scheduler
+++ b/bin/nova-scheduler
@@ -22,6 +22,7 @@
import eventlet
eventlet.monkey_patch()
+import gettext
import os
import sys
@@ -33,6 +34,7 @@ possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]),
if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')):
sys.path.insert(0, possible_topdir)
+gettext.install('nova', unicode=1)
from nova import flags
from nova import log as logging
@@ -43,6 +45,7 @@ if __name__ == '__main__':
utils.default_flagfile()
flags.FLAGS(sys.argv)
logging.setup()
+ utils.monkey_patch()
server = service.Service.create(binary='nova-scheduler')
service.serve(server)
service.wait()
diff --git a/bin/nova-volume b/bin/nova-volume
index 5405aebbb..8caa0f44a 100755
--- a/bin/nova-volume
+++ b/bin/nova-volume
@@ -43,6 +43,7 @@ if __name__ == '__main__':
utils.default_flagfile()
flags.FLAGS(sys.argv)
logging.setup()
+ utils.monkey_patch()
server = service.Service.create(binary='nova-volume')
service.serve(server)
service.wait()
diff --git a/etc/nova/api-paste.ini b/etc/nova/api-paste.ini
index b540509a2..cd24efb13 100644
--- a/etc/nova/api-paste.ini
+++ b/etc/nova/api-paste.ini
@@ -19,12 +19,18 @@ use = egg:Paste#urlmap
/1.0: ec2metadata
[pipeline:ec2cloud]
-pipeline = logrequest authenticate cloudrequest authorizer ec2executor
+pipeline = logrequest ec2noauth cloudrequest authorizer ec2executor
+# NOTE(vish): use the following pipeline for deprecated auth
+#pipeline = logrequest 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
+pipeline = logrequest ec2noauth adminrequest authorizer ec2executor
+# NOTE(vish): use the following pipeline for deprecated auth
+#pipeline = logrequest authenticate adminrequest authorizer ec2executor
+# NOTE(vish): use the following pipeline for keystone
+#pipeline = logrequest totoken authtoken keystonecontext adminrequest authorizer ec2executor
[pipeline:ec2metadata]
pipeline = logrequest ec2md
@@ -41,6 +47,9 @@ paste.filter_factory = nova.api.ec2:Lockout.factory
[filter:totoken]
paste.filter_factory = nova.api.ec2:ToToken.factory
+[filter:ec2noauth]
+paste.filter_factory = nova.api.ec2:NoAuth.factory
+
[filter:authenticate]
paste.filter_factory = nova.api.ec2:Authenticate.factory
@@ -75,12 +84,16 @@ use = egg:Paste#urlmap
/v1.1: openstackapi11
[pipeline:openstackapi10]
-pipeline = faultwrap auth ratelimit osapiapp10
+pipeline = faultwrap noauth ratelimit osapiapp10
+# NOTE(vish): use the following pipeline for deprecated auth
+# 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
+pipeline = faultwrap noauth ratelimit extensions osapiapp11
+# NOTE(vish): use the following pipeline for deprecated auth
+# pipeline = faultwrap auth ratelimit extensions osapiapp11
# NOTE(vish): use the following pipeline for keystone
# pipeline = faultwrap authtoken keystonecontext ratelimit extensions osapiapp11
@@ -90,6 +103,9 @@ paste.filter_factory = nova.api.openstack:FaultWrapper.factory
[filter:auth]
paste.filter_factory = nova.api.openstack.auth:AuthMiddleware.factory
+[filter:noauth]
+paste.filter_factory = nova.api.openstack.auth:NoAuthMiddleware.factory
+
[filter:ratelimit]
paste.filter_factory = nova.api.openstack.limits:RateLimitingMiddleware.factory
diff --git a/nova/api/auth.py b/nova/api/auth.py
index cd3e3e8a0..cd0d38b3f 100644
--- a/nova/api/auth.py
+++ b/nova/api/auth.py
@@ -62,6 +62,7 @@ class KeystoneContext(wsgi.Middleware):
req.headers.get('X_STORAGE_TOKEN'))
# Build a context, including the auth_token...
+ remote_address = getattr(req, 'remote_address', '127.0.0.1')
remote_address = req.remote_addr
if FLAGS.use_forwarded_for:
remote_address = req.headers.get('X-Forwarded-For', remote_address)
diff --git a/nova/api/ec2/__init__.py b/nova/api/ec2/__init__.py
index 17969099d..5430f443d 100644
--- a/nova/api/ec2/__init__.py
+++ b/nova/api/ec2/__init__.py
@@ -183,6 +183,27 @@ class ToToken(wsgi.Middleware):
return self.application
+class NoAuth(wsgi.Middleware):
+ """Add user:project as 'nova.context' to WSGI environ."""
+
+ @webob.dec.wsgify(RequestClass=wsgi.Request)
+ def __call__(self, req):
+ if 'AWSAccessKeyId' not in req.params:
+ raise webob.exc.HTTPBadRequest()
+ user_id, _sep, project_id = req.params['AWSAccessKeyId'].partition(':')
+ project_id = project_id or user_id
+ remote_address = getattr(req, 'remote_address', '127.0.0.1')
+ if FLAGS.use_forwarded_for:
+ remote_address = req.headers.get('X-Forwarded-For', remote_address)
+ ctx = context.RequestContext(user_id,
+ project_id,
+ is_admin=True,
+ remote_address=remote_address)
+
+ req.environ['nova.context'] = ctx
+ return self.application
+
+
class Authenticate(wsgi.Middleware):
"""Authenticate an EC2 request and add 'nova.context' to WSGI environ."""
diff --git a/nova/api/ec2/admin.py b/nova/api/ec2/admin.py
index df7876b9d..dfbbc0a2b 100644
--- a/nova/api/ec2/admin.py
+++ b/nova/api/ec2/admin.py
@@ -283,8 +283,10 @@ class AdminController(object):
# NOTE(vish) import delayed because of __init__.py
from nova.cloudpipe import pipelib
pipe = pipelib.CloudPipe()
+ proj = manager.AuthManager().get_project(project)
+ user_id = proj.project_manager_id
try:
- pipe.launch_vpn_instance(project)
+ pipe.launch_vpn_instance(project, user_id)
except db.NoMoreNetworks:
raise exception.ApiError("Unable to claim IP for VPN instance"
", ensure it isn't running, and try "
diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py
index e0c1e9d04..3b74fefc9 100644
--- a/nova/api/openstack/__init__.py
+++ b/nova/api/openstack/__init__.py
@@ -68,6 +68,22 @@ class FaultWrapper(base_wsgi.Middleware):
return faults.Fault(exc)
+class ProjectMapper(routes.Mapper):
+
+ def resource(self, member_name, collection_name, **kwargs):
+ if not ('parent_resource' in kwargs):
+ kwargs['path_prefix'] = '{project_id}/'
+ else:
+ parent_resource = kwargs['parent_resource']
+ p_collection = parent_resource['collection_name']
+ p_member = parent_resource['member_name']
+ kwargs['path_prefix'] = '{project_id}/%s/:%s_id' % (p_collection,
+ p_member)
+ routes.Mapper.resource(self, member_name,
+ collection_name,
+ **kwargs)
+
+
class APIRouter(base_wsgi.Router):
"""
Routes requests on the OpenStack API to the appropriate controller
@@ -81,10 +97,13 @@ class APIRouter(base_wsgi.Router):
def __init__(self, ext_mgr=None):
self.server_members = {}
- mapper = routes.Mapper()
+ mapper = self._mapper()
self._setup_routes(mapper)
super(APIRouter, self).__init__(mapper)
+ def _mapper(self):
+ return routes.Mapper()
+
def _setup_routes(self, mapper):
raise NotImplementedError(_("You must implement _setup_routes."))
@@ -174,6 +193,9 @@ class APIRouterV10(APIRouter):
class APIRouterV11(APIRouter):
"""Define routes specific to OpenStack API V1.1."""
+ def _mapper(self):
+ return ProjectMapper()
+
def _setup_routes(self, mapper):
self._setup_base_routes(mapper, '1.1')
@@ -184,7 +206,7 @@ class APIRouterV11(APIRouter):
parent_resource=dict(member_name='image',
collection_name='images'))
- mapper.connect("metadata", "/images/{image_id}/metadata",
+ mapper.connect("metadata", "/{project_id}/images/{image_id}/metadata",
controller=image_metadata_controller,
action='update_all',
conditions={"method": ['PUT']})
@@ -196,7 +218,8 @@ class APIRouterV11(APIRouter):
parent_resource=dict(member_name='server',
collection_name='servers'))
- mapper.connect("metadata", "/servers/{server_id}/metadata",
+ mapper.connect("metadata",
+ "/{project_id}/servers/{server_id}/metadata",
controller=server_metadata_controller,
action='update_all',
conditions={"method": ['PUT']})
diff --git a/nova/api/openstack/auth.py b/nova/api/openstack/auth.py
index d42abe1f8..6754fea27 100644
--- a/nova/api/openstack/auth.py
+++ b/nova/api/openstack/auth.py
@@ -28,10 +28,51 @@ from nova import flags
from nova import log as logging
from nova import utils
from nova import wsgi
+from nova.api.openstack import common
from nova.api.openstack import faults
LOG = logging.getLogger('nova.api.openstack')
FLAGS = flags.FLAGS
+flags.DECLARE('use_forwarded_for', 'nova.api.auth')
+
+
+class NoAuthMiddleware(wsgi.Middleware):
+ """Return a fake token if one isn't specified."""
+
+ @webob.dec.wsgify(RequestClass=wsgi.Request)
+ def __call__(self, req):
+ if 'X-Auth-Token' not in req.headers:
+ os_url = req.url
+ version = common.get_version_from_href(os_url)
+ user_id = req.headers.get('X-Auth-User', 'admin')
+ project_id = req.headers.get('X-Auth-Project-Id', 'admin')
+ if version == '1.1':
+ os_url += '/' + project_id
+ res = webob.Response()
+ # NOTE(vish): This is expecting and returning Auth(1.1), whereas
+ # keystone uses 2.0 auth. We should probably allow
+ # 2.0 auth here as well.
+ res.headers['X-Auth-Token'] = '%s:%s' % (user_id, project_id)
+ res.headers['X-Server-Management-Url'] = os_url
+ res.headers['X-Storage-Url'] = ''
+ res.headers['X-CDN-Management-Url'] = ''
+ res.content_type = 'text/plain'
+ res.status = '204'
+ return res
+
+ token = req.headers['X-Auth-Token']
+ user_id, _sep, project_id = token.partition(':')
+ project_id = project_id or user_id
+ remote_address = getattr(req, 'remote_address', '127.0.0.1')
+ if FLAGS.use_forwarded_for:
+ remote_address = req.headers.get('X-Forwarded-For', remote_address)
+ ctx = context.RequestContext(user_id,
+ project_id,
+ is_admin=True,
+ remote_address=remote_address)
+
+ req.environ['nova.context'] = ctx
+ return self.application
class AuthMiddleware(wsgi.Middleware):
@@ -55,21 +96,44 @@ class AuthMiddleware(wsgi.Middleware):
LOG.warn(msg % locals())
return faults.Fault(webob.exc.HTTPUnauthorized())
- try:
- project_id = req.headers["X-Auth-Project-Id"]
- except KeyError:
- # FIXME(usrleon): It needed only for compatibility
- # while osapi clients don't use this header
- projects = self.auth.get_projects(user_id)
- if projects:
- project_id = projects[0].id
- else:
+ # Get all valid projects for the user
+ projects = self.auth.get_projects(user_id)
+ if not projects:
+ return faults.Fault(webob.exc.HTTPUnauthorized())
+
+ project_id = ""
+ path_parts = req.path.split('/')
+ # TODO(wwolf): this v1.1 check will be temporary as
+ # keystone should be taking this over at some point
+ if len(path_parts) > 1 and path_parts[1] == 'v1.1':
+ project_id = path_parts[2]
+ # Check that the project for project_id exists, and that user
+ # is authorized to use it
+ try:
+ project = self.auth.get_project(project_id)
+ except exception.ProjectNotFound:
return faults.Fault(webob.exc.HTTPUnauthorized())
+ if project_id not in [p.id for p in projects]:
+ return faults.Fault(webob.exc.HTTPUnauthorized())
+ else:
+ # As a fallback, set project_id from the headers, which is the v1.0
+ # behavior. As a last resort, be forgiving to the user and set
+ # project_id based on a valid project of theirs.
+ try:
+ project_id = req.headers["X-Auth-Project-Id"]
+ except KeyError:
+ project_id = projects[0].id
is_admin = self.auth.is_admin(user_id)
- req.environ['nova.context'] = context.RequestContext(user_id,
- project_id,
- is_admin)
+ remote_address = getattr(req, 'remote_address', '127.0.0.1')
+ if FLAGS.use_forwarded_for:
+ remote_address = req.headers.get('X-Forwarded-For', remote_address)
+ ctx = context.RequestContext(user_id,
+ project_id,
+ is_admin=is_admin,
+ remote_address=remote_address)
+ req.environ['nova.context'] = ctx
+
if not is_admin and not self.auth.is_project_member(user_id,
project_id):
msg = _("%(user_id)s must be an admin or a "
@@ -95,12 +159,19 @@ class AuthMiddleware(wsgi.Middleware):
LOG.warn(msg)
return faults.Fault(webob.exc.HTTPUnauthorized(explanation=msg))
+ def _get_auth_header(key):
+ """Ensures that the KeyError returned is meaningful."""
+ try:
+ return req.headers[key]
+ except KeyError as ex:
+ raise KeyError(key)
try:
- username = req.headers['X-Auth-User']
- key = req.headers['X-Auth-Key']
+ username = _get_auth_header('X-Auth-User')
+ key = _get_auth_header('X-Auth-Key')
except KeyError as ex:
- LOG.warn(_("Could not find %s in request.") % ex)
- return faults.Fault(webob.exc.HTTPUnauthorized())
+ msg = _("Could not find %s in request.") % ex
+ LOG.warn(msg)
+ return faults.Fault(webob.exc.HTTPUnauthorized(explanation=msg))
token, user = self._authorize_user(username, key, req)
if user and token:
@@ -149,6 +220,16 @@ class AuthMiddleware(wsgi.Middleware):
"""
ctxt = context.get_admin_context()
+ project_id = req.headers.get('X-Auth-Project-Id')
+ if project_id is None:
+ # If the project_id is not provided in the headers, be forgiving to
+ # the user and set project_id based on a valid project of theirs.
+ user = self.auth.get_user_from_access_key(key)
+ projects = self.auth.get_projects(user.id)
+ if not projects:
+ raise webob.exc.HTTPUnauthorized()
+ project_id = projects[0].id
+
try:
user = self.auth.get_user_from_access_key(key)
except exception.NotFound:
@@ -162,7 +243,10 @@ class AuthMiddleware(wsgi.Middleware):
token_dict['token_hash'] = token_hash
token_dict['cdn_management_url'] = ''
os_url = req.url
- token_dict['server_management_url'] = os_url
+ token_dict['server_management_url'] = os_url.strip('/')
+ version = common.get_version_from_href(os_url)
+ if version == '1.1':
+ token_dict['server_management_url'] += '/' + project_id
token_dict['storage_url'] = ''
token_dict['user_id'] = user.id
token = self.db.auth_token_create(ctxt, token_dict)
diff --git a/nova/api/openstack/contrib/createserverext.py b/nova/api/openstack/contrib/createserverext.py
new file mode 100644
index 000000000..ba72fdb0b
--- /dev/null
+++ b/nova/api/openstack/contrib/createserverext.py
@@ -0,0 +1,66 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2011 OpenStack LLC.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License
+
+from nova.api.openstack import create_instance_helper as helper
+from nova.api.openstack import extensions
+from nova.api.openstack import servers
+from nova.api.openstack import wsgi
+
+
+class Createserverext(extensions.ExtensionDescriptor):
+ """The servers create ext
+
+ Exposes addFixedIp and removeFixedIp actions on servers.
+
+ """
+ def get_name(self):
+ return "Createserverext"
+
+ def get_alias(self):
+ return "os-create-server-ext"
+
+ def get_description(self):
+ return "Extended support to the Create Server v1.1 API"
+
+ def get_namespace(self):
+ return "http://docs.openstack.org/ext/createserverext/api/v1.1"
+
+ def get_updated(self):
+ return "2011-07-19T00:00:00+00:00"
+
+ def get_resources(self):
+ resources = []
+
+ headers_serializer = servers.HeadersSerializer()
+ body_serializers = {
+ 'application/xml': servers.ServerXMLSerializer(),
+ }
+
+ body_deserializers = {
+ 'application/xml': helper.ServerXMLDeserializerV11(),
+ }
+
+ serializer = wsgi.ResponseSerializer(body_serializers,
+ headers_serializer)
+ deserializer = wsgi.RequestDeserializer(body_deserializers)
+
+ res = extensions.ResourceExtension('os-create-server-ext',
+ controller=servers.ControllerV11(),
+ deserializer=deserializer,
+ serializer=serializer)
+ resources.append(res)
+
+ return resources
diff --git a/nova/api/openstack/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/volumes.py b/nova/api/openstack/contrib/volumes.py
index 867fe301e..d62225e58 100644
--- a/nova/api/openstack/contrib/volumes.py
+++ b/nova/api/openstack/contrib/volumes.py
@@ -24,6 +24,7 @@ from nova import flags
from nova import log as logging
from nova import quota
from nova import volume
+from nova.volume import volume_types
from nova.api.openstack import common
from nova.api.openstack import extensions
from nova.api.openstack import faults
@@ -63,6 +64,22 @@ def _translate_volume_summary_view(context, vol):
d['displayName'] = vol['display_name']
d['displayDescription'] = vol['display_description']
+
+ if vol['volume_type_id'] and vol.get('volume_type'):
+ d['volumeType'] = vol['volume_type']['name']
+ else:
+ d['volumeType'] = vol['volume_type_id']
+
+ LOG.audit(_("vol=%s"), vol, context=context)
+
+ if vol.get('volume_metadata'):
+ meta_dict = {}
+ for i in vol['volume_metadata']:
+ meta_dict[i['key']] = i['value']
+ d['metadata'] = meta_dict
+ else:
+ d['metadata'] = {}
+
return d
@@ -80,6 +97,8 @@ class VolumeController(object):
"createdAt",
"displayName",
"displayDescription",
+ "volumeType",
+ "metadata",
]}}}
def __init__(self):
@@ -136,12 +155,25 @@ class VolumeController(object):
vol = body['volume']
size = vol['size']
LOG.audit(_("Create volume of %s GB"), size, context=context)
+
+ vol_type = vol.get('volume_type', None)
+ if vol_type:
+ try:
+ vol_type = volume_types.get_volume_type_by_name(context,
+ vol_type)
+ except exception.NotFound:
+ return faults.Fault(exc.HTTPNotFound())
+
+ metadata = vol.get('metadata', None)
+
new_volume = self.volume_api.create(context, size, None,
vol.get('display_name'),
- vol.get('display_description'))
+ vol.get('display_description'),
+ volume_type=vol_type,
+ metadata=metadata)
# Work around problem that instance is lazy-loaded...
- new_volume['instance'] = None
+ new_volume = self.volume_api.get(context, new_volume['id'])
retval = _translate_volume_detail_view(context, new_volume)
diff --git a/nova/api/openstack/contrib/volumetypes.py b/nova/api/openstack/contrib/volumetypes.py
new file mode 100644
index 000000000..ed33a8819
--- /dev/null
+++ b/nova/api/openstack/contrib/volumetypes.py
@@ -0,0 +1,197 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright (c) 2011 Zadara Storage Inc.
+# 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.
+
+""" The volume type & volume types extra specs extension"""
+
+from webob import exc
+
+from nova import db
+from nova import exception
+from nova import quota
+from nova.volume import volume_types
+from nova.api.openstack import extensions
+from nova.api.openstack import faults
+from nova.api.openstack import wsgi
+
+
+class VolumeTypesController(object):
+ """ The volume types API controller for the Openstack API """
+
+ def index(self, req):
+ """ Returns the list of volume types """
+ context = req.environ['nova.context']
+ return volume_types.get_all_types(context)
+
+ def create(self, req, body):
+ """Creates a new volume type."""
+ context = req.environ['nova.context']
+
+ if not body or body == "":
+ return faults.Fault(exc.HTTPUnprocessableEntity())
+
+ vol_type = body.get('volume_type', None)
+ if vol_type is None or vol_type == "":
+ return faults.Fault(exc.HTTPUnprocessableEntity())
+
+ name = vol_type.get('name', None)
+ specs = vol_type.get('extra_specs', {})
+
+ if name is None or name == "":
+ return faults.Fault(exc.HTTPUnprocessableEntity())
+
+ try:
+ volume_types.create(context, name, specs)
+ vol_type = volume_types.get_volume_type_by_name(context, name)
+ except quota.QuotaError as error:
+ self._handle_quota_error(error)
+ except exception.NotFound:
+ return faults.Fault(exc.HTTPNotFound())
+
+ return {'volume_type': vol_type}
+
+ def show(self, req, id):
+ """ Return a single volume type item """
+ context = req.environ['nova.context']
+
+ try:
+ vol_type = volume_types.get_volume_type(context, id)
+ except exception.NotFound or exception.ApiError:
+ return faults.Fault(exc.HTTPNotFound())
+
+ return {'volume_type': vol_type}
+
+ def delete(self, req, id):
+ """ Deletes an existing volume type """
+ context = req.environ['nova.context']
+
+ try:
+ vol_type = volume_types.get_volume_type(context, id)
+ volume_types.destroy(context, vol_type['name'])
+ except exception.NotFound:
+ return faults.Fault(exc.HTTPNotFound())
+
+ def _handle_quota_error(self, error):
+ """Reraise quota errors as api-specific http exceptions."""
+ if error.code == "MetadataLimitExceeded":
+ raise exc.HTTPBadRequest(explanation=error.message)
+ raise error
+
+
+class VolumeTypeExtraSpecsController(object):
+ """ The volume type extra specs API controller for the Openstack API """
+
+ def _get_extra_specs(self, context, vol_type_id):
+ extra_specs = db.api.volume_type_extra_specs_get(context, vol_type_id)
+ specs_dict = {}
+ for key, value in extra_specs.iteritems():
+ specs_dict[key] = value
+ return dict(extra_specs=specs_dict)
+
+ def _check_body(self, body):
+ if body == None or body == "":
+ expl = _('No Request Body')
+ raise exc.HTTPBadRequest(explanation=expl)
+
+ def index(self, req, vol_type_id):
+ """ Returns the list of extra specs for a given volume type """
+ context = req.environ['nova.context']
+ return self._get_extra_specs(context, vol_type_id)
+
+ def create(self, req, vol_type_id, body):
+ self._check_body(body)
+ context = req.environ['nova.context']
+ specs = body.get('extra_specs')
+ try:
+ db.api.volume_type_extra_specs_update_or_create(context,
+ vol_type_id,
+ specs)
+ except quota.QuotaError as error:
+ self._handle_quota_error(error)
+ return body
+
+ def update(self, req, vol_type_id, id, body):
+ self._check_body(body)
+ context = req.environ['nova.context']
+ if not id in body:
+ expl = _('Request body and URI mismatch')
+ raise exc.HTTPBadRequest(explanation=expl)
+ if len(body) > 1:
+ expl = _('Request body contains too many items')
+ raise exc.HTTPBadRequest(explanation=expl)
+ try:
+ db.api.volume_type_extra_specs_update_or_create(context,
+ vol_type_id,
+ body)
+ except quota.QuotaError as error:
+ self._handle_quota_error(error)
+
+ return body
+
+ def show(self, req, vol_type_id, id):
+ """ Return a single extra spec item """
+ context = req.environ['nova.context']
+ specs = self._get_extra_specs(context, vol_type_id)
+ if id in specs['extra_specs']:
+ return {id: specs['extra_specs'][id]}
+ else:
+ return faults.Fault(exc.HTTPNotFound())
+
+ def delete(self, req, vol_type_id, id):
+ """ Deletes an existing extra spec """
+ context = req.environ['nova.context']
+ db.api.volume_type_extra_specs_delete(context, vol_type_id, id)
+
+ def _handle_quota_error(self, error):
+ """Reraise quota errors as api-specific http exceptions."""
+ if error.code == "MetadataLimitExceeded":
+ raise exc.HTTPBadRequest(explanation=error.message)
+ raise error
+
+
+class Volumetypes(extensions.ExtensionDescriptor):
+
+ def get_name(self):
+ return "VolumeTypes"
+
+ def get_alias(self):
+ return "os-volume-types"
+
+ def get_description(self):
+ return "Volume types support"
+
+ def get_namespace(self):
+ return \
+ "http://docs.openstack.org/ext/volume_types/api/v1.1"
+
+ def get_updated(self):
+ return "2011-08-24T00:00:00+00:00"
+
+ def get_resources(self):
+ resources = []
+ res = extensions.ResourceExtension(
+ 'os-volume-types',
+ VolumeTypesController())
+ resources.append(res)
+
+ res = extensions.ResourceExtension('extra_specs',
+ VolumeTypeExtraSpecsController(),
+ parent=dict(
+ member_name='vol_type',
+ collection_name='os-volume-types'))
+ resources.append(res)
+
+ return resources
diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py
index 978741682..483ff4985 100644
--- a/nova/api/openstack/create_instance_helper.py
+++ b/nova/api/openstack/create_instance_helper.py
@@ -1,4 +1,5 @@
# Copyright 2011 OpenStack LLC.
+# Copyright 2011 Piston Cloud Computing, Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
@@ -29,7 +30,7 @@ from nova import utils
from nova.compute import instance_types
from nova.api.openstack import common
from nova.api.openstack import wsgi
-
+from nova.rpc.common import RemoteError
LOG = logging.getLogger('nova.api.openstack.create_instance_helper')
FLAGS = flags.FLAGS
@@ -106,11 +107,26 @@ class CreateInstanceHelper(object):
raise exc.HTTPBadRequest(explanation=msg)
personality = server_dict.get('personality')
+ config_drive = server_dict.get('config_drive')
injected_files = []
if personality:
injected_files = self._get_injected_files(personality)
+ sg_names = []
+ security_groups = server_dict.get('security_groups')
+ if security_groups is not None:
+ sg_names = [sg['name'] for sg in security_groups if sg.get('name')]
+ if not sg_names:
+ sg_names.append('default')
+
+ sg_names = list(set(sg_names))
+
+ requested_networks = server_dict.get('networks')
+ if requested_networks is not None:
+ requested_networks = self._get_requested_networks(
+ requested_networks)
+
try:
flavor_id = self.controller._flavor_id_from_req_data(body)
except ValueError as error:
@@ -145,6 +161,7 @@ class CreateInstanceHelper(object):
extra_values = {
'instance_type': inst_type,
'image_ref': image_href,
+ 'config_drive': config_drive,
'password': password}
return (extra_values,
@@ -158,14 +175,19 @@ class CreateInstanceHelper(object):
key_name=key_name,
key_data=key_data,
metadata=server_dict.get('metadata', {}),
+ access_ip_v4=server_dict.get('accessIPv4'),
+ access_ip_v6=server_dict.get('accessIPv6'),
injected_files=injected_files,
admin_password=password,
zone_blob=zone_blob,
reservation_id=reservation_id,
min_count=min_count,
max_count=max_count,
+ requested_networks=requested_networks,
+ security_group=sg_names,
user_data=user_data,
- availability_zone=availability_zone))
+ availability_zone=availability_zone,
+ config_drive=config_drive,))
except quota.QuotaError as error:
self._handle_quota_error(error)
except exception.ImageNotFound as error:
@@ -174,6 +196,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):
@@ -302,6 +330,46 @@ class CreateInstanceHelper(object):
raise exc.HTTPBadRequest(explanation=msg)
return password
+ def _get_requested_networks(self, requested_networks):
+ """
+ Create a list of requested networks from the networks attribute
+ """
+ networks = []
+ for network in requested_networks:
+ try:
+ network_uuid = network['uuid']
+
+ if not utils.is_uuid_like(network_uuid):
+ msg = _("Bad networks format: network uuid is not in"
+ " proper format (%s)") % network_uuid
+ raise exc.HTTPBadRequest(explanation=msg)
+
+ #fixed IP address is optional
+ #if the fixed IP address is not provided then
+ #it will use one of the available IP address from the network
+ address = network.get('fixed_ip', None)
+ if address is not None and not utils.is_valid_ipv4(address):
+ msg = _("Invalid fixed IP address (%s)") % address
+ raise exc.HTTPBadRequest(explanation=msg)
+ # check if the network id is already present in the list,
+ # we don't want duplicate networks to be passed
+ # at the boot time
+ for id, ip in networks:
+ if id == network_uuid:
+ expl = _("Duplicate networks (%s) are not allowed")\
+ % network_uuid
+ raise exc.HTTPBadRequest(explanation=expl)
+
+ networks.append((network_uuid, address))
+ except KeyError as key:
+ expl = _('Bad network format: missing %s') % key
+ raise exc.HTTPBadRequest(explanation=expl)
+ except TypeError:
+ expl = _('Bad networks format')
+ raise exc.HTTPBadRequest(explanation=expl)
+
+ return networks
+
class ServerXMLDeserializer(wsgi.XMLDeserializer):
"""
@@ -452,7 +520,8 @@ class ServerXMLDeserializerV11(wsgi.MetadataXMLDeserializer):
server = {}
server_node = self.find_first_child_named(node, 'server')
- attributes = ["name", "imageRef", "flavorRef", "adminPass"]
+ attributes = ["name", "imageRef", "flavorRef", "adminPass",
+ "accessIPv4", "accessIPv6"]
for attr in attributes:
if server_node.getAttribute(attr):
server[attr] = server_node.getAttribute(attr)
@@ -465,6 +534,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):
@@ -481,3 +558,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..dbd169a83
--- /dev/null
+++ b/nova/api/openstack/schemas/v1.1/server.rng
@@ -0,0 +1,50 @@
+<element name="server" ns="http://docs.openstack.org/compute/api/v1.1"
+ xmlns="http://relaxng.org/ns/structure/1.0">
+ <attribute name="name"> <text/> </attribute>
+ <attribute name="id"> <text/> </attribute>
+ <attribute name="uuid"> <text/> </attribute>
+ <attribute name="updated"> <text/> </attribute>
+ <attribute name="created"> <text/> </attribute>
+ <attribute name="hostId"> <text/> </attribute>
+ <attribute name="accessIPv4"> <text/> </attribute>
+ <attribute name="accessIPv6"> <text/> </attribute>
+ <attribute name="status"> <text/> </attribute>
+ <optional>
+ <attribute name="progress"> <text/> </attribute>
+ </optional>
+ <optional>
+ <attribute name="adminPass"> <text/> </attribute>
+ </optional>
+ <zeroOrMore>
+ <externalRef href="../atom-link.rng"/>
+ </zeroOrMore>
+ <element name="image">
+ <attribute name="id"> <text/> </attribute>
+ <externalRef href="../atom-link.rng"/>
+ </element>
+ <element name="flavor">
+ <attribute name="id"> <text/> </attribute>
+ <externalRef href="../atom-link.rng"/>
+ </element>
+ <element name="metadata">
+ <zeroOrMore>
+ <element name="meta">
+ <attribute name="key"> <text/> </attribute>
+ <text/>
+ </element>
+ </zeroOrMore>
+ </element>
+ <element name="addresses">
+ <zeroOrMore>
+ <element name="network">
+ <attribute name="id"> <text/> </attribute>
+ <zeroOrMore>
+ <element name="ip">
+ <attribute name="version"> <text/> </attribute>
+ <attribute name="addr"> <text/> </attribute>
+ </element>
+ </zeroOrMore>
+ </element>
+ </zeroOrMore>
+ </element>
+</element>
diff --git a/nova/api/openstack/schemas/v1.1/servers.rng b/nova/api/openstack/schemas/v1.1/servers.rng
new file mode 100644
index 000000000..4e2bb8853
--- /dev/null
+++ b/nova/api/openstack/schemas/v1.1/servers.rng
@@ -0,0 +1,6 @@
+<element name="servers" xmlns="http://relaxng.org/ns/structure/1.0"
+ ns="http://docs.openstack.org/compute/api/v1.1">
+ <zeroOrMore>
+ <externalRef href="server.rng"/>
+ </zeroOrMore>
+</element>
diff --git a/nova/api/openstack/schemas/v1.1/servers_index.rng b/nova/api/openstack/schemas/v1.1/servers_index.rng
new file mode 100644
index 000000000..768f0912d
--- /dev/null
+++ b/nova/api/openstack/schemas/v1.1/servers_index.rng
@@ -0,0 +1,12 @@
+<element name="servers" ns="http://docs.openstack.org/compute/api/v1.1"
+ xmlns="http://relaxng.org/ns/structure/1.0">
+ <zeroOrMore>
+ <element name="server">
+ <attribute name="name"> <text/> </attribute>
+ <attribute name="id"> <text/> </attribute>
+ <zeroOrMore>
+ <externalRef href="../atom-link.rng"/>
+ </zeroOrMore>
+ </element>
+ </zeroOrMore>
+</element>
diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py
index 41e63ec3c..27c67e79e 100644
--- a/nova/api/openstack/servers.py
+++ b/nova/api/openstack/servers.py
@@ -163,7 +163,7 @@ class Controller(object):
@scheduler_api.redirect_handler
def update(self, req, id, body):
- """Update server name then pass on to version-specific controller"""
+ """Update server then pass on to version-specific controller"""
if len(req.body) == 0:
raise exc.HTTPUnprocessableEntity()
@@ -178,6 +178,14 @@ class Controller(object):
self.helper._validate_server_name(name)
update_dict['display_name'] = name.strip()
+ if 'accessIPv4' in body['server']:
+ access_ipv4 = body['server']['accessIPv4']
+ update_dict['access_ip_v4'] = access_ipv4.strip()
+
+ if 'accessIPv6' in body['server']:
+ access_ipv6 = body['server']['accessIPv6']
+ update_dict['access_ip_v6'] = access_ipv6.strip()
+
try:
self.compute_api.update(ctxt, id, **update_dict)
except exception.NotFound:
@@ -596,8 +604,10 @@ class ControllerV10(Controller):
LOG.debug(msg)
raise exc.HTTPBadRequest(explanation=msg)
+ password = utils.generate_password(16)
+
try:
- self.compute_api.rebuild(context, instance_id, image_id)
+ self.compute_api.rebuild(context, instance_id, image_id, password)
except exception.BuildInProgress:
msg = _("Instance %s is currently being rebuilt.") % instance_id
LOG.debug(msg)
@@ -642,14 +652,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)
@@ -731,15 +743,26 @@ class ControllerV11(Controller):
self._validate_metadata(metadata)
self._decode_personalities(personalities)
+ password = info["rebuild"].get("adminPass",
+ utils.generate_password(16))
+
try:
- self.compute_api.rebuild(context, instance_id, image_href, name,
- metadata, personalities)
+ self.compute_api.rebuild(context, instance_id, image_href,
+ password, name=name, metadata=metadata,
+ files_to_inject=personalities)
except exception.BuildInProgress:
msg = _("Instance %s is currently being rebuilt.") % instance_id
LOG.debug(msg)
raise exc.HTTPConflict(explanation=msg)
+ except exception.InstanceNotFound:
+ msg = _("Instance %s could not be found") % instance_id
+ raise exc.HTTPNotFound(explanation=msg)
- return webob.Response(status_int=202)
+ instance = self.compute_api.routing_get(context, instance_id)
+ view = self._build_view(request, instance, is_detail=True)
+ view['server']['adminPass'] = password
+
+ return view
@common.check_snapshots_enabled
def _action_create_image(self, input_dict, req, instance_id):
@@ -806,6 +829,9 @@ class HeadersSerializer(wsgi.ResponseHeadersSerializer):
def delete(self, response, data):
response.status_int = 204
+ def action(self, response, data):
+ response.status_int = 202
+
class ServerXMLSerializer(wsgi.XMLDictSerializer):
@@ -837,6 +863,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']))
@@ -923,6 +953,11 @@ class ServerXMLSerializer(wsgi.XMLDictSerializer):
node.setAttribute('adminPass', server_dict['server']['adminPass'])
return self.to_xml_string(node, True)
+ def action(self, server_dict):
+ #NOTE(bcwaldon): We need a way to serialize actions individually. This
+ # assumes all actions return a server entity
+ return self.create(server_dict)
+
def update(self, server_dict):
xml_doc = minidom.Document()
node = self._server_to_xml_detailed(xml_doc,
diff --git a/nova/api/openstack/views/addresses.py b/nova/api/openstack/views/addresses.py
index ddbf7a144..8f07a2289 100644
--- a/nova/api/openstack/views/addresses.py
+++ b/nova/api/openstack/views/addresses.py
@@ -17,9 +17,11 @@
from nova import flags
from nova import utils
+from nova import log as logging
from nova.api.openstack import common
FLAGS = flags.FLAGS
+LOG = logging.getLogger('nova.api.openstack.views.addresses')
class ViewBuilder(object):
@@ -48,7 +50,10 @@ class ViewBuilderV11(ViewBuilder):
def build(self, interfaces):
networks = {}
for interface in interfaces:
- network_label = interface['network']['label']
+ try:
+ network_label = self._extract_network_label(interface)
+ except TypeError:
+ continue
if network_label not in networks:
networks[network_label] = []
@@ -64,9 +69,14 @@ class ViewBuilderV11(ViewBuilder):
return networks
- def build_network(self, interfaces, network_label):
+ def build_network(self, interfaces, requested_network):
for interface in interfaces:
- if interface['network']['label'] == network_label:
+ try:
+ network_label = self._extract_network_label(interface)
+ except TypeError:
+ continue
+
+ if network_label == requested_network:
ips = list(self._extract_ipv4_addresses(interface))
ipv6 = self._extract_ipv6_address(interface)
if ipv6 is not None:
@@ -74,6 +84,13 @@ class ViewBuilderV11(ViewBuilder):
return {network_label: ips}
return None
+ def _extract_network_label(self, interface):
+ try:
+ return interface['network']['label']
+ except (TypeError, KeyError) as exc:
+ LOG.exception(exc)
+ raise TypeError
+
def _extract_ipv4_addresses(self, interface):
for fixed_ip in interface['fixed_ips']:
yield self._build_ip_entity(fixed_ip['address'], 4)
diff --git a/nova/api/openstack/views/flavors.py b/nova/api/openstack/views/flavors.py
index 0403ece1b..aea34b424 100644
--- a/nova/api/openstack/views/flavors.py
+++ b/nova/api/openstack/views/flavors.py
@@ -15,6 +15,9 @@
# License for the specific language governing permissions and limitations
# under the License.
+import os.path
+
+
from nova.api.openstack import common
@@ -59,11 +62,12 @@ class ViewBuilder(object):
class ViewBuilderV11(ViewBuilder):
"""Openstack API v1.1 flavors view builder."""
- def __init__(self, base_url):
+ def __init__(self, base_url, project_id=""):
"""
:param base_url: url of the root wsgi application
"""
self.base_url = base_url
+ self.project_id = project_id
def _build_extra(self, flavor_obj):
flavor_obj["links"] = self._build_links(flavor_obj)
@@ -88,11 +92,10 @@ class ViewBuilderV11(ViewBuilder):
def generate_href(self, flavor_id):
"""Create an url that refers to a specific flavor id."""
- return "%s/flavors/%s" % (self.base_url, flavor_id)
+ return os.path.join(self.base_url, self.project_id,
+ "flavors", str(flavor_id))
def generate_bookmark(self, flavor_id):
"""Create an url that refers to a specific flavor id."""
- return "%s/flavors/%s" % (
- common.remove_version_from_href(self.base_url),
- flavor_id,
- )
+ return os.path.join(common.remove_version_from_href(self.base_url),
+ self.project_id, "flavors", str(flavor_id))
diff --git a/nova/api/openstack/views/images.py b/nova/api/openstack/views/images.py
index 912303d14..21f1b2d3e 100644
--- a/nova/api/openstack/views/images.py
+++ b/nova/api/openstack/views/images.py
@@ -23,9 +23,10 @@ from nova.api.openstack import common
class ViewBuilder(object):
"""Base class for generating responses to OpenStack API image requests."""
- def __init__(self, base_url):
+ def __init__(self, base_url, project_id=""):
"""Initialize new `ViewBuilder`."""
- self._url = base_url
+ self.base_url = base_url
+ self.project_id = project_id
def _format_dates(self, image):
"""Update all date fields to ensure standardized formatting."""
@@ -54,7 +55,7 @@ class ViewBuilder(object):
def generate_href(self, image_id):
"""Return an href string pointing to this object."""
- return os.path.join(self._url, "images", str(image_id))
+ return os.path.join(self.base_url, "images", str(image_id))
def build(self, image_obj, detail=False):
"""Return a standardized image structure for display by the API."""
@@ -117,6 +118,11 @@ class ViewBuilderV11(ViewBuilder):
except KeyError:
return
+ def generate_href(self, image_id):
+ """Return an href string pointing to this object."""
+ return os.path.join(self.base_url, self.project_id,
+ "images", str(image_id))
+
def build(self, image_obj, detail=False):
"""Return a standardized image structure for display by the API."""
image = ViewBuilder.build(self, image_obj, detail)
@@ -142,5 +148,5 @@ class ViewBuilderV11(ViewBuilder):
def generate_bookmark(self, image_id):
"""Create an url that refers to a specific flavor id."""
- return os.path.join(common.remove_version_from_href(self._url),
- "images", str(image_id))
+ return os.path.join(common.remove_version_from_href(self.base_url),
+ self.project_id, "images", str(image_id))
diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py
index edc328129..0ec98591e 100644
--- a/nova/api/openstack/views/servers.py
+++ b/nova/api/openstack/views/servers.py
@@ -1,6 +1,7 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2010-2011 OpenStack LLC.
+# Copyright 2011 Piston Cloud Computing, Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
@@ -128,11 +129,12 @@ class ViewBuilderV10(ViewBuilder):
class ViewBuilderV11(ViewBuilder):
"""Model an Openstack API V1.0 server response."""
def __init__(self, addresses_builder, flavor_builder, image_builder,
- base_url):
+ base_url, project_id=""):
ViewBuilder.__init__(self, addresses_builder)
self.flavor_builder = flavor_builder
self.image_builder = image_builder
self.base_url = base_url
+ self.project_id = project_id
def _build_detail(self, inst):
response = super(ViewBuilderV11, self)._build_detail(inst)
@@ -143,6 +145,10 @@ class ViewBuilderV11(ViewBuilder):
response['server']['progress'] = 100
elif response['server']['status'] == "BUILD":
response['server']['progress'] = 0
+
+ response['server']['accessIPv4'] = inst.get('access_ip_v4') or ""
+ response['server']['accessIPv6'] = inst.get('access_ip_v6') or ""
+
return response
def _build_image(self, response, inst):
@@ -182,6 +188,7 @@ class ViewBuilderV11(ViewBuilder):
def _build_extra(self, response, inst):
self._build_links(response, inst)
response['uuid'] = inst['uuid']
+ self._build_config_drive(response, inst)
def _build_links(self, response, inst):
href = self.generate_href(inst["id"])
@@ -200,11 +207,15 @@ class ViewBuilderV11(ViewBuilder):
response["links"] = links
+ def _build_config_drive(self, response, inst):
+ response['config_drive'] = inst.get('config_drive')
+
def generate_href(self, server_id):
"""Create an url that refers to a specific server id."""
- return os.path.join(self.base_url, "servers", str(server_id))
+ return os.path.join(self.base_url, self.project_id,
+ "servers", str(server_id))
def generate_bookmark(self, server_id):
"""Create an url that refers to a specific flavor id."""
return os.path.join(common.remove_version_from_href(self.base_url),
- "servers", str(server_id))
+ self.project_id, "servers", str(server_id))
diff --git a/nova/api/openstack/wsgi.py b/nova/api/openstack/wsgi.py
index 0eb47044e..8641e960a 100644
--- a/nova/api/openstack/wsgi.py
+++ b/nova/api/openstack/wsgi.py
@@ -486,6 +486,10 @@ class Resource(wsgi.Application):
msg = _("Malformed request body")
return faults.Fault(webob.exc.HTTPBadRequest(explanation=msg))
+ project_id = args.pop("project_id", None)
+ if 'nova.context' in request.environ and project_id:
+ request.environ['nova.context'].project_id = project_id
+
try:
action_result = self.dispatch(request, action, args)
except webob.exc.HTTPException as ex:
@@ -516,6 +520,6 @@ class Resource(wsgi.Application):
controller_method = getattr(self.controller, action)
try:
return controller_method(req=request, **action_args)
- except TypeError, exc:
- LOG.debug(str(exc))
- return webob.exc.HTTPBadRequest()
+ except TypeError as exc:
+ LOG.exception(exc)
+ return faults.Fault(webob.exc.HTTPBadRequest())
diff --git a/nova/auth/manager.py b/nova/auth/manager.py
index 6205cfb56..44e6e11ac 100644
--- a/nova/auth/manager.py
+++ b/nova/auth/manager.py
@@ -17,6 +17,9 @@
# under the License.
"""
+WARNING: This code is deprecated and will be removed.
+Keystone is the recommended solution for auth management.
+
Nova authentication management
"""
@@ -38,10 +41,13 @@ from nova.auth import signer
FLAGS = flags.FLAGS
+flags.DEFINE_bool('use_deprecated_auth',
+ False,
+ 'This flag must be set to use old style auth')
+
flags.DEFINE_list('allowed_roles',
['cloudadmin', 'itsec', 'sysadmin', 'netadmin', 'developer'],
'Allowed roles for project')
-
# NOTE(vish): a user with one of these roles will be a superuser and
# have access to all api commands
flags.DEFINE_list('superuser_roles', ['cloudadmin'],
@@ -811,7 +817,13 @@ class AuthManager(object):
s3_host = host
ec2_host = host
rc = open(FLAGS.credentials_template).read()
- rc = rc % {'access': user.access,
+ # NOTE(vish): Deprecated auth uses an access key, no auth uses a
+ # the user_id in place of it.
+ if FLAGS.use_deprecated_auth:
+ access = user.access
+ else:
+ access = user.id
+ rc = rc % {'access': access,
'project': pid,
'secret': user.secret,
'ec2': '%s://%s:%s%s' % (FLAGS.ec2_scheme,
diff --git a/nova/cloudpipe/pipelib.py b/nova/cloudpipe/pipelib.py
index 2c4673f9e..3eb372844 100644
--- a/nova/cloudpipe/pipelib.py
+++ b/nova/cloudpipe/pipelib.py
@@ -34,7 +34,6 @@ from nova import exception
from nova import flags
from nova import log as logging
from nova import utils
-from nova.auth import manager
# TODO(eday): Eventually changes these to something not ec2-specific
from nova.api.ec2 import cloud
@@ -57,7 +56,6 @@ LOG = logging.getLogger('nova.cloudpipe')
class CloudPipe(object):
def __init__(self):
self.controller = cloud.CloudController()
- self.manager = manager.AuthManager()
def get_encoded_zip(self, project_id):
# Make a payload.zip
@@ -93,11 +91,10 @@ class CloudPipe(object):
zippy.close()
return encoded
- def launch_vpn_instance(self, project_id):
+ def launch_vpn_instance(self, project_id, user_id):
LOG.debug(_("Launching VPN for %s") % (project_id))
- project = self.manager.get_project(project_id)
- ctxt = context.RequestContext(user=project.project_manager_id,
- project=project.id)
+ ctxt = context.RequestContext(user_id=user_id,
+ project_id=project_id)
key_name = self.setup_key_pair(ctxt)
group_name = self.setup_security_group(ctxt)
diff --git a/nova/compute/api.py b/nova/compute/api.py
index efc9da79b..60a13631a 100644
--- a/nova/compute/api.py
+++ b/nova/compute/api.py
@@ -2,6 +2,7 @@
# Copyright 2010 United States Government as represented by the
# Administrator of the National Aeronautics and Space Administration.
+# Copyright 2011 Piston Cloud Computing, Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
@@ -54,15 +55,15 @@ def generate_default_hostname(instance):
"""Default function to generate a hostname given an instance reference."""
display_name = instance['display_name']
if display_name is None:
- return 'server_%d' % (instance['id'],)
+ return 'server-%d' % (instance['id'],)
table = ''
deletions = ''
for i in xrange(256):
c = chr(i)
if ('a' <= c <= 'z') or ('0' <= c <= '9') or (c == '-'):
table += c
- elif c == ' ':
- table += '_'
+ elif c in " _":
+ table += '-'
elif ('A' <= c <= 'Z'):
table += c.lower()
else:
@@ -146,6 +147,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 +164,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, config_drive=None,):
"""Verify all the input parameters regardless of the provisioning
strategy being performed."""
@@ -182,10 +194,16 @@ 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)
+ config_drive_id = None
+ if config_drive and config_drive is not True:
+ # config_drive is volume id
+ config_drive, config_drive_id = None, config_drive
+
os_type = None
if 'properties' in image and 'os_type' in image['properties']:
os_type = image['properties']['os_type']
@@ -213,6 +231,8 @@ class API(base.Base):
image_service.show(context, kernel_id)
if ramdisk_id:
image_service.show(context, ramdisk_id)
+ if config_drive_id:
+ image_service.show(context, config_drive_id)
self.ensure_default_security_group(context)
@@ -231,6 +251,8 @@ class API(base.Base):
'image_ref': image_href,
'kernel_id': kernel_id or '',
'ramdisk_id': ramdisk_id or '',
+ 'config_drive_id': config_drive_id or '',
+ 'config_drive': config_drive or '',
'state': 0,
'state_description': 'scheduling',
'user_id': context.user_id,
@@ -247,6 +269,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 +422,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
@@ -428,7 +452,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,
@@ -437,7 +462,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, config_drive=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."""
@@ -453,14 +480,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, config_drive)
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']
@@ -471,7 +499,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, config_drive=None,):
"""
Provision the instances by sending off a series of single
instance requests to the Schedulers. This is fine for trival
@@ -495,7 +525,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, config_drive)
block_device_mapping = block_device_mapping or []
instances = []
@@ -509,11 +540,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]
@@ -613,6 +644,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.
@@ -920,8 +1023,8 @@ class API(base.Base):
self._cast_compute_message('reboot_instance', context, instance_id)
@scheduler_api.reroute_compute("rebuild")
- def rebuild(self, context, instance_id, image_href, name=None,
- metadata=None, files_to_inject=None):
+ def rebuild(self, context, instance_id, image_href, admin_password,
+ name=None, metadata=None, files_to_inject=None):
"""Rebuild the given instance with the provided metadata."""
instance = db.api.instance_get(context, instance_id)
@@ -941,6 +1044,7 @@ class API(base.Base):
self.db.instance_update(context, instance_id, values)
rebuild_params = {
+ "new_pass": admin_password,
"image_ref": image_href,
"injected_files": files_to_inject,
}
diff --git a/nova/compute/manager.py b/nova/compute/manager.py
index 091b3b6b2..ade15e310 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.
@@ -524,6 +527,7 @@ class ComputeManager(manager.SchedulerDependentManager):
:param context: `nova.RequestContext` object
:param instance_id: Instance identifier (integer)
:param image_ref: Image identifier (href or integer)
+ :param new_pass: password to set on rebuilt instance
"""
context = context.elevated()
@@ -541,6 +545,11 @@ class ComputeManager(manager.SchedulerDependentManager):
network_info = self.network_api.get_instance_nw_info(context,
instance_ref)
bd_mapping = self._setup_block_device_mapping(context, instance_id)
+
+ # pull in new password here since the original password isn't in the db
+ instance_ref.admin_pass = kwargs.get('new_pass',
+ utils.generate_password(FLAGS.password_length))
+
self.driver.spawn(context, instance_ref, network_info, bd_mapping)
self._update_image_ref(context, instance_id, image_ref)
diff --git a/nova/db/api.py b/nova/db/api.py
index b9ea8757c..3bb9b4970 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)
@@ -1424,3 +1436,79 @@ def instance_type_extra_specs_update_or_create(context, instance_type_id,
key/value pairs specified in the extra specs dict argument"""
IMPL.instance_type_extra_specs_update_or_create(context, instance_type_id,
extra_specs)
+
+
+##################
+
+
+def volume_metadata_get(context, volume_id):
+ """Get all metadata for a volume."""
+ return IMPL.volume_metadata_get(context, volume_id)
+
+
+def volume_metadata_delete(context, volume_id, key):
+ """Delete the given metadata item."""
+ IMPL.volume_metadata_delete(context, volume_id, key)
+
+
+def volume_metadata_update(context, volume_id, metadata, delete):
+ """Update metadata if it exists, otherwise create it."""
+ IMPL.volume_metadata_update(context, volume_id, metadata, delete)
+
+
+##################
+
+
+def volume_type_create(context, values):
+ """Create a new volume type."""
+ return IMPL.volume_type_create(context, values)
+
+
+def volume_type_get_all(context, inactive=False):
+ """Get all volume types."""
+ return IMPL.volume_type_get_all(context, inactive)
+
+
+def volume_type_get(context, id):
+ """Get volume type by id."""
+ return IMPL.volume_type_get(context, id)
+
+
+def volume_type_get_by_name(context, name):
+ """Get volume type by name."""
+ return IMPL.volume_type_get_by_name(context, name)
+
+
+def volume_type_destroy(context, name):
+ """Delete a volume type."""
+ return IMPL.volume_type_destroy(context, name)
+
+
+def volume_type_purge(context, name):
+ """Purges (removes) a volume type from DB.
+
+ Use volume_type_destroy for most cases
+
+ """
+ return IMPL.volume_type_purge(context, name)
+
+
+####################
+
+
+def volume_type_extra_specs_get(context, volume_type_id):
+ """Get all extra specs for a volume type."""
+ return IMPL.volume_type_extra_specs_get(context, volume_type_id)
+
+
+def volume_type_extra_specs_delete(context, volume_type_id, key):
+ """Delete the given extra specs item."""
+ IMPL.volume_type_extra_specs_delete(context, volume_type_id, key)
+
+
+def volume_type_extra_specs_update_or_create(context, volume_type_id,
+ extra_specs):
+ """Create or update volume type extra specs. This adds or modifies the
+ key/value pairs specified in the extra specs dict argument"""
+ IMPL.volume_type_extra_specs_update_or_create(context, volume_type_id,
+ extra_specs)
diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py
index fe80056ab..d1fbf8cab 100644
--- a/nova/db/sqlalchemy/api.py
+++ b/nova/db/sqlalchemy/api.py
@@ -132,6 +132,20 @@ def require_instance_exists(f):
return wrapper
+def require_volume_exists(f):
+ """Decorator to require the specified volume to exist.
+
+ Requres the wrapped function to use context and volume_id as
+ their first two arguments.
+ """
+
+ def wrapper(context, volume_id, *args, **kwargs):
+ db.api.volume_get(context, volume_id)
+ return f(context, volume_id, *args, **kwargs)
+ wrapper.__name__ = f.__name__
+ return wrapper
+
+
###################
@@ -652,23 +666,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
@@ -1006,11 +1033,11 @@ def virtual_interface_delete_by_instance(context, instance_id):
###################
-def _metadata_refs(metadata_dict):
+def _metadata_refs(metadata_dict, meta_class):
metadata_refs = []
if metadata_dict:
for k, v in metadata_dict.iteritems():
- metadata_ref = models.InstanceMetadata()
+ metadata_ref = meta_class()
metadata_ref['key'] = k
metadata_ref['value'] = v
metadata_refs.append(metadata_ref)
@@ -1024,8 +1051,8 @@ def instance_create(context, values):
context - request context object
values - dict containing column values.
"""
- values['metadata'] = _metadata_refs(values.get('metadata'))
-
+ values['metadata'] = _metadata_refs(values.get('metadata'),
+ models.InstanceMetadata)
instance_ref = models.Instance()
instance_ref['uuid'] = str(utils.gen_uuid())
@@ -1502,6 +1529,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 +1782,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
@@ -2084,6 +2158,8 @@ def volume_attached(context, volume_id, instance_id, mountpoint):
@require_context
def volume_create(context, values):
+ values['volume_metadata'] = _metadata_refs(values.get('metadata'),
+ models.VolumeMetadata)
volume_ref = models.Volume()
volume_ref.update(values)
@@ -2120,6 +2196,11 @@ def volume_destroy(context, volume_id):
session.query(models.IscsiTarget).\
filter_by(volume_id=volume_id).\
update({'volume_id': None})
+ session.query(models.VolumeMetadata).\
+ filter_by(volume_id=volume_id).\
+ update({'deleted': True,
+ 'deleted_at': utils.utcnow(),
+ 'updated_at': literal_column('updated_at')})
@require_admin_context
@@ -2143,12 +2224,16 @@ def volume_get(context, volume_id, session=None):
if is_admin_context(context):
result = session.query(models.Volume).\
options(joinedload('instance')).\
+ options(joinedload('volume_metadata')).\
+ options(joinedload('volume_type')).\
filter_by(id=volume_id).\
filter_by(deleted=can_read_deleted(context)).\
first()
elif is_user_context(context):
result = session.query(models.Volume).\
options(joinedload('instance')).\
+ options(joinedload('volume_metadata')).\
+ options(joinedload('volume_type')).\
filter_by(project_id=context.project_id).\
filter_by(id=volume_id).\
filter_by(deleted=False).\
@@ -2164,6 +2249,8 @@ def volume_get_all(context):
session = get_session()
return session.query(models.Volume).\
options(joinedload('instance')).\
+ options(joinedload('volume_metadata')).\
+ options(joinedload('volume_type')).\
filter_by(deleted=can_read_deleted(context)).\
all()
@@ -2173,6 +2260,8 @@ def volume_get_all_by_host(context, host):
session = get_session()
return session.query(models.Volume).\
options(joinedload('instance')).\
+ options(joinedload('volume_metadata')).\
+ options(joinedload('volume_type')).\
filter_by(host=host).\
filter_by(deleted=can_read_deleted(context)).\
all()
@@ -2182,6 +2271,8 @@ def volume_get_all_by_host(context, host):
def volume_get_all_by_instance(context, instance_id):
session = get_session()
result = session.query(models.Volume).\
+ options(joinedload('volume_metadata')).\
+ options(joinedload('volume_type')).\
filter_by(instance_id=instance_id).\
filter_by(deleted=False).\
all()
@@ -2197,6 +2288,8 @@ def volume_get_all_by_project(context, project_id):
session = get_session()
return session.query(models.Volume).\
options(joinedload('instance')).\
+ options(joinedload('volume_metadata')).\
+ options(joinedload('volume_type')).\
filter_by(project_id=project_id).\
filter_by(deleted=can_read_deleted(context)).\
all()
@@ -2209,6 +2302,8 @@ def volume_get_instance(context, volume_id):
filter_by(id=volume_id).\
filter_by(deleted=can_read_deleted(context)).\
options(joinedload('instance')).\
+ options(joinedload('volume_metadata')).\
+ options(joinedload('volume_type')).\
first()
if not result:
raise exception.VolumeNotFound(volume_id=volume_id)
@@ -2243,12 +2338,116 @@ def volume_get_iscsi_target_num(context, volume_id):
@require_context
def volume_update(context, volume_id, values):
session = get_session()
+ metadata = values.get('metadata')
+ if metadata is not None:
+ volume_metadata_update(context,
+ volume_id,
+ values.pop('metadata'),
+ delete=True)
with session.begin():
volume_ref = volume_get(context, volume_id, session=session)
volume_ref.update(values)
volume_ref.save(session=session)
+####################
+
+
+@require_context
+@require_volume_exists
+def volume_metadata_get(context, volume_id):
+ session = get_session()
+
+ meta_results = session.query(models.VolumeMetadata).\
+ filter_by(volume_id=volume_id).\
+ filter_by(deleted=False).\
+ all()
+
+ meta_dict = {}
+ for i in meta_results:
+ meta_dict[i['key']] = i['value']
+ return meta_dict
+
+
+@require_context
+@require_volume_exists
+def volume_metadata_delete(context, volume_id, key):
+ session = get_session()
+ session.query(models.VolumeMetadata).\
+ filter_by(volume_id=volume_id).\
+ filter_by(key=key).\
+ filter_by(deleted=False).\
+ update({'deleted': True,
+ 'deleted_at': utils.utcnow(),
+ 'updated_at': literal_column('updated_at')})
+
+
+@require_context
+@require_volume_exists
+def volume_metadata_delete_all(context, volume_id):
+ session = get_session()
+ session.query(models.VolumeMetadata).\
+ filter_by(volume_id=volume_id).\
+ filter_by(deleted=False).\
+ update({'deleted': True,
+ 'deleted_at': utils.utcnow(),
+ 'updated_at': literal_column('updated_at')})
+
+
+@require_context
+@require_volume_exists
+def volume_metadata_get_item(context, volume_id, key, session=None):
+ if not session:
+ session = get_session()
+
+ meta_result = session.query(models.VolumeMetadata).\
+ filter_by(volume_id=volume_id).\
+ filter_by(key=key).\
+ filter_by(deleted=False).\
+ first()
+
+ if not meta_result:
+ raise exception.VolumeMetadataNotFound(metadata_key=key,
+ volume_id=volume_id)
+ return meta_result
+
+
+@require_context
+@require_volume_exists
+def volume_metadata_update(context, volume_id, metadata, delete):
+ session = get_session()
+
+ # Set existing metadata to deleted if delete argument is True
+ if delete:
+ original_metadata = volume_metadata_get(context, volume_id)
+ for meta_key, meta_value in original_metadata.iteritems():
+ if meta_key not in metadata:
+ meta_ref = volume_metadata_get_item(context, volume_id,
+ meta_key, session)
+ meta_ref.update({'deleted': True})
+ meta_ref.save(session=session)
+
+ meta_ref = None
+
+ # Now update all existing items with new values, or create new meta objects
+ for meta_key, meta_value in metadata.iteritems():
+
+ # update the value whether it exists or not
+ item = {"value": meta_value}
+
+ try:
+ meta_ref = volume_metadata_get_item(context, volume_id,
+ meta_key, session)
+ except exception.VolumeMetadataNotFound, e:
+ meta_ref = models.VolumeMetadata()
+ item.update({"key": meta_key, "volume_id": volume_id})
+
+ meta_ref.update(item)
+ meta_ref.save(session=session)
+
+ return metadata
+
+
###################
@@ -2437,6 +2636,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 +2644,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(
@@ -3081,7 +3282,7 @@ def instance_type_create(_context, values):
def _dict_with_extra_specs(inst_type_query):
- """Takes an instance type query returned by sqlalchemy
+ """Takes an instance OR volume type query returned by sqlalchemy
and returns it as a dictionary, converting the extra_specs
entry from a list of dicts:
@@ -3463,3 +3664,176 @@ def instance_type_extra_specs_update_or_create(context, instance_type_id,
"deleted": 0})
spec_ref.save(session=session)
return specs
+
+
+##################
+
+
+@require_admin_context
+def volume_type_create(_context, values):
+ """Create a new instance type. In order to pass in extra specs,
+ the values dict should contain a 'extra_specs' key/value pair:
+
+ {'extra_specs' : {'k1': 'v1', 'k2': 'v2', ...}}
+
+ """
+ try:
+ specs = values.get('extra_specs')
+
+ values['extra_specs'] = _metadata_refs(values.get('extra_specs'),
+ models.VolumeTypeExtraSpecs)
+ volume_type_ref = models.VolumeTypes()
+ volume_type_ref.update(values)
+ volume_type_ref.save()
+ except Exception, e:
+ raise exception.DBError(e)
+ return volume_type_ref
+
+
+@require_context
+def volume_type_get_all(context, inactive=False, filters={}):
+ """
+ Returns a dict describing all volume_types with name as key.
+ """
+ session = get_session()
+ if inactive:
+ vol_types = session.query(models.VolumeTypes).\
+ options(joinedload('extra_specs')).\
+ order_by("name").\
+ all()
+ else:
+ vol_types = session.query(models.VolumeTypes).\
+ options(joinedload('extra_specs')).\
+ filter_by(deleted=False).\
+ order_by("name").\
+ all()
+ vol_dict = {}
+ if vol_types:
+ for i in vol_types:
+ vol_dict[i['name']] = _dict_with_extra_specs(i)
+ return vol_dict
+
+
+@require_context
+def volume_type_get(context, id):
+ """Returns a dict describing specific volume_type"""
+ session = get_session()
+ vol_type = session.query(models.VolumeTypes).\
+ options(joinedload('extra_specs')).\
+ filter_by(id=id).\
+ first()
+
+ if not vol_type:
+ raise exception.VolumeTypeNotFound(volume_type=id)
+ else:
+ return _dict_with_extra_specs(vol_type)
+
+
+@require_context
+def volume_type_get_by_name(context, name):
+ """Returns a dict describing specific volume_type"""
+ session = get_session()
+ vol_type = session.query(models.VolumeTypes).\
+ options(joinedload('extra_specs')).\
+ filter_by(name=name).\
+ first()
+ if not vol_type:
+ raise exception.VolumeTypeNotFoundByName(volume_type_name=name)
+ else:
+ return _dict_with_extra_specs(vol_type)
+
+
+@require_admin_context
+def volume_type_destroy(context, name):
+ """ Marks specific volume_type as deleted"""
+ session = get_session()
+ volume_type_ref = session.query(models.VolumeTypes).\
+ filter_by(name=name)
+ records = volume_type_ref.update(dict(deleted=True))
+ if records == 0:
+ raise exception.VolumeTypeNotFoundByName(volume_type_name=name)
+ else:
+ return volume_type_ref
+
+
+@require_admin_context
+def volume_type_purge(context, name):
+ """ Removes specific volume_type from DB
+ Usually volume_type_destroy should be used
+ """
+ session = get_session()
+ volume_type_ref = session.query(models.VolumeTypes).\
+ filter_by(name=name)
+ records = volume_type_ref.delete()
+ if records == 0:
+ raise exception.VolumeTypeNotFoundByName(volume_type_name=name)
+ else:
+ return volume_type_ref
+
+
+####################
+
+
+@require_context
+def volume_type_extra_specs_get(context, volume_type_id):
+ session = get_session()
+
+ spec_results = session.query(models.VolumeTypeExtraSpecs).\
+ filter_by(volume_type_id=volume_type_id).\
+ filter_by(deleted=False).\
+ all()
+
+ spec_dict = {}
+ for i in spec_results:
+ spec_dict[i['key']] = i['value']
+ return spec_dict
+
+
+@require_context
+def volume_type_extra_specs_delete(context, volume_type_id, key):
+ session = get_session()
+ session.query(models.VolumeTypeExtraSpecs).\
+ filter_by(volume_type_id=volume_type_id).\
+ filter_by(key=key).\
+ filter_by(deleted=False).\
+ update({'deleted': True,
+ 'deleted_at': utils.utcnow(),
+ 'updated_at': literal_column('updated_at')})
+
+
+@require_context
+def volume_type_extra_specs_get_item(context, volume_type_id, key,
+ session=None):
+
+ if not session:
+ session = get_session()
+
+ spec_result = session.query(models.VolumeTypeExtraSpecs).\
+ filter_by(volume_type_id=volume_type_id).\
+ filter_by(key=key).\
+ filter_by(deleted=False).\
+ first()
+
+ if not spec_result:
+ raise exception.\
+ VolumeTypeExtraSpecsNotFound(extra_specs_key=key,
+ volume_type_id=volume_type_id)
+ return spec_result
+
+
+@require_context
+def volume_type_extra_specs_update_or_create(context, volume_type_id,
+ specs):
+ session = get_session()
+ spec_ref = None
+ for key, value in specs.iteritems():
+ try:
+ spec_ref = volume_type_extra_specs_get_item(
+ context, volume_type_id, key, session)
+ except exception.VolumeTypeExtraSpecsNotFound, e:
+ spec_ref = models.VolumeTypeExtraSpecs()
+ spec_ref.update({"key": key, "value": value,
+ "volume_type_id": volume_type_id,
+ "deleted": 0})
+ spec_ref.save(session=session)
+ return specs
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/migrate_repo/versions/041_add_config_drive_to_instances.py b/nova/db/sqlalchemy/migrate_repo/versions/041_add_config_drive_to_instances.py
new file mode 100644
index 000000000..d3058f00d
--- /dev/null
+++ b/nova/db/sqlalchemy/migrate_repo/versions/041_add_config_drive_to_instances.py
@@ -0,0 +1,38 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2011 Piston Cloud Computing, Inc.
+#
+# 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()
+
+instances = Table("instances", meta,
+ Column("id", Integer(), primary_key=True, nullable=False))
+
+# matches the size of an image_ref
+config_drive_column = Column("config_drive", String(255), nullable=True)
+
+
+def upgrade(migrate_engine):
+ meta.bind = migrate_engine
+ instances.create_column(config_drive_column)
+
+
+def downgrade(migrate_engine):
+ meta.bind = migrate_engine
+ instances.drop_column(config_drive_column)
diff --git a/nova/db/sqlalchemy/migrate_repo/versions/042_add_volume_types_and_extradata.py b/nova/db/sqlalchemy/migrate_repo/versions/042_add_volume_types_and_extradata.py
new file mode 100644
index 000000000..dd4cccb9e
--- /dev/null
+++ b/nova/db/sqlalchemy/migrate_repo/versions/042_add_volume_types_and_extradata.py
@@ -0,0 +1,115 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright (c) 2011 Zadara Storage Inc.
+# 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.
+
+from sqlalchemy import Column, DateTime, Integer, MetaData, String, Table
+from sqlalchemy import Text, Boolean, ForeignKey
+
+from nova import log as logging
+
+meta = MetaData()
+
+# Just for the ForeignKey and column creation to succeed, these are not the
+# actual definitions of tables .
+#
+
+volumes = Table('volumes', meta,
+ Column('id', Integer(), primary_key=True, nullable=False),
+ )
+
+volume_type_id = Column('volume_type_id', Integer(), nullable=True)
+
+
+# New Tables
+#
+
+volume_types = Table('volume_types', meta,
+ Column('created_at', DateTime(timezone=False)),
+ Column('updated_at', DateTime(timezone=False)),
+ Column('deleted_at', DateTime(timezone=False)),
+ Column('deleted', Boolean(create_constraint=True, name=None)),
+ Column('id', Integer(), primary_key=True, nullable=False),
+ Column('name',
+ String(length=255, convert_unicode=False, assert_unicode=None,
+ unicode_error=None, _warn_on_bytestring=False),
+ unique=True))
+
+volume_type_extra_specs_table = Table('volume_type_extra_specs', meta,
+ Column('created_at', DateTime(timezone=False)),
+ Column('updated_at', DateTime(timezone=False)),
+ Column('deleted_at', DateTime(timezone=False)),
+ Column('deleted', Boolean(create_constraint=True, name=None)),
+ Column('id', Integer(), primary_key=True, nullable=False),
+ Column('volume_type_id',
+ Integer(),
+ ForeignKey('volume_types.id'),
+ nullable=False),
+ Column('key',
+ String(length=255, convert_unicode=False, assert_unicode=None,
+ unicode_error=None, _warn_on_bytestring=False)),
+ Column('value',
+ String(length=255, convert_unicode=False, assert_unicode=None,
+ unicode_error=None, _warn_on_bytestring=False)))
+
+
+volume_metadata_table = Table('volume_metadata', meta,
+ Column('created_at', DateTime(timezone=False)),
+ Column('updated_at', DateTime(timezone=False)),
+ Column('deleted_at', DateTime(timezone=False)),
+ Column('deleted', Boolean(create_constraint=True, name=None)),
+ Column('id', Integer(), primary_key=True, nullable=False),
+ Column('volume_id',
+ Integer(),
+ ForeignKey('volumes.id'),
+ nullable=False),
+ Column('key',
+ String(length=255, convert_unicode=False, assert_unicode=None,
+ unicode_error=None, _warn_on_bytestring=False)),
+ Column('value',
+ String(length=255, convert_unicode=False, assert_unicode=None,
+ unicode_error=None, _warn_on_bytestring=False)))
+
+
+new_tables = (volume_types,
+ volume_type_extra_specs_table,
+ volume_metadata_table)
+
+#
+# Tables to alter
+#
+
+
+def upgrade(migrate_engine):
+ meta.bind = migrate_engine
+
+ for table in new_tables:
+ try:
+ table.create()
+ except Exception:
+ logging.info(repr(table))
+ logging.exception('Exception while creating table')
+ raise
+
+ volumes.create_column(volume_type_id)
+
+
+def downgrade(migrate_engine):
+ meta.bind = migrate_engine
+
+ volumes.drop_column(volume_type_id)
+
+ for table in new_tables:
+ table.drop()
diff --git a/nova/db/sqlalchemy/migration.py b/nova/db/sqlalchemy/migration.py
index d9e303599..765deb479 100644
--- a/nova/db/sqlalchemy/migration.py
+++ b/nova/db/sqlalchemy/migration.py
@@ -64,7 +64,8 @@ def db_version():
'users', 'user_project_association',
'user_project_role_association',
'user_role_association',
- 'volumes'):
+ 'volumes', 'volume_metadata',
+ 'volume_types', 'volume_type_extra_specs'):
assert table in meta.tables
return db_version_control(1)
except AssertionError:
diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py
index 0e2bace83..a37ccf91a 100644
--- a/nova/db/sqlalchemy/models.py
+++ b/nova/db/sqlalchemy/models.py
@@ -2,6 +2,7 @@
# Copyright 2010 United States Government as represented by the
# Administrator of the National Aeronautics and Space Administration.
+# Copyright 2011 Piston Cloud Computing, Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
@@ -230,6 +231,12 @@ class Instance(BASE, NovaBase):
uuid = Column(String(36))
root_device_name = Column(String(255))
+ config_drive = 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
@@ -311,6 +318,50 @@ class Volume(BASE, NovaBase):
provider_location = Column(String(255))
provider_auth = Column(String(255))
+ volume_type_id = Column(Integer)
+
+
+class VolumeMetadata(BASE, NovaBase):
+ """Represents a metadata key/value pair for a volume"""
+ __tablename__ = 'volume_metadata'
+ id = Column(Integer, primary_key=True)
+ key = Column(String(255))
+ value = Column(String(255))
+ volume_id = Column(Integer, ForeignKey('volumes.id'), nullable=False)
+ volume = relationship(Volume, backref="volume_metadata",
+ foreign_keys=volume_id,
+ primaryjoin='and_('
+ 'VolumeMetadata.volume_id == Volume.id,'
+ 'VolumeMetadata.deleted == False)')
+
+
+class VolumeTypes(BASE, NovaBase):
+ """Represent possible volume_types of volumes offered"""
+ __tablename__ = "volume_types"
+ id = Column(Integer, primary_key=True)
+ name = Column(String(255), unique=True)
+
+ volumes = relationship(Volume,
+ backref=backref('volume_type', uselist=False),
+ foreign_keys=id,
+ primaryjoin='and_(Volume.volume_type_id == '
+ 'VolumeTypes.id)')
+
+
+class VolumeTypeExtraSpecs(BASE, NovaBase):
+ """Represents additional specs as key/value pairs for a volume_type"""
+ __tablename__ = 'volume_type_extra_specs'
+ id = Column(Integer, primary_key=True)
+ key = Column(String(255))
+ value = Column(String(255))
+ volume_type_id = Column(Integer, ForeignKey('volume_types.id'),
+ nullable=False)
+ volume_type = relationship(VolumeTypes, backref="extra_specs",
+ foreign_keys=volume_type_id,
+ primaryjoin='and_('
+ 'VolumeTypeExtraSpecs.volume_type_id == VolumeTypes.id,'
+ 'VolumeTypeExtraSpecs.deleted == False)')
+
class Quota(BASE, NovaBase):
"""Represents a single quota override for a project.
@@ -556,6 +607,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):
@@ -795,6 +847,7 @@ def register_models():
Network, SecurityGroup, SecurityGroupIngressRule,
SecurityGroupInstanceAssociation, AuthToken, User,
Project, Certificate, ConsolePool, Console, Zone,
+ VolumeMetadata, VolumeTypes, VolumeTypeExtraSpecs,
AgentBuild, InstanceMetadata, InstanceTypeExtraSpecs, Migration)
engine = create_engine(FLAGS.sql_connection, echo=False)
for model in models:
diff --git a/nova/exception.py b/nova/exception.py
index b09d50797..067639042 100644
--- a/nova/exception.py
+++ b/nova/exception.py
@@ -197,6 +197,10 @@ class InvalidInstanceType(Invalid):
message = _("Invalid instance type %(instance_type)s.")
+class InvalidVolumeType(Invalid):
+ message = _("Invalid volume type %(volume_type)s.")
+
+
class InvalidPortRange(Invalid):
message = _("Invalid port range %(from_port)s:%(to_port)s.")
@@ -338,6 +342,29 @@ class VolumeNotFoundForInstance(VolumeNotFound):
message = _("Volume not found for instance %(instance_id)s.")
+class VolumeMetadataNotFound(NotFound):
+ message = _("Volume %(volume_id)s has no metadata with "
+ "key %(metadata_key)s.")
+
+
+class NoVolumeTypesFound(NotFound):
+ message = _("Zero volume types found.")
+
+
+class VolumeTypeNotFound(NotFound):
+ message = _("Volume type %(volume_type_id)s could not be found.")
+
+
+class VolumeTypeNotFoundByName(VolumeTypeNotFound):
+ message = _("Volume type with name %(volume_type_name)s "
+ "could not be found.")
+
+
+class VolumeTypeExtraSpecsNotFound(NotFound):
+ message = _("Volume Type %(volume_type_id)s has no extra specs with "
+ "key %(extra_specs_key)s.")
+
+
class SnapshotNotFound(NotFound):
message = _("Snapshot %(snapshot_id)s could not be found.")
@@ -423,6 +450,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 +492,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 +590,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/flags.py b/nova/flags.py
index 48d5e8168..95000df1b 100644
--- a/nova/flags.py
+++ b/nova/flags.py
@@ -402,3 +402,14 @@ DEFINE_bool('resume_guests_state_on_host_boot', False,
DEFINE_string('root_helper', 'sudo',
'Command prefix to use for running commands as root')
+
+DEFINE_bool('use_ipv6', False, 'use ipv6')
+
+DEFINE_bool('monkey_patch', False,
+ 'Whether to log monkey patching')
+
+DEFINE_list('monkey_patch_modules',
+ ['nova.api.ec2.cloud:nova.notifier.api.notify_decorator',
+ 'nova.compute.api:nova.notifier.api.notify_decorator'],
+ 'Module list representing monkey '
+ 'patched module and decorator')
diff --git a/nova/ipv6/account_identifier.py b/nova/ipv6/account_identifier.py
index 258678f0a..27bb01988 100644
--- a/nova/ipv6/account_identifier.py
+++ b/nova/ipv6/account_identifier.py
@@ -34,8 +34,12 @@ def to_global(prefix, mac, project_id):
mac_addr = netaddr.IPAddress(int_addr)
maskIP = netaddr.IPNetwork(prefix).ip
return (project_hash ^ static_num ^ mac_addr | maskIP).format()
- except TypeError:
+ except netaddr.AddrFormatError:
raise TypeError(_('Bad mac for to_global_ipv6: %s') % mac)
+ except TypeError:
+ raise TypeError(_('Bad prefix for to_global_ipv6: %s') % prefix)
+ except NameError:
+ raise TypeError(_('Bad project_id for to_global_ipv6: %s') % project_id)
def to_mac(ipv6_address):
diff --git a/nova/ipv6/rfc2462.py b/nova/ipv6/rfc2462.py
index 0074efe98..acf42d201 100644
--- a/nova/ipv6/rfc2462.py
+++ b/nova/ipv6/rfc2462.py
@@ -30,8 +30,10 @@ def to_global(prefix, mac, project_id):
maskIP = netaddr.IPNetwork(prefix).ip
return (mac64_addr ^ netaddr.IPAddress('::0200:0:0:0') | maskIP).\
format()
- except TypeError:
+ except netaddr.AddrFormatError:
raise TypeError(_('Bad mac for to_global_ipv6: %s') % mac)
+ except TypeError:
+ raise TypeError(_('Bad prefix for to_global_ipv6: %s') % prefix)
def to_mac(ipv6_address):
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 921c27e45..404a3180e 100644
--- a/nova/network/manager.py
+++ b/nova/network/manager.py
@@ -106,8 +106,6 @@ flags.DEFINE_integer('create_unique_mac_address_attempts', 5,
'Number of attempts to create unique mac address')
flags.DEFINE_bool('auto_assign_floating_ip', False,
'Autoassigning floating ip to VM')
-flags.DEFINE_bool('use_ipv6', False,
- 'use the ipv6')
flags.DEFINE_string('network_host', socket.gethostname(),
'Network host to use for ip allocation in flat modes')
flags.DEFINE_bool('fake_call', False,
@@ -131,7 +129,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 +154,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 +162,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 +207,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 +389,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 +417,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):
@@ -570,9 +586,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
@@ -798,6 +820,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.
@@ -832,8 +883,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."""
@@ -927,9 +986,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,
@@ -945,10 +1010,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."""
@@ -997,6 +1070,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/notifier/api.py b/nova/notifier/api.py
index e18f3e280..6ef4a050e 100644
--- a/nova/notifier/api.py
+++ b/nova/notifier/api.py
@@ -25,6 +25,9 @@ FLAGS = flags.FLAGS
flags.DEFINE_string('default_notification_level', 'INFO',
'Default notification level for outgoing notifications')
+flags.DEFINE_string('default_publisher_id', FLAGS.host,
+ 'Default publisher_id for outgoing notifications')
+
WARN = 'WARN'
INFO = 'INFO'
@@ -39,6 +42,30 @@ class BadPriorityException(Exception):
pass
+def notify_decorator(name, fn):
+ """ decorator for notify which is used from utils.monkey_patch()
+
+ :param name: name of the function
+ :param function: - object of the function
+ :returns: function -- decorated function
+
+ """
+ def wrapped_func(*args, **kwarg):
+ body = {}
+ body['args'] = []
+ body['kwarg'] = {}
+ for arg in args:
+ body['args'].append(arg)
+ for key in kwarg:
+ body['kwarg'][key] = kwarg[key]
+ notify(FLAGS.default_publisher_id,
+ name,
+ FLAGS.default_notification_level,
+ body)
+ return fn(*args, **kwarg)
+ return wrapped_func
+
+
def publisher_id(service, host=None):
if not host:
host = FLAGS.host
diff --git a/nova/notifier/list_notifier.py b/nova/notifier/list_notifier.py
new file mode 100644
index 000000000..955ae1b57
--- /dev/null
+++ b/nova/notifier/list_notifier.py
@@ -0,0 +1,68 @@
+# Copyright 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.
+
+from nova import flags
+from nova import log as logging
+from nova import utils
+from nova.exception import ClassNotFound
+
+flags.DEFINE_multistring('list_notifier_drivers',
+ ['nova.notifier.no_op_notifier'],
+ 'List of drivers to send notifications')
+
+FLAGS = flags.FLAGS
+
+LOG = logging.getLogger('nova.notifier.list_notifier')
+
+drivers = None
+
+
+class ImportFailureNotifier(object):
+ """Noisily re-raises some exception over-and-over when notify is called."""
+
+ def __init__(self, exception):
+ self.exception = exception
+
+ def notify(self, message):
+ raise self.exception
+
+
+def _get_drivers():
+ """Instantiates and returns drivers based on the flag values."""
+ global drivers
+ if not drivers:
+ drivers = []
+ for notification_driver in FLAGS.list_notifier_drivers:
+ try:
+ drivers.append(utils.import_object(notification_driver))
+ except ClassNotFound as e:
+ drivers.append(ImportFailureNotifier(e))
+ return drivers
+
+
+def notify(message):
+ """Passes notification to mulitple notifiers in a list."""
+ for driver in _get_drivers():
+ try:
+ driver.notify(message)
+ except Exception as e:
+ LOG.exception(_("Problem '%(e)s' attempting to send to "
+ "notification driver %(driver)s." % locals()))
+
+
+def _reset_drivers():
+ """Used by unit tests to reset the drivers."""
+ global drivers
+ drivers = None
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 d2ca9c365..568faf867 100644
--- a/nova/tests/api/openstack/contrib/test_floating_ips.py
+++ b/nova/tests/api/openstack/contrib/test_floating_ips.py
@@ -120,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)
@@ -135,7 +135,7 @@ 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)
@@ -144,7 +144,7 @@ class FloatingIpTest(test.TestCase):
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())
@@ -159,14 +159,14 @@ class FloatingIpTest(test.TestCase):
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, 202)
def test_add_floating_ip_to_instance(self):
body = dict(addFloatingIp=dict(address='11.0.0.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"
@@ -176,7 +176,7 @@ class FloatingIpTest(test.TestCase):
def test_remove_floating_ip_from_instance(self):
body = dict(removeFloatingIp=dict(address='11.0.0.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"
@@ -186,7 +186,7 @@ class FloatingIpTest(test.TestCase):
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/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"
@@ -196,7 +196,7 @@ class FloatingIpTest(test.TestCase):
def test_missing_dict_param_in_remove_floating_ip(self):
body = dict(removeFloatingIp='11.0.0.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"
@@ -206,7 +206,7 @@ class FloatingIpTest(test.TestCase):
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/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"
@@ -216,7 +216,7 @@ class FloatingIpTest(test.TestCase):
def test_missing_dict_param_in_add_floating_ip(self):
body = dict(addFloatingIp='11.0.0.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"
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
index d541a9e95..1db253b35 100644
--- a/nova/tests/api/openstack/contrib/test_virtual_interfaces.py
+++ b/nova/tests/api/openstack/contrib/test_virtual_interfaces.py
@@ -43,7 +43,7 @@ class ServerVirtualInterfaceTest(test.TestCase):
super(ServerVirtualInterfaceTest, self).tearDown()
def test_get_virtual_interfaces_list(self):
- req = webob.Request.blank('/v1.1/servers/1/os-virtual-interfaces')
+ 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)
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/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 d89cb28d6..c78588d65 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",
@@ -96,13 +97,14 @@ class ExtensionControllerTest(test.TestCase):
"SecurityGroups",
"VirtualInterfaces",
"Volumes",
+ "VolumeTypes",
]
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)
@@ -128,7 +130,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)
@@ -144,7 +146,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)
@@ -171,7 +173,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)
@@ -212,7 +214,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)
@@ -223,7 +225,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)
@@ -247,7 +249,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)
@@ -280,23 +282,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)
@@ -317,13 +322,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)
@@ -334,7 +339,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..3dfdeb79c 100644
--- a/nova/tests/api/openstack/test_server_actions.py
+++ b/nova/tests/api/openstack/test_server_actions.py
@@ -1,14 +1,13 @@
import base64
+import datetime
import json
-import unittest
-from xml.dom import minidom
import stubout
import webob
from nova import context
-from nova import db
from nova import utils
+from nova import exception
from nova import flags
from nova.api.openstack import create_instance_helper
from nova.compute import instance_types
@@ -23,61 +22,58 @@ FLAGS = flags.FLAGS
def return_server_by_id(context, id):
- return _get_instance()
+ return stub_instance(id)
def instance_update(context, instance_id, kwargs):
- return _get_instance()
+ return stub_instance(instance_id)
-def return_server_with_power_state(power_state):
+def return_server_with_attributes(**kwargs):
def _return_server(context, id):
- instance = _get_instance()
- instance['state'] = power_state
- return instance
+ return stub_instance(id, **kwargs)
return _return_server
+def return_server_with_power_state(power_state):
+ return return_server_with_attributes(power_state=power_state)
+
+
def return_server_with_uuid_and_power_state(power_state):
- def _return_server(context, id):
- return return_server_with_power_state(power_state)
- return _return_server
+ return return_server_with_power_state(power_state)
-class MockSetAdminPassword(object):
- def __init__(self):
- self.instance_id = None
- self.password = None
+def stub_instance(id, power_state=0, metadata=None,
+ image_ref="10", flavor_id="1", name=None):
- def __call__(self, context, instance_id, password):
- self.instance_id = instance_id
- self.password = password
+ if metadata is not None:
+ metadata_items = [{'key':k, 'value':v} for k, v in metadata.items()]
+ else:
+ metadata_items = [{'key':'seq', 'value':id}]
+ inst_type = instance_types.get_instance_type_by_flavor_id(int(flavor_id))
-def _get_instance():
instance = {
- "id": 1,
- "created_at": "2010-10-10 12:00:00",
- "updated_at": "2010-11-11 11:00:00",
+ "id": int(id),
+ "created_at": datetime.datetime(2010, 10, 10, 12, 0, 0),
+ "updated_at": datetime.datetime(2010, 11, 11, 11, 0, 0),
"admin_pass": "",
- "user_id": "",
- "project_id": "",
- "image_ref": "5",
+ "user_id": "fake",
+ "project_id": "fake",
+ "image_ref": image_ref,
"kernel_id": "",
"ramdisk_id": "",
"launch_index": 0,
"key_name": "",
"key_data": "",
- "state": 0,
+ "state": power_state,
"state_description": "",
"memory_mb": 0,
"vcpus": 0,
"local_gb": 0,
"hostname": "",
"host": "",
- "instance_type": {
- "flavorid": 1,
- },
+ "instance_type": dict(inst_type),
"user_data": "",
"reservation_id": "",
"mac_address": "",
@@ -85,17 +81,34 @@ def _get_instance():
"launched_at": utils.utcnow(),
"terminated_at": utils.utcnow(),
"availability_zone": "",
- "display_name": "test_server",
+ "display_name": name or "server%s" % id,
"display_description": "",
"locked": False,
- "metadata": [],
- #"address": ,
- #"floating_ips": [{"address":ip} for ip in public_addresses]}
- "uuid": "deadbeef-feed-edee-beef-d0ea7beefedd"}
+ "metadata": metadata_items,
+ "access_ip_v4": "",
+ "access_ip_v6": "",
+ "uuid": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
+ "virtual_interfaces": [],
+ }
+
+ instance["fixed_ips"] = {
+ "address": '192.168.0.1',
+ "floating_ips": [],
+ }
return instance
+class MockSetAdminPassword(object):
+ def __init__(self):
+ self.instance_id = None
+ self.password = None
+
+ def __call__(self, context, instance_id, password):
+ self.instance_id = instance_id
+ self.password = password
+
+
class ServerActionsTest(test.TestCase):
def setUp(self):
@@ -103,8 +116,6 @@ class ServerActionsTest(test.TestCase):
super(ServerActionsTest, self).setUp()
self.flags(verbose=True)
self.stubs = stubout.StubOutForTesting()
- fakes.FakeAuthManager.reset_fake_data()
- fakes.FakeAuthDatabase.data = {}
fakes.stub_out_auth(self.stubs)
self.stubs.Set(nova.db.api, 'instance_get', return_server_by_id)
self.stubs.Set(nova.db.api, 'instance_update', instance_update)
@@ -468,8 +479,6 @@ class ServerActionsTestV11(test.TestCase):
self.maxDiff = None
super(ServerActionsTestV11, self).setUp()
self.stubs = stubout.StubOutForTesting()
- fakes.FakeAuthManager.reset_fake_data()
- fakes.FakeAuthDatabase.data = {}
fakes.stub_out_auth(self.stubs)
self.stubs.Set(nova.db.api, 'instance_get', return_server_by_id)
self.stubs.Set(nova.db.api, 'instance_update', instance_update)
@@ -489,7 +498,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 +507,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 +518,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 +530,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 +544,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 +553,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 +562,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 +571,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 +580,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 +589,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 +598,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 +607,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)
@@ -606,19 +615,25 @@ class ServerActionsTestV11(test.TestCase):
self.assertEqual(res.status_int, 400)
def test_server_rebuild_accepted_minimum(self):
+ new_return_server = return_server_with_attributes(image_ref='2')
+ self.stubs.Set(nova.db.api, 'instance_get', new_return_server)
+
body = {
"rebuild": {
"imageRef": "http://localhost/images/2",
},
}
- 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)
res = req.get_response(fakes.wsgi_app())
self.assertEqual(res.status_int, 202)
+ body = json.loads(res.body)
+ self.assertEqual(body['server']['image']['id'], '2')
+ self.assertEqual(len(body['server']['adminPass']), 16)
def test_server_rebuild_rejected_when_building(self):
body = {
@@ -633,7 +648,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)
@@ -642,22 +657,27 @@ class ServerActionsTestV11(test.TestCase):
self.assertEqual(res.status_int, 409)
def test_server_rebuild_accepted_with_metadata(self):
+ metadata = {'new': 'metadata'}
+
+ new_return_server = return_server_with_attributes(metadata=metadata)
+ self.stubs.Set(nova.db.api, 'instance_get', new_return_server)
+
body = {
"rebuild": {
"imageRef": "http://localhost/images/2",
- "metadata": {
- "new": "metadata",
- },
+ "metadata": metadata,
},
}
- 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)
res = req.get_response(fakes.wsgi_app())
self.assertEqual(res.status_int, 202)
+ body = json.loads(res.body)
+ self.assertEqual(body['server']['metadata'], metadata)
def test_server_rebuild_accepted_with_bad_metadata(self):
body = {
@@ -667,7 +687,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 +702,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 +721,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,17 +740,60 @@ 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)
res = req.get_response(fakes.wsgi_app())
self.assertEqual(res.status_int, 202)
+ body = json.loads(res.body)
+ self.assertTrue('personality' not in body['server'])
+
+ def test_server_rebuild_admin_pass(self):
+ new_return_server = return_server_with_attributes(image_ref='2')
+ self.stubs.Set(nova.db.api, 'instance_get', new_return_server)
+
+ body = {
+ "rebuild": {
+ "imageRef": "http://localhost/images/2",
+ "adminPass": "asdf",
+ },
+ }
+
+ req = webob.Request.blank('/v1.1/fake/servers/1/action')
+ req.method = 'POST'
+ req.content_type = 'application/json'
+ req.body = json.dumps(body)
+
+ res = req.get_response(fakes.wsgi_app())
+ self.assertEqual(res.status_int, 202)
+ body = json.loads(res.body)
+ self.assertEqual(body['server']['image']['id'], '2')
+ self.assertEqual(body['server']['adminPass'], 'asdf')
+
+ def test_server_rebuild_server_not_found(self):
+ def server_not_found(self, instance_id):
+ raise exception.InstanceNotFound(instance_id=instance_id)
+ self.stubs.Set(nova.db.api, 'instance_get', server_not_found)
+
+ body = {
+ "rebuild": {
+ "imageRef": "http://localhost/images/2",
+ },
+ }
+
+ req = webob.Request.blank('/v1.1/fake/servers/1/action')
+ req.method = 'POST'
+ req.content_type = 'application/json'
+ req.body = json.dumps(body)
+
+ res = req.get_response(fakes.wsgi_app())
+ self.assertEqual(res.status_int, 404)
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 +811,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 +821,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 +831,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 +849,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 +872,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 +891,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 +905,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 +923,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 +934,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 +948,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 +967,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 437620854..3559e6de5 100644
--- a/nova/tests/api/openstack/test_servers.py
+++ b/nova/tests/api/openstack/test_servers.py
@@ -1,6 +1,7 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2010-2011 OpenStack LLC.
+# Copyright 2011 Piston Cloud Computing, Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
@@ -19,6 +20,7 @@ import base64
import datetime
import json
import unittest
+from lxml import etree
from xml.dom import minidom
import webob
@@ -32,6 +34,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 +49,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():
@@ -145,7 +150,8 @@ def instance_addresses(context, instance_id):
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):
+ flavor_id="1", interfaces=None, name=None,
+ access_ipv4=None, access_ipv6=None):
metadata = []
metadata.append(InstanceMetadata(key='seq', value=id))
@@ -197,6 +203,8 @@ def stub_instance(id, user_id='fake', project_id='fake', private_address=None,
"display_description": "",
"locked": False,
"metadata": metadata,
+ "access_ip_v4": access_ipv4,
+ "access_ip_v6": access_ipv6,
"uuid": uuid,
"virtual_interfaces": interfaces}
@@ -226,7 +234,6 @@ class MockSetAdminPassword(object):
class ServersTest(test.TestCase):
-
def setUp(self):
self.maxDiff = None
super(ServersTest, self).setUp()
@@ -258,6 +265,7 @@ class ServersTest(test.TestCase):
self.stubs.Set(nova.compute.API, "get_actions", fake_compute_api)
self.webreq = common.webob_factory('/v1.0/servers')
+ self.config_drive = None
def test_get_server_by_id(self):
req = webob.Request.blank('/v1.0/servers/1')
@@ -297,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'
@@ -322,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 = {
@@ -334,6 +342,8 @@ class ServersTest(test.TestCase):
"progress": 0,
"name": "server1",
"status": "BUILD",
+ "accessIPv4": "",
+ "accessIPv6": "",
"hostId": '',
"image": {
"id": "10",
@@ -370,15 +380,16 @@ class ServersTest(test.TestCase):
"metadata": {
"seq": "1",
},
+ "config_drive": None,
"links": [
{
"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",
},
],
}
@@ -387,12 +398,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'
@@ -414,7 +425,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(' ', ''))
@@ -431,6 +442,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"/>
@@ -459,10 +472,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"
@@ -484,7 +497,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 = {
@@ -496,6 +509,8 @@ class ServersTest(test.TestCase):
"progress": 100,
"name": "server1",
"status": "ACTIVE",
+ "accessIPv4": "",
+ "accessIPv6": "",
"hostId": '',
"image": {
"id": "10",
@@ -532,14 +547,15 @@ class ServersTest(test.TestCase):
"metadata": {
"seq": "1",
},
+ "config_drive": None,
"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",
},
],
}
@@ -549,10 +565,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"
@@ -575,7 +591,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 = {
@@ -587,6 +603,8 @@ class ServersTest(test.TestCase):
"progress": 100,
"name": "server1",
"status": "ACTIVE",
+ "accessIPv4": "",
+ "accessIPv6": "",
"hostId": '',
"image": {
"id": "10",
@@ -623,14 +641,15 @@ class ServersTest(test.TestCase):
"metadata": {
"seq": "1",
},
+ "config_drive": None,
"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",
},
],
}
@@ -752,6 +771,27 @@ class ServersTest(test.TestCase):
(ip,) = private_node.getElementsByTagName('ip')
self.assertEquals(ip.getAttribute('addr'), private)
+ # NOTE(bcwaldon): lp830817
+ def test_get_server_by_id_malformed_networks_v1_1(self):
+ ifaces = [
+ {
+ 'network': None,
+ 'fixed_ips': [
+ {'address': '192.168.0.3'},
+ {'address': '192.168.0.4'},
+ ],
+ },
+ ]
+ new_return_server = return_server_with_attributes(interfaces=ifaces)
+ self.stubs.Set(nova.db.api, 'instance_get', new_return_server)
+
+ req = webob.Request.blank('/v1.1/fake/servers/1')
+ 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'], 'server1')
+
def test_get_server_by_id_with_addresses_v1_1(self):
self.flags(use_ipv6=True)
interfaces = [
@@ -775,7 +815,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)
@@ -819,7 +859,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)
@@ -869,7 +909,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)
@@ -919,7 +959,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)
@@ -939,7 +979,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)
@@ -949,7 +989,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)
@@ -1018,7 +1058,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)
@@ -1031,11 +1071,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'],
},
]
@@ -1082,19 +1122,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)
@@ -1120,7 +1160,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']
@@ -1137,7 +1177,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
@@ -1157,7 +1197,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
@@ -1177,7 +1217,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
@@ -1191,7 +1231,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
@@ -1208,7 +1248,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
@@ -1239,7 +1279,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)
@@ -1273,7 +1313,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)
@@ -1306,7 +1346,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)
@@ -1332,7 +1372,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)
@@ -1358,7 +1398,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)
@@ -1379,9 +1419,12 @@ 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,
"created_at": datetime.datetime(2010, 10, 10, 12, 0, 0),
"updated_at": datetime.datetime(2010, 11, 11, 11, 0, 0),
+ "config_drive": self.config_drive,
}
def server_update(context, id, params):
@@ -1407,8 +1450,7 @@ class ServersTest(test.TestCase):
self.stubs.Set(nova.db.api, 'instance_create', instance_create)
self.stubs.Set(nova.rpc, 'cast', fake_method)
self.stubs.Set(nova.rpc, 'call', fake_method)
- self.stubs.Set(nova.db.api, 'instance_update',
- server_update)
+ self.stubs.Set(nova.db.api, 'instance_update', server_update)
self.stubs.Set(nova.db.api, 'queue_get_for', queue_get_for)
self.stubs.Set(nova.network.manager.VlanManager, 'allocate_fixed_ip',
fake_method)
@@ -1579,18 +1621,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',
},
],
}
@@ -1599,7 +1705,7 @@ class ServersTest(test.TestCase):
"links": [
{
"rel": "bookmark",
- "href": 'http://localhost/images/2',
+ "href": 'http://localhost/fake/images/2',
},
],
}
@@ -1621,7 +1727,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"
@@ -1636,6 +1742,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()
@@ -1646,7 +1754,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"
@@ -1656,13 +1764,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"
@@ -1678,13 +1786,136 @@ 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"
res = req.get_response(fakes.wsgi_app())
self.assertEqual(res.status_int, 400)
+ def test_create_instance_with_config_drive_v1_1(self):
+ self.config_drive = True
+ self._setup_for_create_instance()
+
+ image_href = 'http://localhost/v1.1/123/images/2'
+ flavor_ref = 'http://localhost/v1.1/123/flavors/3'
+ body = {
+ 'server': {
+ 'name': 'config_drive_test',
+ 'imageRef': image_href,
+ 'flavorRef': flavor_ref,
+ 'metadata': {
+ 'hello': 'world',
+ 'open': 'stack',
+ },
+ 'personality': {},
+ 'config_drive': True,
+ },
+ }
+
+ 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())
+ print res
+ self.assertEqual(res.status_int, 202)
+ server = json.loads(res.body)['server']
+ self.assertEqual(1, server['id'])
+ self.assertTrue(server['config_drive'])
+
+ def test_create_instance_with_config_drive_as_id_v1_1(self):
+ self.config_drive = 2
+ self._setup_for_create_instance()
+
+ image_href = 'http://localhost/v1.1/123/images/2'
+ flavor_ref = 'http://localhost/v1.1/123/flavors/3'
+ body = {
+ 'server': {
+ 'name': 'config_drive_test',
+ 'imageRef': image_href,
+ 'flavorRef': flavor_ref,
+ 'metadata': {
+ 'hello': 'world',
+ 'open': 'stack',
+ },
+ 'personality': {},
+ 'config_drive': 2,
+ },
+ }
+
+ 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(1, server['id'])
+ self.assertTrue(server['config_drive'])
+ self.assertEqual(2, server['config_drive'])
+
+ def test_create_instance_with_bad_config_drive_v1_1(self):
+ self.config_drive = "asdf"
+ self._setup_for_create_instance()
+
+ image_href = 'http://localhost/v1.1/123/images/2'
+ flavor_ref = 'http://localhost/v1.1/123/flavors/3'
+ body = {
+ 'server': {
+ 'name': 'config_drive_test',
+ 'imageRef': image_href,
+ 'flavorRef': flavor_ref,
+ 'metadata': {
+ 'hello': 'world',
+ 'open': 'stack',
+ },
+ 'personality': {},
+ 'config_drive': 'asdf',
+ },
+ }
+
+ 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, 400)
+
+ def test_create_instance_without_config_drive_v1_1(self):
+ self._setup_for_create_instance()
+
+ image_href = 'http://localhost/v1.1/123/images/2'
+ flavor_ref = 'http://localhost/v1.1/123/flavors/3'
+ body = {
+ 'server': {
+ 'name': 'config_drive_test',
+ 'imageRef': image_href,
+ 'flavorRef': flavor_ref,
+ 'metadata': {
+ 'hello': 'world',
+ 'open': 'stack',
+ },
+ 'personality': {},
+ 'config_drive': True,
+ },
+ }
+
+ 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(1, server['id'])
+ self.assertFalse(server['config_drive'])
+
def test_create_instance_v1_1_bad_href(self):
self._setup_for_create_instance()
@@ -1694,7 +1925,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"
@@ -1711,7 +1942,7 @@ class ServersTest(test.TestCase):
"links": [
{
"rel": "bookmark",
- "href": 'http://localhost/flavors/3',
+ "href": 'http://localhost/fake/flavors/3',
},
],
}
@@ -1720,7 +1951,7 @@ class ServersTest(test.TestCase):
"links": [
{
"rel": "bookmark",
- "href": 'http://localhost/images/2',
+ "href": 'http://localhost/fake/images/2',
},
],
}
@@ -1732,7 +1963,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"
@@ -1779,7 +2010,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"
@@ -1800,13 +2031,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'
@@ -1874,15 +2128,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'}})
@@ -1892,6 +2168,32 @@ class ServersTest(test.TestCase):
self.assertEqual(res_dict['server']['id'], 1)
self.assertEqual(res_dict['server']['name'], 'server_test')
+ 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))
@@ -1905,7 +2207,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
@@ -1938,7 +2240,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)
@@ -1978,7 +2280,7 @@ class ServersTest(test.TestCase):
"links": [
{
"rel": "bookmark",
- "href": 'http://localhost/flavors/1',
+ "href": 'http://localhost/fake/flavors/1',
},
],
}
@@ -1987,11 +2289,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)
@@ -2150,7 +2452,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
@@ -2491,6 +2793,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"
@@ -2642,6 +3000,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):
@@ -2712,12 +3228,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',
'uuid': FAKE_UUID}]
@@ -3039,24 +3557,28 @@ class ServersViewBuilderV11Test(test.TestCase):
"display_description": "",
"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):
@@ -3075,12 +3597,37 @@ class ServersViewBuilderV11Test(test.TestCase):
"href": "http://localhost/servers/1",
},
],
+ "config_drive": None,
}
}
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",
+ "config_drive": None,
+ "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"
@@ -3093,6 +3640,8 @@ class ServersViewBuilderV11Test(test.TestCase):
"progress": 0,
"name": "test_server",
"status": "BUILD",
+ "accessIPv4": "",
+ "accessIPv6": "",
"hostId": '',
"image": {
"id": "5",
@@ -3114,6 +3663,7 @@ class ServersViewBuilderV11Test(test.TestCase):
},
"addresses": {},
"metadata": {},
+ "config_drive": None,
"links": [
{
"rel": "self",
@@ -3144,6 +3694,8 @@ class ServersViewBuilderV11Test(test.TestCase):
"progress": 100,
"name": "test_server",
"status": "ACTIVE",
+ "accessIPv4": "",
+ "accessIPv6": "",
"hostId": '',
"image": {
"id": "5",
@@ -3165,6 +3717,117 @@ class ServersViewBuilderV11Test(test.TestCase):
},
"addresses": {},
"metadata": {},
+ "config_drive": None,
+ "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_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'],
+ "updated": "2010-11-11T11:00:00Z",
+ "created": "2010-10-10T12:00:00Z",
+ "progress": 0,
+ "name": "test_server",
+ "status": "BUILD",
+ "hostId": '',
+ "image": {
+ "id": "5",
+ "links": [
+ {
+ "rel": "bookmark",
+ "href": image_bookmark,
+ },
+ ],
+ },
+ "flavor": {
+ "id": "1",
+ "links": [
+ {
+ "rel": "bookmark",
+ "href": flavor_bookmark,
+ },
+ ],
+ },
+ "addresses": {},
+ "metadata": {},
+ "config_drive": None,
+ "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'],
+ "updated": "2010-11-11T11:00:00Z",
+ "created": "2010-10-10T12:00:00Z",
+ "progress": 0,
+ "name": "test_server",
+ "status": "BUILD",
+ "hostId": '',
+ "image": {
+ "id": "5",
+ "links": [
+ {
+ "rel": "bookmark",
+ "href": image_bookmark,
+ },
+ ],
+ },
+ "flavor": {
+ "id": "1",
+ "links": [
+ {
+ "rel": "bookmark",
+ "href": flavor_bookmark,
+ },
+ ],
+ },
+ "addresses": {},
+ "metadata": {},
+ "config_drive": None,
+ "accessIPv4": "",
+ "accessIPv6": "fead::1234",
"links": [
{
"rel": "self",
@@ -3199,6 +3862,8 @@ class ServersViewBuilderV11Test(test.TestCase):
"progress": 0,
"name": "test_server",
"status": "BUILD",
+ "accessIPv4": "",
+ "accessIPv6": "",
"hostId": '',
"image": {
"id": "5",
@@ -3223,6 +3888,7 @@ class ServersViewBuilderV11Test(test.TestCase):
"Open": "Stack",
"Number": "1",
},
+ "config_drive": None,
"links": [
{
"rel": "self",
@@ -3265,6 +3931,8 @@ class ServerXMLSerializationTest(test.TestCase):
"name": "test_server",
"status": "BUILD",
"hostId": 'e4d909c290d0fb1ca068ffaddf22cbd0',
+ "accessIPv4": "1.2.3.4",
+ "accessIPv6": "fead::1234",
"image": {
"id": "5",
"links": [
@@ -3323,7 +3991,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
@@ -3331,47 +4001,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"
- xmlns="http://docs.openstack.org/compute/api/v1.1"
- xmlns:atom="http://www.w3.org/2005/Atom"
- name="test_server"
- 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()
@@ -3385,6 +4065,8 @@ class ServerXMLSerializationTest(test.TestCase):
"progress": 0,
"name": "test_server",
"status": "BUILD",
+ "accessIPv4": "1.2.3.4",
+ "accessIPv6": "fead::1234",
"hostId": "e4d909c290d0fb1ca068ffaddf22cbd0",
"adminPass": "test_password",
"image": {
@@ -3445,7 +4127,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
@@ -3453,48 +4137,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"
- xmlns="http://docs.openstack.org/compute/api/v1.1"
- xmlns:atom="http://www.w3.org/2005/Atom"
- name="test_server"
- 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()
@@ -3535,23 +4228,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()
@@ -3574,6 +4265,8 @@ class ServerXMLSerializationTest(test.TestCase):
"progress": 0,
"name": "test_server",
"status": "BUILD",
+ "accessIPv4": "1.2.3.4",
+ "accessIPv6": "fead::1234",
"hostId": 'e4d909c290d0fb1ca068ffaddf22cbd0',
"image": {
"id": "5",
@@ -3627,6 +4320,8 @@ class ServerXMLSerializationTest(test.TestCase):
"progress": 100,
"name": "test_server_2",
"status": "ACTIVE",
+ "accessIPv4": "1.2.3.4",
+ "accessIPv6": "fead::1234",
"hostId": 'e4d909c290d0fb1ca068ffaddf22cbd0',
"image": {
"id": "5",
@@ -3675,71 +4370,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"
- name="test_server"
- 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"
- name="test_server_2"
- 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()
@@ -3754,6 +4441,8 @@ class ServerXMLSerializationTest(test.TestCase):
"name": "test_server",
"status": "BUILD",
"hostId": 'e4d909c290d0fb1ca068ffaddf22cbd0',
+ "accessIPv4": "1.2.3.4",
+ "accessIPv6": "fead::1234",
"image": {
"id": "5",
"links": [
@@ -3812,7 +4501,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
@@ -3820,44 +4511,189 @@ 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"
- xmlns="http://docs.openstack.org/compute/api/v1.1"
- xmlns:atom="http://www.w3.org/2005/Atom"
- name="test_server"
- 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()))
+ 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_action(self):
+ serializer = servers.ServerXMLSerializer()
- self.assertEqual(expected.toxml(), actual.toxml())
+ fixture = {
+ "server": {
+ "id": 1,
+ "uuid": FAKE_UUID,
+ 'created': self.TIMESTAMP,
+ 'updated': self.TIMESTAMP,
+ "progress": 0,
+ "name": "test_server",
+ "status": "BUILD",
+ "accessIPv4": "1.2.3.4",
+ "accessIPv6": "fead::1234",
+ "hostId": "e4d909c290d0fb1ca068ffaddf22cbd0",
+ "adminPass": "test_password",
+ "image": {
+ "id": "5",
+ "links": [
+ {
+ "rel": "bookmark",
+ "href": self.IMAGE_BOOKMARK,
+ },
+ ],
+ },
+ "flavor": {
+ "id": "1",
+ "links": [
+ {
+ "rel": "bookmark",
+ "href": self.FLAVOR_BOOKMARK,
+ },
+ ],
+ },
+ "addresses": {
+ "network_one": [
+ {
+ "version": 4,
+ "addr": "67.23.10.138",
+ },
+ {
+ "version": 6,
+ "addr": "::babe:67.23.10.138",
+ },
+ ],
+ "network_two": [
+ {
+ "version": 4,
+ "addr": "67.23.10.139",
+ },
+ {
+ "version": 6,
+ "addr": "::babe:67.23.10.139",
+ },
+ ],
+ },
+ "metadata": {
+ "Open": "Stack",
+ "Number": "1",
+ },
+ 'links': [
+ {
+ 'href': self.SERVER_HREF,
+ 'rel': 'self',
+ },
+ {
+ 'href': self.SERVER_BOOKMARK,
+ 'rel': 'bookmark',
+ },
+ ],
+ }
+ }
+
+ output = serializer.serialize(fixture, 'action')
+ root = etree.XML(output)
+ xmlutil.validate_schema(root, 'server')
+
+ expected_server_href = self.SERVER_HREF
+ expected_server_bookmark = self.SERVER_BOOKMARK
+ expected_image_bookmark = self.IMAGE_BOOKMARK
+ expected_flavor_bookmark = self.FLAVOR_BOOKMARK
+ expected_now = self.TIMESTAMP
+ expected_uuid = FAKE_UUID
+ 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']))
diff --git a/nova/tests/api/openstack/test_volume_types.py b/nova/tests/api/openstack/test_volume_types.py
new file mode 100644
index 000000000..192e66854
--- /dev/null
+++ b/nova/tests/api/openstack/test_volume_types.py
@@ -0,0 +1,171 @@
+# Copyright 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 json
+import stubout
+import webob
+
+from nova import exception
+from nova import context
+from nova import test
+from nova import log as logging
+from nova.volume import volume_types
+from nova.tests.api.openstack import fakes
+
+LOG = logging.getLogger('nova.tests.api.openstack.test_volume_types')
+
+last_param = {}
+
+
+def stub_volume_type(id):
+ specs = {
+ "key1": "value1",
+ "key2": "value2",
+ "key3": "value3",
+ "key4": "value4",
+ "key5": "value5"}
+ return dict(id=id, name='vol_type_%s' % str(id), extra_specs=specs)
+
+
+def return_volume_types_get_all_types(context):
+ return dict(vol_type_1=stub_volume_type(1),
+ vol_type_2=stub_volume_type(2),
+ vol_type_3=stub_volume_type(3))
+
+
+def return_empty_volume_types_get_all_types(context):
+ return {}
+
+
+def return_volume_types_get_volume_type(context, id):
+ if id == "777":
+ raise exception.VolumeTypeNotFound(volume_type_id=id)
+ return stub_volume_type(int(id))
+
+
+def return_volume_types_destroy(context, name):
+ if name == "777":
+ raise exception.VolumeTypeNotFoundByName(volume_type_name=name)
+ pass
+
+
+def return_volume_types_create(context, name, specs):
+ pass
+
+
+def return_volume_types_get_by_name(context, name):
+ if name == "777":
+ raise exception.VolumeTypeNotFoundByName(volume_type_name=name)
+ return stub_volume_type(int(name.split("_")[2]))
+
+
+class VolumeTypesApiTest(test.TestCase):
+ def setUp(self):
+ super(VolumeTypesApiTest, self).setUp()
+ fakes.stub_out_key_pair_funcs(self.stubs)
+
+ def tearDown(self):
+ self.stubs.UnsetAll()
+ super(VolumeTypesApiTest, self).tearDown()
+
+ def test_volume_types_index(self):
+ self.stubs.Set(volume_types, 'get_all_types',
+ return_volume_types_get_all_types)
+ req = webob.Request.blank('/v1.1/123/os-volume-types')
+ res = req.get_response(fakes.wsgi_app())
+ self.assertEqual(200, res.status_int)
+ res_dict = json.loads(res.body)
+ self.assertEqual('application/json', res.headers['Content-Type'])
+
+ self.assertEqual(3, len(res_dict))
+ for name in ['vol_type_1', 'vol_type_2', 'vol_type_3']:
+ self.assertEqual(name, res_dict[name]['name'])
+ self.assertEqual('value1', res_dict[name]['extra_specs']['key1'])
+
+ def test_volume_types_index_no_data(self):
+ self.stubs.Set(volume_types, 'get_all_types',
+ return_empty_volume_types_get_all_types)
+ req = webob.Request.blank('/v1.1/123/os-volume-types')
+ res = req.get_response(fakes.wsgi_app())
+ res_dict = json.loads(res.body)
+ self.assertEqual(200, res.status_int)
+ self.assertEqual('application/json', res.headers['Content-Type'])
+ self.assertEqual(0, len(res_dict))
+
+ def test_volume_types_show(self):
+ self.stubs.Set(volume_types, 'get_volume_type',
+ return_volume_types_get_volume_type)
+ req = webob.Request.blank('/v1.1/123/os-volume-types/1')
+ res = req.get_response(fakes.wsgi_app())
+ self.assertEqual(200, res.status_int)
+ res_dict = json.loads(res.body)
+ self.assertEqual('application/json', res.headers['Content-Type'])
+ self.assertEqual(1, len(res_dict))
+ self.assertEqual('vol_type_1', res_dict['volume_type']['name'])
+
+ def test_volume_types_show_not_found(self):
+ self.stubs.Set(volume_types, 'get_volume_type',
+ return_volume_types_get_volume_type)
+ req = webob.Request.blank('/v1.1/123/os-volume-types/777')
+ res = req.get_response(fakes.wsgi_app())
+ self.assertEqual(404, res.status_int)
+
+ def test_volume_types_delete(self):
+ self.stubs.Set(volume_types, 'get_volume_type',
+ return_volume_types_get_volume_type)
+ self.stubs.Set(volume_types, 'destroy',
+ return_volume_types_destroy)
+ req = webob.Request.blank('/v1.1/123/os-volume-types/1')
+ req.method = 'DELETE'
+ res = req.get_response(fakes.wsgi_app())
+ self.assertEqual(200, res.status_int)
+
+ def test_volume_types_delete_not_found(self):
+ self.stubs.Set(volume_types, 'get_volume_type',
+ return_volume_types_get_volume_type)
+ self.stubs.Set(volume_types, 'destroy',
+ return_volume_types_destroy)
+ req = webob.Request.blank('/v1.1/123/os-volume-types/777')
+ req.method = 'DELETE'
+ res = req.get_response(fakes.wsgi_app())
+ self.assertEqual(404, res.status_int)
+
+ def test_create(self):
+ self.stubs.Set(volume_types, 'create',
+ return_volume_types_create)
+ self.stubs.Set(volume_types, 'get_volume_type_by_name',
+ return_volume_types_get_by_name)
+ req = webob.Request.blank('/v1.1/123/os-volume-types')
+ req.method = 'POST'
+ req.body = '{"volume_type": {"name": "vol_type_1", '\
+ '"extra_specs": {"key1": "value1"}}}'
+ req.headers["content-type"] = "application/json"
+ res = req.get_response(fakes.wsgi_app())
+ self.assertEqual(200, res.status_int)
+ res_dict = json.loads(res.body)
+ self.assertEqual('application/json', res.headers['Content-Type'])
+ self.assertEqual(1, len(res_dict))
+ self.assertEqual('vol_type_1', res_dict['volume_type']['name'])
+
+ def test_create_empty_body(self):
+ self.stubs.Set(volume_types, 'create',
+ return_volume_types_create)
+ self.stubs.Set(volume_types, 'get_volume_type_by_name',
+ return_volume_types_get_by_name)
+ req = webob.Request.blank('/v1.1/123/os-volume-types')
+ req.method = 'POST'
+ req.headers["content-type"] = "application/json"
+ res = req.get_response(fakes.wsgi_app())
+ self.assertEqual(400, res.status_int)
diff --git a/nova/tests/api/openstack/test_volume_types_extra_specs.py b/nova/tests/api/openstack/test_volume_types_extra_specs.py
new file mode 100644
index 000000000..34bdada22
--- /dev/null
+++ b/nova/tests/api/openstack/test_volume_types_extra_specs.py
@@ -0,0 +1,181 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright (c) 2011 Zadara Storage Inc.
+# Copyright (c) 2011 OpenStack LLC.
+# Copyright 2011 University of Southern California
+# 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
+import os.path
+
+
+from nova import test
+from nova.api import openstack
+from nova.api.openstack import extensions
+from nova.tests.api.openstack import fakes
+import nova.wsgi
+
+
+def return_create_volume_type_extra_specs(context, volume_type_id,
+ extra_specs):
+ return stub_volume_type_extra_specs()
+
+
+def return_volume_type_extra_specs(context, volume_type_id):
+ return stub_volume_type_extra_specs()
+
+
+def return_empty_volume_type_extra_specs(context, volume_type_id):
+ return {}
+
+
+def delete_volume_type_extra_specs(context, volume_type_id, key):
+ pass
+
+
+def stub_volume_type_extra_specs():
+ specs = {
+ "key1": "value1",
+ "key2": "value2",
+ "key3": "value3",
+ "key4": "value4",
+ "key5": "value5"}
+ return specs
+
+
+class VolumeTypesExtraSpecsTest(test.TestCase):
+
+ def setUp(self):
+ super(VolumeTypesExtraSpecsTest, self).setUp()
+ fakes.stub_out_key_pair_funcs(self.stubs)
+ self.api_path = '/v1.1/123/os-volume-types/1/extra_specs'
+
+ def test_index(self):
+ self.stubs.Set(nova.db.api, 'volume_type_extra_specs_get',
+ return_volume_type_extra_specs)
+ request = webob.Request.blank(self.api_path)
+ res = request.get_response(fakes.wsgi_app())
+ self.assertEqual(200, res.status_int)
+ res_dict = json.loads(res.body)
+ self.assertEqual('application/json', res.headers['Content-Type'])
+ self.assertEqual('value1', res_dict['extra_specs']['key1'])
+
+ def test_index_no_data(self):
+ self.stubs.Set(nova.db.api, 'volume_type_extra_specs_get',
+ return_empty_volume_type_extra_specs)
+ req = webob.Request.blank(self.api_path)
+ res = req.get_response(fakes.wsgi_app())
+ res_dict = json.loads(res.body)
+ self.assertEqual(200, res.status_int)
+ self.assertEqual('application/json', res.headers['Content-Type'])
+ self.assertEqual(0, len(res_dict['extra_specs']))
+
+ def test_show(self):
+ self.stubs.Set(nova.db.api, 'volume_type_extra_specs_get',
+ return_volume_type_extra_specs)
+ req = webob.Request.blank(self.api_path + '/key5')
+ res = req.get_response(fakes.wsgi_app())
+ self.assertEqual(200, res.status_int)
+ res_dict = json.loads(res.body)
+ self.assertEqual('application/json', res.headers['Content-Type'])
+ self.assertEqual('value5', res_dict['key5'])
+
+ def test_show_spec_not_found(self):
+ self.stubs.Set(nova.db.api, 'volume_type_extra_specs_get',
+ return_empty_volume_type_extra_specs)
+ req = webob.Request.blank(self.api_path + '/key6')
+ res = req.get_response(fakes.wsgi_app())
+ res_dict = json.loads(res.body)
+ self.assertEqual(404, res.status_int)
+
+ def test_delete(self):
+ self.stubs.Set(nova.db.api, 'volume_type_extra_specs_delete',
+ delete_volume_type_extra_specs)
+ req = webob.Request.blank(self.api_path + '/key5')
+ req.method = 'DELETE'
+ res = req.get_response(fakes.wsgi_app())
+ self.assertEqual(200, res.status_int)
+
+ def test_create(self):
+ self.stubs.Set(nova.db.api,
+ 'volume_type_extra_specs_update_or_create',
+ return_create_volume_type_extra_specs)
+ req = webob.Request.blank(self.api_path)
+ req.method = 'POST'
+ req.body = '{"extra_specs": {"key1": "value1"}}'
+ req.headers["content-type"] = "application/json"
+ res = req.get_response(fakes.wsgi_app())
+ res_dict = json.loads(res.body)
+ self.assertEqual(200, res.status_int)
+ self.assertEqual('application/json', res.headers['Content-Type'])
+ self.assertEqual('value1', res_dict['extra_specs']['key1'])
+
+ def test_create_empty_body(self):
+ self.stubs.Set(nova.db.api,
+ 'volume_type_extra_specs_update_or_create',
+ return_create_volume_type_extra_specs)
+ req = webob.Request.blank(self.api_path)
+ req.method = 'POST'
+ req.headers["content-type"] = "application/json"
+ res = req.get_response(fakes.wsgi_app())
+ self.assertEqual(400, res.status_int)
+
+ def test_update_item(self):
+ self.stubs.Set(nova.db.api,
+ 'volume_type_extra_specs_update_or_create',
+ return_create_volume_type_extra_specs)
+ req = webob.Request.blank(self.api_path + '/key1')
+ req.method = 'PUT'
+ req.body = '{"key1": "value1"}'
+ req.headers["content-type"] = "application/json"
+ res = req.get_response(fakes.wsgi_app())
+ self.assertEqual(200, res.status_int)
+ self.assertEqual('application/json', res.headers['Content-Type'])
+ res_dict = json.loads(res.body)
+ self.assertEqual('value1', res_dict['key1'])
+
+ def test_update_item_empty_body(self):
+ self.stubs.Set(nova.db.api,
+ 'volume_type_extra_specs_update_or_create',
+ return_create_volume_type_extra_specs)
+ req = webob.Request.blank(self.api_path + '/key1')
+ req.method = 'PUT'
+ req.headers["content-type"] = "application/json"
+ res = req.get_response(fakes.wsgi_app())
+ self.assertEqual(400, res.status_int)
+
+ def test_update_item_too_many_keys(self):
+ self.stubs.Set(nova.db.api,
+ 'volume_type_extra_specs_update_or_create',
+ return_create_volume_type_extra_specs)
+ req = webob.Request.blank(self.api_path + '/key1')
+ req.method = 'PUT'
+ req.body = '{"key1": "value1", "key2": "value2"}'
+ req.headers["content-type"] = "application/json"
+ res = req.get_response(fakes.wsgi_app())
+ self.assertEqual(400, res.status_int)
+
+ def test_update_item_body_uri_mismatch(self):
+ self.stubs.Set(nova.db.api,
+ 'volume_type_extra_specs_update_or_create',
+ return_create_volume_type_extra_specs)
+ req = webob.Request.blank(self.api_path + '/bad')
+ req.method = 'PUT'
+ req.body = '{"key1": "value1"}'
+ req.headers["content-type"] = "application/json"
+ res = req.get_response(fakes.wsgi_app())
+ self.assertEqual(400, res.status_int)
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/integrated_helpers.py b/nova/tests/integrated/integrated_helpers.py
index fb2f88502..343190427 100644
--- a/nova/tests/integrated/integrated_helpers.py
+++ b/nova/tests/integrated/integrated_helpers.py
@@ -22,10 +22,8 @@ Provides common functionality for integrated unit tests
import random
import string
-from nova import exception
from nova import service
from nova import test # For the flags
-from nova.auth import manager
import nova.image.glance
from nova.log import logging
from nova.tests.integrated.api import client
@@ -58,90 +56,6 @@ def generate_new_element(items, prefix, numeric=False):
LOG.debug("Random collision on %s" % candidate)
-class TestUser(object):
- def __init__(self, name, secret, auth_url):
- self.name = name
- self.secret = secret
- self.auth_url = auth_url
-
- if not auth_url:
- raise exception.Error("auth_url is required")
- self.openstack_api = client.TestOpenStackClient(self.name,
- self.secret,
- self.auth_url)
-
- def get_unused_server_name(self):
- servers = self.openstack_api.get_servers()
- server_names = [server['name'] for server in servers]
- return generate_new_element(server_names, 'server')
-
- def get_invalid_image(self):
- images = self.openstack_api.get_images()
- image_ids = [image['id'] for image in images]
- return generate_new_element(image_ids, '', numeric=True)
-
- def get_valid_image(self, create=False):
- images = self.openstack_api.get_images()
- if create and not images:
- # TODO(justinsb): No way currently to create an image through API
- #created_image = self.openstack_api.post_image(image)
- #images.append(created_image)
- raise exception.Error("No way to create an image through API")
-
- if images:
- return images[0]
- return None
-
-
-class IntegratedUnitTestContext(object):
- def __init__(self, auth_url):
- self.auth_manager = manager.AuthManager()
-
- self.auth_url = auth_url
- self.project_name = None
-
- self.test_user = None
-
- self.setup()
-
- def setup(self):
- self._create_test_user()
-
- def _create_test_user(self):
- self.test_user = self._create_unittest_user()
-
- # No way to currently pass this through the OpenStack API
- self.project_name = 'openstack'
- self._configure_project(self.project_name, self.test_user)
-
- def cleanup(self):
- self.test_user = None
-
- def _create_unittest_user(self):
- users = self.auth_manager.get_users()
- user_names = [user.name for user in users]
- auth_name = generate_new_element(user_names, 'unittest_user_')
- auth_key = generate_random_alphanumeric(16)
-
- # Right now there's a bug where auth_name and auth_key are reversed
- # bug732907
- auth_key = auth_name
-
- self.auth_manager.create_user(auth_name, auth_name, auth_key, False)
- return TestUser(auth_name, auth_key, self.auth_url)
-
- def _configure_project(self, project_name, user):
- projects = self.auth_manager.get_projects()
- project_names = [project.name for project in projects]
- if not project_name in project_names:
- project = self.auth_manager.create_project(project_name,
- user.name,
- description=None,
- member_users=None)
- else:
- self.auth_manager.add_to_project(user.name, project_name)
-
-
class _IntegratedTestBase(test.TestCase):
def setUp(self):
super(_IntegratedTestBase, self).setUp()
@@ -163,10 +77,7 @@ class _IntegratedTestBase(test.TestCase):
self._start_api_service()
- self.context = IntegratedUnitTestContext(self.auth_url)
-
- self.user = self.context.test_user
- self.api = self.user.openstack_api
+ self.api = client.TestOpenStackClient('fake', 'fake', self.auth_url)
def _start_api_service(self):
osapi = service.WSGIService("osapi")
@@ -174,10 +85,6 @@ class _IntegratedTestBase(test.TestCase):
self.auth_url = 'http://%s:%s/v1.1' % (osapi.host, osapi.port)
LOG.warn(self.auth_url)
- def tearDown(self):
- self.context.cleanup()
- super(_IntegratedTestBase, self).tearDown()
-
def _get_flags(self):
"""An opportunity to setup flags, before the services are started."""
f = {}
@@ -190,10 +97,20 @@ class _IntegratedTestBase(test.TestCase):
f['fake_network'] = True
return f
+ def get_unused_server_name(self):
+ servers = self.api.get_servers()
+ server_names = [server['name'] for server in servers]
+ return generate_new_element(server_names, 'server')
+
+ def get_invalid_image(self):
+ images = self.api.get_images()
+ image_ids = [image['id'] for image in images]
+ return generate_new_element(image_ids, '', numeric=True)
+
def _build_minimal_create_server_request(self):
server = {}
- image = self.user.get_valid_image(create=True)
+ image = self.api.get_images()[0]
LOG.debug("Image: %s" % image)
if 'imageRef' in image:
@@ -211,7 +128,7 @@ class _IntegratedTestBase(test.TestCase):
server['flavorRef'] = 'http://fake.server/%s' % flavor['id']
# Set a valid server name
- server_name = self.user.get_unused_server_name()
+ server_name = self.get_unused_server_name()
server['name'] = server_name
return server
diff --git a/nova/tests/integrated/test_login.py b/nova/tests/integrated/test_login.py
index 06359a52f..3a863d0f9 100644
--- a/nova/tests/integrated/test_login.py
+++ b/nova/tests/integrated/test_login.py
@@ -15,11 +15,9 @@
# License for the specific language governing permissions and limitations
# under the License.
-import unittest
from nova.log import logging
from nova.tests.integrated import integrated_helpers
-from nova.tests.integrated.api import client
LOG = logging.getLogger('nova.tests.integrated')
@@ -31,34 +29,3 @@ class LoginTest(integrated_helpers._IntegratedTestBase):
flavors = self.api.get_flavors()
for flavor in flavors:
LOG.debug(_("flavor: %s") % flavor)
-
- def test_bad_login_password(self):
- """Test that I get a 401 with a bad username."""
- bad_credentials_api = client.TestOpenStackClient(self.user.name,
- "notso_password",
- self.user.auth_url)
-
- self.assertRaises(client.OpenStackApiAuthenticationException,
- bad_credentials_api.get_flavors)
-
- def test_bad_login_username(self):
- """Test that I get a 401 with a bad password."""
- bad_credentials_api = client.TestOpenStackClient("notso_username",
- self.user.secret,
- self.user.auth_url)
-
- self.assertRaises(client.OpenStackApiAuthenticationException,
- bad_credentials_api.get_flavors)
-
- def test_bad_login_both_bad(self):
- """Test that I get a 401 with both bad username and bad password."""
- bad_credentials_api = client.TestOpenStackClient("notso_username",
- "notso_password",
- self.user.auth_url)
-
- self.assertRaises(client.OpenStackApiAuthenticationException,
- bad_credentials_api.get_flavors)
-
-
-if __name__ == "__main__":
- unittest.main()
diff --git a/nova/tests/integrated/test_servers.py b/nova/tests/integrated/test_servers.py
index 725f6d529..c2f800689 100644
--- a/nova/tests/integrated/test_servers.py
+++ b/nova/tests/integrated/test_servers.py
@@ -51,7 +51,7 @@ class ServersTest(integrated_helpers._IntegratedTestBase):
self.api.post_server, post)
# With an invalid imageRef, this throws 500.
- server['imageRef'] = self.user.get_invalid_image()
+ server['imageRef'] = self.get_invalid_image()
# TODO(justinsb): Check whatever the spec says should be thrown here
self.assertRaises(client.OpenStackApiException,
self.api.post_server, post)
diff --git a/nova/tests/integrated/test_volumes.py b/nova/tests/integrated/test_volumes.py
index d3e936462..d6c5e1ba1 100644
--- a/nova/tests/integrated/test_volumes.py
+++ b/nova/tests/integrated/test_volumes.py
@@ -285,6 +285,23 @@ class VolumesTest(integrated_helpers._IntegratedTestBase):
self.assertEquals(undisco_move['mountpoint'], device)
self.assertEquals(undisco_move['instance_id'], server_id)
+ def test_create_volume_with_metadata(self):
+ """Creates and deletes a volume."""
+
+ # Create volume
+ metadata = {'key1': 'value1',
+ 'key2': 'value2'}
+ created_volume = self.api.post_volume(
+ {'volume': {'size': 1,
+ 'metadata': metadata}})
+ LOG.debug("created_volume: %s" % created_volume)
+ self.assertTrue(created_volume['id'])
+ created_volume_id = created_volume['id']
+
+ # Check it's there and metadata present
+ found_volume = self.api.get_volume(created_volume_id)
+ self.assertEqual(created_volume_id, found_volume['id'])
+ self.assertEqual(metadata, found_volume['metadata'])
if __name__ == "__main__":
unittest.main()
diff --git a/nova/tests/monkey_patch_example/__init__.py b/nova/tests/monkey_patch_example/__init__.py
new file mode 100644
index 000000000..25cf9ccfe
--- /dev/null
+++ b/nova/tests/monkey_patch_example/__init__.py
@@ -0,0 +1,33 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 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.
+"""Example Module for testing utils.monkey_patch()."""
+
+
+CALLED_FUNCTION = []
+
+
+def example_decorator(name, function):
+ """ decorator for notify which is used from utils.monkey_patch()
+
+ :param name: name of the function
+ :param function: - object of the function
+ :returns: function -- decorated function
+ """
+ def wrapped_func(*args, **kwarg):
+ CALLED_FUNCTION.append(name)
+ return function(*args, **kwarg)
+ return wrapped_func
diff --git a/nova/tests/monkey_patch_example/example_a.py b/nova/tests/monkey_patch_example/example_a.py
new file mode 100644
index 000000000..21e79bcb0
--- /dev/null
+++ b/nova/tests/monkey_patch_example/example_a.py
@@ -0,0 +1,29 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 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.
+"""Example Module A for testing utils.monkey_patch()."""
+
+
+def example_function_a():
+ return 'Example function'
+
+
+class ExampleClassA():
+ def example_method(self):
+ return 'Example method'
+
+ def example_method_add(self, arg1, arg2):
+ return arg1 + arg2
diff --git a/nova/tests/monkey_patch_example/example_b.py b/nova/tests/monkey_patch_example/example_b.py
new file mode 100644
index 000000000..9d8f6d339
--- /dev/null
+++ b/nova/tests/monkey_patch_example/example_b.py
@@ -0,0 +1,30 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 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.
+
+"""Example Module B for testing utils.monkey_patch()."""
+
+
+def example_function_b():
+ return 'Example function'
+
+
+class ExampleClassB():
+ def example_method(self):
+ return 'Example method'
+
+ def example_method_add(self, arg1, arg2):
+ return arg1 + arg2
diff --git a/nova/tests/notifier/__init__.py b/nova/tests/notifier/__init__.py
new file mode 100644
index 000000000..bd862c46a
--- /dev/null
+++ b/nova/tests/notifier/__init__.py
@@ -0,0 +1,16 @@
+# Copyright 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.
+
+from nova.tests import *
diff --git a/nova/tests/notifier/test_list_notifier.py b/nova/tests/notifier/test_list_notifier.py
new file mode 100644
index 000000000..b77720759
--- /dev/null
+++ b/nova/tests/notifier/test_list_notifier.py
@@ -0,0 +1,88 @@
+# Copyright 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 stubout
+import sys
+
+import nova
+from nova import log as logging
+import nova.notifier.api
+from nova.notifier.api import notify
+from nova.notifier import log_notifier
+from nova.notifier import no_op_notifier
+from nova.notifier import list_notifier
+from nova import test
+
+
+class NotifierListTestCase(test.TestCase):
+ """Test case for notifications"""
+
+ def setUp(self):
+ super(NotifierListTestCase, self).setUp()
+ list_notifier._reset_drivers()
+ self.stubs = stubout.StubOutForTesting()
+ # Mock log to add one to exception_count when log.exception is called
+
+ def mock_exception(cls, *args):
+ self.exception_count += 1
+
+ self.exception_count = 0
+ list_notifier_log = logging.getLogger('nova.notifier.list_notifier')
+ self.stubs.Set(list_notifier_log, "exception", mock_exception)
+ # Mock no_op notifier to add one to notify_count when called.
+
+ def mock_notify(cls, *args):
+ self.notify_count += 1
+
+ self.notify_count = 0
+ self.stubs.Set(nova.notifier.no_op_notifier, 'notify', mock_notify)
+ # Mock log_notifier to raise RuntimeError when called.
+
+ def mock_notify2(cls, *args):
+ raise RuntimeError("Bad notifier.")
+
+ self.stubs.Set(nova.notifier.log_notifier, 'notify', mock_notify2)
+
+ def tearDown(self):
+ self.stubs.UnsetAll()
+ list_notifier._reset_drivers()
+ super(NotifierListTestCase, self).tearDown()
+
+ def test_send_notifications_successfully(self):
+ self.flags(notification_driver='nova.notifier.list_notifier',
+ list_notifier_drivers=['nova.notifier.no_op_notifier',
+ 'nova.notifier.no_op_notifier'])
+ notify('publisher_id', 'event_type',
+ nova.notifier.api.WARN, dict(a=3))
+ self.assertEqual(self.notify_count, 2)
+ self.assertEqual(self.exception_count, 0)
+
+ def test_send_notifications_with_errors(self):
+
+ self.flags(notification_driver='nova.notifier.list_notifier',
+ list_notifier_drivers=['nova.notifier.no_op_notifier',
+ 'nova.notifier.log_notifier'])
+ notify('publisher_id', 'event_type', nova.notifier.api.WARN, dict(a=3))
+ self.assertEqual(self.notify_count, 1)
+ self.assertEqual(self.exception_count, 1)
+
+ def test_when_driver_fails_to_import(self):
+ self.flags(notification_driver='nova.notifier.list_notifier',
+ list_notifier_drivers=['nova.notifier.no_op_notifier',
+ 'nova.notifier.logo_notifier',
+ 'fdsjgsdfhjkhgsfkj'])
+ notify('publisher_id', 'event_type', nova.notifier.api.WARN, dict(a=3))
+ self.assertEqual(self.exception_count, 2)
+ self.assertEqual(self.notify_count, 1)
diff --git a/nova/tests/test_auth.py b/nova/tests/test_auth.py
index 4561eb7f2..1b3166af7 100644
--- a/nova/tests/test_auth.py
+++ b/nova/tests/test_auth.py
@@ -147,6 +147,7 @@ class _AuthManagerBaseTestCase(test.TestCase):
'/services/Cloud'))
def test_can_get_credentials(self):
+ self.flags(use_deprecated_auth=True)
st = {'access': 'access', 'secret': 'secret'}
with user_and_project_generator(self.manager, user_state=st) as (u, p):
credentials = self.manager.get_environment_rc(u, p)
diff --git a/nova/tests/test_compute.py b/nova/tests/test_compute.py
index 4f5d36f14..6659b81eb 100644
--- a/nova/tests/test_compute.py
+++ b/nova/tests/test_compute.py
@@ -2,6 +2,7 @@
# Copyright 2010 United States Government as represented by the
# Administrator of the National Aeronautics and Space Administration.
+# Copyright 2011 Piston Cloud Computing, Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
@@ -159,9 +160,24 @@ class ComputeTestCase(test.TestCase):
db.security_group_destroy(self.context, group['id'])
db.instance_destroy(self.context, ref[0]['id'])
+ def test_create_instance_associates_config_drive(self):
+ """Make sure create associates a config drive."""
+
+ instance_id = self._create_instance(params={'config_drive': True, })
+
+ try:
+ self.compute.run_instance(self.context, instance_id)
+ instances = db.instance_get_all(context.get_admin_context())
+ instance = instances[0]
+
+ self.assertTrue(instance.config_drive)
+ finally:
+ db.instance_destroy(self.context, instance_id)
+
def test_default_hostname_generator(self):
- cases = [(None, 'server_1'), ('Hello, Server!', 'hello_server'),
- ('<}\x1fh\x10e\x08l\x02l\x05o\x12!{>', 'hello')]
+ cases = [(None, 'server-1'), ('Hello, Server!', 'hello-server'),
+ ('<}\x1fh\x10e\x08l\x02l\x05o\x12!{>', 'hello'),
+ ('hello_server', 'hello-server')]
for display_name, hostname in cases:
ref = self.compute_api.create(self.context,
instance_types.get_default_instance_type(), None,
@@ -347,7 +363,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 +387,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 +470,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_instance_types.py b/nova/tests/test_instance_types.py
index ef271518c..09f532239 100644
--- a/nova/tests/test_instance_types.py
+++ b/nova/tests/test_instance_types.py
@@ -47,6 +47,29 @@ class InstanceTypeTestCase(test.TestCase):
self.id = max_id["id"] + 1
self.name = str(int(time.time()))
+ def _nonexistent_flavor_name(self):
+ """return an instance type name not in the DB"""
+ nonexistent_flavor = "sdfsfsdf"
+ flavors = instance_types.get_all_types()
+ while nonexistent_flavor in flavors:
+ nonexistent_flavor += "z"
+ else:
+ return nonexistent_flavor
+
+ def _nonexistent_flavor_id(self):
+ """return an instance type ID not in the DB"""
+ nonexistent_flavor = 2700
+ flavor_ids = [value["id"] for key, value in\
+ instance_types.get_all_types().iteritems()]
+ while nonexistent_flavor in flavor_ids:
+ nonexistent_flavor += 1
+ else:
+ return nonexistent_flavor
+
+ def _existing_flavor(self):
+ """return first instance type name"""
+ return instance_types.get_all_types().keys()[0]
+
def test_instance_type_create_then_delete(self):
"""Ensure instance types can be created"""
starting_inst_list = instance_types.get_all_types()
@@ -84,10 +107,11 @@ class InstanceTypeTestCase(test.TestCase):
exception.InvalidInput,
instance_types.create, self.name, 256, 1, "aa", self.flavorid)
- def test_non_existant_inst_type_shouldnt_delete(self):
+ def test_non_existent_inst_type_shouldnt_delete(self):
"""Ensures that instance type creation fails with invalid args"""
self.assertRaises(exception.ApiError,
- instance_types.destroy, "sfsfsdfdfs")
+ instance_types.destroy,
+ self._nonexistent_flavor_name())
def test_repeated_inst_types_should_raise_api_error(self):
"""Ensures that instance duplicates raises ApiError"""
@@ -97,3 +121,43 @@ class InstanceTypeTestCase(test.TestCase):
self.assertRaises(
exception.ApiError,
instance_types.create, new_name, 256, 1, 120, self.flavorid)
+
+ def test_will_not_destroy_with_no_name(self):
+ """Ensure destroy sad path of no name raises error"""
+ self.assertRaises(exception.ApiError,
+ instance_types.destroy,
+ self._nonexistent_flavor_name())
+
+ def test_will_not_purge_without_name(self):
+ """Ensure purge without a name raises error"""
+ self.assertRaises(exception.InvalidInstanceType,
+ instance_types.purge, None)
+
+ def test_will_not_purge_with_wrong_name(self):
+ """Ensure purge without correct name raises error"""
+ self.assertRaises(exception.ApiError,
+ instance_types.purge,
+ self._nonexistent_flavor_name())
+
+ def test_will_not_get_bad_default_instance_type(self):
+ """ensures error raised on bad default instance type"""
+ FLAGS.default_instance_type = self._nonexistent_flavor_name()
+ self.assertRaises(exception.InstanceTypeNotFoundByName,
+ instance_types.get_default_instance_type)
+
+ def test_will_not_get_instance_type_by_name_with_no_name(self):
+ """Ensure get by name returns default flavor with no name"""
+ self.assertEqual(instance_types.get_default_instance_type(),
+ instance_types.get_instance_type_by_name(None))
+
+ def test_will_not_get_instance_type_with_bad_name(self):
+ """Ensure get by name returns default flavor with bad name"""
+ self.assertRaises(exception.InstanceTypeNotFound,
+ instance_types.get_instance_type,
+ self._nonexistent_flavor_name())
+
+ def test_will_not_get_flavor_by_bad_flavor_id(self):
+ """Ensure get by flavor raises error with wrong flavorid"""
+ self.assertRaises(exception.InstanceTypeNotFound,
+ instance_types.get_instance_type_by_name,
+ self._nonexistent_flavor_id())
diff --git a/nova/tests/test_ipv6.py b/nova/tests/test_ipv6.py
index d123df6f1..04c1b5598 100644
--- a/nova/tests/test_ipv6.py
+++ b/nova/tests/test_ipv6.py
@@ -40,6 +40,25 @@ class IPv6RFC2462TestCase(test.TestCase):
mac = ipv6.to_mac('2001:db8::216:3eff:fe33:4455')
self.assertEquals(mac, '00:16:3e:33:44:55')
+ def test_to_global_with_bad_mac(self):
+ bad_mac = '02:16:3e:33:44:5Z'
+ self.assertRaises(TypeError, ipv6.to_global,
+ '2001:db8::', bad_mac, 'test')
+
+ def test_to_global_with_bad_prefix(self):
+ bad_prefix = '82'
+ self.assertRaises(TypeError, ipv6.to_global,
+ bad_prefix,
+ '2001:db8::216:3eff:fe33:4455',
+ 'test')
+
+ def test_to_global_with_bad_project(self):
+ bad_project = 'non-existent-project-name'
+ self.assertRaises(TypeError, ipv6.to_global,
+ '2001:db8::',
+ '2001:db8::a94a:8fe5:ff33:4455',
+ bad_project)
+
class IPv6AccountIdentiferTestCase(test.TestCase):
"""Unit tests for IPv6 account_identifier backend operations."""
@@ -55,3 +74,22 @@ class IPv6AccountIdentiferTestCase(test.TestCase):
def test_to_mac(self):
mac = ipv6.to_mac('2001:db8::a94a:8fe5:ff33:4455')
self.assertEquals(mac, '02:16:3e:33:44:55')
+
+ def test_to_global_with_bad_mac(self):
+ bad_mac = '02:16:3e:33:44:5X'
+ self.assertRaises(TypeError, ipv6.to_global,
+ '2001:db8::', bad_mac, 'test')
+
+ def test_to_global_with_bad_prefix(self):
+ bad_prefix = '78'
+ self.assertRaises(TypeError, ipv6.to_global,
+ bad_prefix,
+ '2001:db8::a94a:8fe5:ff33:4455',
+ 'test')
+
+ def test_to_global_with_bad_project(self):
+ bad_project = 'non-existent-project-name'
+ self.assertRaises(TypeError, ipv6.to_global,
+ '2001:db8::',
+ '2001:db8::a94a:8fe5:ff33:4455',
+ bad_project)
diff --git a/nova/tests/test_network.py b/nova/tests/test_network.py
index e5c80b6f6..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,
@@ -126,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')
@@ -183,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')
@@ -232,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,
@@ -243,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_notifier.py b/nova/tests/test_notifier.py
index 64b799a2c..7de3a4a99 100644
--- a/nova/tests/test_notifier.py
+++ b/nova/tests/test_notifier.py
@@ -134,3 +134,24 @@ class NotifierTestCase(test.TestCase):
self.assertEqual(msg['event_type'], 'error_notification')
self.assertEqual(msg['priority'], 'ERROR')
self.assertEqual(msg['payload']['error'], 'foo')
+
+ def test_send_notification_by_decorator(self):
+ self.notify_called = False
+
+ def example_api(arg1, arg2):
+ return arg1 + arg2
+
+ example_api = nova.notifier.api.notify_decorator(
+ 'example_api',
+ example_api)
+
+ def mock_notify(cls, *args):
+ self.notify_called = True
+
+ self.stubs.Set(nova.notifier.no_op_notifier, 'notify',
+ mock_notify)
+
+ class Mock(object):
+ pass
+ self.assertEqual(3, example_api(1, 2))
+ self.assertEqual(self.notify_called, True)
diff --git a/nova/tests/test_nova_manage.py b/nova/tests/test_nova_manage.py
index 9c6563f14..520bfbea1 100644
--- a/nova/tests/test_nova_manage.py
+++ b/nova/tests/test_nova_manage.py
@@ -28,55 +28,199 @@ 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
+import StringIO
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')
+
+
+class NetworkCommandsTestCase(test.TestCase):
+ def setUp(self):
+ super(NetworkCommandsTestCase, self).setUp()
+ self.stubs = stubout.StubOutForTesting()
+ self.commands = nova_manage.NetworkCommands()
+ self.context = context.get_admin_context()
+ self.net = {'id': 0,
+ 'label': 'fake',
+ 'injected': False,
+ 'cidr': '192.168.0.0/24',
+ 'cidr_v6': 'dead:beef::/64',
+ 'multi_host': False,
+ 'gateway_v6': 'dead:beef::1',
+ 'netmask_v6': '64',
+ 'netmask': '255.255.255.0',
+ 'bridge': 'fa0',
+ 'bridge_interface': 'fake_fa0',
+ 'gateway': '192.168.0.1',
+ 'broadcast': '192.168.0.255',
+ 'dns1': '8.8.8.8',
+ 'dns2': '8.8.4.4',
+ 'vlan': 200,
+ 'vpn_public_address': '10.0.0.2',
+ 'vpn_public_port': '2222',
+ 'vpn_private_address': '192.168.0.2',
+ 'dhcp_start': '192.168.0.3',
+ 'project_id': 'fake_project',
+ 'host': 'fake_host',
+ 'uuid': 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa'}
+
+ def fake_network_get_by_cidr(context, cidr):
+ self.assertTrue(context.to_dict()['is_admin'])
+ self.assertEqual(cidr, self.fake_net['cidr'])
+ return db_fakes.FakeModel(self.fake_net)
+
+ def fake_network_update(context, network_id, values):
+ self.assertTrue(context.to_dict()['is_admin'])
+ self.assertEqual(network_id, self.fake_net['id'])
+ self.assertEqual(values, self.fake_update_value)
+ self.fake_network_get_by_cidr = fake_network_get_by_cidr
+ self.fake_network_update = fake_network_update
+
+ def tearDown(self):
+ super(NetworkCommandsTestCase, self).tearDown()
+ self.stubs.UnsetAll()
+
+ def test_create(self):
+
+ def fake_create_networks(obj, context, **kwargs):
+ self.assertTrue(context.to_dict()['is_admin'])
+ self.assertEqual(kwargs['label'], 'Test')
+ self.assertEqual(kwargs['cidr'], '10.2.0.0/24')
+ self.assertEqual(kwargs['multi_host'], False)
+ self.assertEqual(kwargs['num_networks'], 1)
+ self.assertEqual(kwargs['network_size'], 256)
+ self.assertEqual(kwargs['vlan_start'], 200)
+ self.assertEqual(kwargs['vpn_start'], 2000)
+ self.assertEqual(kwargs['cidr_v6'], 'fd00:2::/120')
+ self.assertEqual(kwargs['gateway_v6'], 'fd00:2::22')
+ self.assertEqual(kwargs['bridge'], 'br200')
+ self.assertEqual(kwargs['bridge_interface'], 'eth0')
+ self.assertEqual(kwargs['dns1'], '8.8.8.8')
+ self.assertEqual(kwargs['dns2'], '8.8.4.4')
+ self.flags(network_manager='nova.network.manager.VlanManager')
+ from nova.network import manager as net_manager
+ self.stubs.Set(net_manager.VlanManager, 'create_networks',
+ fake_create_networks)
+ self.commands.create(
+ label='Test',
+ fixed_range_v4='10.2.0.0/24',
+ num_networks=1,
+ network_size=256,
+ multi_host='F',
+ vlan_start=200,
+ vpn_start=2000,
+ fixed_range_v6='fd00:2::/120',
+ gateway_v6='fd00:2::22',
+ bridge='br200',
+ bridge_interface='eth0',
+ dns1='8.8.8.8',
+ dns2='8.8.4.4')
+
+ def test_list(self):
+
+ def fake_network_get_all(context):
+ return [db_fakes.FakeModel(self.net)]
+ self.stubs.Set(db, 'network_get_all', fake_network_get_all)
+ output = StringIO.StringIO()
+ sys.stdout = output
+ self.commands.list()
+ sys.stdout = sys.__stdout__
+ result = output.getvalue()
+ _fmt = "%(id)-5s\t%(cidr)-18s\t%(cidr_v6)-15s\t%(dhcp_start)-15s\t" +\
+ "%(dns1)-15s\t%(dns2)-15s\t%(vlan)-15s\t%(project_id)-15s\t" +\
+ "%(uuid)-15s"
+ head = _fmt % {'id': _('id'),
+ 'cidr': _('IPv4'),
+ 'cidr_v6': _('IPv6'),
+ 'dhcp_start': _('start address'),
+ 'dns1': _('DNS1'),
+ 'dns2': _('DNS2'),
+ 'vlan': _('VlanID'),
+ 'project_id': _('project'),
+ 'uuid': _("uuid")}
+ body = _fmt % {'id': self.net['id'],
+ 'cidr': self.net['cidr'],
+ 'cidr_v6': self.net['cidr_v6'],
+ 'dhcp_start': self.net['dhcp_start'],
+ 'dns1': self.net['dns1'],
+ 'dns2': self.net['dns2'],
+ 'vlan': self.net['vlan'],
+ 'project_id': self.net['project_id'],
+ 'uuid': self.net['uuid']}
+ answer = '%s\n%s\n' % (head, body)
+ self.assertEqual(result, answer)
+
+ def test_delete(self):
+ self.fake_net = self.net
+ self.fake_net['project_id'] = None
+ self.fake_net['host'] = None
+ self.stubs.Set(db, 'network_get_by_cidr',
+ self.fake_network_get_by_cidr)
+
+ def fake_network_delete_safe(context, network_id):
+ self.assertTrue(context.to_dict()['is_admin'])
+ self.assertEqual(network_id, self.fake_net['id'])
+ self.stubs.Set(db, 'network_delete_safe', fake_network_delete_safe)
+ self.commands.delete(fixed_range=self.fake_net['cidr'])
+
+ def _test_modify_base(self, update_value, project, host, dis_project=None,
+ dis_host=None):
+ self.fake_net = self.net
+ self.fake_update_value = update_value
+ self.stubs.Set(db, 'network_get_by_cidr',
+ self.fake_network_get_by_cidr)
+ self.stubs.Set(db, 'network_update', self.fake_network_update)
+ self.commands.modify(self.fake_net['cidr'], project=project, host=host,
+ dis_project=dis_project, dis_host=dis_host)
+
+ def test_modify_associate(self):
+ self._test_modify_base(update_value={'project_id': 'test_project',
+ 'host': 'test_host'},
+ project='test_project', host='test_host')
+
+ def test_modify_unchanged(self):
+ self._test_modify_base(update_value={}, project=None, host=None)
+
+ def test_modify_disassociate(self):
+ self._test_modify_base(update_value={'project_id': None, 'host': None},
+ project=None, host=None, dis_project=True,
+ dis_host=True)
diff --git a/nova/tests/test_utils.py b/nova/tests/test_utils.py
index ec5098a37..1ba794a1a 100644
--- a/nova/tests/test_utils.py
+++ b/nova/tests/test_utils.py
@@ -18,6 +18,7 @@ import datetime
import os
import tempfile
+import nova
from nova import exception
from nova import test
from nova import utils
@@ -384,3 +385,57 @@ 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>')
+
+
+class MonkeyPatchTestCase(test.TestCase):
+ """Unit test for utils.monkey_patch()."""
+ def setUp(self):
+ super(MonkeyPatchTestCase, self).setUp()
+ self.example_package = 'nova.tests.monkey_patch_example.'
+ self.flags(
+ monkey_patch=True,
+ monkey_patch_modules=[self.example_package + 'example_a' + ':'
+ + self.example_package + 'example_decorator'])
+
+ def test_monkey_patch(self):
+ utils.monkey_patch()
+ nova.tests.monkey_patch_example.CALLED_FUNCTION = []
+ from nova.tests.monkey_patch_example import example_a, example_b
+
+ self.assertEqual('Example function', example_a.example_function_a())
+ exampleA = example_a.ExampleClassA()
+ exampleA.example_method()
+ ret_a = exampleA.example_method_add(3, 5)
+ self.assertEqual(ret_a, 8)
+
+ self.assertEqual('Example function', example_b.example_function_b())
+ exampleB = example_b.ExampleClassB()
+ exampleB.example_method()
+ ret_b = exampleB.example_method_add(3, 5)
+
+ self.assertEqual(ret_b, 8)
+ package_a = self.example_package + 'example_a.'
+ self.assertTrue(package_a + 'example_function_a'
+ in nova.tests.monkey_patch_example.CALLED_FUNCTION)
+
+ self.assertTrue(package_a + 'ExampleClassA.example_method'
+ in nova.tests.monkey_patch_example.CALLED_FUNCTION)
+ self.assertTrue(package_a + 'ExampleClassA.example_method_add'
+ in nova.tests.monkey_patch_example.CALLED_FUNCTION)
+ package_b = self.example_package + 'example_b.'
+ self.assertFalse(package_b + 'example_function_b'
+ in nova.tests.monkey_patch_example.CALLED_FUNCTION)
+ self.assertFalse(package_b + 'ExampleClassB.example_method'
+ in nova.tests.monkey_patch_example.CALLED_FUNCTION)
+ self.assertFalse(package_b + 'ExampleClassB.example_method_add'
+ in nova.tests.monkey_patch_example.CALLED_FUNCTION)
diff --git a/nova/tests/test_versions.py b/nova/tests/test_versions.py
new file mode 100644
index 000000000..4621b042b
--- /dev/null
+++ b/nova/tests/test_versions.py
@@ -0,0 +1,61 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2011 Ken Pepple
+#
+# 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 import exception
+from nova import test
+from nova import utils
+from nova import version
+
+
+class VersionTestCase(test.TestCase):
+ """Test cases for Versions code"""
+ def setUp(self):
+ """setup test with unchanging values"""
+ super(VersionTestCase, self).setUp()
+ self.version = version
+ self.version.FINAL = False
+ self.version.NOVA_VERSION = ['2012', '10']
+ self.version.YEAR, self.version.COUNT = self.version.NOVA_VERSION
+ self.version.version_info = {'branch_nick': u'LOCALBRANCH',
+ 'revision_id': 'LOCALREVISION',
+ 'revno': 0}
+
+ def test_version_string_is_good(self):
+ """Ensure version string works"""
+ self.assertEqual("2012.10-dev", self.version.version_string())
+
+ def test_canonical_version_string_is_good(self):
+ """Ensure canonical version works"""
+ self.assertEqual("2012.10", self.version.canonical_version_string())
+
+ def test_final_version_strings_are_identical(self):
+ """Ensure final version strings match only at release"""
+ self.assertNotEqual(self.version.canonical_version_string(),
+ self.version.version_string())
+ self.version.FINAL = True
+ self.assertEqual(self.version.canonical_version_string(),
+ self.version.version_string())
+
+ def test_vcs_version_string_is_good(self):
+ """Ensure uninstalled code generates local """
+ self.assertEqual("LOCALBRANCH:LOCALREVISION",
+ self.version.vcs_version_string())
+
+ def test_version_string_with_vcs_is_good(self):
+ """Ensure uninstalled code get version string"""
+ self.assertEqual("2012.10-LOCALBRANCH:LOCALREVISION",
+ self.version.version_string_with_vcs())
diff --git a/nova/tests/test_volume_types.py b/nova/tests/test_volume_types.py
new file mode 100644
index 000000000..1e190805c
--- /dev/null
+++ b/nova/tests/test_volume_types.py
@@ -0,0 +1,207 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright (c) 2011 Zadara Storage Inc.
+# 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.
+"""
+Unit Tests for volume types code
+"""
+import time
+
+from nova import context
+from nova import db
+from nova import exception
+from nova import flags
+from nova import log as logging
+from nova import test
+from nova import utils
+from nova.volume import volume_types
+from nova.db.sqlalchemy.session import get_session
+from nova.db.sqlalchemy import models
+
+FLAGS = flags.FLAGS
+LOG = logging.getLogger('nova.tests.test_volume_types')
+
+
+class VolumeTypeTestCase(test.TestCase):
+ """Test cases for volume type code"""
+ def setUp(self):
+ super(VolumeTypeTestCase, self).setUp()
+
+ self.ctxt = context.get_admin_context()
+ self.vol_type1_name = str(int(time.time()))
+ self.vol_type1_specs = dict(
+ type="physical drive",
+ drive_type="SAS",
+ size="300",
+ rpm="7200",
+ visible="True")
+ self.vol_type1 = dict(name=self.vol_type1_name,
+ extra_specs=self.vol_type1_specs)
+
+ def test_volume_type_create_then_destroy(self):
+ """Ensure volume types can be created and deleted"""
+ prev_all_vtypes = volume_types.get_all_types(self.ctxt)
+
+ volume_types.create(self.ctxt,
+ self.vol_type1_name,
+ self.vol_type1_specs)
+ new = volume_types.get_volume_type_by_name(self.ctxt,
+ self.vol_type1_name)
+
+ LOG.info(_("Given data: %s"), self.vol_type1_specs)
+ LOG.info(_("Result data: %s"), new)
+
+ for k, v in self.vol_type1_specs.iteritems():
+ self.assertEqual(v, new['extra_specs'][k],
+ 'one of fields doesnt match')
+
+ new_all_vtypes = volume_types.get_all_types(self.ctxt)
+ self.assertEqual(len(prev_all_vtypes) + 1,
+ len(new_all_vtypes),
+ 'drive type was not created')
+
+ volume_types.destroy(self.ctxt, self.vol_type1_name)
+ new_all_vtypes = volume_types.get_all_types(self.ctxt)
+ self.assertEqual(prev_all_vtypes,
+ new_all_vtypes,
+ 'drive type was not deleted')
+
+ def test_volume_type_create_then_purge(self):
+ """Ensure volume types can be created and deleted"""
+ prev_all_vtypes = volume_types.get_all_types(self.ctxt, inactive=1)
+
+ volume_types.create(self.ctxt,
+ self.vol_type1_name,
+ self.vol_type1_specs)
+ new = volume_types.get_volume_type_by_name(self.ctxt,
+ self.vol_type1_name)
+
+ for k, v in self.vol_type1_specs.iteritems():
+ self.assertEqual(v, new['extra_specs'][k],
+ 'one of fields doesnt match')
+
+ new_all_vtypes = volume_types.get_all_types(self.ctxt, inactive=1)
+ self.assertEqual(len(prev_all_vtypes) + 1,
+ len(new_all_vtypes),
+ 'drive type was not created')
+
+ volume_types.destroy(self.ctxt, self.vol_type1_name)
+ new_all_vtypes2 = volume_types.get_all_types(self.ctxt, inactive=1)
+ self.assertEqual(len(new_all_vtypes),
+ len(new_all_vtypes2),
+ 'drive type was incorrectly deleted')
+
+ volume_types.purge(self.ctxt, self.vol_type1_name)
+ new_all_vtypes2 = volume_types.get_all_types(self.ctxt, inactive=1)
+ self.assertEqual(len(new_all_vtypes) - 1,
+ len(new_all_vtypes2),
+ 'drive type was not purged')
+
+ def test_get_all_volume_types(self):
+ """Ensures that all volume types can be retrieved"""
+ session = get_session()
+ total_volume_types = session.query(models.VolumeTypes).\
+ count()
+ vol_types = volume_types.get_all_types(self.ctxt)
+ self.assertEqual(total_volume_types, len(vol_types))
+
+ def test_non_existant_inst_type_shouldnt_delete(self):
+ """Ensures that volume type creation fails with invalid args"""
+ self.assertRaises(exception.ApiError,
+ volume_types.destroy, self.ctxt, "sfsfsdfdfs")
+
+ def test_repeated_vol_types_should_raise_api_error(self):
+ """Ensures that volume duplicates raises ApiError"""
+ new_name = self.vol_type1_name + "dup"
+ volume_types.create(self.ctxt, new_name)
+ volume_types.destroy(self.ctxt, new_name)
+ self.assertRaises(
+ exception.ApiError,
+ volume_types.create, self.ctxt, new_name)
+
+ def test_invalid_volume_types_params(self):
+ """Ensures that volume type creation fails with invalid args"""
+ self.assertRaises(exception.InvalidVolumeType,
+ volume_types.destroy, self.ctxt, None)
+ self.assertRaises(exception.InvalidVolumeType,
+ volume_types.purge, self.ctxt, None)
+ self.assertRaises(exception.InvalidVolumeType,
+ volume_types.get_volume_type, self.ctxt, None)
+ self.assertRaises(exception.InvalidVolumeType,
+ volume_types.get_volume_type_by_name,
+ self.ctxt, None)
+
+ def test_volume_type_get_by_id_and_name(self):
+ """Ensure volume types get returns same entry"""
+ volume_types.create(self.ctxt,
+ self.vol_type1_name,
+ self.vol_type1_specs)
+ new = volume_types.get_volume_type_by_name(self.ctxt,
+ self.vol_type1_name)
+
+ new2 = volume_types.get_volume_type(self.ctxt, new['id'])
+ self.assertEqual(new, new2)
+
+ def test_volume_type_search_by_extra_spec(self):
+ """Ensure volume types get by extra spec returns correct type"""
+ volume_types.create(self.ctxt, "type1", {"key1": "val1",
+ "key2": "val2"})
+ volume_types.create(self.ctxt, "type2", {"key2": "val2",
+ "key3": "val3"})
+ volume_types.create(self.ctxt, "type3", {"key3": "another_value",
+ "key4": "val4"})
+
+ vol_types = volume_types.get_all_types(self.ctxt,
+ search_opts={'extra_specs': {"key1": "val1"}})
+ LOG.info("vol_types: %s" % vol_types)
+ self.assertEqual(len(vol_types), 1)
+ self.assertTrue("type1" in vol_types.keys())
+ self.assertEqual(vol_types['type1']['extra_specs'],
+ {"key1": "val1", "key2": "val2"})
+
+ vol_types = volume_types.get_all_types(self.ctxt,
+ search_opts={'extra_specs': {"key2": "val2"}})
+ LOG.info("vol_types: %s" % vol_types)
+ self.assertEqual(len(vol_types), 2)
+ self.assertTrue("type1" in vol_types.keys())
+ self.assertTrue("type2" in vol_types.keys())
+
+ vol_types = volume_types.get_all_types(self.ctxt,
+ search_opts={'extra_specs': {"key3": "val3"}})
+ LOG.info("vol_types: %s" % vol_types)
+ self.assertEqual(len(vol_types), 1)
+ self.assertTrue("type2" in vol_types.keys())
+
+ def test_volume_type_search_by_extra_spec_multiple(self):
+ """Ensure volume types get by extra spec returns correct type"""
+ volume_types.create(self.ctxt, "type1", {"key1": "val1",
+ "key2": "val2",
+ "key3": "val3"})
+ volume_types.create(self.ctxt, "type2", {"key2": "val2",
+ "key3": "val3"})
+ volume_types.create(self.ctxt, "type3", {"key1": "val1",
+ "key3": "val3",
+ "key4": "val4"})
+
+ vol_types = volume_types.get_all_types(self.ctxt,
+ search_opts={'extra_specs': {"key1": "val1",
+ "key3": "val3"}})
+ LOG.info("vol_types: %s" % vol_types)
+ self.assertEqual(len(vol_types), 2)
+ self.assertTrue("type1" in vol_types.keys())
+ self.assertTrue("type3" in vol_types.keys())
+ self.assertEqual(vol_types['type1']['extra_specs'],
+ {"key1": "val1", "key2": "val2", "key3": "val3"})
+ self.assertEqual(vol_types['type3']['extra_specs'],
+ {"key1": "val1", "key3": "val3", "key4": "val4"})
diff --git a/nova/tests/test_volume_types_extra_specs.py b/nova/tests/test_volume_types_extra_specs.py
new file mode 100644
index 000000000..017b187a1
--- /dev/null
+++ b/nova/tests/test_volume_types_extra_specs.py
@@ -0,0 +1,132 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright (c) 2011 Zadara Storage Inc.
+# Copyright (c) 2011 OpenStack LLC.
+# Copyright 2011 University of Southern California
+# 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.
+"""
+Unit Tests for volume types extra specs code
+"""
+
+from nova import context
+from nova import db
+from nova import test
+from nova.db.sqlalchemy.session import get_session
+from nova.db.sqlalchemy import models
+
+
+class VolumeTypeExtraSpecsTestCase(test.TestCase):
+
+ def setUp(self):
+ super(VolumeTypeExtraSpecsTestCase, self).setUp()
+ self.context = context.get_admin_context()
+ self.vol_type1 = dict(name="TEST: Regular volume test")
+ self.vol_type1_specs = dict(vol_extra1="value1",
+ vol_extra2="value2",
+ vol_extra3=3)
+ self.vol_type1['extra_specs'] = self.vol_type1_specs
+ ref = db.api.volume_type_create(self.context, self.vol_type1)
+ self.volume_type1_id = ref.id
+ for k, v in self.vol_type1_specs.iteritems():
+ self.vol_type1_specs[k] = str(v)
+
+ self.vol_type2_noextra = dict(name="TEST: Volume type without extra")
+ ref = db.api.volume_type_create(self.context, self.vol_type2_noextra)
+ self.vol_type2_id = ref.id
+
+ def tearDown(self):
+ # Remove the instance type from the database
+ db.api.volume_type_purge(context.get_admin_context(),
+ self.vol_type1['name'])
+ db.api.volume_type_purge(context.get_admin_context(),
+ self.vol_type2_noextra['name'])
+ super(VolumeTypeExtraSpecsTestCase, self).tearDown()
+
+ def test_volume_type_specs_get(self):
+ expected_specs = self.vol_type1_specs.copy()
+ actual_specs = db.api.volume_type_extra_specs_get(
+ context.get_admin_context(),
+ self.volume_type1_id)
+ self.assertEquals(expected_specs, actual_specs)
+
+ def test_volume_type_extra_specs_delete(self):
+ expected_specs = self.vol_type1_specs.copy()
+ del expected_specs['vol_extra2']
+ db.api.volume_type_extra_specs_delete(context.get_admin_context(),
+ self.volume_type1_id,
+ 'vol_extra2')
+ actual_specs = db.api.volume_type_extra_specs_get(
+ context.get_admin_context(),
+ self.volume_type1_id)
+ self.assertEquals(expected_specs, actual_specs)
+
+ def test_volume_type_extra_specs_update(self):
+ expected_specs = self.vol_type1_specs.copy()
+ expected_specs['vol_extra3'] = "4"
+ db.api.volume_type_extra_specs_update_or_create(
+ context.get_admin_context(),
+ self.volume_type1_id,
+ dict(vol_extra3=4))
+ actual_specs = db.api.volume_type_extra_specs_get(
+ context.get_admin_context(),
+ self.volume_type1_id)
+ self.assertEquals(expected_specs, actual_specs)
+
+ def test_volume_type_extra_specs_create(self):
+ expected_specs = self.vol_type1_specs.copy()
+ expected_specs['vol_extra4'] = 'value4'
+ expected_specs['vol_extra5'] = 'value5'
+ db.api.volume_type_extra_specs_update_or_create(
+ context.get_admin_context(),
+ self.volume_type1_id,
+ dict(vol_extra4="value4",
+ vol_extra5="value5"))
+ actual_specs = db.api.volume_type_extra_specs_get(
+ context.get_admin_context(),
+ self.volume_type1_id)
+ self.assertEquals(expected_specs, actual_specs)
+
+ def test_volume_type_get_with_extra_specs(self):
+ volume_type = db.api.volume_type_get(
+ context.get_admin_context(),
+ self.volume_type1_id)
+ self.assertEquals(volume_type['extra_specs'],
+ self.vol_type1_specs)
+
+ volume_type = db.api.volume_type_get(
+ context.get_admin_context(),
+ self.vol_type2_id)
+ self.assertEquals(volume_type['extra_specs'], {})
+
+ def test_volume_type_get_by_name_with_extra_specs(self):
+ volume_type = db.api.volume_type_get_by_name(
+ context.get_admin_context(),
+ self.vol_type1['name'])
+ self.assertEquals(volume_type['extra_specs'],
+ self.vol_type1_specs)
+
+ volume_type = db.api.volume_type_get_by_name(
+ context.get_admin_context(),
+ self.vol_type2_noextra['name'])
+ self.assertEquals(volume_type['extra_specs'], {})
+
+ def test_volume_type_get_all(self):
+ expected_specs = self.vol_type1_specs.copy()
+
+ types = db.api.volume_type_get_all(context.get_admin_context())
+
+ self.assertEquals(
+ types[self.vol_type1['name']]['extra_specs'], expected_specs)
+
+ self.assertEquals(
+ types[self.vol_type2_noextra['name']]['extra_specs'], {})
diff --git a/nova/utils.py b/nova/utils.py
index 54126f644..21e6221b2 100644
--- a/nova/utils.py
+++ b/nova/utils.py
@@ -35,6 +35,7 @@ import sys
import time
import types
import uuid
+import pyclbr
from xml.sax import saxutils
from eventlet import event
@@ -295,7 +296,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 +548,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 +845,59 @@ 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
+
+
+def monkey_patch():
+ """ If the Flags.monkey_patch set as True,
+ this functuion patches a decorator
+ for all functions in specified modules.
+ You can set decorators for each modules
+ using FLAGS.monkey_patch_modules.
+ The format is "Module path:Decorator function".
+ Example: 'nova.api.ec2.cloud:nova.notifier.api.notify_decorator'
+
+ Parameters of the decorator is as follows.
+ (See nova.notifier.api.notify_decorator)
+
+ name - name of the function
+ function - object of the function
+ """
+ # If FLAGS.monkey_patch is not True, this function do nothing.
+ if not FLAGS.monkey_patch:
+ return
+ # Get list of modules and decorators
+ for module_and_decorator in FLAGS.monkey_patch_modules:
+ module, decorator_name = module_and_decorator.split(':')
+ # import decorator function
+ decorator = import_class(decorator_name)
+ __import__(module)
+ # Retrieve module information using pyclbr
+ module_data = pyclbr.readmodule_ex(module)
+ for key in module_data.keys():
+ # set the decorator for the class methods
+ if isinstance(module_data[key], pyclbr.Class):
+ clz = import_class("%s.%s" % (module, key))
+ for method, func in inspect.getmembers(clz, inspect.ismethod):
+ setattr(clz, method,\
+ decorator("%s.%s.%s" % (module, key, method), func))
+ # set the decorator for the function
+ if isinstance(module_data[key], pyclbr.Function):
+ func = import_class("%s.%s" % (module, key))
+ setattr(sys.modules[module], key,\
+ decorator("%s.%s" % (module, key), func))
diff --git a/nova/virt/disk.py b/nova/virt/disk.py
index 19f3ec185..52b2881e8 100644
--- a/nova/virt/disk.py
+++ b/nova/virt/disk.py
@@ -2,6 +2,9 @@
# Copyright 2010 United States Government as represented by the
# Administrator of the National Aeronautics and Space Administration.
+#
+# Copyright 2011, Piston Cloud Computing, Inc.
+#
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
@@ -22,6 +25,7 @@ Includes injection of SSH PGP keys into authorized_keys file.
"""
+import json
import os
import tempfile
import time
@@ -60,7 +64,8 @@ def extend(image, size):
utils.execute('resize2fs', image, check_exit_code=False)
-def inject_data(image, key=None, net=None, partition=None, nbd=False):
+def inject_data(image, key=None, net=None, metadata=None,
+ partition=None, nbd=False, tune2fs=True):
"""Injects a ssh key and optionally net data into a disk image.
it will mount the image as a fully partitioned disk and attempt to inject
@@ -89,10 +94,10 @@ def inject_data(image, key=None, net=None, partition=None, nbd=False):
' only inject raw disk images): %s' %
mapped_device)
- # Configure ext2fs so that it doesn't auto-check every N boots
- out, err = utils.execute('tune2fs', '-c', 0, '-i', 0,
- mapped_device, run_as_root=True)
-
+ if tune2fs:
+ # Configure ext2fs so that it doesn't auto-check every N boots
+ out, err = utils.execute('tune2fs', '-c', 0, '-i', 0,
+ mapped_device, run_as_root=True)
tmpdir = tempfile.mkdtemp()
try:
# mount loopback to dir
@@ -103,7 +108,8 @@ def inject_data(image, key=None, net=None, partition=None, nbd=False):
% err)
try:
- inject_data_into_fs(tmpdir, key, net, utils.execute)
+ inject_data_into_fs(tmpdir, key, net, metadata,
+ utils.execute)
finally:
# unmount device
utils.execute('umount', mapped_device, run_as_root=True)
@@ -155,6 +161,7 @@ def destroy_container(target, instance, nbd=False):
def _link_device(image, nbd):
"""Link image to device using loopback or nbd"""
+
if nbd:
device = _allocate_device()
utils.execute('qemu-nbd', '-c', device, image, run_as_root=True)
@@ -190,6 +197,7 @@ def _allocate_device():
# NOTE(vish): This assumes no other processes are allocating nbd devices.
# It may race cause a race condition if multiple
# workers are running on a given machine.
+
while True:
if not _DEVICES:
raise exception.Error(_('No free nbd devices'))
@@ -203,7 +211,7 @@ def _free_device(device):
_DEVICES.append(device)
-def inject_data_into_fs(fs, key, net, execute):
+def inject_data_into_fs(fs, key, net, metadata, execute):
"""Injects data into a filesystem already mounted by the caller.
Virt connections can call this directly if they mount their fs
in a different way to inject_data
@@ -212,6 +220,16 @@ def inject_data_into_fs(fs, key, net, execute):
_inject_key_into_fs(key, fs, execute=execute)
if net:
_inject_net_into_fs(net, fs, execute=execute)
+ if metadata:
+ _inject_metadata_into_fs(metadata, fs, execute=execute)
+
+
+def _inject_metadata_into_fs(metadata, fs, execute=None):
+ metadata_path = os.path.join(fs, "meta.js")
+ metadata = dict([(m.key, m.value) for m in metadata])
+
+ utils.execute('sudo', 'tee', metadata_path,
+ process_input=json.dumps(metadata))
def _inject_key_into_fs(key, fs, execute=None):
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.xml.template b/nova/virt/libvirt.xml.template
index 210e2b0fb..6a02cfa24 100644
--- a/nova/virt/libvirt.xml.template
+++ b/nova/virt/libvirt.xml.template
@@ -106,6 +106,13 @@
</disk>
#end for
#end if
+ #if $getVar('config_drive', False)
+ <disk type='file'>
+ <driver type='raw' />
+ <source file='${basepath}/disk.config' />
+ <target dev='${disk_prefix}z' bus='${disk_bus}' />
+ </disk>
+ #end if
#end if
#for $nic in $nics
diff --git a/nova/virt/libvirt/connection.py b/nova/virt/libvirt/connection.py
index e8a657bac..4388291db 100644
--- a/nova/virt/libvirt/connection.py
+++ b/nova/virt/libvirt/connection.py
@@ -4,6 +4,7 @@
# Administrator of the National Aeronautics and Space Administration.
# All Rights Reserved.
# Copyright (c) 2010 Citrix Systems, Inc.
+# Copyright (c) 2011 Piston Cloud Computing, Inc
#
# 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
@@ -130,6 +131,10 @@ flags.DEFINE_string('libvirt_vif_type', 'bridge',
flags.DEFINE_string('libvirt_vif_driver',
'nova.virt.libvirt.vif.LibvirtBridgeDriver',
'The libvirt VIF driver to configure the VIFs.')
+flags.DEFINE_string('default_local_format',
+ None,
+ 'The default format a local_volume will be formatted with '
+ 'on creation.')
def get_connection(read_only):
@@ -586,6 +591,7 @@ class LibvirtConnection(driver.ComputeDriver):
self.firewall_driver.prepare_instance_filter(instance, network_info)
self._create_image(context, instance, xml, network_info=network_info,
block_device_info=block_device_info)
+
domain = self._create_new_domain(xml)
LOG.debug(_("instance %s: is running"), instance['name'])
self.firewall_driver.apply_instance_filter(instance, network_info)
@@ -759,10 +765,15 @@ class LibvirtConnection(driver.ComputeDriver):
if size:
disk.extend(target, size)
- def _create_local(self, target, local_gb):
+ def _create_local(self, target, local_size, prefix='G', fs_format=None):
"""Create a blank image of specified size"""
- utils.execute('truncate', target, '-s', "%dG" % local_gb)
- # TODO(vish): should we format disk by default?
+
+ if not fs_format:
+ fs_format = FLAGS.default_local_format
+
+ utils.execute('truncate', target, '-s', "%d%c" % (local_size, prefix))
+ if fs_format:
+ utils.execute('mkfs', '-t', fs_format, target)
def _create_swap(self, target, swap_gb):
"""Create a swap file of specified size"""
@@ -849,14 +860,14 @@ class LibvirtConnection(driver.ComputeDriver):
target=basepath('disk.local'),
fname="local_%s" % local_gb,
cow=FLAGS.use_cow_images,
- local_gb=local_gb)
+ local_size=local_gb)
for eph in driver.block_device_info_get_ephemerals(block_device_info):
self._cache_image(fn=self._create_local,
target=basepath(_get_eph_disk(eph)),
fname="local_%s" % eph['size'],
cow=FLAGS.use_cow_images,
- local_gb=eph['size'])
+ local_size=eph['size'])
swap_gb = 0
@@ -882,9 +893,24 @@ class LibvirtConnection(driver.ComputeDriver):
if not inst['kernel_id']:
target_partition = "1"
- if FLAGS.libvirt_type == 'lxc':
+ config_drive_id = inst.get('config_drive_id')
+ config_drive = inst.get('config_drive')
+
+ if any((FLAGS.libvirt_type == 'lxc', config_drive, config_drive_id)):
target_partition = None
+ if config_drive_id:
+ fname = '%08x' % int(config_drive_id)
+ self._cache_image(fn=self._fetch_image,
+ target=basepath('disk.config'),
+ fname=fname,
+ image_id=config_drive_id,
+ user=user,
+ project=project)
+ elif config_drive:
+ self._create_local(basepath('disk.config'), 64, prefix="M",
+ fs_format='msdos') # 64MB
+
if inst['key_data']:
key = str(inst['key_data'])
else:
@@ -928,19 +954,29 @@ class LibvirtConnection(driver.ComputeDriver):
searchList=[{'interfaces': nets,
'use_ipv6': FLAGS.use_ipv6}]))
- if key or net:
+ metadata = inst.get('metadata')
+ if any((key, net, metadata)):
inst_name = inst['name']
- img_id = inst.image_ref
- if key:
- LOG.info(_('instance %(inst_name)s: injecting key into'
- ' image %(img_id)s') % locals())
- if net:
- LOG.info(_('instance %(inst_name)s: injecting net into'
- ' image %(img_id)s') % locals())
+
+ if config_drive: # Should be True or None by now.
+ injection_path = basepath('disk.config')
+ img_id = 'config-drive'
+ tune2fs = False
+ else:
+ injection_path = basepath('disk')
+ img_id = inst.image_ref
+ tune2fs = True
+
+ for injection in ('metadata', 'key', 'net'):
+ if locals()[injection]:
+ LOG.info(_('instance %(inst_name)s: injecting '
+ '%(injection)s into image %(img_id)s'
+ % locals()))
try:
- disk.inject_data(basepath('disk'), key, net,
+ disk.inject_data(injection_path, key, net, metadata,
partition=target_partition,
- nbd=FLAGS.use_cow_images)
+ nbd=FLAGS.use_cow_images,
+ tune2fs=tune2fs)
if FLAGS.libvirt_type == 'lxc':
disk.setup_container(basepath('disk'),
@@ -1070,6 +1106,10 @@ class LibvirtConnection(driver.ComputeDriver):
block_device_info)):
xml_info['swap_device'] = self.default_swap_device
+ config_drive = False
+ if instance.get('config_drive') or instance.get('config_drive_id'):
+ xml_info['config_drive'] = xml_info['basepath'] + "/disk.config"
+
if FLAGS.vnc_enabled and FLAGS.libvirt_type not in ('lxc', 'uml'):
xml_info['vncserver_host'] = FLAGS.vncserver_host
xml_info['vnc_keymap'] = FLAGS.vnc_keymap
diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py
index 4a1f07bb1..efbea7076 100644
--- a/nova/virt/xenapi/vm_utils.py
+++ b/nova/virt/xenapi/vm_utils.py
@@ -1,6 +1,7 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright (c) 2010 Citrix Systems, Inc.
+# Copyright 2011 Piston Cloud Computing, Inc.
#
# 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
@@ -740,13 +741,14 @@ class VMHelper(HelperBase):
# if at all, so determine whether it's required first, and then do
# everything
mount_required = False
- key, net = _prepare_injectables(instance, network_info)
- mount_required = key or net
+ key, net, metadata = _prepare_injectables(instance, network_info)
+ mount_required = key or net or metadata
if not mount_required:
return
with_vdi_attached_here(session, vdi_ref, False,
- lambda dev: _mounted_processing(dev, key, net))
+ lambda dev: _mounted_processing(dev, key, net,
+ metadata))
@classmethod
def lookup_kernel_ramdisk(cls, session, vm):
@@ -1198,7 +1200,7 @@ def _find_guest_agent(base_dir, agent_rel_path):
return False
-def _mounted_processing(device, key, net):
+def _mounted_processing(device, key, net, metadata):
"""Callback which runs with the image VDI attached"""
dev_path = '/dev/' + device + '1' # NB: Partition 1 hardcoded
@@ -1212,7 +1214,7 @@ def _mounted_processing(device, key, net):
if not _find_guest_agent(tmpdir, FLAGS.xenapi_agent_path):
LOG.info(_('Manipulating interface files '
'directly'))
- disk.inject_data_into_fs(tmpdir, key, net,
+ disk.inject_data_into_fs(tmpdir, key, net, metadata,
utils.execute)
finally:
utils.execute('umount', dev_path, run_as_root=True)
@@ -1235,6 +1237,7 @@ def _prepare_injectables(inst, networks_info):
template = t.Template
template_data = open(FLAGS.injected_network_template).read()
+ metadata = inst['metadata']
key = str(inst['key_data'])
net = None
if networks_info:
@@ -1272,4 +1275,4 @@ def _prepare_injectables(inst, networks_info):
net = str(template(template_data,
searchList=[{'interfaces': interfaces_info,
'use_ipv6': FLAGS.use_ipv6}]))
- return key, net
+ return key, net, metadata
diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py
index 9a6215f88..c5f105f40 100644
--- a/nova/virt/xenapi/vmops.py
+++ b/nova/virt/xenapi/vmops.py
@@ -239,7 +239,7 @@ class VMOps(object):
self._attach_disks(instance, disk_image_type, vm_ref, first_vdi_ref,
vdis)
- # Alter the image before VM start for, e.g. network injection
+ # Alter the image before VM start for network injection.
if FLAGS.flat_injected:
VMHelper.preconfigure_instance(self._session, instance,
first_vdi_ref, network_info)
diff --git a/nova/volume/api.py b/nova/volume/api.py
index 52b3a9fed..195ab24aa 100644
--- a/nova/volume/api.py
+++ b/nova/volume/api.py
@@ -41,7 +41,8 @@ LOG = logging.getLogger('nova.volume')
class API(base.Base):
"""API for interacting with the volume manager."""
- def create(self, context, size, snapshot_id, name, description):
+ def create(self, context, size, snapshot_id, name, description,
+ volume_type=None, metadata=None, availability_zone=None):
if snapshot_id != None:
snapshot = self.get_snapshot(context, snapshot_id)
if snapshot['status'] != "available":
@@ -57,16 +58,27 @@ class API(base.Base):
raise quota.QuotaError(_("Volume quota exceeded. You cannot "
"create a volume of size %sG") % size)
+ if availability_zone is None:
+ availability_zone = FLAGS.storage_availability_zone
+
+ if volume_type is None:
+ volume_type_id = None
+ else:
+ volume_type_id = volume_type.get('id', None)
+
options = {
'size': size,
'user_id': context.user_id,
'project_id': context.project_id,
'snapshot_id': snapshot_id,
- 'availability_zone': FLAGS.storage_availability_zone,
+ 'availability_zone': availability_zone,
'status': "creating",
'attach_status': "detached",
'display_name': name,
- 'display_description': description}
+ 'display_description': description,
+ 'volume_type_id': volume_type_id,
+ 'metadata': metadata,
+ }
volume = self.db.volume_create(context, options)
rpc.cast(context,
@@ -105,10 +117,44 @@ class API(base.Base):
rv = self.db.volume_get(context, volume_id)
return dict(rv.iteritems())
- def get_all(self, context):
+ def get_all(self, context, search_opts={}):
if context.is_admin:
- return self.db.volume_get_all(context)
- return self.db.volume_get_all_by_project(context, context.project_id)
+ volumes = self.db.volume_get_all(context)
+ else:
+ volumes = self.db.volume_get_all_by_project(context,
+ context.project_id)
+
+ if search_opts:
+ LOG.debug(_("Searching by: %s") % str(search_opts))
+
+ def _check_metadata_match(volume, searchdict):
+ volume_metadata = {}
+ for i in volume.get('volume_metadata'):
+ volume_metadata[i['key']] = i['value']
+
+ for k, v in searchdict:
+ if k not in volume_metadata.keys()\
+ or volume_metadata[k] != v:
+ return False
+ return True
+
+ # search_option to filter_name mapping.
+ filter_mapping = {'metadata': _check_metadata_match}
+
+ for volume in volumes:
+ # go over all filters in the list
+ for opt, values in search_opts.iteritems():
+ try:
+ filter_func = filter_mapping[opt]
+ except KeyError:
+ # no such filter - ignore it, go to next filter
+ continue
+ else:
+ if filter_func(volume, values) == False:
+ # if one of conditions didn't match - remove
+ volumes.remove(volume)
+ break
+ return volumes
def get_snapshot(self, context, snapshot_id):
rv = self.db.snapshot_get(context, snapshot_id)
@@ -183,3 +229,29 @@ class API(base.Base):
{"method": "delete_snapshot",
"args": {"topic": FLAGS.volume_topic,
"snapshot_id": snapshot_id}})
+
+ def get_volume_metadata(self, context, volume_id):
+ """Get all metadata associated with a volume."""
+ rv = self.db.volume_metadata_get(context, volume_id)
+ return dict(rv.iteritems())
+
+ def delete_volume_metadata(self, context, volume_id, key):
+ """Delete the given metadata item from an volume."""
+ self.db.volume_metadata_delete(context, volume_id, key)
+
+ def update_volume_metadata(self, context, volume_id,
+ metadata, delete=False):
+ """Updates or creates volume metadata.
+
+ If delete is True, metadata items that are not specified in the
+ `metadata` argument will be deleted.
+
+ """
+ if delete:
+ _metadata = metadata
+ else:
+ _metadata = self.get_volume_metadata(context, volume_id)
+ _metadata.update(metadata)
+
+ self.db.volume_metadata_update(context, volume_id, _metadata, True)
+ return _metadata
diff --git a/nova/volume/driver.py b/nova/volume/driver.py
index c99534c07..7d2fb45d4 100644
--- a/nova/volume/driver.py
+++ b/nova/volume/driver.py
@@ -495,7 +495,7 @@ class ISCSIDriver(VolumeDriver):
(out, err) = self._execute('iscsiadm', '-m', 'node', '-T',
iscsi_properties['target_iqn'],
'-p', iscsi_properties['target_portal'],
- iscsi_command, run_as_root=True)
+ *iscsi_command, run_as_root=True)
LOG.debug("iscsiadm %s: stdout=%s stderr=%s" %
(iscsi_command, out, err))
return (out, err)
diff --git a/nova/volume/volume_types.py b/nova/volume/volume_types.py
new file mode 100644
index 000000000..9b02d4ccc
--- /dev/null
+++ b/nova/volume/volume_types.py
@@ -0,0 +1,129 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright (c) 2011 Zadara Storage Inc.
+# Copyright (c) 2011 OpenStack LLC.
+# Copyright 2010 United States Government as represented by the
+# Administrator of the National Aeronautics and Space Administration.
+# Copyright (c) 2010 Citrix Systems, Inc.
+# Copyright 2011 Ken Pepple
+#
+# 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.
+
+"""Built-in volume type properties."""
+
+from nova import context
+from nova import db
+from nova import exception
+from nova import flags
+from nova import log as logging
+
+FLAGS = flags.FLAGS
+LOG = logging.getLogger('nova.volume.volume_types')
+
+
+def create(context, name, extra_specs={}):
+ """Creates volume types."""
+ try:
+ db.volume_type_create(context,
+ dict(name=name,
+ extra_specs=extra_specs))
+ except exception.DBError, e:
+ LOG.exception(_('DB error: %s') % e)
+ raise exception.ApiError(_("Cannot create volume_type with "
+ "name %(name)s and specs %(extra_specs)s")
+ % locals())
+
+
+def destroy(context, name):
+ """Marks volume types as deleted."""
+ if name is None:
+ raise exception.InvalidVolumeType(volume_type=name)
+ else:
+ try:
+ db.volume_type_destroy(context, name)
+ except exception.NotFound:
+ LOG.exception(_('Volume type %s not found for deletion') % name)
+ raise exception.ApiError(_("Unknown volume type: %s") % name)
+
+
+def purge(context, name):
+ """Removes volume types from database."""
+ if name is None:
+ raise exception.InvalidVolumeType(volume_type=name)
+ else:
+ try:
+ db.volume_type_purge(context, name)
+ except exception.NotFound:
+ LOG.exception(_('Volume type %s not found for purge') % name)
+ raise exception.ApiError(_("Unknown volume type: %s") % name)
+
+
+def get_all_types(context, inactive=0, search_opts={}):
+ """Get all non-deleted volume_types.
+
+ Pass true as argument if you want deleted volume types returned also.
+
+ """
+ vol_types = db.volume_type_get_all(context, inactive)
+
+ if search_opts:
+ LOG.debug(_("Searching by: %s") % str(search_opts))
+
+ def _check_extra_specs_match(vol_type, searchdict):
+ for k, v in searchdict.iteritems():
+ if k not in vol_type['extra_specs'].keys()\
+ or vol_type['extra_specs'][k] != v:
+ return False
+ return True
+
+ # search_option to filter_name mapping.
+ filter_mapping = {'extra_specs': _check_extra_specs_match}
+
+ result = {}
+ for type_name, type_args in vol_types.iteritems():
+ # go over all filters in the list
+ for opt, values in search_opts.iteritems():
+ try:
+ filter_func = filter_mapping[opt]
+ except KeyError:
+ # no such filter - ignore it, go to next filter
+ continue
+ else:
+ if filter_func(type_args, values):
+ # if one of conditions didn't match - remove
+ result[type_name] = type_args
+ break
+ vol_types = result
+ return vol_types
+
+
+def get_volume_type(context, id):
+ """Retrieves single volume type by id."""
+ if id is None:
+ raise exception.InvalidVolumeType(volume_type=id)
+
+ try:
+ return db.volume_type_get(context, id)
+ except exception.DBError:
+ raise exception.ApiError(_("Unknown volume type: %s") % id)
+
+
+def get_volume_type_by_name(context, name):
+ """Retrieves single volume type by name."""
+ if name is None:
+ raise exception.InvalidVolumeType(volume_type=name)
+
+ try:
+ return db.volume_type_get_by_name(context, name)
+ except exception.DBError:
+ raise exception.ApiError(_("Unknown volume type: %s") % name)
diff --git a/po/ast.po b/po/ast.po
index 449cddb07..48682ec90 100644
--- a/po/ast.po
+++ b/po/ast.po
@@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-Launchpad-Export-Date: 2011-07-23 05:11+0000\n"
-"X-Generator: Launchpad (build 13405)\n"
+"X-Launchpad-Export-Date: 2011-08-03 04:43+0000\n"
+"X-Generator: Launchpad (build 13573)\n"
#: ../nova/scheduler/chance.py:37 ../nova/scheduler/zone.py:55
#: ../nova/scheduler/simple.py:75 ../nova/scheduler/simple.py:110
diff --git a/po/cs.po b/po/cs.po
index 2dc763838..07bdf1928 100644
--- a/po/cs.po
+++ b/po/cs.po
@@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-Launchpad-Export-Date: 2011-07-23 05:11+0000\n"
-"X-Generator: Launchpad (build 13405)\n"
+"X-Launchpad-Export-Date: 2011-08-03 04:43+0000\n"
+"X-Generator: Launchpad (build 13573)\n"
#: ../nova/scheduler/chance.py:37 ../nova/scheduler/zone.py:55
#: ../nova/scheduler/simple.py:75 ../nova/scheduler/simple.py:110
diff --git a/po/da.po b/po/da.po
index 570629119..0b379c9d7 100644
--- a/po/da.po
+++ b/po/da.po
@@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-Launchpad-Export-Date: 2011-07-23 05:12+0000\n"
-"X-Generator: Launchpad (build 13405)\n"
+"X-Launchpad-Export-Date: 2011-08-03 04:43+0000\n"
+"X-Generator: Launchpad (build 13573)\n"
#: ../nova/scheduler/chance.py:37 ../nova/scheduler/zone.py:55
#: ../nova/scheduler/simple.py:75 ../nova/scheduler/simple.py:110
diff --git a/po/de.po b/po/de.po
index 772ae236c..1f652c373 100644
--- a/po/de.po
+++ b/po/de.po
@@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-Launchpad-Export-Date: 2011-07-23 05:12+0000\n"
-"X-Generator: Launchpad (build 13405)\n"
+"X-Launchpad-Export-Date: 2011-08-03 04:44+0000\n"
+"X-Generator: Launchpad (build 13573)\n"
#: ../nova/scheduler/chance.py:37 ../nova/scheduler/zone.py:55
#: ../nova/scheduler/simple.py:75 ../nova/scheduler/simple.py:110
@@ -2833,3 +2833,21 @@ msgstr ""
#~ msgid "Data store %s is unreachable. Trying again in %d seconds."
#~ msgstr ""
#~ "Datastore %s ist nicht erreichbar. Versuche es erneut in %d Sekunden."
+
+#~ msgid "Full set of FLAGS:"
+#~ msgstr "Alle vorhandenen FLAGS:"
+
+#, python-format
+#~ msgid "pidfile %s does not exist. Daemon not running?\n"
+#~ msgstr "PID-Datei %s existiert nicht. Läuft der Daemon nicht?\n"
+
+#, python-format
+#~ msgid "Starting %s"
+#~ msgstr "%s wird gestartet"
+
+#~ msgid "No such process"
+#~ msgstr "Kein passender Prozess gefunden"
+
+#, python-format
+#~ msgid "Serving %s"
+#~ msgstr "Bedient %s"
diff --git a/po/en_AU.po b/po/en_AU.po
index 3fa62c006..a51b9ff2d 100644
--- a/po/en_AU.po
+++ b/po/en_AU.po
@@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-Launchpad-Export-Date: 2011-07-23 05:12+0000\n"
-"X-Generator: Launchpad (build 13405)\n"
+"X-Launchpad-Export-Date: 2011-08-03 04:44+0000\n"
+"X-Generator: Launchpad (build 13573)\n"
#: ../nova/scheduler/chance.py:37 ../nova/scheduler/zone.py:55
#: ../nova/scheduler/simple.py:75 ../nova/scheduler/simple.py:110
diff --git a/po/en_GB.po b/po/en_GB.po
index b204c93a1..59247f4fa 100644
--- a/po/en_GB.po
+++ b/po/en_GB.po
@@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-Launchpad-Export-Date: 2011-07-23 05:12+0000\n"
-"X-Generator: Launchpad (build 13405)\n"
+"X-Launchpad-Export-Date: 2011-08-03 04:44+0000\n"
+"X-Generator: Launchpad (build 13573)\n"
#: ../nova/scheduler/chance.py:37 ../nova/scheduler/zone.py:55
#: ../nova/scheduler/simple.py:75 ../nova/scheduler/simple.py:110
@@ -2812,3 +2812,24 @@ msgstr ""
#, python-format
msgid "Removing user %(user)s from project %(project)s"
msgstr ""
+
+#~ msgid "Wrong number of arguments."
+#~ msgstr "Wrong number of arguments."
+
+#~ msgid "No such process"
+#~ msgstr "No such process"
+
+#~ msgid "Full set of FLAGS:"
+#~ msgstr "Full set of FLAGS:"
+
+#, python-format
+#~ msgid "pidfile %s does not exist. Daemon not running?\n"
+#~ msgstr "pidfile %s does not exist. Daemon not running?\n"
+
+#, python-format
+#~ msgid "Starting %s"
+#~ msgstr "Starting %s"
+
+#, python-format
+#~ msgid "Serving %s"
+#~ msgstr "Serving %s"
diff --git a/po/es.po b/po/es.po
index f97434041..7371eae8c 100644
--- a/po/es.po
+++ b/po/es.po
@@ -8,20 +8,20 @@ msgstr ""
"Project-Id-Version: nova\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2011-02-21 10:03-0500\n"
-"PO-Revision-Date: 2011-06-30 16:42+0000\n"
-"Last-Translator: David Caro <Unknown>\n"
+"PO-Revision-Date: 2011-08-01 03:23+0000\n"
+"Last-Translator: Juan Alfredo Salas Santillana <Unknown>\n"
"Language-Team: Spanish <es@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-Launchpad-Export-Date: 2011-07-23 05:12+0000\n"
-"X-Generator: Launchpad (build 13405)\n"
+"X-Launchpad-Export-Date: 2011-08-03 04:44+0000\n"
+"X-Generator: Launchpad (build 13573)\n"
#: ../nova/scheduler/chance.py:37 ../nova/scheduler/zone.py:55
#: ../nova/scheduler/simple.py:75 ../nova/scheduler/simple.py:110
#: ../nova/scheduler/simple.py:122
msgid "No hosts found"
-msgstr "No se han encontrado hosts"
+msgstr "No se encontro anfitriones."
#: ../nova/exception.py:33
msgid "Unexpected error while running command."
@@ -2566,7 +2566,7 @@ msgstr ""
#: ../nova/auth/manager.py:289
#, python-format
msgid "User %(uid)s is not a member of project %(pjid)s"
-msgstr ""
+msgstr "El usuario %(uid)s no es miembro del proyecto %(pjid)s"
#: ../nova/auth/manager.py:298 ../nova/auth/manager.py:309
#, python-format
@@ -2584,7 +2584,7 @@ msgstr "Debes especificar un proyecto"
#: ../nova/auth/manager.py:414
#, python-format
msgid "The %s role can not be found"
-msgstr "El rol %s no se ha podido encontrar"
+msgstr ""
#: ../nova/auth/manager.py:416
#, python-format
@@ -2614,27 +2614,27 @@ msgstr ""
#: ../nova/auth/manager.py:515
#, python-format
msgid "Created project %(name)s with manager %(manager_user)s"
-msgstr ""
+msgstr "Creado el proyecto %(name)s con administrador %(manager_user)s"
#: ../nova/auth/manager.py:533
#, python-format
msgid "modifying project %s"
-msgstr "modificando proyecto %s"
+msgstr "Modificando proyecto %s"
#: ../nova/auth/manager.py:545
#, python-format
msgid "Adding user %(uid)s to project %(pid)s"
-msgstr ""
+msgstr "Agregando usuario %(uid)s para el proyecto %(pid)s"
#: ../nova/auth/manager.py:566
#, python-format
msgid "Remove user %(uid)s from project %(pid)s"
-msgstr ""
+msgstr "Borrar usuario %(uid)s del proyecto %(pid)s"
#: ../nova/auth/manager.py:592
#, python-format
msgid "Deleting project %s"
-msgstr "Eliminando proyecto %s"
+msgstr "Borrando proyecto %s"
#: ../nova/auth/manager.py:650
#, python-format
@@ -2644,7 +2644,7 @@ msgstr ""
#: ../nova/auth/manager.py:659
#, python-format
msgid "Deleting user %s"
-msgstr "Eliminando usuario %s"
+msgstr "Borrando usuario %s"
#: ../nova/auth/manager.py:669
#, python-format
@@ -2710,7 +2710,7 @@ msgstr ""
#: ../nova/auth/ldapdriver.py:478
#, python-format
msgid "Group can't be created because user %s doesn't exist"
-msgstr ""
+msgstr "El grupo no se puede crear porque el usuario %s no existe"
#: ../nova/auth/ldapdriver.py:495
#, python-format
@@ -2730,18 +2730,20 @@ msgstr ""
#: ../nova/auth/ldapdriver.py:513
#, python-format
msgid "User %(uid)s is already a member of the group %(group_dn)s"
-msgstr ""
+msgstr "El usuario %(uid)s es actualmente miembro del grupo %(group_dn)s"
#: ../nova/auth/ldapdriver.py:524
#, python-format
msgid ""
"User %s can't be removed from the group because the user doesn't exist"
msgstr ""
+"El usuario %s no se pudo borrar de el grupo a causa de que el usuario no "
+"existe"
#: ../nova/auth/ldapdriver.py:528
#, python-format
msgid "User %s is not a member of the group"
-msgstr ""
+msgstr "El usuario %s no es miembro de el grupo"
#: ../nova/auth/ldapdriver.py:542
#, python-format
@@ -2878,6 +2880,10 @@ msgstr "Eliminando el usuario %(user)s del proyecto %(project)s"
#~ "El almacen de datos %s es inalcanzable. Reintentandolo en %d segundos."
#, python-format
+#~ msgid "Serving %s"
+#~ msgstr "Sirviendo %s"
+
+#, python-format
#~ msgid "Couldn't get IP, using 127.0.0.1 %s"
#~ msgstr "No puedo obtener IP, usando 127.0.0.1 %s"
@@ -3037,11 +3043,25 @@ msgstr "Eliminando el usuario %(user)s del proyecto %(project)s"
#~ msgid "Detach volume %s from mountpoint %s on instance %s"
#~ msgstr "Desvinculando volumen %s del punto de montaje %s en la instancia %s"
+#~ msgid "unexpected exception getting connection"
+#~ msgstr "excepción inexperada al obtener la conexión"
+
+#~ msgid "unexpected error during update"
+#~ msgstr "error inesperado durante la actualización"
+
#, python-format
#~ msgid "Cannot get blockstats for \"%s\" on \"%s\""
#~ msgstr "No puedo obtener estadísticas del bloque para \"%s\" en \"%s\""
#, python-format
+#~ msgid "updating %s..."
+#~ msgstr "actualizando %s..."
+
+#, python-format
+#~ msgid "Found instance: %s"
+#~ msgstr "Encontrada interfaz: %s"
+
+#, python-format
#~ msgid "Cannot get ifstats for \"%s\" on \"%s\""
#~ msgstr "No puedo obtener estadísticas de la interfaz para \"%s\" en \"%s\""
@@ -3319,3 +3339,20 @@ msgstr "Eliminando el usuario %(user)s del proyecto %(project)s"
#, python-format
#~ msgid "Spawning VM %s created %s."
#~ msgstr "Iniciando VM %s creado %s."
+
+#~ msgid "No such process"
+#~ msgstr "No existe el proceso"
+
+#~ msgid "Full set of FLAGS:"
+#~ msgstr "Conjunto completo de opciones (FLAGS):"
+
+#~ msgid "Wrong number of arguments."
+#~ msgstr "Cantidad de argumentos incorrecta"
+
+#, python-format
+#~ msgid "pidfile %s does not exist. Daemon not running?\n"
+#~ msgstr "El \"pidfile\" %s no existe. Quizás el servicio no este corriendo.\n"
+
+#, python-format
+#~ msgid "Starting %s"
+#~ msgstr "Iniciando %s"
diff --git a/po/fr.po b/po/fr.po
index 83e4e7af0..7cb298a94 100644
--- a/po/fr.po
+++ b/po/fr.po
@@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-Launchpad-Export-Date: 2011-07-23 05:12+0000\n"
-"X-Generator: Launchpad (build 13405)\n"
+"X-Launchpad-Export-Date: 2011-08-03 04:43+0000\n"
+"X-Generator: Launchpad (build 13573)\n"
#: ../nova/scheduler/chance.py:37 ../nova/scheduler/zone.py:55
#: ../nova/scheduler/simple.py:75 ../nova/scheduler/simple.py:110
@@ -2929,3 +2929,51 @@ msgstr "Ajout de l'utilisateur %(user)s au projet %(project)s"
#, python-format
msgid "Removing user %(user)s from project %(project)s"
msgstr "Suppression de l'utilisateur %(user)s du projet %(project)s"
+
+#~ msgid "Wrong number of arguments."
+#~ msgstr "Nombre d'arguments incorrect."
+
+#~ msgid "No such process"
+#~ msgstr "Aucun processus de ce type"
+
+#, python-format
+#~ msgid "Starting %s"
+#~ msgstr "Démarrage de %s"
+
+#~ msgid "Full set of FLAGS:"
+#~ msgstr "Ensemble de propriétés complet :"
+
+#, python-format
+#~ msgid "pidfile %s does not exist. Daemon not running?\n"
+#~ msgstr ""
+#~ "Le fichier pid %s n'existe pas. Est-ce que le processus est en cours "
+#~ "d'exécution ?\n"
+
+#, python-format
+#~ msgid "Serving %s"
+#~ msgstr "En train de servir %s"
+
+#, python-format
+#~ msgid "Cannot get blockstats for \"%(disk)s\" on \"%(iid)s\""
+#~ msgstr "Ne peut pas récupérer blockstats pour \"%(disk)s\" sur \"%(iid)s\""
+
+#, python-format
+#~ msgid "Cannot get ifstats for \"%(interface)s\" on \"%(iid)s\""
+#~ msgstr "Ne peut pas récupérer ifstats pour \"%(interface)s\" sur \"%(iid)s\""
+
+#~ msgid "unexpected error during update"
+#~ msgstr "erreur inopinée pendant la ise à jour"
+
+#, python-format
+#~ msgid "updating %s..."
+#~ msgstr "mise à jour %s..."
+
+#, python-format
+#~ msgid "Found instance: %s"
+#~ msgstr "Instance trouvée : %s"
+
+#~ msgid "unexpected exception getting connection"
+#~ msgstr "erreur inopinée pendant la connexion"
+
+#~ msgid "Starting instance monitor"
+#~ msgstr "Démarrage du superviseur d'instance"
diff --git a/po/it.po b/po/it.po
index 6bfcf1274..e166297f1 100644
--- a/po/it.po
+++ b/po/it.po
@@ -8,14 +8,14 @@ msgstr ""
"Project-Id-Version: nova\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2011-02-21 10:03-0500\n"
-"PO-Revision-Date: 2011-02-22 19:34+0000\n"
-"Last-Translator: Armando Migliaccio <Unknown>\n"
+"PO-Revision-Date: 2011-08-21 22:50+0000\n"
+"Last-Translator: Guido Davide Dall'Olio <Unknown>\n"
"Language-Team: Italian <it@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-Launchpad-Export-Date: 2011-07-23 05:12+0000\n"
-"X-Generator: Launchpad (build 13405)\n"
+"X-Launchpad-Export-Date: 2011-08-22 04:48+0000\n"
+"X-Generator: Launchpad (build 13697)\n"
#: ../nova/scheduler/chance.py:37 ../nova/scheduler/zone.py:55
#: ../nova/scheduler/simple.py:75 ../nova/scheduler/simple.py:110
@@ -449,24 +449,24 @@ msgstr ""
#: ../nova/scheduler/simple.py:53
#, python-format
msgid "Host %s is not alive"
-msgstr ""
+msgstr "L'host %s non è attivo"
#: ../nova/scheduler/simple.py:65
msgid "All hosts have too many cores"
-msgstr ""
+msgstr "Gli host hanno troppi core"
#: ../nova/scheduler/simple.py:87
#, python-format
msgid "Host %s not available"
-msgstr ""
+msgstr "Host %s non disponibile"
#: ../nova/scheduler/simple.py:99
msgid "All hosts have too many gigabytes"
-msgstr ""
+msgstr "Gli Host hanno troppy gigabyte"
#: ../nova/scheduler/simple.py:119
msgid "All hosts have too many networks"
-msgstr ""
+msgstr "Gli host hanno troppe reti"
#: ../nova/volume/manager.py:85
#, python-format
@@ -496,7 +496,7 @@ msgstr ""
#: ../nova/volume/manager.py:123
#, python-format
msgid "volume %s: created successfully"
-msgstr ""
+msgstr "volume %s: creato con successo"
#: ../nova/volume/manager.py:131
msgid "Volume is still attached"
@@ -514,12 +514,12 @@ msgstr ""
#: ../nova/volume/manager.py:138
#, python-format
msgid "volume %s: deleting"
-msgstr ""
+msgstr "volume %s: rimuovendo"
#: ../nova/volume/manager.py:147
#, python-format
msgid "volume %s: deleted successfully"
-msgstr ""
+msgstr "volume %s: rimosso con successo"
#: ../nova/virt/xenapi/fake.py:74
#, python-format
@@ -529,7 +529,7 @@ msgstr ""
#: ../nova/virt/xenapi/fake.py:304 ../nova/virt/xenapi/fake.py:404
#: ../nova/virt/xenapi/fake.py:422 ../nova/virt/xenapi/fake.py:478
msgid "Raising NotImplemented"
-msgstr ""
+msgstr "Sollevando NotImplemented"
#: ../nova/virt/xenapi/fake.py:306
#, python-format
@@ -539,7 +539,7 @@ msgstr ""
#: ../nova/virt/xenapi/fake.py:341
#, python-format
msgid "Calling %(localname)s %(impl)s"
-msgstr ""
+msgstr "Chiamando %(localname)s %(impl)s"
#: ../nova/virt/xenapi/fake.py:346
#, python-format
@@ -564,17 +564,17 @@ msgstr ""
#: ../nova/virt/connection.py:73
msgid "Failed to open connection to the hypervisor"
-msgstr ""
+msgstr "Fallita l'apertura della connessione verso l'hypervisor"
#: ../nova/network/linux_net.py:187
#, python-format
msgid "Starting VLAN inteface %s"
-msgstr ""
+msgstr "Avviando l'interfaccia VLAN %s"
#: ../nova/network/linux_net.py:208
#, python-format
msgid "Starting Bridge interface for %s"
-msgstr ""
+msgstr "Avviando l'interfaccia Bridge per %s"
#. pylint: disable=W0703
#: ../nova/network/linux_net.py:314
@@ -632,7 +632,7 @@ msgstr "Il risultato é %s"
#: ../nova/utils.py:159
#, python-format
msgid "Running cmd (SSH): %s"
-msgstr ""
+msgstr "Eseguendo cmd (SSH): %s"
#: ../nova/utils.py:217
#, python-format
@@ -642,7 +642,7 @@ msgstr "debug in callback: %s"
#: ../nova/utils.py:222
#, python-format
msgid "Running %s"
-msgstr ""
+msgstr "Eseguendo %s"
#: ../nova/utils.py:262
#, python-format
@@ -697,12 +697,12 @@ msgstr ""
#: ../nova/virt/xenapi/vm_utils.py:135 ../nova/virt/hyperv.py:171
#, python-format
msgid "Created VM %s..."
-msgstr ""
+msgstr "Creata VM %s.."
#: ../nova/virt/xenapi/vm_utils.py:138
#, python-format
msgid "Created VM %(instance_name)s as %(vm_ref)s."
-msgstr ""
+msgstr "Creata VM %(instance_name)s come %(vm_ref)s"
#: ../nova/virt/xenapi/vm_utils.py:168
#, python-format
@@ -771,7 +771,7 @@ msgstr ""
#: ../nova/virt/xenapi/vm_utils.py:332
#, python-format
msgid "Glance image %s"
-msgstr ""
+msgstr "Immagine Glance %s"
#. we need to invoke a plugin for copying VDI's
#. content into proper path
@@ -783,7 +783,7 @@ msgstr ""
#: ../nova/virt/xenapi/vm_utils.py:352
#, python-format
msgid "Kernel/Ramdisk VDI %s destroyed"
-msgstr ""
+msgstr "Kernel/Ramdisk VDI %s distrutti"
#: ../nova/virt/xenapi/vm_utils.py:361
#, python-format
@@ -793,7 +793,7 @@ msgstr ""
#: ../nova/virt/xenapi/vm_utils.py:386 ../nova/virt/xenapi/vm_utils.py:402
#, python-format
msgid "Looking up vdi %s for PV kernel"
-msgstr ""
+msgstr "Cercando vdi %s per kernel PV"
#: ../nova/virt/xenapi/vm_utils.py:397
#, python-format
@@ -2802,37 +2802,24 @@ msgstr ""
msgid "Removing user %(user)s from project %(project)s"
msgstr ""
-#, python-format
-#~ msgid ""
-#~ "%s\n"
-#~ "Command: %s\n"
-#~ "Exit code: %s\n"
-#~ "Stdout: %r\n"
-#~ "Stderr: %r"
-#~ msgstr ""
-#~ "%s\n"
-#~ "Comando: %s\n"
-#~ "Exit code: %s\n"
-#~ "Stdout: %r\n"
-#~ "Stderr: %r"
-
-#, python-format
-#~ msgid "(%s) publish (key: %s) %s"
-#~ msgstr "(%s) pubblica (chiave: %s) %s"
+#~ msgid "Full set of FLAGS:"
+#~ msgstr "Insieme di FLAGS:"
#, python-format
-#~ msgid "AMQP server on %s:%d is unreachable. Trying again in %d seconds."
+#~ msgid "pidfile %s does not exist. Daemon not running?\n"
#~ msgstr ""
-#~ "Il server AMQP su %s:%d non é raggiungibile. Riprovare in %d secondi."
+#~ "Il pidfile %s non esiste. Assicurarsi che il demone é in esecuzione.\n"
#, python-format
-#~ msgid "Binding %s to %s with key %s"
-#~ msgstr "Collegando %s a %s con la chiave %s"
+#~ msgid "Starting %s"
+#~ msgstr "Avvio di %s"
#, python-format
-#~ msgid "Starting %s node"
-#~ msgstr "Avviando il nodo %s"
+#~ msgid "Serving %s"
+#~ msgstr "Servire %s"
-#, python-format
-#~ msgid "Data store %s is unreachable. Trying again in %d seconds."
-#~ msgstr "Datastore %s é irrangiungibile. Riprovare in %d seconds."
+#~ msgid "Wrong number of arguments."
+#~ msgstr "Numero errato di argomenti"
+
+#~ msgid "No such process"
+#~ msgstr "Nessun processo trovato"
diff --git a/po/ja.po b/po/ja.po
index a7906ede8..179302b55 100644
--- a/po/ja.po
+++ b/po/ja.po
@@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-Launchpad-Export-Date: 2011-07-23 05:12+0000\n"
-"X-Generator: Launchpad (build 13405)\n"
+"X-Launchpad-Export-Date: 2011-08-03 04:44+0000\n"
+"X-Generator: Launchpad (build 13573)\n"
#: ../nova/scheduler/chance.py:37 ../nova/scheduler/zone.py:55
#: ../nova/scheduler/simple.py:75 ../nova/scheduler/simple.py:110
@@ -2879,6 +2879,17 @@ msgstr "ユーザ %(user)s をプロジェクト %(project)s から削除しま
#~ msgstr "データストア %s に接続できません。 %d 秒後に再接続します。"
#, python-format
+#~ msgid "Serving %s"
+#~ msgstr "%s サービスの開始"
+
+#~ msgid "Full set of FLAGS:"
+#~ msgstr "FLAGSの一覧:"
+
+#, python-format
+#~ msgid "pidfile %s does not exist. Daemon not running?\n"
+#~ msgstr "pidfile %s が存在しません。デーモンは実行中ですか?\n"
+
+#, python-format
#~ msgid "Couldn't get IP, using 127.0.0.1 %s"
#~ msgstr "IPを取得できません。127.0.0.1 を %s として使います。"
@@ -3039,6 +3050,13 @@ msgstr "ユーザ %(user)s をプロジェクト %(project)s から削除しま
#~ msgstr "Detach volume: ボリューム %s をマウントポイント %s (インスタンス%s)からデタッチします。"
#, python-format
+#~ msgid "updating %s..."
+#~ msgstr "%s の情報の更新…"
+
+#~ msgid "unexpected error during update"
+#~ msgstr "更新の最中に予期しないエラーが発生しました。"
+
+#, python-format
#~ msgid "Cannot get blockstats for \"%s\" on \"%s\""
#~ msgstr "ブロックデバイス \"%s\" の統計を \"%s\" について取得できません。"
@@ -3046,6 +3064,13 @@ msgstr "ユーザ %(user)s をプロジェクト %(project)s から削除しま
#~ msgid "Cannot get ifstats for \"%s\" on \"%s\""
#~ msgstr "インタフェース \"%s\" の統計を \"%s\" について取得できません。"
+#~ msgid "unexpected exception getting connection"
+#~ msgstr "接続に際し予期しないエラーが発生しました。"
+
+#, python-format
+#~ msgid "Found instance: %s"
+#~ msgstr "インスタンス %s が見つかりました。"
+
#, python-format
#~ msgid "No service for %s, %s"
#~ msgstr "%s, %s のserviceが存在しません。"
@@ -3318,3 +3343,24 @@ msgstr "ユーザ %(user)s をプロジェクト %(project)s から削除しま
#, python-format
#~ msgid "volume %s: creating lv of size %sG"
#~ msgstr "ボリューム%sの%sGのlv (論理ボリューム) を作成します。"
+
+#~ msgid "Wrong number of arguments."
+#~ msgstr "引数の数が異なります。"
+
+#~ msgid "No such process"
+#~ msgstr "そのようなプロセスはありません"
+
+#, python-format
+#~ msgid "Cannot get blockstats for \"%(disk)s\" on \"%(iid)s\""
+#~ msgstr "\"%(iid)s\" 上の \"%(disk)s\" 用のブロック統計(blockstats)が取得できません"
+
+#, python-format
+#~ msgid "Cannot get ifstats for \"%(interface)s\" on \"%(iid)s\""
+#~ msgstr "\"%(iid)s\" 上の %(interface)s\" 用インターフェース統計(ifstats)が取得できません"
+
+#~ msgid "Starting instance monitor"
+#~ msgstr "インスタンスモニタを開始しています"
+
+#, python-format
+#~ msgid "Starting %s"
+#~ msgstr "%s を起動中"
diff --git a/po/pt_BR.po b/po/pt_BR.po
index b3aefce44..d6d57a9b1 100644
--- a/po/pt_BR.po
+++ b/po/pt_BR.po
@@ -8,14 +8,14 @@ msgstr ""
"Project-Id-Version: nova\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2011-02-21 10:03-0500\n"
-"PO-Revision-Date: 2011-03-24 14:51+0000\n"
+"PO-Revision-Date: 2011-07-25 17:40+0000\n"
"Last-Translator: msinhore <msinhore@gmail.com>\n"
"Language-Team: Brazilian Portuguese <pt_BR@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-Launchpad-Export-Date: 2011-07-23 05:12+0000\n"
-"X-Generator: Launchpad (build 13405)\n"
+"X-Launchpad-Export-Date: 2011-08-03 04:44+0000\n"
+"X-Generator: Launchpad (build 13573)\n"
#: ../nova/scheduler/chance.py:37 ../nova/scheduler/zone.py:55
#: ../nova/scheduler/simple.py:75 ../nova/scheduler/simple.py:110
@@ -36,6 +36,11 @@ msgid ""
"Stdout: %(stdout)r\n"
"Stderr: %(stderr)r"
msgstr ""
+"%(description)s\n"
+"Comando: %(cmd)s\n"
+"Código de saída: %(exit_code)s\n"
+"Saída padrão: %(stdout)r\n"
+"Erro: %(stderr)r"
#: ../nova/exception.py:107
msgid "DB exception wrapped"
@@ -392,7 +397,7 @@ msgstr "instância %s: suspendendo"
#: ../nova/compute/manager.py:472
#, python-format
msgid "instance %s: resuming"
-msgstr ""
+msgstr "instância %s: resumindo"
#: ../nova/compute/manager.py:491
#, python-format
@@ -407,12 +412,12 @@ msgstr "instância %s: desbloqueando"
#: ../nova/compute/manager.py:513
#, python-format
msgid "instance %s: getting locked state"
-msgstr ""
+msgstr "instância %s: obtendo estado de bloqueio"
#: ../nova/compute/manager.py:526
#, python-format
msgid "instance %s: reset network"
-msgstr ""
+msgstr "instância %s: reset da rede"
#: ../nova/compute/manager.py:535 ../nova/api/ec2/cloud.py:515
#, python-format
@@ -429,6 +434,7 @@ msgstr "instância %s: obtendo console ajax"
msgid ""
"instance %(instance_id)s: attaching volume %(volume_id)s to %(mountpoint)s"
msgstr ""
+"instância %(instance_id)s: atachando volume %(volume_id)s para %(mountpoint)s"
#. pylint: disable=W0702
#. NOTE(vish): The inline callback eats the exception info so we
@@ -438,6 +444,8 @@ msgstr ""
#, python-format
msgid "instance %(instance_id)s: attach failed %(mountpoint)s, removing"
msgstr ""
+"instância %(instance_id)s: falha ao atachar ponto de montagem "
+"%(mountpoint)s, removendo"
#: ../nova/compute/manager.py:585
#, python-format
@@ -458,7 +466,7 @@ msgstr "Host %s não está ativo"
#: ../nova/scheduler/simple.py:65
msgid "All hosts have too many cores"
-msgstr ""
+msgstr "Todos os hosts tem muitos núcleos de CPU"
#: ../nova/scheduler/simple.py:87
#, python-format
@@ -783,7 +791,7 @@ msgstr "Tamanho da imagem %(image)s:%(virtual_size)d"
#: ../nova/virt/xenapi/vm_utils.py:332
#, python-format
msgid "Glance image %s"
-msgstr ""
+msgstr "Visão geral da imagem %s"
#. we need to invoke a plugin for copying VDI's
#. content into proper path
@@ -815,7 +823,7 @@ msgstr "Kernel PV no VDI: %s"
#: ../nova/virt/xenapi/vm_utils.py:405
#, python-format
msgid "Running pygrub against %s"
-msgstr ""
+msgstr "Rodando pygrub novamente %s"
#: ../nova/virt/xenapi/vm_utils.py:411
#, python-format
@@ -849,12 +857,12 @@ msgstr "(VM_UTILS) xenapi power_state -> |%s|"
#: ../nova/virt/xenapi/vm_utils.py:525
#, python-format
msgid "VHD %(vdi_uuid)s has parent %(parent_ref)s"
-msgstr ""
+msgstr "O VHD %(vdi_uuid)s tem pai %(parent_ref)s"
#: ../nova/virt/xenapi/vm_utils.py:542
#, python-format
msgid "Re-scanning SR %s"
-msgstr ""
+msgstr "Re-escaneando SR %s"
#: ../nova/virt/xenapi/vm_utils.py:567
#, python-format
@@ -2857,6 +2865,17 @@ msgstr ""
#~ "Repositório de dados %s não pode ser atingido. Tentando novamente em %d "
#~ "segundos."
+#~ msgid "Full set of FLAGS:"
+#~ msgstr "Conjunto completo de FLAGS:"
+
+#, python-format
+#~ msgid "Starting %s"
+#~ msgstr "Iniciando %s"
+
+#, python-format
+#~ msgid "Serving %s"
+#~ msgstr "Servindo %s"
+
#, python-format
#~ msgid "Couldn't get IP, using 127.0.0.1 %s"
#~ msgstr "Não foi possível obter IP, usando 127.0.0.1 %s"
@@ -2965,3 +2984,14 @@ msgstr ""
#, python-format
#~ msgid "Created user %s (admin: %r)"
#~ msgstr "Criado usuário %s (administrador: %r)"
+
+#~ msgid "No such process"
+#~ msgstr "Processo inexistente"
+
+#, python-format
+#~ msgid "pidfile %s does not exist. Daemon not running?\n"
+#~ msgstr ""
+#~ "Arquivo do id do processo (pidfile) %s não existe. O Daemon está parado?\n"
+
+#~ msgid "Wrong number of arguments."
+#~ msgstr "Número errado de argumentos."
diff --git a/po/ru.po b/po/ru.po
index 1bf672fc3..746db964a 100644
--- a/po/ru.po
+++ b/po/ru.po
@@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-Launchpad-Export-Date: 2011-07-23 05:12+0000\n"
-"X-Generator: Launchpad (build 13405)\n"
+"X-Launchpad-Export-Date: 2011-08-03 04:44+0000\n"
+"X-Generator: Launchpad (build 13573)\n"
#: ../nova/scheduler/chance.py:37 ../nova/scheduler/zone.py:55
#: ../nova/scheduler/simple.py:75 ../nova/scheduler/simple.py:110
@@ -2790,6 +2790,10 @@ msgid "Removing user %(user)s from project %(project)s"
msgstr ""
#, python-format
+#~ msgid "Starting %s"
+#~ msgstr "Запускается %s"
+
+#, python-format
#~ msgid "arg: %s\t\tval: %s"
#~ msgstr "arg: %s\t\tval: %s"
@@ -2841,6 +2845,13 @@ msgstr ""
#~ msgid "Adding role %s to user %s in project %s"
#~ msgstr "Добавление роли %s для пользователя %s в проект %s"
+#~ msgid "unexpected error during update"
+#~ msgstr "неожиданная ошибка во время обновления"
+
+#, python-format
+#~ msgid "updating %s..."
+#~ msgstr "обновление %s..."
+
#, python-format
#~ msgid "Getting object: %s / %s"
#~ msgstr "Получение объекта: %s / %s"
@@ -2892,6 +2903,10 @@ msgstr ""
#~ msgstr "Не удалось получить IP, используем 127.0.0.1 %s"
#, python-format
+#~ msgid "pidfile %s does not exist. Daemon not running?\n"
+#~ msgstr "pidfile %s не обнаружен. Демон не запущен?\n"
+
+#, python-format
#~ msgid "Getting from %s: %s"
#~ msgstr "Получение из %s: %s"
@@ -2906,3 +2921,6 @@ msgstr ""
#, python-format
#~ msgid "Authenticated Request For %s:%s)"
#~ msgstr "Запрос аутентификации для %s:%s)"
+
+#~ msgid "Wrong number of arguments."
+#~ msgstr "Неверное число аргументов."
diff --git a/po/tl.po b/po/tl.po
index 1ae59330b..84e9d26e6 100644
--- a/po/tl.po
+++ b/po/tl.po
@@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-Launchpad-Export-Date: 2011-07-23 05:12+0000\n"
-"X-Generator: Launchpad (build 13405)\n"
+"X-Launchpad-Export-Date: 2011-08-03 04:44+0000\n"
+"X-Generator: Launchpad (build 13573)\n"
#: ../nova/scheduler/chance.py:37 ../nova/scheduler/zone.py:55
#: ../nova/scheduler/simple.py:75 ../nova/scheduler/simple.py:110
diff --git a/po/uk.po b/po/uk.po
index 481851e1c..bcc53fed3 100644
--- a/po/uk.po
+++ b/po/uk.po
@@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-Launchpad-Export-Date: 2011-07-23 05:12+0000\n"
-"X-Generator: Launchpad (build 13405)\n"
+"X-Launchpad-Export-Date: 2011-08-03 04:44+0000\n"
+"X-Generator: Launchpad (build 13573)\n"
#: ../nova/scheduler/chance.py:37 ../nova/scheduler/zone.py:55
#: ../nova/scheduler/simple.py:75 ../nova/scheduler/simple.py:110
@@ -2793,6 +2793,14 @@ msgstr ""
#~ msgstr "AMQP сервер %s:%d недоступний. Спроба під'єднання через %d секунд."
#, python-format
+#~ msgid "Starting %s"
+#~ msgstr "Запускається %s"
+
+#, python-format
+#~ msgid "Serving %s"
+#~ msgstr "Обслуговування %s"
+
+#, python-format
#~ msgid "Couldn't get IP, using 127.0.0.1 %s"
#~ msgstr "Не вдалось отримати IP, використовуючи 127.0.0.1 %s"
diff --git a/po/zh_CN.po b/po/zh_CN.po
index d0ddcd2f7..6284ee46c 100644
--- a/po/zh_CN.po
+++ b/po/zh_CN.po
@@ -8,14 +8,18 @@ msgstr ""
"Project-Id-Version: nova\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2011-02-21 10:03-0500\n"
-"PO-Revision-Date: 2011-06-14 14:44+0000\n"
-"Last-Translator: chong <Unknown>\n"
+"PO-Revision-Date: 2011-08-19 09:26+0000\n"
+"Last-Translator: zhangjunfeng <Unknown>\n"
"Language-Team: Chinese (Simplified) <zh_CN@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-Launchpad-Export-Date: 2011-07-23 05:12+0000\n"
-"X-Generator: Launchpad (build 13405)\n"
+"X-Launchpad-Export-Date: 2011-08-20 05:06+0000\n"
+"X-Generator: Launchpad (build 13697)\n"
+
+#, python-format
+#~ msgid "Starting %s"
+#~ msgstr "启动 %s 中"
#: ../nova/scheduler/chance.py:37 ../nova/scheduler/zone.py:55
#: ../nova/scheduler/simple.py:75 ../nova/scheduler/simple.py:110
@@ -44,7 +48,7 @@ msgstr ""
#: ../nova/exception.py:107
msgid "DB exception wrapped"
-msgstr ""
+msgstr "数据库异常"
#. exc_type, exc_value, exc_traceback = sys.exc_info()
#: ../nova/exception.py:120
@@ -84,7 +88,7 @@ msgstr "获取外网IP失败"
#: ../nova/api/openstack/servers.py:152
#, python-format
msgid "%(param)s property not found for image %(_image_id)s"
-msgstr ""
+msgstr "没有找到镜像文件%(_image_id)s 的属性 %(param)s"
#: ../nova/api/openstack/servers.py:168
msgid "No keypairs defined"
@@ -93,55 +97,55 @@ msgstr "未定义密钥对"
#: ../nova/api/openstack/servers.py:238
#, python-format
msgid "Compute.api::lock %s"
-msgstr ""
+msgstr "compute.api::加锁 %s"
#: ../nova/api/openstack/servers.py:253
#, python-format
msgid "Compute.api::unlock %s"
-msgstr ""
+msgstr "compute.api::解锁 %s"
#: ../nova/api/openstack/servers.py:267
#, python-format
msgid "Compute.api::get_lock %s"
-msgstr ""
+msgstr "Compute.api::得到锁 %s"
#: ../nova/api/openstack/servers.py:281
#, python-format
msgid "Compute.api::reset_network %s"
-msgstr ""
+msgstr "Compute.api::重置网络 %s"
#: ../nova/api/openstack/servers.py:292
#, python-format
msgid "Compute.api::pause %s"
-msgstr ""
+msgstr "Compute.api::暂停 %s"
#: ../nova/api/openstack/servers.py:303
#, python-format
msgid "Compute.api::unpause %s"
-msgstr ""
+msgstr "Compute.api::继续 %s"
#: ../nova/api/openstack/servers.py:314
#, python-format
msgid "compute.api::suspend %s"
-msgstr ""
+msgstr "compute.api::挂起 %s"
#: ../nova/api/openstack/servers.py:325
#, python-format
msgid "compute.api::resume %s"
-msgstr ""
+msgstr "compute.api::回复 %s"
#: ../nova/virt/xenapi/volumeops.py:48 ../nova/virt/xenapi/volumeops.py:101
#: ../nova/db/sqlalchemy/api.py:731 ../nova/virt/libvirt_conn.py:741
#: ../nova/api/ec2/__init__.py:317
#, python-format
msgid "Instance %s not found"
-msgstr ""
+msgstr "实例 %s 没有找到"
#. NOTE: No Resource Pool concept so far
#: ../nova/virt/xenapi/volumeops.py:51
#, python-format
msgid "Attach_volume: %(instance_name)s, %(device_path)s, %(mountpoint)s"
-msgstr ""
+msgstr "挂载卷:%(instance_name)s, %(device_path)s, %(mountpoint)s"
#: ../nova/virt/xenapi/volumeops.py:69
#, python-format
@@ -2666,12 +2670,12 @@ msgstr "用户 %s 不存在"
#: ../nova/auth/ldapdriver.py:472
#, python-format
msgid "Group can't be created because group %s already exists"
-msgstr ""
+msgstr "组不能被创建,因为组 %s 已经存在"
#: ../nova/auth/ldapdriver.py:478
#, python-format
msgid "Group can't be created because user %s doesn't exist"
-msgstr ""
+msgstr "组不能被创建,因为用户 %s 不存在"
#: ../nova/auth/ldapdriver.py:495
#, python-format
@@ -2686,50 +2690,50 @@ msgstr ""
#: ../nova/auth/ldapdriver.py:510 ../nova/auth/ldapdriver.py:521
#, python-format
msgid "The group at dn %s doesn't exist"
-msgstr ""
+msgstr "识别名为 %s 的组不存在"
#: ../nova/auth/ldapdriver.py:513
#, python-format
msgid "User %(uid)s is already a member of the group %(group_dn)s"
-msgstr ""
+msgstr "用户 %(uid)s 已经是 组 %(group_dn)s 中的成员"
#: ../nova/auth/ldapdriver.py:524
#, python-format
msgid ""
"User %s can't be removed from the group because the user doesn't exist"
-msgstr ""
+msgstr "用户 %s 不能从组中删除,因为这个用户不存在"
#: ../nova/auth/ldapdriver.py:528
#, python-format
msgid "User %s is not a member of the group"
-msgstr ""
+msgstr "用户 %s 不是这个组的成员"
#: ../nova/auth/ldapdriver.py:542
#, python-format
msgid ""
"Attempted to remove the last member of a group. Deleting the group at %s "
"instead."
-msgstr ""
+msgstr "尝试删除组中最后一个成员,用删除组 %s 来代替。"
#: ../nova/auth/ldapdriver.py:549
#, python-format
msgid "User %s can't be removed from all because the user doesn't exist"
-msgstr ""
+msgstr "用户 %s 不能从系统中删除,因为这个用户不存在"
#: ../nova/auth/ldapdriver.py:564
#, python-format
msgid "Group at dn %s doesn't exist"
-msgstr ""
+msgstr "可识别名为 %s 的组不存在"
#: ../nova/virt/xenapi/network_utils.py:40
#, python-format
msgid "Found non-unique network for bridge %s"
-msgstr ""
+msgstr "发现网桥 %s 的网络不唯一"
#: ../nova/virt/xenapi/network_utils.py:43
#, python-format
msgid "Found no network for bridge %s"
-msgstr ""
+msgstr "发现网桥 %s 没有网络"
#: ../nova/api/ec2/admin.py:97
#, python-format
@@ -2744,22 +2748,22 @@ msgstr "删除用户: %s"
#: ../nova/api/ec2/admin.py:127
#, python-format
msgid "Adding role %(role)s to user %(user)s for project %(project)s"
-msgstr ""
+msgstr "添加角色 %(role)s 给项目 %(project)s 中的用户 %(user)s"
#: ../nova/api/ec2/admin.py:131
#, python-format
msgid "Adding sitewide role %(role)s to user %(user)s"
-msgstr ""
+msgstr "给用户 %(user)s 添加站点角色 %(role)s"
#: ../nova/api/ec2/admin.py:137
#, python-format
msgid "Removing role %(role)s from user %(user)s for project %(project)s"
-msgstr ""
+msgstr "删除项目 %(project)s中用户 %(user)s的角色 %(role)s"
#: ../nova/api/ec2/admin.py:141
#, python-format
msgid "Removing sitewide role %(role)s from user %(user)s"
-msgstr ""
+msgstr "删除用户 %(user)s 的站点角色 %(role)s"
#: ../nova/api/ec2/admin.py:146 ../nova/api/ec2/admin.py:223
msgid "operation must be add or remove"
@@ -2768,22 +2772,22 @@ msgstr "操作必须为添加或删除"
#: ../nova/api/ec2/admin.py:159
#, python-format
msgid "Getting x509 for user: %(name)s on project: %(project)s"
-msgstr ""
+msgstr "获得用户: %(name)s 在项目 :%(project)s中的x509"
#: ../nova/api/ec2/admin.py:177
#, python-format
msgid "Create project %(name)s managed by %(manager_user)s"
-msgstr ""
+msgstr "创建被%(manager_user)s 管理的项目 %(name)s"
#: ../nova/api/ec2/admin.py:190
#, python-format
msgid "Modify project: %(name)s managed by %(manager_user)s"
-msgstr ""
+msgstr "更改被 %(manager_user)s 管理的项目: %(name)s"
#: ../nova/api/ec2/admin.py:200
#, python-format
msgid "Delete project: %s"
-msgstr "删除工程 %s"
+msgstr ""
#: ../nova/api/ec2/admin.py:214
#, python-format
@@ -2795,94 +2799,19 @@ msgstr "添加用户 %(user)s 到项目 %(project)s 中"
msgid "Removing user %(user)s from project %(project)s"
msgstr "从项目 %(project)s 中移除用户 %(user)s"
-#, python-format
-#~ msgid ""
-#~ "%s\n"
-#~ "Command: %s\n"
-#~ "Exit code: %s\n"
-#~ "Stdout: %r\n"
-#~ "Stderr: %r"
-#~ msgstr ""
-#~ "%s\n"
-#~ "命令:%s\n"
-#~ "退出代码:%s\n"
-#~ "标准输出(stdout):%r\n"
-#~ "标准错误(stderr):%r"
+#~ msgid "Full set of FLAGS:"
+#~ msgstr "FLAGS全集:"
-#, python-format
-#~ msgid "Binding %s to %s with key %s"
-#~ msgstr "将%s绑定到%s(以%s键值)"
+#~ msgid "No such process"
+#~ msgstr "没有该进程"
#, python-format
-#~ msgid "AMQP server on %s:%d is unreachable. Trying again in %d seconds."
-#~ msgstr "位于%s:%d的AMQP服务器不可用。%d秒后重试。"
+#~ msgid "Serving %s"
+#~ msgstr "正在为 %s 服务"
#, python-format
-#~ msgid "Getting from %s: %s"
-#~ msgstr "从%s获得如下内容:%s"
+#~ msgid "pidfile %s does not exist. Daemon not running?\n"
+#~ msgstr "pidfile %s 不存在,守护进程是否运行?\n"
-#, python-format
-#~ msgid "Starting %s node"
-#~ msgstr "启动%s节点"
-
-#, python-format
-#~ msgid "Data store %s is unreachable. Trying again in %d seconds."
-#~ msgstr "数据储存服务%s不可用。%d秒之后继续尝试。"
-
-#, python-format
-#~ msgid "(%s) publish (key: %s) %s"
-#~ msgstr "(%s)发布(键值:%s)%s"
-
-#, python-format
-#~ msgid "Couldn't get IP, using 127.0.0.1 %s"
-#~ msgstr "不能获取IP,将使用 127.0.0.1 %s"
-
-#, python-format
-#~ msgid ""
-#~ "Access key %s has had %d failed authentications and will be locked out for "
-#~ "%d minutes."
-#~ msgstr "访问键 %s时,存在%d个失败的认证,将于%d分钟后解锁"
-
-#, python-format
-#~ msgid "Authenticated Request For %s:%s)"
-#~ msgstr "为%s:%s申请认证"
-
-#, python-format
-#~ msgid "arg: %s\t\tval: %s"
-#~ msgstr "键为: %s\t\t值为: %s"
-
-#, python-format
-#~ msgid "Getting x509 for user: %s on project: %s"
-#~ msgstr "为用户 %s从工程%s中获取 x509"
-
-#, python-format
-#~ msgid "Create project %s managed by %s"
-#~ msgstr "创建工程%s,此工程由%s管理"
-
-#, python-format
-#~ msgid "Unsupported API request: controller = %s,action = %s"
-#~ msgstr "不支持的API请求: 控制器 = %s,执行 = %s"
-
-#, python-format
-#~ msgid "Adding sitewide role %s to user %s"
-#~ msgstr "增加站点范围的 %s角色给用户 %s"
-
-#, python-format
-#~ msgid "Adding user %s to project %s"
-#~ msgstr "增加用户%s到%s工程"
-
-#, python-format
-#~ msgid "Unauthorized request for controller=%s and action=%s"
-#~ msgstr "对控制器=%s及动作=%s未经授权"
-
-#, python-format
-#~ msgid "Removing user %s from project %s"
-#~ msgstr "正将用户%s从工程%s中移除"
-
-#, python-format
-#~ msgid "Adding role %s to user %s for project %s"
-#~ msgstr "正将%s角色赋予用户%s(在工程%s中)"
-
-#, python-format
-#~ msgid "Removing role %s from user %s for project %s"
-#~ msgstr "正将角色%s从用户%s在工程%s中移除"
+#~ msgid "Wrong number of arguments."
+#~ msgstr "错误参数个数。"
diff --git a/po/zh_TW.po b/po/zh_TW.po
index 896e69618..a5a826aa0 100644
--- a/po/zh_TW.po
+++ b/po/zh_TW.po
@@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-Launchpad-Export-Date: 2011-07-23 05:12+0000\n"
-"X-Generator: Launchpad (build 13405)\n"
+"X-Launchpad-Export-Date: 2011-08-03 04:44+0000\n"
+"X-Generator: Launchpad (build 13573)\n"
#: ../nova/scheduler/chance.py:37 ../nova/scheduler/zone.py:55
#: ../nova/scheduler/simple.py:75 ../nova/scheduler/simple.py:110
@@ -2787,3 +2787,14 @@ msgstr ""
#, python-format
msgid "Removing user %(user)s from project %(project)s"
msgstr ""
+
+#~ msgid "No such process"
+#~ msgstr "沒有此一程序"
+
+#, python-format
+#~ msgid "pidfile %s does not exist. Daemon not running?\n"
+#~ msgstr "pidfile %s 不存在. Daemon未啟動?\n"
+
+#, python-format
+#~ msgid "Starting %s"
+#~ msgstr "正在啟動 %s"
diff --git a/run_tests.sh b/run_tests.sh
index 8f2b51757..871332b4a 100755
--- a/run_tests.sh
+++ b/run_tests.sh
@@ -67,7 +67,7 @@ function run_tests {
ERRSIZE=`wc -l run_tests.log | awk '{print \$1}'`
if [ "$ERRSIZE" -lt "40" ];
then
- cat run_tests.log
+ cat run_tests.log
fi
fi
return $RESULT