diff options
| author | jaypipes@gmail.com <> | 2011-02-21 10:04:32 -0500 |
|---|---|---|
| committer | jaypipes@gmail.com <> | 2011-02-21 10:04:32 -0500 |
| commit | 6facb35c26b2c90d4ba7a34f3eccd10de2fb7207 (patch) | |
| tree | 29fff56413ee20c45b99ac6596796eadfc94841d | |
| parent | f02c41a7fe332b215421320d041a944e4b9ee9ee (diff) | |
| parent | bd0ca93866b48a7a65de8b97ab0ac0ac9c737f73 (diff) | |
| download | nova-6facb35c26b2c90d4ba7a34f3eccd10de2fb7207.tar.gz nova-6facb35c26b2c90d4ba7a34f3eccd10de2fb7207.tar.xz nova-6facb35c26b2c90d4ba7a34f3eccd10de2fb7207.zip | |
Merge trunk and re-run build_i18n
54 files changed, 1747 insertions, 438 deletions
@@ -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> @@ -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> @@ -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 "" @@ -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']) |
