summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorjaypipes@gmail.com <>2011-02-21 10:04:32 -0500
committerjaypipes@gmail.com <>2011-02-21 10:04:32 -0500
commit6facb35c26b2c90d4ba7a34f3eccd10de2fb7207 (patch)
tree29fff56413ee20c45b99ac6596796eadfc94841d
parentf02c41a7fe332b215421320d041a944e4b9ee9ee (diff)
parentbd0ca93866b48a7a65de8b97ab0ac0ac9c737f73 (diff)
downloadnova-6facb35c26b2c90d4ba7a34f3eccd10de2fb7207.tar.gz
nova-6facb35c26b2c90d4ba7a34f3eccd10de2fb7207.tar.xz
nova-6facb35c26b2c90d4ba7a34f3eccd10de2fb7207.zip
Merge trunk and re-run build_i18n
-rw-r--r--.mailmap45
-rw-r--r--Authors11
-rw-r--r--HACKING19
-rw-r--r--MANIFEST.in10
-rwxr-xr-xbin/nova-manage60
-rw-r--r--nova/api/ec2/__init__.py18
-rw-r--r--nova/api/ec2/apirequest.py3
-rw-r--r--nova/api/ec2/cloud.py20
-rw-r--r--nova/api/openstack/__init__.py5
-rw-r--r--nova/api/openstack/auth.py1
-rw-r--r--nova/api/openstack/common.py33
-rw-r--r--nova/api/openstack/servers.py43
-rw-r--r--nova/api/openstack/zones.py80
-rw-r--r--nova/auth/ldapdriver.py37
-rw-r--r--nova/auth/novarc.template7
-rw-r--r--nova/compute/api.py66
-rw-r--r--nova/compute/manager.py75
-rw-r--r--nova/compute/power_state.py4
-rw-r--r--nova/context.py5
-rw-r--r--nova/db/api.py50
-rw-r--r--nova/db/sqlalchemy/api.py88
-rw-r--r--nova/db/sqlalchemy/migrate_repo/versions/001_austin.py14
-rw-r--r--nova/db/sqlalchemy/migrate_repo/versions/002_bexar.py7
-rw-r--r--nova/db/sqlalchemy/migrate_repo/versions/003_add_label_to_networks.py51
-rw-r--r--nova/db/sqlalchemy/migrate_repo/versions/004_add_zone_tables.py61
-rw-r--r--nova/db/sqlalchemy/migration.py16
-rw-r--r--nova/db/sqlalchemy/models.py12
-rw-r--r--nova/flags.py2
-rw-r--r--nova/log.py21
-rw-r--r--nova/network/linux_net.py2
-rw-r--r--nova/network/manager.py43
-rw-r--r--nova/objectstore/bucket.py2
-rw-r--r--nova/objectstore/image.py2
-rw-r--r--nova/rpc.py9
-rw-r--r--nova/tests/api/openstack/__init__.py28
-rw-r--r--nova/tests/api/openstack/test_common.py161
-rw-r--r--nova/tests/api/openstack/test_servers.py78
-rw-r--r--nova/tests/api/openstack/test_zones.py140
-rw-r--r--nova/tests/test_compute.py8
-rw-r--r--nova/tests/test_log.py21
-rw-r--r--nova/tests/test_xenapi.py17
-rw-r--r--nova/twistd.py2
-rw-r--r--nova/utils.py25
-rw-r--r--nova/virt/fake.py15
-rw-r--r--nova/virt/xenapi/fake.py4
-rw-r--r--nova/virt/xenapi/vm_utils.py12
-rw-r--r--nova/virt/xenapi/vmops.py123
-rw-r--r--nova/virt/xenapi_conn.py10
-rw-r--r--nova/volume/api.py4
-rw-r--r--nova/volume/manager.py8
-rwxr-xr-xplugins/xenserver/xenapi/etc/xapi.d/plugins/agent14
-rwxr-xr-xplugins/xenserver/xenapi/etc/xapi.d/plugins/xenstore.py10
-rw-r--r--po/nova.pot576
-rw-r--r--setup.py7
54 files changed, 1747 insertions, 438 deletions
diff --git a/.mailmap b/.mailmap
index c6f6c9a8b..a839eba6c 100644
--- a/.mailmap
+++ b/.mailmap
@@ -1,36 +1,43 @@
# Format is:
-# <preferred e-mail> <other e-mail>
-<code@term.ie> <github@anarkystic.com>
-<code@term.ie> <termie@preciousroy.local>
+# <preferred e-mail> <other e-mail 1>
+# <preferred e-mail> <other e-mail 2>
+<anotherjesse@gmail.com> <jesse@dancelamb>
+<anotherjesse@gmail.com> <jesse@gigantor.local>
+<anotherjesse@gmail.com> <jesse@ubuntu>
+<ant@openstack.org> <amesserl@rackspace.com>
<Armando.Migliaccio@eu.citrix.com> <armando.migliaccio@citrix.com>
-<matt.dietz@rackspace.com> <matthewdietz@Matthew-Dietzs-MacBook-Pro.local>
-<matt.dietz@rackspace.com> <mdietz@openstack>
+<brian.lamar@rackspace.com> <brian.lamar@gmail.com>
+<bschott@isi.edu> <bfschott@gmail.com>
<cbehrens@codestud.com> <chris.behrens@rackspace.com>
+<chiradeep@cloud.com> <chiradeep@chiradeep-lt2>
+<code@term.ie> <github@anarkystic.com>
+<code@term.ie> <termie@preciousroy.local>
+<corywright@gmail.com> <cory.wright@rackspace.com>
<devin.carlen@gmail.com> <devcamcar@illian.local>
<ewan.mellor@citrix.com> <emellor@silver>
<jaypipes@gmail.com> <jpipes@serialcoder>
-<anotherjesse@gmail.com> <jesse@dancelamb>
-<anotherjesse@gmail.com> <jesse@gigantor.local>
-<anotherjesse@gmail.com> <jesse@ubuntu>
-<jmckenty@gmail.com> <jmckenty@yyj-dhcp171.corp.flock.com>
<jmckenty@gmail.com> <jmckenty@joshua-mckentys-macbook-pro.local>
+<jmckenty@gmail.com> <jmckenty@yyj-dhcp171.corp.flock.com>
<jmckenty@gmail.com> <joshua.mckenty@nasa.gov>
<justin@fathomdb.com> <justinsb@justinsb-desktop>
-<masumotok@nttdata.co.jp> <root@openstack2-api>
+<justin@fathomdb.com> <superstack@superstack.org>
<masumotok@nttdata.co.jp> Masumoto<masumotok@nttdata.co.jp>
+<masumotok@nttdata.co.jp> <root@openstack2-api>
+<matt.dietz@rackspace.com> <matthewdietz@Matthew-Dietzs-MacBook-Pro.local>
+<matt.dietz@rackspace.com> <mdietz@openstack>
<mordred@inaugust.com> <mordred@hudson>
-<paul@openstack.org> <pvoccio@castor.local>
<paul@openstack.org> <paul.voccio@rackspace.com>
+<paul@openstack.org> <pvoccio@castor.local>
+<rconradharris@gmail.com> <rick.harris@rackspace.com>
+<rlane@wikimedia.org> <laner@controller>
+<sleepsonthefloor@gmail.com> <root@tonbuntu>
<soren.hansen@rackspace.com> <soren@linux2go.dk>
<todd@ansolabs.com> <todd@lapex>
<todd@ansolabs.com> <todd@rubidine.com>
-<vishvananda@gmail.com> <vishvananda@yahoo.com>
+<tushar.vitthal.patil@gmail.com> <tpatil@vertex.co.in>
+<ueno.nachi@lab.ntt.co.jp> <nati.ueno@gmail.com>
+<ueno.nachi@lab.ntt.co.jp> <nova@u4>
+<ueno.nachi@lab.ntt.co.jp> <openstack@lab.ntt.co.jp>
<vishvananda@gmail.com> <root@mirror.nasanebula.net>
<vishvananda@gmail.com> <root@ubuntu>
-<sleepsonthefloor@gmail.com> <root@tonbuntu>
-<rlane@wikimedia.org> <laner@controller>
-<rconradharris@gmail.com> <rick.harris@rackspace.com>
-<corywright@gmail.com> <cory.wright@rackspace.com>
-<ant@openstack.org> <amesserl@rackspace.com>
-<chiradeep@cloud.com> <chiradeep@chiradeep-lt2>
-<justin@fathomdb.com> <superstack@superstack.org>
+<vishvananda@gmail.com> <vishvananda@yahoo.com>
diff --git a/Authors b/Authors
index b359fec22..494e614a0 100644
--- a/Authors
+++ b/Authors
@@ -4,13 +4,16 @@ Anthony Young <sleepsonthefloor@gmail.com>
Antony Messerli <ant@openstack.org>
Armando Migliaccio <Armando.Migliaccio@eu.citrix.com>
Bilal Akhtar <bilalakhtar@ubuntu.com>
+Brian Lamar <brian.lamar@rackspace.com>
+Brian Schott <bschott@isi.edu>
+Brian Waldon <brian.waldon@rackspace.com>
Chiradeep Vittal <chiradeep@cloud.com>
Chmouel Boudjnah <chmouel@chmouel.com>
Chris Behrens <cbehrens@codestud.com>
Christian Berendt <berendt@b1-systems.de>
Cory Wright <corywright@gmail.com>
-David Pravec <David.Pravec@danix.org>
Dan Prince <dan.prince@rackspace.com>
+David Pravec <David.Pravec@danix.org>
Dean Troyer <dtroyer@gmail.com>
Devin Carlen <devin.carlen@gmail.com>
Ed Leafe <ed@leafe.com>
@@ -41,7 +44,8 @@ Monsyne Dragon <mdragon@rackspace.com>
Monty Taylor <mordred@inaugust.com>
MORITA Kazutaka <morita.kazutaka@gmail.com>
Muneyuki Noguchi <noguchimn@nttdata.co.jp>
-Nachi Ueno <ueno.nachi@lab.ntt.co.jp> <openstack@lab.ntt.co.jp> <nati.ueno@gmail.com> <nova@u4>
+Nachi Ueno <ueno.nachi@lab.ntt.co.jp>
+Naveed Massjouni <naveed.massjouni@rackspace.com>
Paul Voccio <paul@openstack.org>
Ricardo Carrillo Cruz <emaildericky@gmail.com>
Rick Clark <rick@openstack.org>
@@ -55,7 +59,8 @@ Soren Hansen <soren.hansen@rackspace.com>
Thierry Carrez <thierry@openstack.org>
Todd Willey <todd@ansolabs.com>
Trey Morris <trey.morris@rackspace.com>
-Tushar Patil <tushar.vitthal.patil@gmail.com> <tpatil@vertex.co.in>
+Tushar Patil <tushar.vitthal.patil@gmail.com>
+Vasiliy Shlykov <vash@vasiliyshlykov.org>
Vishvananda Ishaya <vishvananda@gmail.com>
Youcef Laribi <Youcef.Laribi@eu.citrix.com>
Zhixue Wu <Zhixue.Wu@citrix.com>
diff --git a/HACKING b/HACKING
index 3af2381bf..e58d60e58 100644
--- a/HACKING
+++ b/HACKING
@@ -47,3 +47,22 @@ Human Alphabetical Order Examples
from nova.auth import users
from nova.endpoint import api
from nova.endpoint import cloud
+
+Docstrings
+----------
+ """Summary of the function, class or method, less than 80 characters.
+
+ New paragraph after newline that explains in more detail any general
+ information about the function, class or method. After this, if defining
+ parameters and return types use the Sphinx format. After that an extra
+ newline then close the quotations.
+
+ When writing the docstring for a class, an extra line should be placed
+ after the closing quotations. For more in-depth explanations for these
+ decisions see http://www.python.org/dev/peps/pep-0257/
+
+ :param foo: the foo parameter
+ :param bar: the bar parameter
+ :returns: description of the return value
+
+ """
diff --git a/MANIFEST.in b/MANIFEST.in
index 3908830d7..f0a9cffb3 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -6,14 +6,23 @@ graft doc
graft smoketests
graft tools
graft etc
+graft bzrplugins
+graft contrib
+graft po
+graft plugins
include nova/api/openstack/notes.txt
+include nova/auth/*.schema
include nova/auth/novarc.template
+include nova/auth/opendj.sh
include nova/auth/slap.sh
include nova/cloudpipe/bootscript.sh
include nova/cloudpipe/client.ovpn.template
+include nova/cloudpipe/bootscript.template
include nova/compute/fakevirtinstance.xml
include nova/compute/interfaces.template
+include nova/console/xvp.conf.template
include nova/db/sqlalchemy/migrate_repo/migrate.cfg
+include nova/db/sqlalchemy/migrate_repo/README
include nova/virt/interfaces.template
include nova/virt/libvirt*.xml.template
include nova/tests/CA/
@@ -25,6 +34,7 @@ include nova/tests/bundle/1mb.manifest.xml
include nova/tests/bundle/1mb.no_kernel_or_ramdisk.manifest.xml
include nova/tests/bundle/1mb.part.0
include nova/tests/bundle/1mb.part.1
+include nova/tests/db/nova.austin.sqlite
include plugins/xenapi/README
include plugins/xenapi/etc/xapi.d/plugins/objectstore
include plugins/xenapi/etc/xapi.d/plugins/pluginlib_nova.py
diff --git a/bin/nova-manage b/bin/nova-manage
index 7835ca551..6d67252b8 100755
--- a/bin/nova-manage
+++ b/bin/nova-manage
@@ -433,6 +433,37 @@ class ProjectCommands(object):
"nova-api server on this host.")
+class FixedIpCommands(object):
+ """Class for managing fixed ip."""
+
+ def list(self, host=None):
+ """Lists all fixed ips (optionally by host) arguments: [host]"""
+ ctxt = context.get_admin_context()
+ if host == None:
+ fixed_ips = db.fixed_ip_get_all(ctxt)
+ else:
+ fixed_ips = db.fixed_ip_get_all_by_host(ctxt, host)
+
+ print "%-18s\t%-15s\t%-17s\t%-15s\t%s" % (_('network'),
+ _('IP address'),
+ _('MAC address'),
+ _('hostname'),
+ _('host'))
+ for fixed_ip in fixed_ips:
+ hostname = None
+ host = None
+ mac_address = None
+ if fixed_ip['instance']:
+ instance = fixed_ip['instance']
+ hostname = instance['hostname']
+ host = instance['host']
+ mac_address = instance['mac_address']
+ print "%-18s\t%-15s\t%-17s\t%-15s\t%s" % (
+ fixed_ip['network']['cidr'],
+ fixed_ip['address'],
+ mac_address, hostname, host)
+
+
class FloatingIpCommands(object):
"""Class for managing floating ip."""
@@ -472,8 +503,8 @@ class NetworkCommands(object):
"""Class for managing networks."""
def create(self, fixed_range=None, num_networks=None,
- network_size=None, vlan_start=None, vpn_start=None,
- fixed_range_v6=None):
+ network_size=None, vlan_start=None,
+ vpn_start=None, fixed_range_v6=None, label='public'):
"""Creates fixed ips for host by range
arguments: [fixed_range=FLAG], [num_networks=FLAG],
[network_size=FLAG], [vlan_start=FLAG],
@@ -495,9 +526,22 @@ class NetworkCommands(object):
cidr=fixed_range,
num_networks=int(num_networks),
network_size=int(network_size),
- cidr_v6=fixed_range_v6,
vlan_start=int(vlan_start),
- vpn_start=int(vpn_start))
+ vpn_start=int(vpn_start),
+ cidr_v6=fixed_range_v6,
+ label=label)
+
+ def list(self):
+ """List all created networks"""
+ print "%-18s\t%-15s\t%-15s\t%-15s" % (_('network'),
+ _('netmask'),
+ _('start address'),
+ 'DNS')
+ for network in db.network_get_all(context.get_admin_context()):
+ print "%-18s\t%-15s\t%-15s\t%-15s" % (network.cidr,
+ network.netmask,
+ network.dhcp_start,
+ network.dns)
class ServiceCommands(object):
@@ -579,6 +623,13 @@ class VolumeCommands(object):
ctxt = context.get_admin_context()
volume = db.volume_get(ctxt, param2id(volume_id))
host = volume['host']
+
+ if not host:
+ print "Volume not yet assigned to host."
+ print "Deleting volume from database and skipping rpc."
+ db.volume_destroy(ctxt, param2id(volume_id))
+ return
+
if volume['status'] == 'in-use':
print "Volume is in-use."
print "Detach volume from instance and then try again."
@@ -615,6 +666,7 @@ CATEGORIES = [
('role', RoleCommands),
('shell', ShellCommands),
('vpn', VpnCommands),
+ ('fixed', FixedIpCommands),
('floating', FloatingIpCommands),
('network', NetworkCommands),
('service', ServiceCommands),
diff --git a/nova/api/ec2/__init__.py b/nova/api/ec2/__init__.py
index 1a06b3f01..5adc2c075 100644
--- a/nova/api/ec2/__init__.py
+++ b/nova/api/ec2/__init__.py
@@ -20,7 +20,6 @@ Starting point for routing EC2 requests.
"""
-import datetime
import webob
import webob.dec
import webob.exc
@@ -56,23 +55,20 @@ class RequestLogging(wsgi.Middleware):
@webob.dec.wsgify
def __call__(self, req):
+ start = utils.utcnow()
rv = req.get_response(self.application)
- self.log_request_completion(rv, req)
+ self.log_request_completion(rv, req, start)
return rv
- def log_request_completion(self, response, request):
+ def log_request_completion(self, response, request, start):
controller = request.environ.get('ec2.controller', None)
if controller:
controller = controller.__class__.__name__
action = request.environ.get('ec2.action', None)
ctxt = request.environ.get('ec2.context', None)
- seconds = 'X'
- microseconds = 'X'
- if ctxt:
- delta = datetime.datetime.utcnow() - \
- ctxt.timestamp
- seconds = delta.seconds
- microseconds = delta.microseconds
+ delta = utils.utcnow() - start
+ seconds = delta.seconds
+ microseconds = delta.microseconds
LOG.info(
"%s.%ss %s %s %s %s:%s %s [%s] %s %s",
seconds,
@@ -294,7 +290,7 @@ class Authorizer(wsgi.Middleware):
return True
if 'none' in roles:
return False
- return any(context.project.has_role(context.user.id, role)
+ return any(context.project.has_role(context.user_id, role)
for role in roles)
diff --git a/nova/api/ec2/apirequest.py b/nova/api/ec2/apirequest.py
index 7e72d67fb..00b527d62 100644
--- a/nova/api/ec2/apirequest.py
+++ b/nova/api/ec2/apirequest.py
@@ -20,6 +20,7 @@
APIRequest class
"""
+import datetime
import re
# TODO(termie): replace minidom with etree
from xml.dom import minidom
@@ -171,6 +172,8 @@ class APIRequest(object):
self._render_dict(xml, data_el, data.__dict__)
elif isinstance(data, bool):
data_el.appendChild(xml.createTextNode(str(data).lower()))
+ elif isinstance(data, datetime.datetime):
+ data_el.appendChild(xml.createTextNode(data.isoformat()))
elif data != None:
data_el.appendChild(xml.createTextNode(str(data)))
diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py
index 6919cd8d2..882cdcfc9 100644
--- a/nova/api/ec2/cloud.py
+++ b/nova/api/ec2/cloud.py
@@ -282,7 +282,7 @@ class CloudController(object):
'description': 'fixme'}]}
def describe_key_pairs(self, context, key_name=None, **kwargs):
- key_pairs = db.key_pair_get_all_by_user(context, context.user.id)
+ key_pairs = db.key_pair_get_all_by_user(context, context.user_id)
if not key_name is None:
key_pairs = [x for x in key_pairs if x['name'] in key_name]
@@ -290,7 +290,7 @@ class CloudController(object):
for key_pair in key_pairs:
# filter out the vpn keys
suffix = FLAGS.vpn_key_suffix
- if context.user.is_admin() or \
+ if context.is_admin or \
not key_pair['name'].endswith(suffix):
result.append({
'keyName': key_pair['name'],
@@ -301,7 +301,7 @@ class CloudController(object):
def create_key_pair(self, context, key_name, **kwargs):
LOG.audit(_("Create key pair %s"), key_name, context=context)
- data = _gen_key(context, context.user.id, key_name)
+ data = _gen_key(context, context.user_id, key_name)
return {'keyName': key_name,
'keyFingerprint': data['fingerprint'],
'keyMaterial': data['private_key']}
@@ -310,7 +310,7 @@ class CloudController(object):
def delete_key_pair(self, context, key_name, **kwargs):
LOG.audit(_("Delete key pair %s"), key_name, context=context)
try:
- db.key_pair_destroy(context, context.user.id, key_name)
+ db.key_pair_destroy(context, context.user_id, key_name)
except exception.NotFound:
# aws returns true even if the key doesn't exist
pass
@@ -318,7 +318,7 @@ class CloudController(object):
def describe_security_groups(self, context, group_name=None, **kwargs):
self.compute_api.ensure_default_security_group(context)
- if context.user.is_admin():
+ if context.is_admin:
groups = db.security_group_get_all(context)
else:
groups = db.security_group_get_by_project(context,
@@ -494,7 +494,7 @@ class CloudController(object):
if db.security_group_exists(context, context.project_id, group_name):
raise exception.ApiError(_('group %s already exists') % group_name)
- group = {'user_id': context.user.id,
+ group = {'user_id': context.user_id,
'project_id': context.project_id,
'name': group_name,
'description': group_description}
@@ -674,7 +674,7 @@ class CloudController(object):
else:
instances = self.compute_api.get_all(context, **kwargs)
for instance in instances:
- if not context.user.is_admin():
+ if not context.is_admin:
if instance['image_id'] == FLAGS.vpn_image_id:
continue
i = {}
@@ -702,7 +702,7 @@ class CloudController(object):
i['dnsName'] = i['publicDnsName'] or i['privateDnsName']
i['keyName'] = instance['key_name']
- if context.user.is_admin():
+ if context.is_admin:
i['keyName'] = '%s (%s, %s)' % (i['keyName'],
instance['project_id'],
instance['host'])
@@ -736,7 +736,7 @@ class CloudController(object):
def format_addresses(self, context):
addresses = []
- if context.user.is_admin():
+ if context.is_admin:
iterator = db.floating_ip_get_all(context)
else:
iterator = db.floating_ip_get_all_by_project(context,
@@ -750,7 +750,7 @@ class CloudController(object):
ec2_id = id_to_ec2_id(instance_id)
address_rv = {'public_ip': address,
'instance_id': ec2_id}
- if context.user.is_admin():
+ if context.is_admin:
details = "%s (%s)" % (address_rv['instance_id'],
floating_ip_ref['project_id'])
address_rv['instance_id'] = details
diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py
index 056c7dd27..d0b18eced 100644
--- a/nova/api/openstack/__init__.py
+++ b/nova/api/openstack/__init__.py
@@ -34,6 +34,7 @@ from nova.api.openstack import flavors
from nova.api.openstack import images
from nova.api.openstack import servers
from nova.api.openstack import shared_ip_groups
+from nova.api.openstack import zones
LOG = logging.getLogger('nova.api.openstack')
@@ -79,6 +80,10 @@ class APIRouter(wsgi.Router):
server_members["actions"] = "GET"
server_members['suspend'] = 'POST'
server_members['resume'] = 'POST'
+ server_members['reset_network'] = 'POST'
+
+ mapper.resource("zone", "zones", controller=zones.Controller(),
+ collection={'detail': 'GET'})
mapper.resource("server", "servers", controller=servers.Controller(),
collection={'detail': 'GET'},
diff --git a/nova/api/openstack/auth.py b/nova/api/openstack/auth.py
index 1dfdd5318..473071738 100644
--- a/nova/api/openstack/auth.py
+++ b/nova/api/openstack/auth.py
@@ -19,6 +19,7 @@ import datetime
import hashlib
import json
import time
+import logging
import webob.exc
import webob.dec
diff --git a/nova/api/openstack/common.py b/nova/api/openstack/common.py
index 6d2fa16e8..1dc3767e2 100644
--- a/nova/api/openstack/common.py
+++ b/nova/api/openstack/common.py
@@ -18,22 +18,29 @@
from nova import exception
-def limited(items, req):
- """Return a slice of items according to requested offset and limit.
-
- items - a sliceable
- req - wobob.Request possibly containing offset and limit GET variables.
- offset is where to start in the list, and limit is the maximum number
- of items to return.
+def limited(items, request, max_limit=1000):
+ """
+ Return a slice of items according to requested offset and limit.
- If limit is not specified, 0, or > 1000, defaults to 1000.
+ @param items: A sliceable entity
+ @param request: `webob.Request` possibly containing 'offset' and 'limit'
+ GET variables. 'offset' is where to start in the list,
+ and 'limit' is the maximum number of items to return. If
+ 'limit' is not specified, 0, or > max_limit, we default
+ to max_limit.
+ @kwarg max_limit: The maximum number of items to return from 'items'
"""
+ try:
+ offset = int(request.GET.get('offset', 0))
+ except ValueError:
+ offset = 0
+
+ try:
+ limit = int(request.GET.get('limit', max_limit))
+ except ValueError:
+ limit = max_limit
- offset = int(req.GET.get('offset', 0))
- limit = int(req.GET.get('limit', 0))
- if not limit:
- limit = 1000
- limit = min(1000, limit)
+ limit = min(max_limit, limit or max_limit)
range_end = offset + limit
return items[offset:range_end]
diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py
index 17c5519a1..ce9601ecb 100644
--- a/nova/api/openstack/servers.py
+++ b/nova/api/openstack/servers.py
@@ -1,5 +1,3 @@
-# vim: tabstop=4 shiftwidth=4 softtabstop=4
-
# Copyright 2010 OpenStack LLC.
# All Rights Reserved.
#
@@ -64,6 +62,22 @@ def _translate_detail_keys(inst):
inst_dict['status'] = power_mapping[inst_dict['status']]
inst_dict['addresses'] = dict(public=[], private=[])
+
+ # grab single private fixed ip
+ try:
+ private_ip = inst['fixed_ip']['address']
+ if private_ip:
+ inst_dict['addresses']['private'].append(private_ip)
+ except KeyError:
+ LOG.debug(_("Failed to read private ip"))
+
+ # grab all public floating ips
+ try:
+ for floating in inst['fixed_ip']['floating_ips']:
+ inst_dict['addresses']['public'].append(floating['address'])
+ except KeyError:
+ LOG.debug(_("Failed to read public ip(s)"))
+
inst_dict['metadata'] = {}
inst_dict['hostId'] = ''
@@ -148,8 +162,12 @@ class Controller(wsgi.Controller):
if not env:
return faults.Fault(exc.HTTPUnprocessableEntity())
- key_pair = auth_manager.AuthManager.get_key_pairs(
- req.environ['nova.context'])[0]
+ key_pairs = auth_manager.AuthManager.get_key_pairs(
+ req.environ['nova.context'])
+ if not key_pairs:
+ raise exception.NotFound(_("No keypairs defined"))
+ key_pair = key_pairs[0]
+
image_id = common.get_image_id_from_image_hash(self._image_service,
req.environ['nova.context'], env['server']['imageId'])
kernel_id, ramdisk_id = self._get_kernel_ramdisk_from_image(
@@ -163,7 +181,8 @@ class Controller(wsgi.Controller):
display_name=env['server']['name'],
display_description=env['server']['name'],
key_name=key_pair['name'],
- key_data=key_pair['public_key'])
+ key_data=key_pair['public_key'],
+ onset_files=env.get('onset_files', []))
return _translate_keys(instances[0])
def update(self, req, id):
@@ -249,6 +268,20 @@ class Controller(wsgi.Controller):
return faults.Fault(exc.HTTPUnprocessableEntity())
return exc.HTTPAccepted()
+ def reset_network(self, req, id):
+ """
+ Reset networking on an instance (admin only).
+
+ """
+ context = req.environ['nova.context']
+ try:
+ self.compute_api.reset_network(context, id)
+ except:
+ readable = traceback.format_exc()
+ LOG.exception(_("Compute.api::reset_network %s"), readable)
+ return faults.Fault(exc.HTTPUnprocessableEntity())
+ return exc.HTTPAccepted()
+
def pause(self, req, id):
""" Permit Admins to Pause the server. """
ctxt = req.environ['nova.context']
diff --git a/nova/api/openstack/zones.py b/nova/api/openstack/zones.py
new file mode 100644
index 000000000..830464ffd
--- /dev/null
+++ b/nova/api/openstack/zones.py
@@ -0,0 +1,80 @@
+# Copyright 2010 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 common
+import logging
+
+from nova import flags
+from nova import wsgi
+from nova import db
+
+
+FLAGS = flags.FLAGS
+
+
+def _filter_keys(item, keys):
+ """
+ Filters all model attributes except for keys
+ item is a dict
+
+ """
+ return dict((k, v) for k, v in item.iteritems() if k in keys)
+
+
+def _scrub_zone(zone):
+ return _filter_keys(zone, ('id', 'api_url'))
+
+
+class Controller(wsgi.Controller):
+
+ _serialization_metadata = {
+ 'application/xml': {
+ "attributes": {
+ "zone": ["id", "api_url"]}}}
+
+ def index(self, req):
+ """Return all zones in brief"""
+ items = db.zone_get_all(req.environ['nova.context'])
+ items = common.limited(items, req)
+ items = [_scrub_zone(item) for item in items]
+ return dict(zones=items)
+
+ def detail(self, req):
+ """Return all zones in detail"""
+ return self.index(req)
+
+ def show(self, req, id):
+ """Return data about the given zone id"""
+ zone_id = int(id)
+ zone = db.zone_get(req.environ['nova.context'], zone_id)
+ return dict(zone=_scrub_zone(zone))
+
+ def delete(self, req, id):
+ zone_id = int(id)
+ db.zone_delete(req.environ['nova.context'], zone_id)
+ return {}
+
+ def create(self, req):
+ context = req.environ['nova.context']
+ env = self._deserialize(req.body, req)
+ zone = db.zone_create(context, env["zone"])
+ return dict(zone=_scrub_zone(zone))
+
+ def update(self, req, id):
+ context = req.environ['nova.context']
+ env = self._deserialize(req.body, req)
+ zone_id = int(id)
+ zone = db.zone_update(context, zone_id, env["zone"])
+ return dict(zone=_scrub_zone(zone))
diff --git a/nova/auth/ldapdriver.py b/nova/auth/ldapdriver.py
index e652f1caa..5da7751a0 100644
--- a/nova/auth/ldapdriver.py
+++ b/nova/auth/ldapdriver.py
@@ -74,6 +74,25 @@ LOG = logging.getLogger("nova.ldapdriver")
# in which we may want to change the interface a bit more.
+def _clean(attr):
+ """Clean attr for insertion into ldap"""
+ if attr is None:
+ return None
+ if type(attr) is unicode:
+ return str(attr)
+ return attr
+
+
+def sanitize(fn):
+ """Decorator to sanitize all args"""
+ def _wrapped(self, *args, **kwargs):
+ args = [_clean(x) for x in args]
+ kwargs = dict((k, _clean(v)) for (k, v) in kwargs)
+ return fn(self, *args, **kwargs)
+ _wrapped.func_name = fn.func_name
+ return _wrapped
+
+
class LdapDriver(object):
"""Ldap Auth driver
@@ -106,23 +125,27 @@ class LdapDriver(object):
self.conn.unbind_s()
return False
+ @sanitize
def get_user(self, uid):
"""Retrieve user by id"""
attr = self.__get_ldap_user(uid)
return self.__to_user(attr)
+ @sanitize
def get_user_from_access_key(self, access):
"""Retrieve user by access key"""
query = '(accessKey=%s)' % access
dn = FLAGS.ldap_user_subtree
return self.__to_user(self.__find_object(dn, query))
+ @sanitize
def get_project(self, pid):
"""Retrieve project by id"""
dn = self.__project_to_dn(pid)
attr = self.__find_object(dn, LdapDriver.project_pattern)
return self.__to_project(attr)
+ @sanitize
def get_users(self):
"""Retrieve list of users"""
attrs = self.__find_objects(FLAGS.ldap_user_subtree,
@@ -134,6 +157,7 @@ class LdapDriver(object):
users.append(user)
return users
+ @sanitize
def get_projects(self, uid=None):
"""Retrieve list of projects"""
pattern = LdapDriver.project_pattern
@@ -143,6 +167,7 @@ class LdapDriver(object):
pattern)
return [self.__to_project(attr) for attr in attrs]
+ @sanitize
def create_user(self, name, access_key, secret_key, is_admin):
"""Create a user"""
if self.__user_exists(name):
@@ -196,6 +221,7 @@ class LdapDriver(object):
self.conn.add_s(self.__uid_to_dn(name), attr)
return self.__to_user(dict(attr))
+ @sanitize
def create_project(self, name, manager_uid,
description=None, member_uids=None):
"""Create a project"""
@@ -231,6 +257,7 @@ class LdapDriver(object):
self.conn.add_s(dn, attr)
return self.__to_project(dict(attr))
+ @sanitize
def modify_project(self, project_id, manager_uid=None, description=None):
"""Modify an existing project"""
if not manager_uid and not description:
@@ -249,21 +276,25 @@ class LdapDriver(object):
dn = self.__project_to_dn(project_id)
self.conn.modify_s(dn, attr)
+ @sanitize
def add_to_project(self, uid, project_id):
"""Add user to project"""
dn = self.__project_to_dn(project_id)
return self.__add_to_group(uid, dn)
+ @sanitize
def remove_from_project(self, uid, project_id):
"""Remove user from project"""
dn = self.__project_to_dn(project_id)
return self.__remove_from_group(uid, dn)
+ @sanitize
def is_in_project(self, uid, project_id):
"""Check if user is in project"""
dn = self.__project_to_dn(project_id)
return self.__is_in_group(uid, dn)
+ @sanitize
def has_role(self, uid, role, project_id=None):
"""Check if user has role
@@ -273,6 +304,7 @@ class LdapDriver(object):
role_dn = self.__role_to_dn(role, project_id)
return self.__is_in_group(uid, role_dn)
+ @sanitize
def add_role(self, uid, role, project_id=None):
"""Add role for user (or user and project)"""
role_dn = self.__role_to_dn(role, project_id)
@@ -283,11 +315,13 @@ class LdapDriver(object):
else:
return self.__add_to_group(uid, role_dn)
+ @sanitize
def remove_role(self, uid, role, project_id=None):
"""Remove role for user (or user and project)"""
role_dn = self.__role_to_dn(role, project_id)
return self.__remove_from_group(uid, role_dn)
+ @sanitize
def get_user_roles(self, uid, project_id=None):
"""Retrieve list of roles for user (or user and project)"""
if project_id is None:
@@ -307,6 +341,7 @@ class LdapDriver(object):
roles = self.__find_objects(project_dn, query)
return [role['cn'][0] for role in roles]
+ @sanitize
def delete_user(self, uid):
"""Delete a user"""
if not self.__user_exists(uid):
@@ -332,12 +367,14 @@ class LdapDriver(object):
# Delete entry
self.conn.delete_s(self.__uid_to_dn(uid))
+ @sanitize
def delete_project(self, project_id):
"""Delete a project"""
project_dn = self.__project_to_dn(project_id)
self.__delete_roles(project_dn)
self.__delete_group(project_dn)
+ @sanitize
def modify_user(self, uid, access_key=None, secret_key=None, admin=None):
"""Modify an existing user"""
if not access_key and not secret_key and admin is None:
diff --git a/nova/auth/novarc.template b/nova/auth/novarc.template
index c53a4acdc..cda2ecc28 100644
--- a/nova/auth/novarc.template
+++ b/nova/auth/novarc.template
@@ -10,7 +10,6 @@ export NOVA_CERT=${NOVA_KEY_DIR}/%(nova)s
export EUCALYPTUS_CERT=${NOVA_CERT} # euca-bundle-image seems to require this set
alias ec2-bundle-image="ec2-bundle-image --cert ${EC2_CERT} --privatekey ${EC2_PRIVATE_KEY} --user 42 --ec2cert ${NOVA_CERT}"
alias ec2-upload-bundle="ec2-upload-bundle -a ${EC2_ACCESS_KEY} -s ${EC2_SECRET_KEY} --url ${S3_URL} --ec2cert ${NOVA_CERT}"
-export CLOUD_SERVERS_API_KEY="%(access)s"
-export CLOUD_SERVERS_USERNAME="%(user)s"
-export CLOUD_SERVERS_URL="%(os)s"
-
+export NOVA_API_KEY="%(access)s"
+export NOVA_USERNAME="%(user)s"
+export NOVA_URL="%(os)s"
diff --git a/nova/compute/api.py b/nova/compute/api.py
index 740dd3935..81ea6dc53 100644
--- a/nova/compute/api.py
+++ b/nova/compute/api.py
@@ -67,10 +67,10 @@ class API(base.Base):
"""Get the network topic for an instance."""
try:
instance = self.get(context, instance_id)
- except exception.NotFound as e:
+ except exception.NotFound:
LOG.warning(_("Instance %d was not found in get_network_topic"),
instance_id)
- raise e
+ raise
host = instance['host']
if not host:
@@ -85,10 +85,11 @@ class API(base.Base):
min_count=1, max_count=1,
display_name='', display_description='',
key_name=None, key_data=None, security_group='default',
- availability_zone=None, user_data=None):
+ availability_zone=None, user_data=None,
+ onset_files=None):
"""Create the number of instances requested if quota and
- other arguments check out ok."""
-
+ other arguments check out ok.
+ """
type_data = instance_types.INSTANCE_TYPES[instance_type]
num_instances = quota.allowed_instances(context, max_count, type_data)
if num_instances < min_count:
@@ -99,25 +100,23 @@ class API(base.Base):
"run %s more instances of this type.") %
num_instances, "InstanceLimitExceeded")
- is_vpn = image_id == FLAGS.vpn_image_id
- if not is_vpn:
- image = self.image_service.show(context, image_id)
- if kernel_id is None:
- kernel_id = image.get('kernel_id', None)
- if ramdisk_id is None:
- ramdisk_id = image.get('ramdisk_id', None)
- # No kernel and ramdisk for raw images
- if kernel_id == str(FLAGS.null_kernel):
- kernel_id = None
- ramdisk_id = None
- LOG.debug(_("Creating a raw instance"))
- # Make sure we have access to kernel and ramdisk (if not raw)
- logging.debug("Using Kernel=%s, Ramdisk=%s" %
- (kernel_id, ramdisk_id))
- if kernel_id:
- self.image_service.show(context, kernel_id)
- if ramdisk_id:
- self.image_service.show(context, ramdisk_id)
+ image = self.image_service.show(context, image_id)
+ if kernel_id is None:
+ kernel_id = image.get('kernel_id', None)
+ if ramdisk_id is None:
+ ramdisk_id = image.get('ramdisk_id', None)
+ # No kernel and ramdisk for raw images
+ if kernel_id == str(FLAGS.null_kernel):
+ kernel_id = None
+ ramdisk_id = None
+ LOG.debug(_("Creating a raw instance"))
+ # Make sure we have access to kernel and ramdisk (if not raw)
+ logging.debug("Using Kernel=%s, Ramdisk=%s" %
+ (kernel_id, ramdisk_id))
+ if kernel_id:
+ self.image_service.show(context, kernel_id)
+ if ramdisk_id:
+ self.image_service.show(context, ramdisk_id)
if security_group is None:
security_group = ['default']
@@ -156,7 +155,6 @@ class API(base.Base):
'key_data': key_data,
'locked': False,
'availability_zone': availability_zone}
-
elevated = context.elevated()
instances = []
LOG.debug(_("Going to run %s instances..."), num_instances)
@@ -193,7 +191,8 @@ class API(base.Base):
{"method": "run_instance",
"args": {"topic": FLAGS.compute_topic,
"instance_id": instance_id,
- "availability_zone": availability_zone}})
+ "availability_zone": availability_zone,
+ "onset_files": onset_files}})
for group_id in security_groups:
self.trigger_security_group_members_refresh(elevated, group_id)
@@ -293,10 +292,10 @@ class API(base.Base):
LOG.debug(_("Going to try to terminate %s"), instance_id)
try:
instance = self.get(context, instance_id)
- except exception.NotFound as e:
+ except exception.NotFound:
LOG.warning(_("Instance %d was not found during terminate"),
instance_id)
- raise e
+ raise
if (instance['state_description'] == 'terminating'):
LOG.warning(_("Instance %d is already being terminated"),
@@ -434,6 +433,10 @@ class API(base.Base):
"""Set the root/admin password for the given instance."""
self._cast_compute_message('set_admin_password', context, instance_id)
+ def inject_file(self, context, instance_id):
+ """Write a file to the given instance."""
+ self._cast_compute_message('inject_file', context, instance_id)
+
def get_ajax_console(self, context, instance_id):
"""Get a url to an AJAX Console"""
instance = self.get(context, instance_id)
@@ -466,6 +469,13 @@ class API(base.Base):
instance = self.get(context, instance_id)
return instance['locked']
+ def reset_network(self, context, instance_id):
+ """
+ Reset networking on the instance.
+
+ """
+ self._cast_compute_message('reset_network', context, instance_id)
+
def attach_volume(self, context, instance_id, volume_id, device):
if not re.match("^/dev/[a-z]d[a-z]+$", device):
raise exception.ApiError(_("Invalid device specified: %s. "
diff --git a/nova/compute/manager.py b/nova/compute/manager.py
index f4418af26..b8d4b7ee9 100644
--- a/nova/compute/manager.py
+++ b/nova/compute/manager.py
@@ -34,6 +34,7 @@ terminating it.
:func:`nova.utils.import_object`
"""
+import base64
import datetime
import random
import string
@@ -127,10 +128,10 @@ class ComputeManager(manager.Manager):
info = self.driver.get_info(instance_ref['name'])
state = info['state']
except exception.NotFound:
- state = power_state.NOSTATE
+ state = power_state.FAILED
self.db.instance_set_state(context, instance_id, state)
- def get_console_topic(self, context, **_kwargs):
+ def get_console_topic(self, context, **kwargs):
"""Retrieves the console host for a project on this host
Currently this is just set in the flags for each compute
host."""
@@ -139,7 +140,7 @@ class ComputeManager(manager.Manager):
FLAGS.console_topic,
FLAGS.console_host)
- def get_network_topic(self, context, **_kwargs):
+ def get_network_topic(self, context, **kwargs):
"""Retrieves the network host for a project on this host"""
# TODO(vish): This method should be memoized. This will make
# the call to get_network_host cheaper, so that
@@ -158,21 +159,22 @@ class ComputeManager(manager.Manager):
@exception.wrap_exception
def refresh_security_group_rules(self, context,
- security_group_id, **_kwargs):
+ security_group_id, **kwargs):
"""This call passes straight through to the virtualization driver."""
return self.driver.refresh_security_group_rules(security_group_id)
@exception.wrap_exception
def refresh_security_group_members(self, context,
- security_group_id, **_kwargs):
+ security_group_id, **kwargs):
"""This call passes straight through to the virtualization driver."""
return self.driver.refresh_security_group_members(security_group_id)
@exception.wrap_exception
- def run_instance(self, context, instance_id, **_kwargs):
+ def run_instance(self, context, instance_id, **kwargs):
"""Launch a new instance with specified options."""
context = context.elevated()
instance_ref = self.db.instance_get(context, instance_id)
+ instance_ref.onset_files = kwargs.get('onset_files', [])
if instance_ref['name'] in self.driver.list_instances():
raise exception.Error(_("Instance has already been created"))
LOG.audit(_("instance %s: starting..."), instance_id,
@@ -323,28 +325,43 @@ class ComputeManager(manager.Manager):
"""Set the root/admin password for an instance on this server."""
context = context.elevated()
instance_ref = self.db.instance_get(context, instance_id)
- if instance_ref['state'] != power_state.RUNNING:
- logging.warn('trying to reset the password on a non-running '
- 'instance: %s (state: %s expected: %s)',
- instance_ref['id'],
- instance_ref['state'],
- power_state.RUNNING)
-
- logging.debug('instance %s: setting admin password',
+ instance_id = instance_ref['id']
+ instance_state = instance_ref['state']
+ expected_state = power_state.RUNNING
+ if instance_state != expected_state:
+ LOG.warn(_('trying to reset the password on a non-running '
+ 'instance: %(instance_id)s (state: %(instance_state)s '
+ 'expected: %(expected_state)s)') % locals())
+ LOG.audit(_('instance %s: setting admin password'),
instance_ref['name'])
if new_pass is None:
# Generate a random password
- new_pass = self._generate_password(FLAGS.password_length)
-
+ new_pass = utils.generate_password(FLAGS.password_length)
self.driver.set_admin_password(instance_ref, new_pass)
self._update_state(context, instance_id)
- def _generate_password(self, length=20):
- """Generate a random sequence of letters and digits
- to be used as a password.
- """
- chrs = string.letters + string.digits
- return "".join([random.choice(chrs) for i in xrange(length)])
+ @exception.wrap_exception
+ @checks_instance_lock
+ def inject_file(self, context, instance_id, path, file_contents):
+ """Write a file to the specified path on an instance on this server"""
+ context = context.elevated()
+ instance_ref = self.db.instance_get(context, instance_id)
+ instance_id = instance_ref['id']
+ instance_state = instance_ref['state']
+ expected_state = power_state.RUNNING
+ if instance_state != expected_state:
+ LOG.warn(_('trying to inject a file into a non-running '
+ 'instance: %(instance_id)s (state: %(instance_state)s '
+ 'expected: %(expected_state)s)') % locals())
+ # Files/paths *should* be base64-encoded at this point, but
+ # double-check to make sure.
+ b64_path = utils.ensure_b64_encoding(path)
+ b64_contents = utils.ensure_b64_encoding(file_contents)
+ plain_path = base64.b64decode(b64_path)
+ nm = instance_ref['name']
+ msg = _('instance %(nm)s: injecting file to %(plain_path)s') % locals()
+ LOG.audit(msg)
+ self.driver.inject_file(instance_ref, b64_path, b64_contents)
@exception.wrap_exception
@checks_instance_lock
@@ -498,6 +515,18 @@ class ComputeManager(manager.Manager):
instance_ref = self.db.instance_get(context, instance_id)
return instance_ref['locked']
+ @checks_instance_lock
+ def reset_network(self, context, instance_id):
+ """
+ Reset networking on the instance.
+
+ """
+ context = context.elevated()
+ instance_ref = self.db.instance_get(context, instance_id)
+ LOG.debug(_('instance %s: reset network'), instance_id,
+ context=context)
+ self.driver.reset_network(instance_ref)
+
@exception.wrap_exception
def get_console_output(self, context, instance_id):
"""Send the console output for an instance."""
@@ -511,7 +540,7 @@ class ComputeManager(manager.Manager):
def get_ajax_console(self, context, instance_id):
"""Return connection information for an ajax console"""
context = context.elevated()
- logging.debug(_("instance %s: getting ajax console"), instance_id)
+ LOG.debug(_("instance %s: getting ajax console"), instance_id)
instance_ref = self.db.instance_get(context, instance_id)
return self.driver.get_ajax_console(instance_ref)
diff --git a/nova/compute/power_state.py b/nova/compute/power_state.py
index 37039d2ec..adfc2dff0 100644
--- a/nova/compute/power_state.py
+++ b/nova/compute/power_state.py
@@ -27,6 +27,7 @@ SHUTDOWN = 0x04
SHUTOFF = 0x05
CRASHED = 0x06
SUSPENDED = 0x07
+FAILED = 0x08
def name(code):
@@ -38,5 +39,6 @@ def name(code):
SHUTDOWN: 'shutdown',
SHUTOFF: 'shutdown',
CRASHED: 'crashed',
- SUSPENDED: 'suspended'}
+ SUSPENDED: 'suspended',
+ FAILED: 'failed to spawn'}
return d[code]
diff --git a/nova/context.py b/nova/context.py
index f2669c9f1..0256bf448 100644
--- a/nova/context.py
+++ b/nova/context.py
@@ -28,7 +28,6 @@ from nova import utils
class RequestContext(object):
-
def __init__(self, user, project, is_admin=None, read_deleted=False,
remote_address=None, timestamp=None, request_id=None):
if hasattr(user, 'id'):
@@ -53,7 +52,7 @@ class RequestContext(object):
self.read_deleted = read_deleted
self.remote_address = remote_address
if not timestamp:
- timestamp = datetime.datetime.utcnow()
+ timestamp = utils.utcnow()
if isinstance(timestamp, str) or isinstance(timestamp, unicode):
timestamp = utils.parse_isotime(timestamp)
self.timestamp = timestamp
@@ -101,7 +100,7 @@ class RequestContext(object):
return cls(**values)
def elevated(self, read_deleted=False):
- """Return a version of this context with admin flag set"""
+ """Return a version of this context with admin flag set."""
return RequestContext(self.user_id,
self.project_id,
True,
diff --git a/nova/db/api.py b/nova/db/api.py
index 789cb8ebb..d7f3746d2 100644
--- a/nova/db/api.py
+++ b/nova/db/api.py
@@ -288,11 +288,21 @@ def fixed_ip_disassociate_all_by_timeout(context, host, time):
return IMPL.fixed_ip_disassociate_all_by_timeout(context, host, time)
+def fixed_ip_get_all(context):
+ """Get all defined fixed ips."""
+ return IMPL.fixed_ip_get_all(context)
+
+
def fixed_ip_get_by_address(context, address):
"""Get a fixed ip by address or raise if it does not exist."""
return IMPL.fixed_ip_get_by_address(context, address)
+def fixed_ip_get_all_by_instance(context, instance_id):
+ """Get fixed ips by instance or raise if none exist."""
+ return IMPL.fixed_ip_get_all_by_instance(context, instance_id)
+
+
def fixed_ip_get_instance(context, address):
"""Get an instance for a fixed ip by address."""
return IMPL.fixed_ip_get_instance(context, address)
@@ -500,6 +510,11 @@ def network_get(context, network_id):
return IMPL.network_get(context, network_id)
+def network_get_all(context):
+ """Return all defined networks."""
+ return IMPL.network_get_all(context)
+
+
# pylint: disable-msg=C0103
def network_get_associated_fixed_ips(context, network_id):
"""Get all network's ips that have been associated."""
@@ -516,6 +531,11 @@ def network_get_by_instance(context, instance_id):
return IMPL.network_get_by_instance(context, instance_id)
+def network_get_all_by_instance(context, instance_id):
+ """Get all networks by instance id or raise if none exist."""
+ return IMPL.network_get_all_by_instance(context, instance_id)
+
+
def network_get_index(context, network_id):
"""Get non-conflicting index for network."""
return IMPL.network_get_index(context, network_id)
@@ -556,7 +576,7 @@ def project_get_network(context, project_id, associate=True):
"""
- return IMPL.project_get_network(context, project_id)
+ return IMPL.project_get_network(context, project_id, associate)
def project_get_network_v6(context, project_id):
@@ -980,3 +1000,31 @@ def console_get_all_by_instance(context, instance_id):
def console_get(context, console_id, instance_id=None):
"""Get a specific console (possibly on a given instance)."""
return IMPL.console_get(context, console_id, instance_id)
+
+
+####################
+
+
+def zone_create(context, values):
+ """Create a new child Zone entry."""
+ return IMPL.zone_create(context, values)
+
+
+def zone_update(context, zone_id, values):
+ """Update a child Zone entry."""
+ return IMPL.zone_update(context, values)
+
+
+def zone_delete(context, zone_id):
+ """Delete a child Zone."""
+ return IMPL.zone_delete(context, zone_id)
+
+
+def zone_get(context, zone_id):
+ """Get a specific child Zone."""
+ return IMPL.zone_get(context, zone_id)
+
+
+def zone_get_all(context):
+ """Get all child Zones."""
+ return IMPL.zone_get_all(context)
diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py
index 02855e7a9..2697fac73 100644
--- a/nova/db/sqlalchemy/api.py
+++ b/nova/db/sqlalchemy/api.py
@@ -583,6 +583,17 @@ def fixed_ip_disassociate_all_by_timeout(_context, host, time):
return result.rowcount
+@require_admin_context
+def fixed_ip_get_all(context, session=None):
+ if not session:
+ session = get_session()
+ result = session.query(models.FixedIp).all()
+ if not result:
+ raise exception.NotFound(_('No fixed ips defined'))
+
+ return result
+
+
@require_context
def fixed_ip_get_by_address(context, address, session=None):
if not session:
@@ -609,6 +620,17 @@ def fixed_ip_get_instance(context, address):
@require_context
+def fixed_ip_get_all_by_instance(context, instance_id):
+ session = get_session()
+ rv = session.query(models.FixedIp).\
+ filter_by(instance_id=instance_id).\
+ filter_by(deleted=False)
+ if not rv:
+ raise exception.NotFound(_('No address for instance %s') % instance_id)
+ return rv
+
+
+@require_context
def fixed_ip_get_instance_v6(context, address):
session = get_session()
mac = utils.to_mac(address)
@@ -1056,6 +1078,15 @@ def network_get(context, network_id, session=None):
return result
+@require_admin_context
+def network_get_all(context):
+ session = get_session()
+ result = session.query(models.Network)
+ if not result:
+ raise exception.NotFound(_('No networks defined'))
+ 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-msg=C0103
@@ -1100,6 +1131,19 @@ def network_get_by_instance(_context, instance_id):
@require_admin_context
+def network_get_all_by_instance(_context, instance_id):
+ session = get_session()
+ rv = session.query(models.Network).\
+ filter_by(deleted=False).\
+ join(models.Network.fixed_ips).\
+ filter_by(instance_id=instance_id).\
+ filter_by(deleted=False)
+ if not rv:
+ raise exception.NotFound(_('No network for instance %s') % instance_id)
+ return rv
+
+
+@require_admin_context
def network_set_host(context, network_id, host_id):
session = get_session()
with session.begin():
@@ -2014,3 +2058,47 @@ def console_get(context, console_id, instance_id=None):
raise exception.NotFound(_("No console with id %(console_id)s"
" %(idesc)s") % locals())
return result
+
+
+####################
+
+
+@require_admin_context
+def zone_create(context, values):
+ zone = models.Zone()
+ zone.update(values)
+ zone.save()
+ return zone
+
+
+@require_admin_context
+def zone_update(context, zone_id, values):
+ zone = session.query(models.Zone).filter_by(id=zone_id).first()
+ if not zone:
+ raise exception.NotFound(_("No zone with id %(zone_id)s") % locals())
+ zone.update(values)
+ zone.save()
+ return zone
+
+
+@require_admin_context
+def zone_delete(context, zone_id):
+ session = get_session()
+ with session.begin():
+ session.execute('delete from zones '
+ 'where id=:id', {'id': zone_id})
+
+
+@require_admin_context
+def zone_get(context, zone_id):
+ session = get_session()
+ result = session.query(models.Zone).filter_by(id=zone_id).first()
+ if not result:
+ raise exception.NotFound(_("No zone with id %(zone_id)s") % locals())
+ return result
+
+
+@require_admin_context
+def zone_get_all(context):
+ session = get_session()
+ return session.query(models.Zone).all()
diff --git a/nova/db/sqlalchemy/migrate_repo/versions/001_austin.py b/nova/db/sqlalchemy/migrate_repo/versions/001_austin.py
index 366944591..9e7ab3554 100644
--- a/nova/db/sqlalchemy/migrate_repo/versions/001_austin.py
+++ b/nova/db/sqlalchemy/migrate_repo/versions/001_austin.py
@@ -508,17 +508,19 @@ def upgrade(migrate_engine):
# bind migrate_engine to your metadata
meta.bind = migrate_engine
- for table in (auth_tokens, export_devices, fixed_ips, floating_ips,
- instances, key_pairs, networks,
- projects, quotas, security_groups, security_group_inst_assoc,
- security_group_rules, services, users,
- user_project_association, user_project_role_association,
- user_role_association, volumes):
+ tables = [auth_tokens,
+ instances, key_pairs, networks, fixed_ips, floating_ips,
+ quotas, security_groups, security_group_inst_assoc,
+ security_group_rules, services, users, projects,
+ user_project_association, user_project_role_association,
+ user_role_association, volumes, export_devices]
+ for table in tables:
try:
table.create()
except Exception:
logging.info(repr(table))
logging.exception('Exception while creating table')
+ meta.drop_all(tables=tables)
raise
diff --git a/nova/db/sqlalchemy/migrate_repo/versions/002_bexar.py b/nova/db/sqlalchemy/migrate_repo/versions/002_bexar.py
index 699b837f8..413536a59 100644
--- a/nova/db/sqlalchemy/migrate_repo/versions/002_bexar.py
+++ b/nova/db/sqlalchemy/migrate_repo/versions/002_bexar.py
@@ -209,13 +209,16 @@ def upgrade(migrate_engine):
# Upgrade operations go here. Don't create your own engine;
# bind migrate_engine to your metadata
meta.bind = migrate_engine
- for table in (certificates, consoles, console_pools, instance_actions,
- iscsi_targets):
+
+ tables = [certificates, console_pools, consoles, instance_actions,
+ iscsi_targets]
+ for table in tables:
try:
table.create()
except Exception:
logging.info(repr(table))
logging.exception('Exception while creating table')
+ meta.drop_all(tables=tables)
raise
auth_tokens.c.user_id.alter(type=String(length=255,
diff --git a/nova/db/sqlalchemy/migrate_repo/versions/003_add_label_to_networks.py b/nova/db/sqlalchemy/migrate_repo/versions/003_add_label_to_networks.py
new file mode 100644
index 000000000..5ba7910f1
--- /dev/null
+++ b/nova/db/sqlalchemy/migrate_repo/versions/003_add_label_to_networks.py
@@ -0,0 +1,51 @@
+# 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.
+
+from sqlalchemy import *
+from migrate import *
+
+from nova import log as logging
+
+
+meta = MetaData()
+
+
+networks = Table('networks', meta,
+ Column('id', Integer(), primary_key=True, nullable=False),
+ )
+
+
+#
+# New Tables
+#
+
+
+#
+# Tables to alter
+#
+
+networks_label = Column(
+ 'label',
+ String(length=255, convert_unicode=False, assert_unicode=None,
+ unicode_error=None, _warn_on_bytestring=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
+ networks.create_column(networks_label)
diff --git a/nova/db/sqlalchemy/migrate_repo/versions/004_add_zone_tables.py b/nova/db/sqlalchemy/migrate_repo/versions/004_add_zone_tables.py
new file mode 100644
index 000000000..ade981687
--- /dev/null
+++ b/nova/db/sqlalchemy/migrate_repo/versions/004_add_zone_tables.py
@@ -0,0 +1,61 @@
+# Copyright 2010 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 sqlalchemy import *
+from migrate import *
+
+from nova import log as logging
+
+
+meta = MetaData()
+
+
+#
+# New Tables
+#
+zones = Table('zones', 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('api_url',
+ String(length=255, convert_unicode=False, assert_unicode=None,
+ unicode_error=None, _warn_on_bytestring=False)),
+ Column('username',
+ String(length=255, convert_unicode=False, assert_unicode=None,
+ unicode_error=None, _warn_on_bytestring=False)),
+ Column('password',
+ String(length=255, convert_unicode=False, assert_unicode=None,
+ unicode_error=None, _warn_on_bytestring=False)),
+ )
+
+
+#
+# Tables to alter
+#
+
+# (none currently)
+
+
+def upgrade(migrate_engine):
+ # Upgrade operations go here. Don't create your own engine;
+ # bind migrate_engine to your metadata
+ meta.bind = migrate_engine
+ for table in (zones, ):
+ try:
+ table.create()
+ except Exception:
+ logging.info(repr(table))
diff --git a/nova/db/sqlalchemy/migration.py b/nova/db/sqlalchemy/migration.py
index 2a13c5466..9bdaa6d6b 100644
--- a/nova/db/sqlalchemy/migration.py
+++ b/nova/db/sqlalchemy/migration.py
@@ -17,12 +17,22 @@
# under the License.
import os
+import sys
from nova import flags
import sqlalchemy
from migrate.versioning import api as versioning_api
-from migrate.versioning import exceptions as versioning_exceptions
+
+try:
+ from migrate.versioning import exceptions as versioning_exceptions
+except ImportError:
+ try:
+ # python-migration changed location of exceptions after 1.6.3
+ # See LP Bug #717467
+ from migrate import exceptions as versioning_exceptions
+ except ImportError:
+ sys.exit(_("python-migrate is not installed. Exiting."))
FLAGS = flags.FLAGS
@@ -45,8 +55,8 @@ def db_version():
engine = sqlalchemy.create_engine(FLAGS.sql_connection, echo=False)
meta.reflect(bind=engine)
try:
- for table in ('auth_tokens', 'export_devices', 'fixed_ips',
- 'floating_ips', 'instances',
+ for table in ('auth_tokens', 'zones', 'export_devices',
+ 'fixed_ips', 'floating_ips', 'instances',
'key_pairs', 'networks', 'projects', 'quotas',
'security_group_instance_association',
'security_group_rules', 'security_groups',
diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py
index 7efb36c0e..40a96fc17 100644
--- a/nova/db/sqlalchemy/models.py
+++ b/nova/db/sqlalchemy/models.py
@@ -373,6 +373,7 @@ class Network(BASE, NovaBase):
"vpn_public_port"),
{'mysql_engine': 'InnoDB'})
id = Column(Integer, primary_key=True)
+ label = Column(String(255))
injected = Column(Boolean, default=False)
cidr = Column(String(255), unique=True)
@@ -535,6 +536,15 @@ class Console(BASE, NovaBase):
pool = relationship(ConsolePool, backref=backref('consoles'))
+class Zone(BASE, NovaBase):
+ """Represents a child zone of this zone."""
+ __tablename__ = 'zones'
+ id = Column(Integer, primary_key=True)
+ api_url = Column(String(255))
+ username = Column(String(255))
+ password = Column(String(255))
+
+
def register_models():
"""Register Models and create metadata.
@@ -547,7 +557,7 @@ def register_models():
Volume, ExportDevice, IscsiTarget, FixedIp, FloatingIp,
Network, SecurityGroup, SecurityGroupIngressRule,
SecurityGroupInstanceAssociation, AuthToken, User,
- Project, Certificate, ConsolePool, Console) # , Image, Host
+ Project, Certificate, ConsolePool, Console, Zone)
engine = create_engine(FLAGS.sql_connection, echo=False)
for model in models:
model.metadata.create_all(engine)
diff --git a/nova/flags.py b/nova/flags.py
index 3ba3fe6fa..f64a62da9 100644
--- a/nova/flags.py
+++ b/nova/flags.py
@@ -282,6 +282,8 @@ DEFINE_integer('auth_token_ttl', 3600, 'Seconds for auth tokens to linger')
DEFINE_string('state_path', os.path.join(os.path.dirname(__file__), '../'),
"Top-level directory for maintaining nova's state")
+DEFINE_string('logdir', None, 'output to a per-service log file in named '
+ 'directory')
DEFINE_string('sql_connection',
'sqlite:///$state_path/nova.sqlite',
diff --git a/nova/log.py b/nova/log.py
index b541488bd..6b201ffcc 100644
--- a/nova/log.py
+++ b/nova/log.py
@@ -28,9 +28,11 @@ It also allows setting of formatting information through flags.
import cStringIO
+import inspect
import json
import logging
import logging.handlers
+import os
import sys
import traceback
@@ -92,7 +94,7 @@ critical = logging.critical
log = logging.log
# handlers
StreamHandler = logging.StreamHandler
-FileHandler = logging.FileHandler
+WatchedFileHandler = logging.handlers.WatchedFileHandler
# logging.SysLogHandler is nicer than logging.logging.handler.SysLogHandler.
SysLogHandler = logging.handlers.SysLogHandler
@@ -111,6 +113,18 @@ def _dictify_context(context):
return context
+def _get_binary_name():
+ return os.path.basename(inspect.stack()[-1][1])
+
+
+def get_log_file_path(binary=None):
+ if FLAGS.logfile:
+ return FLAGS.logfile
+ if FLAGS.logdir:
+ binary = binary or _get_binary_name()
+ return '%s.log' % (os.path.join(FLAGS.logdir, binary),)
+
+
def basicConfig():
logging.basicConfig()
for handler in logging.root.handlers:
@@ -123,8 +137,9 @@ def basicConfig():
syslog = SysLogHandler(address='/dev/log')
syslog.setFormatter(_formatter)
logging.root.addHandler(syslog)
- if FLAGS.logfile:
- logfile = FileHandler(FLAGS.logfile)
+ logpath = get_log_file_path()
+ if logpath:
+ logfile = WatchedFileHandler(logpath)
logfile.setFormatter(_formatter)
logging.root.addHandler(logfile)
diff --git a/nova/network/linux_net.py b/nova/network/linux_net.py
index c1cbff7d8..535ce87bc 100644
--- a/nova/network/linux_net.py
+++ b/nova/network/linux_net.py
@@ -44,7 +44,7 @@ flags.DEFINE_string('dhcp_domain',
flags.DEFINE_string('networks_path', '$state_path/networks',
'Location to keep network config files')
-flags.DEFINE_string('public_interface', 'vlan1',
+flags.DEFINE_string('public_interface', 'eth0',
'Interface for public IP addresses')
flags.DEFINE_string('vlan_interface', 'eth0',
'network device for vlans')
diff --git a/nova/network/manager.py b/nova/network/manager.py
index 8eb9f041b..c6eba225e 100644
--- a/nova/network/manager.py
+++ b/nova/network/manager.py
@@ -110,6 +110,7 @@ class NetworkManager(manager.Manager):
This class must be subclassed to support specific topologies.
"""
+ timeout_fixed_ips = True
def __init__(self, network_driver=None, *args, **kwargs):
if not network_driver:
@@ -138,6 +139,19 @@ class NetworkManager(manager.Manager):
self.driver.ensure_floating_forward(floating_ip['address'],
fixed_address)
+ def periodic_tasks(self, context=None):
+ """Tasks to be run at a periodic interval."""
+ super(NetworkManager, self).periodic_tasks(context)
+ if self.timeout_fixed_ips:
+ now = utils.utcnow()
+ timeout = FLAGS.fixed_ip_disassociate_timeout
+ time = now - datetime.timedelta(seconds=timeout)
+ num = self.db.fixed_ip_disassociate_all_by_timeout(context,
+ self.host,
+ time)
+ if num:
+ LOG.debug(_("Dissassociated %s stale fixed ip(s)"), num)
+
def set_network_host(self, context, network_id):
"""Safely sets the host of the network."""
LOG.debug(_("setting network host"), context=context)
@@ -306,6 +320,7 @@ class FlatManager(NetworkManager):
not do any setup in this mode, it must be done manually. Requests to
169.254.169.254 port 80 will need to be forwarded to the api server.
"""
+ timeout_fixed_ips = False
def allocate_fixed_ip(self, context, instance_id, *args, **kwargs):
"""Gets a fixed ip from the pool."""
@@ -331,11 +346,12 @@ class FlatManager(NetworkManager):
pass
def create_networks(self, context, cidr, num_networks, network_size,
- cidr_v6, *args, **kwargs):
+ cidr_v6, label, *args, **kwargs):
"""Create networks based on parameters."""
fixed_net = IPy.IP(cidr)
fixed_net_v6 = IPy.IP(cidr_v6)
significant_bits_v6 = 64
+ count = 1
for index in range(num_networks):
start = index * network_size
significant_bits = 32 - int(math.log(network_size, 2))
@@ -348,6 +364,11 @@ class FlatManager(NetworkManager):
net['gateway'] = str(project_net[1])
net['broadcast'] = str(project_net.broadcast())
net['dhcp_start'] = str(project_net[2])
+ if num_networks > 1:
+ net['label'] = "%s_%d" % (label, count)
+ else:
+ net['label'] = label
+ count += 1
if(FLAGS.use_ipv6):
cidr_v6 = "%s/%s" % (fixed_net_v6[0], significant_bits_v6)
@@ -451,18 +472,6 @@ class VlanManager(NetworkManager):
instances in its subnet.
"""
- def periodic_tasks(self, context=None):
- """Tasks to be run at a periodic interval."""
- super(VlanManager, self).periodic_tasks(context)
- now = datetime.datetime.utcnow()
- timeout = FLAGS.fixed_ip_disassociate_timeout
- time = now - datetime.timedelta(seconds=timeout)
- num = self.db.fixed_ip_disassociate_all_by_timeout(context,
- self.host,
- time)
- if num:
- LOG.debug(_("Dissassociated %s stale fixed ip(s)"), num)
-
def init_host(self):
"""Do any initialization that needs to be run if this is a
standalone service.
@@ -503,8 +512,14 @@ class VlanManager(NetworkManager):
network_ref['bridge'])
def create_networks(self, context, cidr, num_networks, network_size,
- cidr_v6, vlan_start, vpn_start):
+ cidr_v6, vlan_start, vpn_start, **kwargs):
"""Create networks based on parameters."""
+ # Check that num_networks + vlan_start is not > 4094, fixes lp708025
+ if num_networks + vlan_start > 4094:
+ raise ValueError(_('The sum between the number of networks and'
+ ' the vlan start cannot be greater'
+ ' than 4094'))
+
fixed_net = IPy.IP(cidr)
fixed_net_v6 = IPy.IP(cidr_v6)
network_size_v6 = 1 << 64
diff --git a/nova/objectstore/bucket.py b/nova/objectstore/bucket.py
index 82767e52f..b213e18e8 100644
--- a/nova/objectstore/bucket.py
+++ b/nova/objectstore/bucket.py
@@ -107,7 +107,7 @@ class Bucket(object):
def is_authorized(self, context):
try:
- return context.user.is_admin() or \
+ return context.is_admin or \
self.owner_id == context.project_id
except Exception, e:
return False
diff --git a/nova/objectstore/image.py b/nova/objectstore/image.py
index 41e0abd80..27227e2ca 100644
--- a/nova/objectstore/image.py
+++ b/nova/objectstore/image.py
@@ -69,7 +69,7 @@ class Image(object):
# but only modified by admin or owner.
try:
return (self.metadata['isPublic'] and readonly) or \
- context.user.is_admin() or \
+ context.is_admin or \
self.metadata['imageOwnerId'] == context.project_id
except:
return False
diff --git a/nova/rpc.py b/nova/rpc.py
index 2b1f7298b..205bb524a 100644
--- a/nova/rpc.py
+++ b/nova/rpc.py
@@ -29,6 +29,7 @@ import uuid
from carrot import connection as carrot_connection
from carrot import messaging
+from eventlet import greenpool
from eventlet import greenthread
from nova import context
@@ -42,6 +43,8 @@ from nova import utils
FLAGS = flags.FLAGS
LOG = logging.getLogger('nova.rpc')
+flags.DEFINE_integer('rpc_thread_pool_size', 1024, 'Size of RPC thread pool')
+
class Connection(carrot_connection.BrokerConnection):
"""Connection instance object"""
@@ -155,11 +158,15 @@ class AdapterConsumer(TopicConsumer):
def __init__(self, connection=None, topic="broadcast", proxy=None):
LOG.debug(_('Initing the Adapter Consumer for %s') % topic)
self.proxy = proxy
+ self.pool = greenpool.GreenPool(FLAGS.rpc_thread_pool_size)
super(AdapterConsumer, self).__init__(connection=connection,
topic=topic)
+ def receive(self, *args, **kwargs):
+ self.pool.spawn_n(self._receive, *args, **kwargs)
+
@exception.wrap_exception
- def receive(self, message_data, message):
+ def _receive(self, message_data, message):
"""Magically looks for a method on the proxy object and calls it
Message data should be a dictionary with two keys:
diff --git a/nova/tests/api/openstack/__init__.py b/nova/tests/api/openstack/__init__.py
index 14eaaa62c..77b1dd37f 100644
--- a/nova/tests/api/openstack/__init__.py
+++ b/nova/tests/api/openstack/__init__.py
@@ -92,31 +92,3 @@ class RateLimitingMiddlewareTest(unittest.TestCase):
self.assertEqual(middleware.limiter.__class__.__name__, "Limiter")
middleware = RateLimitingMiddleware(simple_wsgi, service_host='foobar')
self.assertEqual(middleware.limiter.__class__.__name__, "WSGIAppProxy")
-
-
-class LimiterTest(unittest.TestCase):
-
- def test_limiter(self):
- items = range(2000)
- req = Request.blank('/')
- self.assertEqual(limited(items, req), items[:1000])
- req = Request.blank('/?offset=0')
- self.assertEqual(limited(items, req), items[:1000])
- req = Request.blank('/?offset=3')
- self.assertEqual(limited(items, req), items[3:1003])
- req = Request.blank('/?offset=2005')
- self.assertEqual(limited(items, req), [])
- req = Request.blank('/?limit=10')
- self.assertEqual(limited(items, req), items[:10])
- req = Request.blank('/?limit=0')
- self.assertEqual(limited(items, req), items[:1000])
- req = Request.blank('/?limit=3000')
- self.assertEqual(limited(items, req), items[:1000])
- req = Request.blank('/?offset=1&limit=3')
- self.assertEqual(limited(items, req), items[1:4])
- req = Request.blank('/?offset=3&limit=0')
- self.assertEqual(limited(items, req), items[3:1003])
- req = Request.blank('/?offset=3&limit=1500')
- self.assertEqual(limited(items, req), items[3:1003])
- req = Request.blank('/?offset=3000&limit=10')
- self.assertEqual(limited(items, req), [])
diff --git a/nova/tests/api/openstack/test_common.py b/nova/tests/api/openstack/test_common.py
new file mode 100644
index 000000000..9d9837cc9
--- /dev/null
+++ b/nova/tests/api/openstack/test_common.py
@@ -0,0 +1,161 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2010 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.
+
+"""
+Test suites for 'common' code used throughout the OpenStack HTTP API.
+"""
+
+import unittest
+
+from webob import Request
+
+from nova.api.openstack.common import limited
+
+
+class LimiterTest(unittest.TestCase):
+ """
+ Unit tests for the `nova.api.openstack.common.limited` method which takes
+ in a list of items and, depending on the 'offset' and 'limit' GET params,
+ returns a subset or complete set of the given items.
+ """
+
+ def setUp(self):
+ """
+ Run before each test.
+ """
+ self.tiny = range(1)
+ self.small = range(10)
+ self.medium = range(1000)
+ self.large = range(10000)
+
+ def test_limiter_offset_zero(self):
+ """
+ Test offset key works with 0.
+ """
+ req = Request.blank('/?offset=0')
+ self.assertEqual(limited(self.tiny, req), self.tiny)
+ self.assertEqual(limited(self.small, req), self.small)
+ self.assertEqual(limited(self.medium, req), self.medium)
+ self.assertEqual(limited(self.large, req), self.large[:1000])
+
+ def test_limiter_offset_medium(self):
+ """
+ Test offset key works with a medium sized number.
+ """
+ req = Request.blank('/?offset=10')
+ self.assertEqual(limited(self.tiny, req), [])
+ self.assertEqual(limited(self.small, req), self.small[10:])
+ self.assertEqual(limited(self.medium, req), self.medium[10:])
+ self.assertEqual(limited(self.large, req), self.large[10:1010])
+
+ def test_limiter_offset_over_max(self):
+ """
+ Test offset key works with a number over 1000 (max_limit).
+ """
+ req = Request.blank('/?offset=1001')
+ self.assertEqual(limited(self.tiny, req), [])
+ self.assertEqual(limited(self.small, req), [])
+ self.assertEqual(limited(self.medium, req), [])
+ self.assertEqual(limited(self.large, req), self.large[1001:2001])
+
+ def test_limiter_offset_blank(self):
+ """
+ Test offset key works with a blank offset.
+ """
+ req = Request.blank('/?offset=')
+ self.assertEqual(limited(self.tiny, req), self.tiny)
+ self.assertEqual(limited(self.small, req), self.small)
+ self.assertEqual(limited(self.medium, req), self.medium)
+ self.assertEqual(limited(self.large, req), self.large[:1000])
+
+ def test_limiter_offset_bad(self):
+ """
+ Test offset key works with a BAD offset.
+ """
+ req = Request.blank(u'/?offset=\u0020aa')
+ self.assertEqual(limited(self.tiny, req), self.tiny)
+ self.assertEqual(limited(self.small, req), self.small)
+ self.assertEqual(limited(self.medium, req), self.medium)
+ self.assertEqual(limited(self.large, req), self.large[:1000])
+
+ def test_limiter_nothing(self):
+ """
+ Test request with no offset or limit
+ """
+ req = Request.blank('/')
+ self.assertEqual(limited(self.tiny, req), self.tiny)
+ self.assertEqual(limited(self.small, req), self.small)
+ self.assertEqual(limited(self.medium, req), self.medium)
+ self.assertEqual(limited(self.large, req), self.large[:1000])
+
+ def test_limiter_limit_zero(self):
+ """
+ Test limit of zero.
+ """
+ req = Request.blank('/?limit=0')
+ self.assertEqual(limited(self.tiny, req), self.tiny)
+ self.assertEqual(limited(self.small, req), self.small)
+ self.assertEqual(limited(self.medium, req), self.medium)
+ self.assertEqual(limited(self.large, req), self.large[:1000])
+
+ def test_limiter_limit_medium(self):
+ """
+ Test limit of 10.
+ """
+ req = Request.blank('/?limit=10')
+ self.assertEqual(limited(self.tiny, req), self.tiny)
+ self.assertEqual(limited(self.small, req), self.small)
+ self.assertEqual(limited(self.medium, req), self.medium[:10])
+ self.assertEqual(limited(self.large, req), self.large[:10])
+
+ def test_limiter_limit_over_max(self):
+ """
+ Test limit of 3000.
+ """
+ req = Request.blank('/?limit=3000')
+ self.assertEqual(limited(self.tiny, req), self.tiny)
+ self.assertEqual(limited(self.small, req), self.small)
+ self.assertEqual(limited(self.medium, req), self.medium)
+ self.assertEqual(limited(self.large, req), self.large[:1000])
+
+ def test_limiter_limit_and_offset(self):
+ """
+ Test request with both limit and offset.
+ """
+ items = range(2000)
+ req = Request.blank('/?offset=1&limit=3')
+ self.assertEqual(limited(items, req), items[1:4])
+ req = Request.blank('/?offset=3&limit=0')
+ self.assertEqual(limited(items, req), items[3:1003])
+ req = Request.blank('/?offset=3&limit=1500')
+ self.assertEqual(limited(items, req), items[3:1003])
+ req = Request.blank('/?offset=3000&limit=10')
+ self.assertEqual(limited(items, req), [])
+
+ def test_limiter_custom_max_limit(self):
+ """
+ Test a max_limit other than 1000.
+ """
+ items = range(2000)
+ req = Request.blank('/?offset=1&limit=3')
+ self.assertEqual(limited(items, req, max_limit=2000), items[1:4])
+ req = Request.blank('/?offset=3&limit=0')
+ self.assertEqual(limited(items, req, max_limit=2000), items[3:])
+ req = Request.blank('/?offset=3&limit=2500')
+ self.assertEqual(limited(items, req, max_limit=2000), items[3:])
+ req = Request.blank('/?offset=3000&limit=10')
+ self.assertEqual(limited(items, req, max_limit=2000), [])
diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py
index 724f14f19..a7be0796e 100644
--- a/nova/tests/api/openstack/test_servers.py
+++ b/nova/tests/api/openstack/test_servers.py
@@ -15,6 +15,7 @@
# License for the specific language governing permissions and limitations
# under the License.
+import datetime
import json
import unittest
@@ -39,6 +40,13 @@ def return_server(context, id):
return stub_instance(id)
+def return_server_with_addresses(private, public):
+ def _return_server(context, id):
+ return stub_instance(id, private_address=private,
+ public_addresses=public)
+ return _return_server
+
+
def return_servers(context, user_id=1):
return [stub_instance(i, user_id) for i in xrange(5)]
@@ -55,9 +63,45 @@ def instance_address(context, instance_id):
return None
-def stub_instance(id, user_id=1):
- return Instance(id=id, state=0, image_id=10, user_id=user_id,
- display_name='server%s' % id)
+def stub_instance(id, user_id=1, private_address=None, public_addresses=None):
+ if public_addresses == None:
+ public_addresses = list()
+
+ instance = {
+ "id": id,
+ "admin_pass": "",
+ "user_id": user_id,
+ "project_id": "",
+ "image_id": 10,
+ "kernel_id": "",
+ "ramdisk_id": "",
+ "launch_index": 0,
+ "key_name": "",
+ "key_data": "",
+ "state": 0,
+ "state_description": "",
+ "memory_mb": 0,
+ "vcpus": 0,
+ "local_gb": 0,
+ "hostname": "",
+ "host": "",
+ "instance_type": "",
+ "user_data": "",
+ "reservation_id": "",
+ "mac_address": "",
+ "scheduled_at": datetime.datetime.now(),
+ "launched_at": datetime.datetime.now(),
+ "terminated_at": datetime.datetime.now(),
+ "availability_zone": "",
+ "display_name": "server%s" % id,
+ "display_description": "",
+ "locked": False}
+
+ instance["fixed_ip"] = {
+ "address": private_address,
+ "floating_ips": [{"address":ip} for ip in public_addresses]}
+
+ return instance
def fake_compute_api(cls, req, id):
@@ -105,6 +149,22 @@ class ServersTest(unittest.TestCase):
self.assertEqual(res_dict['server']['id'], '1')
self.assertEqual(res_dict['server']['name'], 'server1')
+ def test_get_server_by_id_with_addresses(self):
+ private = "192.168.0.3"
+ public = ["1.2.3.4"]
+ new_return_server = return_server_with_addresses(private, public)
+ self.stubs.Set(nova.db.api, 'instance_get', new_return_server)
+ req = webob.Request.blank('/v1.0/servers/1')
+ res = req.get_response(fakes.wsgi_app())
+ res_dict = json.loads(res.body)
+ self.assertEqual(res_dict['server']['id'], '1')
+ self.assertEqual(res_dict['server']['name'], 'server1')
+ addresses = res_dict['server']['addresses']
+ self.assertEqual(len(addresses["public"]), len(public))
+ self.assertEqual(addresses["public"][0], public[0])
+ self.assertEqual(len(addresses["private"]), 1)
+ self.assertEqual(addresses["private"][0], private)
+
def test_get_server_list(self):
req = webob.Request.blank('/v1.0/servers')
res = req.get_response(fakes.wsgi_app())
@@ -281,6 +341,18 @@ class ServersTest(unittest.TestCase):
res = req.get_response(fakes.wsgi_app())
self.assertEqual(res.status_int, 202)
+ def test_server_reset_network(self):
+ FLAGS.allow_admin_api = True
+ body = dict(server=dict(
+ name='server_test', imageId=2, flavorId=2, metadata={},
+ personality={}))
+ req = webob.Request.blank('/v1.0/servers/1/reset_network')
+ 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)
+
def test_server_diagnostics(self):
req = webob.Request.blank("/v1.0/servers/1/diagnostics")
req.method = "GET"
diff --git a/nova/tests/api/openstack/test_zones.py b/nova/tests/api/openstack/test_zones.py
new file mode 100644
index 000000000..df497ef1b
--- /dev/null
+++ b/nova/tests/api/openstack/test_zones.py
@@ -0,0 +1,140 @@
+# Copyright 2010 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 unittest
+
+import stubout
+import webob
+import json
+
+import nova.db
+from nova import context
+from nova import flags
+from nova.api.openstack import zones
+from nova.tests.api.openstack import fakes
+
+
+FLAGS = flags.FLAGS
+FLAGS.verbose = True
+
+
+def zone_get(context, zone_id):
+ return dict(id=1, api_url='http://foo.com', username='bob',
+ password='xxx')
+
+
+def zone_create(context, values):
+ zone = dict(id=1)
+ zone.update(values)
+ return zone
+
+
+def zone_update(context, zone_id, values):
+ zone = dict(id=zone_id, api_url='http://foo.com', username='bob',
+ password='xxx')
+ zone.update(values)
+ return zone
+
+
+def zone_delete(context, zone_id):
+ pass
+
+
+def zone_get_all(context):
+ return [
+ dict(id=1, api_url='http://foo.com', username='bob',
+ password='xxx'),
+ dict(id=2, api_url='http://blah.com', username='alice',
+ password='qwerty')]
+
+
+class ZonesTest(unittest.TestCase):
+ def setUp(self):
+ self.stubs = stubout.StubOutForTesting()
+ fakes.FakeAuthManager.auth_data = {}
+ fakes.FakeAuthDatabase.data = {}
+ fakes.stub_out_networking(self.stubs)
+ fakes.stub_out_rate_limiting(self.stubs)
+ fakes.stub_out_auth(self.stubs)
+
+ self.allow_admin = FLAGS.allow_admin_api
+ FLAGS.allow_admin_api = True
+
+ self.stubs.Set(nova.db, 'zone_get', zone_get)
+ self.stubs.Set(nova.db, 'zone_get_all', zone_get_all)
+ self.stubs.Set(nova.db, 'zone_update', zone_update)
+ self.stubs.Set(nova.db, 'zone_create', zone_create)
+ self.stubs.Set(nova.db, 'zone_delete', zone_delete)
+
+ def tearDown(self):
+ self.stubs.UnsetAll()
+ FLAGS.allow_admin_api = self.allow_admin
+
+ def test_get_zone_list(self):
+ req = webob.Request.blank('/v1.0/zones')
+ res = req.get_response(fakes.wsgi_app())
+ res_dict = json.loads(res.body)
+
+ self.assertEqual(res.status_int, 200)
+ self.assertEqual(len(res_dict['zones']), 2)
+
+ def test_get_zone_by_id(self):
+ req = webob.Request.blank('/v1.0/zones/1')
+ res = req.get_response(fakes.wsgi_app())
+ res_dict = json.loads(res.body)
+
+ self.assertEqual(res_dict['zone']['id'], 1)
+ self.assertEqual(res_dict['zone']['api_url'], 'http://foo.com')
+ self.assertFalse('password' in res_dict['zone'])
+ self.assertEqual(res.status_int, 200)
+
+ def test_zone_delete(self):
+ req = webob.Request.blank('/v1.0/zones/1')
+ res = req.get_response(fakes.wsgi_app())
+
+ self.assertEqual(res.status_int, 200)
+
+ def test_zone_create(self):
+ body = dict(zone=dict(api_url='http://blah.zoo', username='fred',
+ password='fubar'))
+ req = webob.Request.blank('/v1.0/zones')
+ req.method = 'POST'
+ req.body = json.dumps(body)
+
+ res = req.get_response(fakes.wsgi_app())
+ res_dict = json.loads(res.body)
+
+ self.assertEqual(res.status_int, 200)
+ self.assertEqual(res_dict['zone']['id'], 1)
+ self.assertEqual(res_dict['zone']['api_url'], 'http://blah.zoo')
+ self.assertFalse('username' in res_dict['zone'])
+
+ def test_zone_update(self):
+ body = dict(zone=dict(username='zeb', password='sneaky'))
+ req = webob.Request.blank('/v1.0/zones/1')
+ req.method = 'PUT'
+ req.body = json.dumps(body)
+
+ res = req.get_response(fakes.wsgi_app())
+ res_dict = json.loads(res.body)
+
+ self.assertEqual(res.status_int, 200)
+ self.assertEqual(res_dict['zone']['id'], 1)
+ self.assertEqual(res_dict['zone']['api_url'], 'http://foo.com')
+ self.assertFalse('username' in res_dict['zone'])
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/nova/tests/test_compute.py b/nova/tests/test_compute.py
index 2aa0690e7..b049ac943 100644
--- a/nova/tests/test_compute.py
+++ b/nova/tests/test_compute.py
@@ -202,6 +202,14 @@ class ComputeTestCase(test.TestCase):
self.compute.set_admin_password(self.context, instance_id)
self.compute.terminate_instance(self.context, instance_id)
+ def test_inject_file(self):
+ """Ensure we can write a file to an instance"""
+ instance_id = self._create_instance()
+ self.compute.run_instance(self.context, instance_id)
+ self.compute.inject_file(self.context, instance_id, "/tmp/test",
+ "File Contents")
+ self.compute.terminate_instance(self.context, instance_id)
+
def test_snapshot(self):
"""Ensure instance can be snapshotted"""
instance_id = self._create_instance()
diff --git a/nova/tests/test_log.py b/nova/tests/test_log.py
index 868a5ead3..c2c9d7772 100644
--- a/nova/tests/test_log.py
+++ b/nova/tests/test_log.py
@@ -46,6 +46,27 @@ class RootLoggerTestCase(test.TestCase):
self.assert_(True) # didn't raise exception
+class LogHandlerTestCase(test.TestCase):
+ def test_log_path_logdir(self):
+ self.flags(logdir='/some/path')
+ self.assertEquals(log.get_log_file_path(binary='foo-bar'),
+ '/some/path/foo-bar.log')
+
+ def test_log_path_logfile(self):
+ self.flags(logfile='/some/path/foo-bar.log')
+ self.assertEquals(log.get_log_file_path(binary='foo-bar'),
+ '/some/path/foo-bar.log')
+
+ def test_log_path_none(self):
+ self.assertTrue(log.get_log_file_path(binary='foo-bar') is None)
+
+ def test_log_path_logfile_overrides_logdir(self):
+ self.flags(logdir='/some/other/path',
+ logfile='/some/path/foo-bar.log')
+ self.assertEquals(log.get_log_file_path(binary='foo-bar'),
+ '/some/path/foo-bar.log')
+
+
class NovaFormatterTestCase(test.TestCase):
def setUp(self):
super(NovaFormatterTestCase, self).setUp()
diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py
index 9f5b266f3..6b8efc9d8 100644
--- a/nova/tests/test_xenapi.py
+++ b/nova/tests/test_xenapi.py
@@ -32,6 +32,7 @@ from nova.virt import xenapi_conn
from nova.virt.xenapi import fake as xenapi_fake
from nova.virt.xenapi import volume_utils
from nova.virt.xenapi.vmops import SimpleDH
+from nova.virt.xenapi.vmops import VMOps
from nova.tests.db import fakes as db_fakes
from nova.tests.xenapi import stubs
from nova.tests.glance import stubs as glance_stubs
@@ -141,6 +142,10 @@ class XenAPIVolumeTestCase(test.TestCase):
self.stubs.UnsetAll()
+def reset_network(*args):
+ pass
+
+
class XenAPIVMTestCase(test.TestCase):
"""
Unit tests for VM operations
@@ -162,6 +167,7 @@ class XenAPIVMTestCase(test.TestCase):
stubs.stubout_session(self.stubs, stubs.FakeSessionForVMTests)
stubs.stubout_get_this_vm_uuid(self.stubs)
stubs.stubout_stream_disk(self.stubs)
+ self.stubs.Set(VMOps, 'reset_network', reset_network)
glance_stubs.stubout_glance_client(self.stubs,
glance_stubs.FakeGlance)
self.conn = xenapi_conn.get_connection(False)
@@ -243,7 +249,8 @@ class XenAPIVMTestCase(test.TestCase):
# Check that the VM is running according to XenAPI.
self.assertEquals(vm['power_state'], 'Running')
- def _test_spawn(self, image_id, kernel_id, ramdisk_id):
+ def _test_spawn(self, image_id, kernel_id, ramdisk_id,
+ instance_type="m1.large"):
stubs.stubout_session(self.stubs, stubs.FakeSessionForVMTests)
values = {'name': 1,
'id': 1,
@@ -252,7 +259,7 @@ class XenAPIVMTestCase(test.TestCase):
'image_id': image_id,
'kernel_id': kernel_id,
'ramdisk_id': ramdisk_id,
- 'instance_type': 'm1.large',
+ 'instance_type': instance_type,
'mac_address': 'aa:bb:cc:dd:ee:ff',
}
conn = xenapi_conn.get_connection(False)
@@ -260,6 +267,12 @@ class XenAPIVMTestCase(test.TestCase):
conn.spawn(instance)
self.check_vm_record(conn)
+ def test_spawn_not_enough_memory(self):
+ FLAGS.xenapi_image_service = 'glance'
+ self.assertRaises(Exception,
+ self._test_spawn,
+ 1, 2, 3, "m1.xlarge")
+
def test_spawn_raw_objectstore(self):
FLAGS.xenapi_image_service = 'objectstore'
self._test_spawn(1, None, None)
diff --git a/nova/twistd.py b/nova/twistd.py
index 6390a8144..60ff7879a 100644
--- a/nova/twistd.py
+++ b/nova/twistd.py
@@ -43,8 +43,6 @@ else:
FLAGS = flags.FLAGS
-flags.DEFINE_string('logdir', None, 'directory to keep log files in '
- '(will be prepended to $logfile)')
class TwistdServerOptions(ServerOptions):
diff --git a/nova/utils.py b/nova/utils.py
index 8d7ff1f64..42efa0008 100644
--- a/nova/utils.py
+++ b/nova/utils.py
@@ -20,13 +20,14 @@
System-level utilities and helper functions.
"""
+import base64
import datetime
import inspect
import json
import os
import random
-import subprocess
import socket
+import string
import struct
import sys
import time
@@ -36,6 +37,7 @@ import netaddr
from eventlet import event
from eventlet import greenthread
+from eventlet.green import subprocess
from nova import exception
from nova.exception import ProcessExecutionError
@@ -235,6 +237,15 @@ def generate_mac():
return ':'.join(map(lambda x: "%02x" % x, mac))
+def generate_password(length=20):
+ """Generate a random sequence of letters and digits
+ to be used as a password. Note that this is not intended
+ to represent the ultimate in security.
+ """
+ chrs = string.letters + string.digits
+ return "".join([random.choice(chrs) for i in xrange(length)])
+
+
def last_octet(address):
return int(address.split(".")[-1])
@@ -476,3 +487,15 @@ def dumps(value):
def loads(s):
return json.loads(s)
+
+
+def ensure_b64_encoding(val):
+ """Safety method to ensure that values expected to be base64-encoded
+ actually are. If they are, the value is returned unchanged. Otherwise,
+ the encoded value is returned.
+ """
+ try:
+ dummy = base64.decode(val)
+ return val
+ except TypeError:
+ return base64.b64encode(val)
diff --git a/nova/virt/fake.py b/nova/virt/fake.py
index 161445b86..92749f38a 100644
--- a/nova/virt/fake.py
+++ b/nova/virt/fake.py
@@ -152,6 +152,21 @@ class FakeConnection(object):
"""
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 rescue(self, instance):
"""
Rescue the specified instance.
diff --git a/nova/virt/xenapi/fake.py b/nova/virt/xenapi/fake.py
index e8352771c..018d0dcd3 100644
--- a/nova/virt/xenapi/fake.py
+++ b/nova/virt/xenapi/fake.py
@@ -286,6 +286,10 @@ class SessionBase(object):
rec['currently_attached'] = False
rec['device'] = ''
+ def host_compute_free_memory(self, _1, ref):
+ #Always return 12GB available
+ return 12 * 1024 * 1024 * 1024
+
def xenapi_request(self, methodname, params):
if methodname.startswith('login'):
self._login(methodname, params)
diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py
index f5c19099a..80cc3035d 100644
--- a/nova/virt/xenapi/vm_utils.py
+++ b/nova/virt/xenapi/vm_utils.py
@@ -139,6 +139,16 @@ class VMHelper(HelperBase):
return vm_ref
@classmethod
+ def ensure_free_mem(cls, session, instance):
+ instance_type = instance_types.INSTANCE_TYPES[instance.instance_type]
+ mem = long(instance_type['memory_mb']) * 1024 * 1024
+ #get free memory from host
+ host = session.get_xenapi_host()
+ host_free_mem = long(session.get_xenapi().host.
+ compute_free_memory(host))
+ return host_free_mem >= mem
+
+ @classmethod
def create_vbd(cls, session, vm_ref, vdi_ref, userdevice, bootable):
"""Create a VBD record. Returns a Deferred that gives the new
VBD reference."""
@@ -384,7 +394,7 @@ class VMHelper(HelperBase):
pv = True
elif pv_str.lower() == 'false':
pv = False
- LOG.debug(_("PV Kernel in VDI:%d"), pv)
+ LOG.debug(_("PV Kernel in VDI:%s"), pv)
return pv
@classmethod
diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py
index fe95d881b..0168681f6 100644
--- a/nova/virt/xenapi/vmops.py
+++ b/nova/virt/xenapi/vmops.py
@@ -67,13 +67,19 @@ class VMOps(object):
raise exception.Duplicate(_('Attempted to create'
' non-unique name %s') % instance.name)
- bridge = db.network_get_by_instance(context.get_admin_context(),
- instance['id'])['bridge']
- network_ref = \
- NetworkHelper.find_network_with_bridge(self._session, bridge)
+ #ensure enough free memory is available
+ if not VMHelper.ensure_free_mem(self._session, instance):
+ name = instance['name']
+ LOG.exception(_('instance %(name)s: not enough free memory')
+ % locals())
+ db.instance_set_state(context.get_admin_context(),
+ instance['id'],
+ power_state.SHUTDOWN)
+ return
user = AuthManager().get_user(instance.user_id)
project = AuthManager().get_project(instance.project_id)
+
#if kernel is not present we must download a raw disk
if instance.kernel_id:
disk_image_type = ImageType.DISK
@@ -99,16 +105,70 @@ class VMOps(object):
instance, kernel, ramdisk, pv_kernel)
VMHelper.create_vbd(self._session, vm_ref, vdi_ref, 0, True)
- if network_ref:
- VMHelper.create_vif(self._session, vm_ref,
- network_ref, instance.mac_address)
+ # write network info
+ admin_context = context.get_admin_context()
+
+ # TODO(tr3buchet) - remove comment in multi-nic
+ # I've decided to go ahead and consider multiple IPs and networks
+ # at this stage even though they aren't implemented because these will
+ # be needed for multi-nic and there was no sense writing it for single
+ # network/single IP and then having to turn around and re-write it
+ IPs = db.fixed_ip_get_all_by_instance(admin_context, instance['id'])
+ for network in db.network_get_all_by_instance(admin_context,
+ instance['id']):
+ network_IPs = [ip for ip in IPs if ip.network_id == network.id]
+
+ def ip_dict(ip):
+ return {'netmask': network['netmask'],
+ 'enabled': '1',
+ 'ip': ip.address}
+
+ mac_id = instance.mac_address.replace(':', '')
+ location = 'vm-data/networking/%s' % mac_id
+ mapping = {'label': network['label'],
+ 'gateway': network['gateway'],
+ 'mac': instance.mac_address,
+ 'dns': [network['dns']],
+ 'ips': [ip_dict(ip) for ip in network_IPs]}
+ self.write_to_param_xenstore(vm_ref, {location: mapping})
+
+ # TODO(tr3buchet) - remove comment in multi-nic
+ # this bit here about creating the vifs will be updated
+ # in multi-nic to handle multiple IPs on the same network
+ # and multiple networks
+ # for now it works as there is only one of each
+ bridge = network['bridge']
+ network_ref = \
+ NetworkHelper.find_network_with_bridge(self._session, bridge)
+
+ if network_ref:
+ VMHelper.create_vif(self._session, vm_ref,
+ network_ref, instance.mac_address)
+
LOG.debug(_('Starting VM %s...'), vm_ref)
self._session.call_xenapi('VM.start', vm_ref, False, False)
instance_name = instance.name
LOG.info(_('Spawning VM %(instance_name)s created %(vm_ref)s.')
% locals())
+ def _inject_onset_files():
+ onset_files = instance.onset_files
+ if onset_files:
+ # Check if this is a JSON-encoded string and convert if needed.
+ if isinstance(onset_files, basestring):
+ try:
+ onset_files = json.loads(onset_files)
+ except ValueError:
+ LOG.exception(_("Invalid value for onset_files: '%s'")
+ % onset_files)
+ onset_files = []
+ # Inject any files, if specified
+ for path, contents in instance.onset_files:
+ LOG.debug(_("Injecting file path: '%s'") % path)
+ self.inject_file(instance, path, contents)
# NOTE(armando): Do we really need to do this in virt?
+ # NOTE(tr3buchet): not sure but wherever we do it, we need to call
+ # reset_network afterwards
timer = utils.LoopingCall(f=None)
def _wait_for_boot():
@@ -119,6 +179,8 @@ class VMOps(object):
if state == power_state.RUNNING:
LOG.debug(_('Instance %s: booted'), instance['name'])
timer.stop()
+ _inject_onset_files()
+ return True
except Exception, exc:
LOG.warn(exc)
LOG.exception(_('instance %s: failed to boot'),
@@ -127,8 +189,13 @@ class VMOps(object):
instance['id'],
power_state.SHUTDOWN)
timer.stop()
+ return False
timer.f = _wait_for_boot
+
+ # call reset networking
+ self.reset_network(instance)
+
return timer.start(interval=0.5, now=True)
def _get_vm_opaque_ref(self, instance_or_vm):
@@ -161,7 +228,8 @@ class VMOps(object):
instance_name = instance_or_vm.name
vm = VMHelper.lookup(self._session, instance_name)
if vm is None:
- raise Exception(_('Instance not present %s') % instance_name)
+ raise exception.NotFound(
+ _('Instance not present %s') % instance_name)
return vm
def snapshot(self, instance, image_id):
@@ -255,6 +323,32 @@ class VMOps(object):
raise RuntimeError(resp_dict['message'])
return resp_dict['message']
+ def inject_file(self, instance, b64_path, b64_contents):
+ """Write a file to the VM instance. The path to which it is to be
+ written and the contents of the file need to be supplied; both should
+ be base64-encoded to prevent errors with non-ASCII characters being
+ transmitted. If the agent does not support file injection, or the user
+ has disabled it, a NotImplementedError will be raised.
+ """
+ # Files/paths *should* be base64-encoded at this point, but
+ # double-check to make sure.
+ b64_path = utils.ensure_b64_encoding(b64_path)
+ b64_contents = utils.ensure_b64_encoding(b64_contents)
+
+ # Need to uniquely identify this request.
+ transaction_id = str(uuid.uuid4())
+ args = {'id': transaction_id, 'b64_path': b64_path,
+ 'b64_contents': b64_contents}
+ # If the agent doesn't support file injection, a NotImplementedError
+ # will be raised with the appropriate message.
+ resp = self._make_agent_call('inject_file', instance, '', args)
+ resp_dict = json.loads(resp)
+ if resp_dict['returncode'] != '0':
+ # There was some other sort of error; the message will contain
+ # a description of the error.
+ raise RuntimeError(resp_dict['message'])
+ return resp_dict['message']
+
def _shutdown(self, instance, vm):
"""Shutdown an instance """
state = self.get_info(instance['name'])['state']
@@ -389,6 +483,14 @@ class VMOps(object):
# TODO: implement this!
return 'http://fakeajaxconsole/fake_url'
+ def reset_network(self, instance):
+ """
+ Creates uuid arg to pass to make_agent_call and calls it.
+
+ """
+ args = {'id': str(uuid.uuid4())}
+ resp = self._make_agent_call('resetnetwork', instance, '', args)
+
def list_from_xenstore(self, vm, path):
"""Runs the xenstore-ls command to get a listing of all records
from 'path' downward. Returns a dict with the sub-paths as keys,
@@ -458,6 +560,11 @@ class VMOps(object):
if 'TIMEOUT:' in err_msg:
LOG.error(_('TIMEOUT: The call to %(method)s timed out. '
'VM id=%(instance_id)s; args=%(strargs)s') % locals())
+ elif 'NOT IMPLEMENTED:' in err_msg:
+ LOG.error(_('NOT IMPLEMENTED: The call to %(method)s is not'
+ ' supported by the agent. VM id=%(instance_id)s;'
+ ' args=%(strargs)s') % locals())
+ raise NotImplementedError(err_msg)
else:
LOG.error(_('The call to %(method)s returned an error: %(e)s. '
'VM id=%(instance_id)s; args=%(strargs)s') % locals())
diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py
index a0b0499b8..c2f65699f 100644
--- a/nova/virt/xenapi_conn.py
+++ b/nova/virt/xenapi_conn.py
@@ -168,6 +168,12 @@ class XenAPIConnection(object):
"""Set the root/admin password on the VM instance"""
self._vmops.set_admin_password(instance, new_pass)
+ 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.
+ """
+ self._vmops.inject_file(instance, b64_path, b64_contents)
+
def destroy(self, instance):
"""Destroy VM instance"""
self._vmops.destroy(instance)
@@ -188,6 +194,10 @@ class XenAPIConnection(object):
"""resume the specified instance"""
self._vmops.resume(instance, callback)
+ def reset_network(self, instance):
+ """reset networking for specified instance"""
+ self._vmops.reset_network(instance)
+
def get_info(self, instance_id):
"""Return data about VM instance"""
return self._vmops.get_info(instance_id)
diff --git a/nova/volume/api.py b/nova/volume/api.py
index 478c83486..2f4494845 100644
--- a/nova/volume/api.py
+++ b/nova/volume/api.py
@@ -49,7 +49,7 @@ class API(base.Base):
options = {
'size': size,
- 'user_id': context.user.id,
+ 'user_id': context.user_id,
'project_id': context.project_id,
'availability_zone': FLAGS.storage_availability_zone,
'status': "creating",
@@ -85,7 +85,7 @@ class API(base.Base):
return self.db.volume_get(context, volume_id)
def get_all(self, context):
- if context.user.is_admin():
+ if context.is_admin:
return self.db.volume_get_all(context)
return self.db.volume_get_all_by_project(context, context.project_id)
diff --git a/nova/volume/manager.py b/nova/volume/manager.py
index 6e70ec881..d2f02e4e0 100644
--- a/nova/volume/manager.py
+++ b/nova/volume/manager.py
@@ -111,10 +111,10 @@ class VolumeManager(manager.Manager):
LOG.debug(_("volume %s: creating export"), volume_ref['name'])
self.driver.create_export(context, volume_ref)
- except Exception as e:
+ except Exception:
self.db.volume_update(context,
volume_ref['id'], {'status': 'error'})
- raise e
+ raise
now = datetime.datetime.utcnow()
self.db.volume_update(context,
@@ -137,11 +137,11 @@ class VolumeManager(manager.Manager):
self.driver.remove_export(context, volume_ref)
LOG.debug(_("volume %s: deleting"), volume_ref['name'])
self.driver.delete_volume(volume_ref)
- except Exception as e:
+ except Exception:
self.db.volume_update(context,
volume_ref['id'],
{'status': 'error_deleting'})
- raise e
+ raise
self.db.volume_destroy(context, volume_id)
LOG.debug(_("volume %s: deleted successfully"), volume_ref['name'])
diff --git a/plugins/xenserver/xenapi/etc/xapi.d/plugins/agent b/plugins/xenserver/xenapi/etc/xapi.d/plugins/agent
index 031a49708..94eaabe73 100755
--- a/plugins/xenserver/xenapi/etc/xapi.d/plugins/agent
+++ b/plugins/xenserver/xenapi/etc/xapi.d/plugins/agent
@@ -91,6 +91,17 @@ def password(self, arg_dict):
return resp
+@jsonify
+def resetnetwork(self, arg_dict):
+ """Writes a resquest to xenstore that tells the agent
+ to reset networking.
+ """
+ arg_dict['value'] = json.dumps({'name': 'resetnetwork', 'value': ''})
+ request_id = arg_dict['id']
+ arg_dict['path'] = "data/host/%s" % request_id
+ xenstore.write_record(self, arg_dict)
+
+
def _wait_for_agent(self, request_id, arg_dict):
"""Periodically checks xenstore for a response from the agent.
The request is always written to 'data/host/{id}', and
@@ -124,4 +135,5 @@ def _wait_for_agent(self, request_id, arg_dict):
if __name__ == "__main__":
XenAPIPlugin.dispatch(
{"key_init": key_init,
- "password": password})
+ "password": password,
+ "resetnetwork": resetnetwork})
diff --git a/plugins/xenserver/xenapi/etc/xapi.d/plugins/xenstore.py b/plugins/xenserver/xenapi/etc/xapi.d/plugins/xenstore.py
index 695bf3448..a35ccd6ab 100755
--- a/plugins/xenserver/xenapi/etc/xapi.d/plugins/xenstore.py
+++ b/plugins/xenserver/xenapi/etc/xapi.d/plugins/xenstore.py
@@ -36,7 +36,15 @@ pluginlib.configure_logging("xenstore")
def jsonify(fnc):
def wrapper(*args, **kwargs):
- return json.dumps(fnc(*args, **kwargs))
+ ret = fnc(*args, **kwargs)
+ try:
+ json.loads(ret)
+ except ValueError:
+ # Value should already be JSON-encoded, but some operations
+ # may write raw sting values; this will catch those and
+ # properly encode them.
+ ret = json.dumps(ret)
+ return ret
return wrapper
diff --git a/po/nova.pot b/po/nova.pot
index 6cbb2f129..ce88d731b 100644
--- a/po/nova.pot
+++ b/po/nova.pot
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2011-02-15 12:38-0500\n"
+"POT-Creation-Date: 2011-02-21 10:03-0500\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -68,76 +68,93 @@ msgstr ""
msgid "Volume is already detached"
msgstr ""
-#: ../nova/api/openstack/servers.py:138
+#: ../nova/api/openstack/servers.py:72
+msgid "Failed to read private ip"
+msgstr ""
+
+#: ../nova/api/openstack/servers.py:79
+msgid "Failed to read public ip(s)"
+msgstr ""
+
+#: ../nova/api/openstack/servers.py:152
#, python-format
msgid "%(param)s property not found for image %(_image_id)s"
msgstr ""
-#: ../nova/api/openstack/servers.py:219
+#: ../nova/api/openstack/servers.py:168
+msgid "No keypairs defined"
+msgstr ""
+
+#: ../nova/api/openstack/servers.py:238
#, python-format
msgid "Compute.api::lock %s"
msgstr ""
-#: ../nova/api/openstack/servers.py:234
+#: ../nova/api/openstack/servers.py:253
#, python-format
msgid "Compute.api::unlock %s"
msgstr ""
-#: ../nova/api/openstack/servers.py:248
+#: ../nova/api/openstack/servers.py:267
#, python-format
msgid "Compute.api::get_lock %s"
msgstr ""
-#: ../nova/api/openstack/servers.py:259
+#: ../nova/api/openstack/servers.py:281
+#, python-format
+msgid "Compute.api::reset_network %s"
+msgstr ""
+
+#: ../nova/api/openstack/servers.py:292
#, python-format
msgid "Compute.api::pause %s"
msgstr ""
-#: ../nova/api/openstack/servers.py:270
+#: ../nova/api/openstack/servers.py:303
#, python-format
msgid "Compute.api::unpause %s"
msgstr ""
-#: ../nova/api/openstack/servers.py:281
+#: ../nova/api/openstack/servers.py:314
#, python-format
msgid "compute.api::suspend %s"
msgstr ""
-#: ../nova/api/openstack/servers.py:292
+#: ../nova/api/openstack/servers.py:325
#, python-format
msgid "compute.api::resume %s"
msgstr ""
-#: ../nova/twistd.py:159
+#: ../nova/twistd.py:157
msgid "Wrong number of arguments."
msgstr ""
-#: ../nova/twistd.py:211
+#: ../nova/twistd.py:209
#, python-format
msgid "pidfile %s does not exist. Daemon not running?\n"
msgstr ""
-#: ../nova/twistd.py:223
+#: ../nova/twistd.py:221
msgid "No such process"
msgstr ""
-#: ../nova/twistd.py:232 ../nova/service.py:224
+#: ../nova/twistd.py:230 ../nova/service.py:224
#, python-format
msgid "Serving %s"
msgstr ""
-#: ../nova/twistd.py:264 ../nova/service.py:225
+#: ../nova/twistd.py:262 ../nova/service.py:225
msgid "Full set of FLAGS:"
msgstr ""
-#: ../nova/twistd.py:268
+#: ../nova/twistd.py:266
#, python-format
msgid "Starting %s"
msgstr ""
#: ../nova/virt/xenapi/volumeops.py:48 ../nova/virt/xenapi/volumeops.py:101
-#: ../nova/db/sqlalchemy/api.py:709 ../nova/virt/libvirt_conn.py:741
-#: ../nova/api/ec2/__init__.py:321
+#: ../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 ""
@@ -243,152 +260,181 @@ msgstr ""
msgid "Casting to %(topic)s %(host)s for %(method)s"
msgstr ""
-#: ../nova/compute/manager.py:77
+#: ../nova/compute/manager.py:78
#, python-format
msgid "check_instance_lock: decorating: |%s|"
msgstr ""
-#: ../nova/compute/manager.py:79
+#: ../nova/compute/manager.py:80
#, python-format
msgid ""
"check_instance_lock: arguments: |%(self)s| |%(context)s| |%(instance_id)s|"
msgstr ""
-#: ../nova/compute/manager.py:83
+#: ../nova/compute/manager.py:84
#, python-format
msgid "check_instance_lock: locked: |%s|"
msgstr ""
-#: ../nova/compute/manager.py:85
+#: ../nova/compute/manager.py:86
#, python-format
msgid "check_instance_lock: admin: |%s|"
msgstr ""
-#: ../nova/compute/manager.py:90
+#: ../nova/compute/manager.py:91
#, python-format
msgid "check_instance_lock: executing: |%s|"
msgstr ""
-#: ../nova/compute/manager.py:94
+#: ../nova/compute/manager.py:95
#, python-format
msgid "check_instance_lock: not executing |%s|"
msgstr ""
-#: ../nova/compute/manager.py:177
+#: ../nova/compute/manager.py:179
msgid "Instance has already been created"
msgstr ""
-#: ../nova/compute/manager.py:178
+#: ../nova/compute/manager.py:180
#, python-format
msgid "instance %s: starting..."
msgstr ""
#. pylint: disable-msg=W0702
-#: ../nova/compute/manager.py:217
+#: ../nova/compute/manager.py:219
#, python-format
msgid "instance %s: Failed to spawn"
msgstr ""
-#: ../nova/compute/manager.py:231 ../nova/tests/test_cloud.py:286
+#: ../nova/compute/manager.py:233 ../nova/tests/test_cloud.py:286
#, python-format
msgid "Terminating instance %s"
msgstr ""
-#: ../nova/compute/manager.py:253
+#: ../nova/compute/manager.py:255
#, python-format
msgid "Deallocating address %s"
msgstr ""
-#: ../nova/compute/manager.py:266
+#: ../nova/compute/manager.py:268
#, python-format
msgid "trying to destroy already destroyed instance: %s"
msgstr ""
-#: ../nova/compute/manager.py:280
+#: ../nova/compute/manager.py:282
#, python-format
msgid "Rebooting instance %s"
msgstr ""
-#: ../nova/compute/manager.py:285
+#: ../nova/compute/manager.py:287
#, python-format
msgid ""
"trying to reboot a non-running instance: %(instance_id)s (state: %(state)s "
"expected: %(running)s)"
msgstr ""
-#: ../nova/compute/manager.py:309
+#: ../nova/compute/manager.py:311
#, python-format
msgid "instance %s: snapshotting"
msgstr ""
-#: ../nova/compute/manager.py:314
+#: ../nova/compute/manager.py:316
#, python-format
msgid ""
"trying to snapshot a non-running instance: %(instance_id)s (state: %(state)s "
"expected: %(running)s)"
msgstr ""
-#: ../nova/compute/manager.py:355
+#: ../nova/compute/manager.py:332
+#, python-format
+msgid ""
+"trying to reset the password on a non-running instance: %(instance_id)s "
+"(state: %(instance_state)s expected: %(expected_state)s)"
+msgstr ""
+
+#: ../nova/compute/manager.py:335
+#, python-format
+msgid "instance %s: setting admin password"
+msgstr ""
+
+#: ../nova/compute/manager.py:353
+#, python-format
+msgid ""
+"trying to inject a file into a non-running instance: %(instance_id)s (state: "
+"%(instance_state)s expected: %(expected_state)s)"
+msgstr ""
+
+#: ../nova/compute/manager.py:362
+#, python-format
+msgid "instance %(nm)s: injecting file to %(plain_path)s"
+msgstr ""
+
+#: ../nova/compute/manager.py:372
#, python-format
msgid "instance %s: rescuing"
msgstr ""
-#: ../nova/compute/manager.py:370
+#: ../nova/compute/manager.py:387
#, python-format
msgid "instance %s: unrescuing"
msgstr ""
-#: ../nova/compute/manager.py:389
+#: ../nova/compute/manager.py:406
#, python-format
msgid "instance %s: pausing"
msgstr ""
-#: ../nova/compute/manager.py:406
+#: ../nova/compute/manager.py:423
#, python-format
msgid "instance %s: unpausing"
msgstr ""
-#: ../nova/compute/manager.py:423
+#: ../nova/compute/manager.py:440
#, python-format
msgid "instance %s: retrieving diagnostics"
msgstr ""
-#: ../nova/compute/manager.py:436
+#: ../nova/compute/manager.py:453
#, python-format
msgid "instance %s: suspending"
msgstr ""
-#: ../nova/compute/manager.py:455
+#: ../nova/compute/manager.py:472
#, python-format
msgid "instance %s: resuming"
msgstr ""
-#: ../nova/compute/manager.py:474
+#: ../nova/compute/manager.py:491
#, python-format
msgid "instance %s: locking"
msgstr ""
-#: ../nova/compute/manager.py:486
+#: ../nova/compute/manager.py:503
#, python-format
msgid "instance %s: unlocking"
msgstr ""
-#: ../nova/compute/manager.py:496
+#: ../nova/compute/manager.py:513
#, python-format
msgid "instance %s: getting locked state"
msgstr ""
-#: ../nova/compute/manager.py:506 ../nova/api/ec2/cloud.py:515
+#: ../nova/compute/manager.py:526
+#, python-format
+msgid "instance %s: reset network"
+msgstr ""
+
+#: ../nova/compute/manager.py:535 ../nova/api/ec2/cloud.py:515
#, python-format
msgid "Get console output for instance %s"
msgstr ""
-#: ../nova/compute/manager.py:514
+#: ../nova/compute/manager.py:543
#, python-format
msgid "instance %s: getting ajax console"
msgstr ""
-#: ../nova/compute/manager.py:524
+#: ../nova/compute/manager.py:553
#, python-format
msgid ""
"instance %(instance_id)s: attaching volume %(volume_id)s to %(mountpoint)s"
@@ -398,19 +444,19 @@ msgstr ""
#. NOTE(vish): The inline callback eats the exception info so we
#. log the traceback here and reraise the same
#. ecxception below.
-#: ../nova/compute/manager.py:540
+#: ../nova/compute/manager.py:569
#, python-format
msgid "instance %(instance_id)s: attach failed %(mountpoint)s, removing"
msgstr ""
-#: ../nova/compute/manager.py:556
+#: ../nova/compute/manager.py:585
#, python-format
msgid ""
"Detach volume %(volume_id)s from mountpoint %(mp)s on instance "
"%(instance_id)s"
msgstr ""
-#: ../nova/compute/manager.py:559
+#: ../nova/compute/manager.py:588
#, python-format
msgid "Detaching volume from unknown instance %s"
msgstr ""
@@ -495,27 +541,27 @@ msgstr ""
msgid "%(text)s: _db_content => %(content)s"
msgstr ""
-#: ../nova/virt/xenapi/fake.py:300 ../nova/virt/xenapi/fake.py:400
-#: ../nova/virt/xenapi/fake.py:418 ../nova/virt/xenapi/fake.py:474
+#: ../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 ""
-#: ../nova/virt/xenapi/fake.py:302
+#: ../nova/virt/xenapi/fake.py:306
#, python-format
msgid "xenapi.fake does not have an implementation for %s"
msgstr ""
-#: ../nova/virt/xenapi/fake.py:337
+#: ../nova/virt/xenapi/fake.py:341
#, python-format
msgid "Calling %(localname)s %(impl)s"
msgstr ""
-#: ../nova/virt/xenapi/fake.py:342
+#: ../nova/virt/xenapi/fake.py:346
#, python-format
msgid "Calling getter %s"
msgstr ""
-#: ../nova/virt/xenapi/fake.py:402
+#: ../nova/virt/xenapi/fake.py:406
#, python-format
msgid ""
"xenapi.fake does not have an implementation for %s or it has been called "
@@ -573,62 +619,62 @@ msgstr ""
msgid "Killing dnsmasq threw %s"
msgstr ""
-#: ../nova/utils.py:56
+#: ../nova/utils.py:58
#, python-format
msgid "Inner Exception: %s"
msgstr ""
-#: ../nova/utils.py:57
+#: ../nova/utils.py:59
#, python-format
msgid "Class %s cannot be found"
msgstr ""
-#: ../nova/utils.py:116
+#: ../nova/utils.py:118
#, python-format
msgid "Fetching %s"
msgstr ""
-#: ../nova/utils.py:128
+#: ../nova/utils.py:130
#, python-format
msgid "Running cmd (subprocess): %s"
msgstr ""
-#: ../nova/utils.py:141 ../nova/utils.py:181
+#: ../nova/utils.py:143 ../nova/utils.py:183
#, python-format
msgid "Result was %s"
msgstr ""
-#: ../nova/utils.py:157
+#: ../nova/utils.py:159
#, python-format
msgid "Running cmd (SSH): %s"
msgstr ""
-#: ../nova/utils.py:215
+#: ../nova/utils.py:217
#, python-format
msgid "debug in callback: %s"
msgstr ""
-#: ../nova/utils.py:220
+#: ../nova/utils.py:222
#, python-format
msgid "Running %s"
msgstr ""
-#: ../nova/utils.py:251
+#: ../nova/utils.py:262
#, python-format
msgid "Link Local address is not found.:%s"
msgstr ""
-#: ../nova/utils.py:254
+#: ../nova/utils.py:265
#, python-format
msgid "Couldn't get Link Local IP of %(interface)s :%(ex)s"
msgstr ""
-#: ../nova/utils.py:352
+#: ../nova/utils.py:363
#, python-format
msgid "Invalid backend: %s"
msgstr ""
-#: ../nova/utils.py:363
+#: ../nova/utils.py:374
#, python-format
msgid "backend %s"
msgstr ""
@@ -673,42 +719,42 @@ msgstr ""
msgid "Created VM %(instance_name)s as %(vm_ref)s."
msgstr ""
-#: ../nova/virt/xenapi/vm_utils.py:158
+#: ../nova/virt/xenapi/vm_utils.py:168
#, python-format
msgid "Creating VBD for VM %(vm_ref)s, VDI %(vdi_ref)s ... "
msgstr ""
-#: ../nova/virt/xenapi/vm_utils.py:161
+#: ../nova/virt/xenapi/vm_utils.py:171
#, python-format
msgid "Created VBD %(vbd_ref)s for VM %(vm_ref)s, VDI %(vdi_ref)s."
msgstr ""
-#: ../nova/virt/xenapi/vm_utils.py:177
+#: ../nova/virt/xenapi/vm_utils.py:187
#, python-format
msgid "VBD not found in instance %s"
msgstr ""
-#: ../nova/virt/xenapi/vm_utils.py:187
+#: ../nova/virt/xenapi/vm_utils.py:197
#, python-format
msgid "Unable to unplug VBD %s"
msgstr ""
-#: ../nova/virt/xenapi/vm_utils.py:199
+#: ../nova/virt/xenapi/vm_utils.py:209
#, python-format
msgid "Unable to destroy VBD %s"
msgstr ""
-#: ../nova/virt/xenapi/vm_utils.py:214
+#: ../nova/virt/xenapi/vm_utils.py:224
#, python-format
msgid "Creating VIF for VM %(vm_ref)s, network %(network_ref)s."
msgstr ""
-#: ../nova/virt/xenapi/vm_utils.py:217
+#: ../nova/virt/xenapi/vm_utils.py:227
#, python-format
msgid "Created VIF %(vif_ref)s for VM %(vm_ref)s, network %(network_ref)s."
msgstr ""
-#: ../nova/virt/xenapi/vm_utils.py:236
+#: ../nova/virt/xenapi/vm_utils.py:246
#, python-format
msgid ""
"Created VDI %(vdi_ref)s (%(name_label)s, %(virtual_size)s, %(read_only)s) on "
@@ -717,205 +763,205 @@ msgstr ""
#. TODO(sirp): Add quiesce and VSS locking support when Windows support
#. is added
-#: ../nova/virt/xenapi/vm_utils.py:248
+#: ../nova/virt/xenapi/vm_utils.py:258
#, python-format
msgid "Snapshotting VM %(vm_ref)s with label '%(label)s'..."
msgstr ""
-#: ../nova/virt/xenapi/vm_utils.py:262
+#: ../nova/virt/xenapi/vm_utils.py:272
#, python-format
msgid "Created snapshot %(template_vm_ref)s from VM %(vm_ref)s."
msgstr ""
-#: ../nova/virt/xenapi/vm_utils.py:276
+#: ../nova/virt/xenapi/vm_utils.py:286
#, python-format
msgid "Asking xapi to upload %(vdi_uuids)s as ID %(image_id)s"
msgstr ""
-#: ../nova/virt/xenapi/vm_utils.py:317
+#: ../nova/virt/xenapi/vm_utils.py:327
#, python-format
msgid "Size for image %(image)s:%(virtual_size)d"
msgstr ""
-#: ../nova/virt/xenapi/vm_utils.py:322
+#: ../nova/virt/xenapi/vm_utils.py:332
#, python-format
msgid "Glance image %s"
msgstr ""
#. we need to invoke a plugin for copying VDI's
#. content into proper path
-#: ../nova/virt/xenapi/vm_utils.py:332
+#: ../nova/virt/xenapi/vm_utils.py:342
#, python-format
msgid "Copying VDI %s to /boot/guest on dom0"
msgstr ""
-#: ../nova/virt/xenapi/vm_utils.py:342
+#: ../nova/virt/xenapi/vm_utils.py:352
#, python-format
msgid "Kernel/Ramdisk VDI %s destroyed"
msgstr ""
-#: ../nova/virt/xenapi/vm_utils.py:351
+#: ../nova/virt/xenapi/vm_utils.py:361
#, python-format
msgid "Asking xapi to fetch %(url)s as %(access)s"
msgstr ""
-#: ../nova/virt/xenapi/vm_utils.py:376 ../nova/virt/xenapi/vm_utils.py:392
+#: ../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 ""
-#: ../nova/virt/xenapi/vm_utils.py:387
+#: ../nova/virt/xenapi/vm_utils.py:397
#, python-format
-msgid "PV Kernel in VDI:%d"
+msgid "PV Kernel in VDI:%s"
msgstr ""
-#: ../nova/virt/xenapi/vm_utils.py:395
+#: ../nova/virt/xenapi/vm_utils.py:405
#, python-format
msgid "Running pygrub against %s"
msgstr ""
-#: ../nova/virt/xenapi/vm_utils.py:401
+#: ../nova/virt/xenapi/vm_utils.py:411
#, python-format
msgid "Found Xen kernel %s"
msgstr ""
-#: ../nova/virt/xenapi/vm_utils.py:403
+#: ../nova/virt/xenapi/vm_utils.py:413
msgid "No Xen kernel found. Booting HVM."
msgstr ""
-#: ../nova/virt/xenapi/vm_utils.py:415 ../nova/virt/hyperv.py:431
+#: ../nova/virt/xenapi/vm_utils.py:425 ../nova/virt/hyperv.py:431
#, python-format
msgid "duplicate name found: %s"
msgstr ""
-#: ../nova/virt/xenapi/vm_utils.py:432
+#: ../nova/virt/xenapi/vm_utils.py:442
#, python-format
msgid "VDI %s is still available"
msgstr ""
-#: ../nova/virt/xenapi/vm_utils.py:453
+#: ../nova/virt/xenapi/vm_utils.py:463
#, python-format
msgid "(VM_UTILS) xenserver vm state -> |%s|"
msgstr ""
-#: ../nova/virt/xenapi/vm_utils.py:455
+#: ../nova/virt/xenapi/vm_utils.py:465
#, python-format
msgid "(VM_UTILS) xenapi power_state -> |%s|"
msgstr ""
-#: ../nova/virt/xenapi/vm_utils.py:515
+#: ../nova/virt/xenapi/vm_utils.py:525
#, python-format
msgid "VHD %(vdi_uuid)s has parent %(parent_ref)s"
msgstr ""
-#: ../nova/virt/xenapi/vm_utils.py:532
+#: ../nova/virt/xenapi/vm_utils.py:542
#, python-format
msgid "Re-scanning SR %s"
msgstr ""
-#: ../nova/virt/xenapi/vm_utils.py:557
+#: ../nova/virt/xenapi/vm_utils.py:567
#, python-format
msgid ""
"VHD coalesce attempts exceeded (%(counter)d > %(max_attempts)d), giving up..."
msgstr ""
-#: ../nova/virt/xenapi/vm_utils.py:564
+#: ../nova/virt/xenapi/vm_utils.py:574
#, python-format
msgid ""
"Parent %(parent_uuid)s doesn't match original parent "
"%(original_parent_uuid)s, waiting for coalesce..."
msgstr ""
-#: ../nova/virt/xenapi/vm_utils.py:580
+#: ../nova/virt/xenapi/vm_utils.py:590
#, python-format
msgid "No VDIs found for VM %s"
msgstr ""
-#: ../nova/virt/xenapi/vm_utils.py:584
+#: ../nova/virt/xenapi/vm_utils.py:594
#, python-format
msgid "Unexpected number of VDIs (%(num_vdis)s) found for VM %(vm_ref)s"
msgstr ""
-#: ../nova/virt/xenapi/vm_utils.py:643
+#: ../nova/virt/xenapi/vm_utils.py:653
#: ../plugins/xenserver/xenapi/etc/xapi.d/plugins/pluginlib_nova.py:188
#, python-format
msgid "Creating VBD for VDI %s ... "
msgstr ""
-#: ../nova/virt/xenapi/vm_utils.py:645
+#: ../nova/virt/xenapi/vm_utils.py:655
#: ../plugins/xenserver/xenapi/etc/xapi.d/plugins/pluginlib_nova.py:190
#, python-format
msgid "Creating VBD for VDI %s done."
msgstr ""
-#: ../nova/virt/xenapi/vm_utils.py:647
+#: ../nova/virt/xenapi/vm_utils.py:657
#: ../plugins/xenserver/xenapi/etc/xapi.d/plugins/pluginlib_nova.py:192
#, python-format
msgid "Plugging VBD %s ... "
msgstr ""
-#: ../nova/virt/xenapi/vm_utils.py:649
+#: ../nova/virt/xenapi/vm_utils.py:659
#: ../plugins/xenserver/xenapi/etc/xapi.d/plugins/pluginlib_nova.py:194
#, python-format
msgid "Plugging VBD %s done."
msgstr ""
-#: ../nova/virt/xenapi/vm_utils.py:651
+#: ../nova/virt/xenapi/vm_utils.py:661
#, python-format
msgid "VBD %(vbd)s plugged as %(orig_dev)s"
msgstr ""
-#: ../nova/virt/xenapi/vm_utils.py:654
+#: ../nova/virt/xenapi/vm_utils.py:664
#, python-format
msgid "VBD %(vbd)s plugged into wrong dev, remapping to %(dev)s"
msgstr ""
-#: ../nova/virt/xenapi/vm_utils.py:658
+#: ../nova/virt/xenapi/vm_utils.py:668
#: ../plugins/xenserver/xenapi/etc/xapi.d/plugins/pluginlib_nova.py:197
#, python-format
msgid "Destroying VBD for VDI %s ... "
msgstr ""
-#: ../nova/virt/xenapi/vm_utils.py:661
+#: ../nova/virt/xenapi/vm_utils.py:671
#: ../plugins/xenserver/xenapi/etc/xapi.d/plugins/pluginlib_nova.py:200
#, python-format
msgid "Destroying VBD for VDI %s done."
msgstr ""
-#: ../nova/virt/xenapi/vm_utils.py:673
+#: ../nova/virt/xenapi/vm_utils.py:683
#: ../plugins/xenserver/xenapi/etc/xapi.d/plugins/pluginlib_nova.py:211
msgid "VBD.unplug successful first time."
msgstr ""
-#: ../nova/virt/xenapi/vm_utils.py:678
+#: ../nova/virt/xenapi/vm_utils.py:688
#: ../plugins/xenserver/xenapi/etc/xapi.d/plugins/pluginlib_nova.py:216
msgid "VBD.unplug rejected: retrying..."
msgstr ""
-#: ../nova/virt/xenapi/vm_utils.py:682
+#: ../nova/virt/xenapi/vm_utils.py:692
#: ../plugins/xenserver/xenapi/etc/xapi.d/plugins/pluginlib_nova.py:220
msgid "VBD.unplug successful eventually."
msgstr ""
-#: ../nova/virt/xenapi/vm_utils.py:685
+#: ../nova/virt/xenapi/vm_utils.py:695
#: ../plugins/xenserver/xenapi/etc/xapi.d/plugins/pluginlib_nova.py:223
#, python-format
msgid "Ignoring XenAPI.Failure in VBD.unplug: %s"
msgstr ""
-#: ../nova/virt/xenapi/vm_utils.py:694
+#: ../nova/virt/xenapi/vm_utils.py:704
#: ../plugins/xenserver/xenapi/etc/xapi.d/plugins/pluginlib_nova.py:66
#, python-format
msgid "Ignoring XenAPI.Failure %s"
msgstr ""
-#: ../nova/virt/xenapi/vm_utils.py:725
+#: ../nova/virt/xenapi/vm_utils.py:735
#, python-format
msgid ""
"Writing partition table %(primary_first)d %(primary_last)d to %(dest)s..."
msgstr ""
-#: ../nova/virt/xenapi/vm_utils.py:737
+#: ../nova/virt/xenapi/vm_utils.py:747
#, python-format
msgid "Writing partition table %s done."
msgstr ""
@@ -949,114 +995,132 @@ msgstr ""
msgid "No service for %(host)s, %(binary)s"
msgstr ""
-#: ../nova/db/sqlalchemy/api.py:597
+#: ../nova/db/sqlalchemy/api.py:592
+msgid "No fixed ips defined"
+msgstr ""
+
+#: ../nova/db/sqlalchemy/api.py:608
#, python-format
msgid "No floating ip for address %s"
msgstr ""
-#: ../nova/db/sqlalchemy/api.py:939
+#: ../nova/db/sqlalchemy/api.py:629
+#, python-format
+msgid "No address for instance %s"
+msgstr ""
+
+#: ../nova/db/sqlalchemy/api.py:961
#, python-format
msgid "no keypair for user %(user_id)s, name %(name)s"
msgstr ""
-#: ../nova/db/sqlalchemy/api.py:1054 ../nova/db/sqlalchemy/api.py:1112
+#: ../nova/db/sqlalchemy/api.py:1076 ../nova/db/sqlalchemy/api.py:1156
#, python-format
msgid "No network for id %s"
msgstr ""
-#: ../nova/db/sqlalchemy/api.py:1084
+#: ../nova/db/sqlalchemy/api.py:1086
+msgid "No networks defined"
+msgstr ""
+
+#: ../nova/db/sqlalchemy/api.py:1115
#, python-format
msgid "No network for bridge %s"
msgstr ""
-#: ../nova/db/sqlalchemy/api.py:1098
+#: ../nova/db/sqlalchemy/api.py:1129 ../nova/db/sqlalchemy/api.py:1142
#, python-format
msgid "No network for instance %s"
msgstr ""
-#: ../nova/db/sqlalchemy/api.py:1233
+#: ../nova/db/sqlalchemy/api.py:1277
#, python-format
msgid "Token %s does not exist"
msgstr ""
-#: ../nova/db/sqlalchemy/api.py:1258
+#: ../nova/db/sqlalchemy/api.py:1302
#, python-format
msgid "No quota for project_id %s"
msgstr ""
-#: ../nova/db/sqlalchemy/api.py:1411 ../nova/db/sqlalchemy/api.py:1457
-#: ../nova/api/ec2/__init__.py:327
+#: ../nova/db/sqlalchemy/api.py:1455 ../nova/db/sqlalchemy/api.py:1501
+#: ../nova/api/ec2/__init__.py:323
#, python-format
msgid "Volume %s not found"
msgstr ""
-#: ../nova/db/sqlalchemy/api.py:1470
+#: ../nova/db/sqlalchemy/api.py:1514
#, python-format
msgid "No export device found for volume %s"
msgstr ""
-#: ../nova/db/sqlalchemy/api.py:1483
+#: ../nova/db/sqlalchemy/api.py:1527
#, python-format
msgid "No target id found for volume %s"
msgstr ""
-#: ../nova/db/sqlalchemy/api.py:1528
+#: ../nova/db/sqlalchemy/api.py:1572
#, python-format
msgid "No security group with id %s"
msgstr ""
-#: ../nova/db/sqlalchemy/api.py:1545
+#: ../nova/db/sqlalchemy/api.py:1589
#, python-format
msgid "No security group named %(group_name)s for project: %(project_id)s"
msgstr ""
-#: ../nova/db/sqlalchemy/api.py:1638
+#: ../nova/db/sqlalchemy/api.py:1682
#, python-format
msgid "No secuity group rule with id %s"
msgstr ""
-#: ../nova/db/sqlalchemy/api.py:1712
+#: ../nova/db/sqlalchemy/api.py:1756
#, python-format
msgid "No user for id %s"
msgstr ""
-#: ../nova/db/sqlalchemy/api.py:1728
+#: ../nova/db/sqlalchemy/api.py:1772
#, python-format
msgid "No user for access key %s"
msgstr ""
-#: ../nova/db/sqlalchemy/api.py:1790
+#: ../nova/db/sqlalchemy/api.py:1834
#, python-format
msgid "No project with id %s"
msgstr ""
-#: ../nova/db/sqlalchemy/api.py:1935
+#: ../nova/db/sqlalchemy/api.py:1979
#, python-format
msgid "No console pool with id %(pool_id)s"
msgstr ""
-#: ../nova/db/sqlalchemy/api.py:1952
+#: ../nova/db/sqlalchemy/api.py:1996
#, python-format
msgid ""
"No console pool of type %(console_type)s for compute host %(compute_host)s "
"on proxy host %(host)s"
msgstr ""
-#: ../nova/db/sqlalchemy/api.py:1991
+#: ../nova/db/sqlalchemy/api.py:2035
#, python-format
msgid "No console for instance %(instance_id)s in pool %(pool_id)s"
msgstr ""
-#: ../nova/db/sqlalchemy/api.py:2013
+#: ../nova/db/sqlalchemy/api.py:2057
#, python-format
msgid "on instance %s"
msgstr ""
-#: ../nova/db/sqlalchemy/api.py:2014
+#: ../nova/db/sqlalchemy/api.py:2058
#, python-format
msgid "No console with id %(console_id)s %(idesc)s"
msgstr ""
+#: ../nova/db/sqlalchemy/api.py:2078 ../nova/db/sqlalchemy/api.py:2097
+#, python-format
+msgid "No zone with id %(zone_id)s"
+msgstr ""
+
#: ../nova/virt/libvirt_conn.py:160
#, python-format
msgid "Checking state of %s"
@@ -1125,7 +1189,7 @@ msgstr ""
msgid "instance %s: booted"
msgstr ""
-#: ../nova/virt/libvirt_conn.py:425 ../nova/virt/xenapi/vmops.py:124
+#: ../nova/virt/libvirt_conn.py:425 ../nova/virt/xenapi/vmops.py:186
#, python-format
msgid "instance %s: failed to boot"
msgstr ""
@@ -1465,71 +1529,93 @@ msgstr ""
msgid "Attempted to create non-unique name %s"
msgstr ""
-#: ../nova/virt/xenapi/vmops.py:105
+#: ../nova/virt/xenapi/vmops.py:73
+#, python-format
+msgid "instance %(name)s: not enough free memory"
+msgstr ""
+
+#: ../nova/virt/xenapi/vmops.py:148
#, python-format
msgid "Starting VM %s..."
msgstr ""
-#: ../nova/virt/xenapi/vmops.py:108
+#: ../nova/virt/xenapi/vmops.py:151
#, python-format
msgid "Spawning VM %(instance_name)s created %(vm_ref)s."
msgstr ""
-#: ../nova/virt/xenapi/vmops.py:120
+#: ../nova/virt/xenapi/vmops.py:162
+#, python-format
+msgid "Invalid value for onset_files: '%s'"
+msgstr ""
+
+#: ../nova/virt/xenapi/vmops.py:167
+#, python-format
+msgid "Injecting file path: '%s'"
+msgstr ""
+
+#: ../nova/virt/xenapi/vmops.py:180
#, python-format
msgid "Instance %s: booted"
msgstr ""
-#: ../nova/virt/xenapi/vmops.py:164
+#: ../nova/virt/xenapi/vmops.py:232
#, python-format
msgid "Instance not present %s"
msgstr ""
#. TODO(sirp): Add quiesce and VSS locking support when Windows support
#. is added
-#: ../nova/virt/xenapi/vmops.py:193
+#: ../nova/virt/xenapi/vmops.py:261
#, python-format
msgid "Starting snapshot for VM %s"
msgstr ""
-#: ../nova/virt/xenapi/vmops.py:201
+#: ../nova/virt/xenapi/vmops.py:269
#, python-format
msgid "Unable to Snapshot %(vm_ref)s: %(exc)s"
msgstr ""
-#: ../nova/virt/xenapi/vmops.py:212
+#: ../nova/virt/xenapi/vmops.py:280
#, python-format
msgid "Finished snapshot and upload for VM %s"
msgstr ""
-#: ../nova/virt/xenapi/vmops.py:262
+#: ../nova/virt/xenapi/vmops.py:356
#, python-format
msgid "VM %(vm)s already halted, skipping shutdown..."
msgstr ""
-#: ../nova/virt/xenapi/vmops.py:295
+#: ../nova/virt/xenapi/vmops.py:389
msgid "Removing kernel/ramdisk files"
msgstr ""
-#: ../nova/virt/xenapi/vmops.py:305
+#: ../nova/virt/xenapi/vmops.py:399
msgid "kernel/ramdisk files removed"
msgstr ""
-#: ../nova/virt/xenapi/vmops.py:459
+#: ../nova/virt/xenapi/vmops.py:561
#, python-format
msgid ""
"TIMEOUT: The call to %(method)s timed out. VM id=%(instance_id)s; args="
"%(strargs)s"
msgstr ""
-#: ../nova/virt/xenapi/vmops.py:462
+#: ../nova/virt/xenapi/vmops.py:564
+#, python-format
+msgid ""
+"NOT IMPLEMENTED: The call to %(method)s is not supported by the agent. VM id="
+"%(instance_id)s; args=%(strargs)s"
+msgstr ""
+
+#: ../nova/virt/xenapi/vmops.py:569
#, python-format
msgid ""
"The call to %(method)s returned an error: %(e)s. VM id=%(instance_id)s; args="
"%(strargs)s"
msgstr ""
-#: ../nova/virt/xenapi/vmops.py:653
+#: ../nova/virt/xenapi/vmops.py:760
#, python-format
msgid "OpenSSL error: %s"
msgstr ""
@@ -1561,74 +1647,78 @@ msgstr ""
msgid "Launching VPN for %s"
msgstr ""
+#: ../nova/db/sqlalchemy/migration.py:35
+msgid "python-migrate is not installed. Exiting."
+msgstr ""
+
#: ../nova/image/s3.py:99
#, python-format
msgid "Image %s could not be found"
msgstr ""
-#: ../nova/api/ec2/__init__.py:125
+#: ../nova/api/ec2/__init__.py:121
msgid "Too many failed authentications."
msgstr ""
-#: ../nova/api/ec2/__init__.py:135
+#: ../nova/api/ec2/__init__.py:131
#, python-format
msgid ""
"Access key %(access_key)s has had %(failures)d failed authentications and "
"will be locked out for %(lock_mins)d minutes."
msgstr ""
-#: ../nova/api/ec2/__init__.py:173 ../nova/objectstore/handler.py:140
+#: ../nova/api/ec2/__init__.py:169 ../nova/objectstore/handler.py:140
#, python-format
msgid "Authentication Failure: %s"
msgstr ""
-#: ../nova/api/ec2/__init__.py:186
+#: ../nova/api/ec2/__init__.py:182
#, python-format
msgid "Authenticated Request For %(uname)s:%(pname)s)"
msgstr ""
-#: ../nova/api/ec2/__init__.py:211
+#: ../nova/api/ec2/__init__.py:207
#, python-format
msgid "action: %s"
msgstr ""
-#: ../nova/api/ec2/__init__.py:213
+#: ../nova/api/ec2/__init__.py:209
#, python-format
msgid "arg: %(key)s\t\tval: %(value)s"
msgstr ""
-#: ../nova/api/ec2/__init__.py:285
+#: ../nova/api/ec2/__init__.py:281
#, python-format
msgid ""
"Unauthorized request for controller=%(controller)s and action=%(action)s"
msgstr ""
-#: ../nova/api/ec2/__init__.py:318
+#: ../nova/api/ec2/__init__.py:314
#, python-format
msgid "InstanceNotFound raised: %s"
msgstr ""
-#: ../nova/api/ec2/__init__.py:324
+#: ../nova/api/ec2/__init__.py:320
#, python-format
msgid "VolumeNotFound raised: %s"
msgstr ""
-#: ../nova/api/ec2/__init__.py:330
+#: ../nova/api/ec2/__init__.py:326
#, python-format
msgid "NotFound raised: %s"
msgstr ""
-#: ../nova/api/ec2/__init__.py:333
+#: ../nova/api/ec2/__init__.py:329
#, python-format
msgid "ApiError raised: %s"
msgstr ""
-#: ../nova/api/ec2/__init__.py:342
+#: ../nova/api/ec2/__init__.py:338
#, python-format
msgid "Unexpected error raised: %s"
msgstr ""
-#: ../nova/api/ec2/__init__.py:347
+#: ../nova/api/ec2/__init__.py:343
msgid "An unknown error has occurred. Please try your request again."
msgstr ""
@@ -1637,22 +1727,22 @@ msgstr ""
msgid "User %s already exists"
msgstr ""
-#: ../nova/auth/dbdriver.py:106 ../nova/auth/ldapdriver.py:206
+#: ../nova/auth/dbdriver.py:106 ../nova/auth/ldapdriver.py:232
#, python-format
msgid "Project can't be created because manager %s doesn't exist"
msgstr ""
-#: ../nova/auth/dbdriver.py:122 ../nova/auth/ldapdriver.py:217
+#: ../nova/auth/dbdriver.py:122 ../nova/auth/ldapdriver.py:243
#, python-format
msgid "Project can't be created because user %s doesn't exist"
msgstr ""
-#: ../nova/auth/dbdriver.py:135 ../nova/auth/ldapdriver.py:203
+#: ../nova/auth/dbdriver.py:135 ../nova/auth/ldapdriver.py:229
#, python-format
msgid "Project can't be created because project %s already exists"
msgstr ""
-#: ../nova/auth/dbdriver.py:157 ../nova/auth/ldapdriver.py:241
+#: ../nova/auth/dbdriver.py:157 ../nova/auth/ldapdriver.py:268
#, python-format
msgid "Project can't be modified because manager %s doesn't exist"
msgstr ""
@@ -1673,17 +1763,17 @@ msgid ""
"and xenapi_connection_password to use connection_type=xenapi"
msgstr ""
-#: ../nova/virt/xenapi_conn.py:301
+#: ../nova/virt/xenapi_conn.py:311
#, python-format
msgid "Task [%(name)s] %(task)s status: success %(result)s"
msgstr ""
-#: ../nova/virt/xenapi_conn.py:307
+#: ../nova/virt/xenapi_conn.py:317
#, python-format
msgid "Task [%(name)s] %(task)s status: %(status)s %(error_info)s"
msgstr ""
-#: ../nova/virt/xenapi_conn.py:321 ../nova/virt/xenapi_conn.py:334
+#: ../nova/virt/xenapi_conn.py:331 ../nova/virt/xenapi_conn.py:344
#, python-format
msgid "Got exception: %s"
msgstr ""
@@ -1721,18 +1811,18 @@ msgstr ""
msgid "Could not find iSCSI export for volume %s"
msgstr ""
-#: ../nova/api/ec2/apirequest.py:99
+#: ../nova/api/ec2/apirequest.py:100
#, python-format
msgid ""
"Unsupported API request: controller = %(controller)s, action = %(action)s"
msgstr ""
-#: ../nova/api/openstack/__init__.py:54
+#: ../nova/api/openstack/__init__.py:55
#, python-format
msgid "Caught error: %s"
msgstr ""
-#: ../nova/api/openstack/__init__.py:75
+#: ../nova/api/openstack/__init__.py:76
msgid "Including admin operations in API."
msgstr ""
@@ -1788,6 +1878,34 @@ msgid ""
"Please create a database by running a nova-api server on this host."
msgstr ""
+#: ../bin/nova-manage.py:447 ../bin/nova-manage.py:536
+msgid "network"
+msgstr ""
+
+#: ../bin/nova-manage.py:448
+msgid "IP address"
+msgstr ""
+
+#: ../bin/nova-manage.py:449
+msgid "MAC address"
+msgstr ""
+
+#: ../bin/nova-manage.py:450
+msgid "hostname"
+msgstr ""
+
+#: ../bin/nova-manage.py:451
+msgid "host"
+msgstr ""
+
+#: ../bin/nova-manage.py:537
+msgid "netmask"
+msgstr ""
+
+#: ../bin/nova-manage.py:538
+msgid "start address"
+msgstr ""
+
#: ../nova/virt/disk.py:69
#, python-format
msgid "Failed to load partition: %s"
@@ -1963,81 +2081,81 @@ msgstr ""
msgid "Instance %d has no host"
msgstr ""
-#: ../nova/compute/api.py:96
+#: ../nova/compute/api.py:97
#, python-format
msgid "Quota exceeeded for %(pid)s, tried to run %(min_count)s instances"
msgstr ""
-#: ../nova/compute/api.py:98
+#: ../nova/compute/api.py:99
#, python-format
msgid ""
"Instance quota exceeded. You can only run %s more instances of this type."
msgstr ""
-#: ../nova/compute/api.py:113
+#: ../nova/compute/api.py:112
msgid "Creating a raw instance"
msgstr ""
-#: ../nova/compute/api.py:162
+#: ../nova/compute/api.py:160
#, python-format
msgid "Going to run %s instances..."
msgstr ""
-#: ../nova/compute/api.py:189
+#: ../nova/compute/api.py:187
#, python-format
msgid "Casting to scheduler for %(pid)s/%(uid)s's instance %(instance_id)s"
msgstr ""
-#: ../nova/compute/api.py:293
+#: ../nova/compute/api.py:292
#, python-format
msgid "Going to try to terminate %s"
msgstr ""
-#: ../nova/compute/api.py:297
+#: ../nova/compute/api.py:296
#, python-format
msgid "Instance %d was not found during terminate"
msgstr ""
-#: ../nova/compute/api.py:302
+#: ../nova/compute/api.py:301
#, python-format
msgid "Instance %d is already being terminated"
msgstr ""
-#: ../nova/compute/api.py:471
+#: ../nova/compute/api.py:481
#, python-format
msgid "Invalid device specified: %s. Example device: /dev/vdb"
msgstr ""
-#: ../nova/compute/api.py:486
+#: ../nova/compute/api.py:496
msgid "Volume isn't attached to anything!"
msgstr ""
-#: ../nova/rpc.py:95
+#: ../nova/rpc.py:98
#, python-format
msgid ""
"AMQP server on %(fl_host)s:%(fl_port)d is unreachable. Trying again in "
"%(fl_intv)d seconds."
msgstr ""
-#: ../nova/rpc.py:100
+#: ../nova/rpc.py:103
#, python-format
msgid "Unable to connect to AMQP server after %d tries. Shutting down."
msgstr ""
-#: ../nova/rpc.py:119
+#: ../nova/rpc.py:122
msgid "Reconnected to queue"
msgstr ""
-#: ../nova/rpc.py:126
+#: ../nova/rpc.py:129
msgid "Failed to fetch message from queue"
msgstr ""
-#: ../nova/rpc.py:156
+#: ../nova/rpc.py:159
#, python-format
msgid "Initing the Adapter Consumer for %s"
msgstr ""
-#: ../nova/rpc.py:171
+#: ../nova/rpc.py:178
#, python-format
msgid "received %s"
msgstr ""
@@ -2046,50 +2164,50 @@ msgstr ""
#. messages stay in the queue indefinitely, so for now
#. we just log the message and send an error string
#. back to the caller
-#: ../nova/rpc.py:184
+#: ../nova/rpc.py:191
#, python-format
msgid "no method for message: %s"
msgstr ""
-#: ../nova/rpc.py:185
+#: ../nova/rpc.py:192
#, python-format
msgid "No method for message: %s"
msgstr ""
-#: ../nova/rpc.py:246
+#: ../nova/rpc.py:253
#, python-format
msgid "Returning exception %s to caller"
msgstr ""
-#: ../nova/rpc.py:287
+#: ../nova/rpc.py:294
#, python-format
msgid "unpacked context: %s"
msgstr ""
-#: ../nova/rpc.py:306
+#: ../nova/rpc.py:313
msgid "Making asynchronous call..."
msgstr ""
-#: ../nova/rpc.py:309
+#: ../nova/rpc.py:316
#, python-format
msgid "MSG_ID is %s"
msgstr ""
-#: ../nova/rpc.py:347
+#: ../nova/rpc.py:354
msgid "Making asynchronous cast..."
msgstr ""
-#: ../nova/rpc.py:357
+#: ../nova/rpc.py:364
#, python-format
msgid "response %s"
msgstr ""
-#: ../nova/rpc.py:366
+#: ../nova/rpc.py:373
#, python-format
msgid "topic is %s"
msgstr ""
-#: ../nova/rpc.py:367
+#: ../nova/rpc.py:374
#, python-format
msgid "message %s"
msgstr ""
@@ -2168,58 +2286,64 @@ msgid ""
"on interface %(interface)s"
msgstr ""
-#: ../nova/virt/fake.py:224
+#: ../nova/virt/fake.py:239
#, python-format
msgid "Instance %s Not Found"
msgstr ""
-#: ../nova/network/manager.py:143
+#: ../nova/network/manager.py:153
+#, python-format
+msgid "Dissassociated %s stale fixed ip(s)"
+msgstr ""
+
+#: ../nova/network/manager.py:157
msgid "setting network host"
msgstr ""
-#: ../nova/network/manager.py:198
+#: ../nova/network/manager.py:212
#, python-format
msgid "Leasing IP %s"
msgstr ""
-#: ../nova/network/manager.py:202
+#: ../nova/network/manager.py:216
#, python-format
msgid "IP %s leased that isn't associated"
msgstr ""
-#: ../nova/network/manager.py:206
+#: ../nova/network/manager.py:220
#, python-format
msgid "IP %(address)s leased to bad mac %(inst_addr)s vs %(mac)s"
msgstr ""
-#: ../nova/network/manager.py:214
+#: ../nova/network/manager.py:228
#, python-format
msgid "IP %s leased that was already deallocated"
msgstr ""
-#: ../nova/network/manager.py:219
+#: ../nova/network/manager.py:233
#, python-format
msgid "Releasing IP %s"
msgstr ""
-#: ../nova/network/manager.py:223
+#: ../nova/network/manager.py:237
#, python-format
msgid "IP %s released that isn't associated"
msgstr ""
-#: ../nova/network/manager.py:227
+#: ../nova/network/manager.py:241
#, python-format
msgid "IP %(address)s released from bad mac %(inst_addr)s vs %(mac)s"
msgstr ""
-#: ../nova/network/manager.py:230
+#: ../nova/network/manager.py:244
#, python-format
msgid "IP %s released that was not leased"
msgstr ""
-#: ../nova/network/manager.py:464
-#, python-format
-msgid "Dissassociated %s stale fixed ip(s)"
+#: ../nova/network/manager.py:519
+msgid ""
+"The sum between the number of networks and the vlan start cannot be greater "
+"than 4094"
msgstr ""
#: ../nova/virt/xenapi/volume_utils.py:57
@@ -2576,74 +2700,74 @@ msgstr ""
msgid "model server went away"
msgstr ""
-#: ../nova/auth/ldapdriver.py:149
+#: ../nova/auth/ldapdriver.py:174
#, python-format
msgid "LDAP user %s already exists"
msgstr ""
-#: ../nova/auth/ldapdriver.py:180
+#: ../nova/auth/ldapdriver.py:205
#, python-format
msgid "LDAP object for %s doesn't exist"
msgstr ""
-#: ../nova/auth/ldapdriver.py:313
+#: ../nova/auth/ldapdriver.py:348
#, python-format
msgid "User %s doesn't exist"
msgstr ""
-#: ../nova/auth/ldapdriver.py:435
+#: ../nova/auth/ldapdriver.py:472
#, python-format
msgid "Group can't be created because group %s already exists"
msgstr ""
-#: ../nova/auth/ldapdriver.py:441
+#: ../nova/auth/ldapdriver.py:478
#, python-format
msgid "Group can't be created because user %s doesn't exist"
msgstr ""
-#: ../nova/auth/ldapdriver.py:458
+#: ../nova/auth/ldapdriver.py:495
#, python-format
msgid "User %s can't be searched in group because the user doesn't exist"
msgstr ""
-#: ../nova/auth/ldapdriver.py:470
+#: ../nova/auth/ldapdriver.py:507
#, python-format
msgid "User %s can't be added to the group because the user doesn't exist"
msgstr ""
-#: ../nova/auth/ldapdriver.py:473 ../nova/auth/ldapdriver.py:484
+#: ../nova/auth/ldapdriver.py:510 ../nova/auth/ldapdriver.py:521
#, python-format
msgid "The group at dn %s doesn't exist"
msgstr ""
-#: ../nova/auth/ldapdriver.py:476
+#: ../nova/auth/ldapdriver.py:513
#, python-format
msgid "User %(uid)s is already a member of the group %(group_dn)s"
msgstr ""
-#: ../nova/auth/ldapdriver.py:487
+#: ../nova/auth/ldapdriver.py:524
#, python-format
msgid "User %s can't be removed from the group because the user doesn't exist"
msgstr ""
-#: ../nova/auth/ldapdriver.py:491
+#: ../nova/auth/ldapdriver.py:528
#, python-format
msgid "User %s is not a member of the group"
msgstr ""
-#: ../nova/auth/ldapdriver.py:505
+#: ../nova/auth/ldapdriver.py:542
#, python-format
msgid ""
"Attempted to remove the last member of a group. Deleting the group at %s "
"instead."
msgstr ""
-#: ../nova/auth/ldapdriver.py:512
+#: ../nova/auth/ldapdriver.py:549
#, python-format
msgid "User %s can't be removed from all because the user doesn't exist"
msgstr ""
-#: ../nova/auth/ldapdriver.py:527
+#: ../nova/auth/ldapdriver.py:564
#, python-format
msgid "Group at dn %s doesn't exist"
msgstr ""
diff --git a/setup.py b/setup.py
index 5f102757e..89add02c3 100644
--- a/setup.py
+++ b/setup.py
@@ -94,9 +94,13 @@ DistUtilsExtra.auto.setup(name='nova',
packages=find_packages(exclude=['bin', 'smoketests']),
include_package_data=True,
test_suite='nose.collector',
- scripts=['bin/nova-api',
+ scripts=['bin/nova-ajax-console-proxy',
+ 'bin/nova-api',
+ 'bin/nova-combined',
'bin/nova-compute',
+ 'bin/nova-console',
'bin/nova-dhcpbridge',
+ 'bin/nova-direct-api',
'bin/nova-import-canonical-imagestore',
'bin/nova-instancemonitor',
'bin/nova-logspool',
@@ -105,5 +109,6 @@ DistUtilsExtra.auto.setup(name='nova',
'bin/nova-objectstore',
'bin/nova-scheduler',
'bin/nova-spoolsentry',
+ 'bin/stack',
'bin/nova-volume',
'tools/nova-debug'])