summaryrefslogtreecommitdiffstats
path: root/nova
diff options
context:
space:
mode:
authorMORITA Kazutaka <morita.kazutaka@gmail.com>2011-05-13 21:07:48 +0900
committerMORITA Kazutaka <morita.kazutaka@gmail.com>2011-05-13 21:07:48 +0900
commit2ecfa05337e7eefbf9791188ffa1d57f0e6ecd19 (patch)
tree442bf5218c6a7f48197875da388cfc2bf3406a62 /nova
parentaad857a18153792d96f300732c3bb5bb16aa02c3 (diff)
parent0576766cdf3480ad02159671d2dfc0bdcb154934 (diff)
downloadnova-2ecfa05337e7eefbf9791188ffa1d57f0e6ecd19.tar.gz
nova-2ecfa05337e7eefbf9791188ffa1d57f0e6ecd19.tar.xz
nova-2ecfa05337e7eefbf9791188ffa1d57f0e6ecd19.zip
Merge trunk
Diffstat (limited to 'nova')
-rwxr-xr-xnova/CA/geninter.sh2
-rw-r--r--nova/CA/openssl.cnf.tmpl2
-rw-r--r--nova/api/ec2/__init__.py10
-rw-r--r--nova/api/ec2/admin.py2
-rw-r--r--nova/api/ec2/cloud.py50
-rw-r--r--nova/api/ec2/ec2utils.py2
-rw-r--r--nova/api/openstack/__init__.py9
-rw-r--r--nova/api/openstack/accounts.py2
-rw-r--r--nova/api/openstack/common.py11
-rw-r--r--nova/api/openstack/images.py2
-rw-r--r--nova/api/openstack/limits.py31
-rw-r--r--nova/api/openstack/server_metadata.py29
-rw-r--r--nova/api/openstack/servers.py135
-rw-r--r--nova/api/openstack/users.py2
-rw-r--r--nova/api/openstack/views/images.py17
-rw-r--r--nova/api/openstack/views/limits.py100
-rw-r--r--nova/api/openstack/views/servers.py8
-rw-r--r--nova/auth/dbdriver.py21
-rw-r--r--nova/auth/ldapdriver.py59
-rw-r--r--nova/auth/manager.py102
-rw-r--r--nova/cloudpipe/pipelib.py3
-rw-r--r--nova/compute/api.py108
-rw-r--r--nova/compute/instance_types.py16
-rw-r--r--nova/compute/manager.py445
-rw-r--r--nova/compute/power_state.py21
-rw-r--r--nova/console/api.py23
-rw-r--r--nova/console/fake.py22
-rw-r--r--nova/console/manager.py17
-rw-r--r--nova/console/vmrc.py50
-rw-r--r--nova/console/vmrc_manager.py79
-rw-r--r--nova/console/xvp.py48
-rw-r--r--nova/context.py10
-rw-r--r--nova/crypto.py61
-rw-r--r--nova/db/api.py71
-rw-r--r--nova/db/base.py8
-rw-r--r--nova/db/migration.py2
-rw-r--r--nova/db/sqlalchemy/api.py174
-rw-r--r--nova/db/sqlalchemy/migrate_repo/versions/001_austin.py8
-rw-r--r--nova/db/sqlalchemy/migrate_repo/versions/002_bexar.py7
-rw-r--r--nova/db/sqlalchemy/migrate_repo/versions/003_add_label_to_networks.py7
-rw-r--r--nova/db/sqlalchemy/migrate_repo/versions/004_add_zone_tables.py7
-rw-r--r--nova/db/sqlalchemy/migrate_repo/versions/005_add_instance_metadata.py7
-rw-r--r--nova/db/sqlalchemy/migrate_repo/versions/006_add_provider_data_to_volumes.py6
-rw-r--r--nova/db/sqlalchemy/migrate_repo/versions/007_add_ipv6_to_fixed_ips.py7
-rw-r--r--nova/db/sqlalchemy/migrate_repo/versions/008_add_instance_types.py9
-rw-r--r--nova/db/sqlalchemy/migrate_repo/versions/009_add_instance_migrations.py6
-rw-r--r--nova/db/sqlalchemy/migrate_repo/versions/010_add_os_type_to_instances.py7
-rw-r--r--nova/db/sqlalchemy/migrate_repo/versions/011_live_migration.py5
-rw-r--r--nova/db/sqlalchemy/migrate_repo/versions/012_add_ipv6_flatmanager.py8
-rw-r--r--nova/db/sqlalchemy/migrate_repo/versions/013_add_flavors_to_migrations.py6
-rw-r--r--nova/db/sqlalchemy/migrate_repo/versions/014_add_instance_type_id_to_instances.py13
-rw-r--r--nova/db/sqlalchemy/migrate_repo/versions/015_add_auto_assign_to_floating_ips.py35
-rw-r--r--nova/db/sqlalchemy/migrate_repo/versions/016_add_volume_snapshot_support.py (renamed from nova/db/sqlalchemy/migrate_repo/versions/015_add_volume_snapshot_support.py)0
-rw-r--r--nova/db/sqlalchemy/models.py1
-rw-r--r--nova/exception.py545
-rw-r--r--nova/fakememcache.py4
-rw-r--r--nova/flags.py21
-rw-r--r--nova/image/fake.py15
-rw-r--r--nova/image/glance.py82
-rw-r--r--nova/image/local.py23
-rw-r--r--nova/image/s3.py44
-rw-r--r--nova/image/service.py69
-rw-r--r--nova/log.py49
-rw-r--r--nova/manager.py34
-rw-r--r--nova/network/api.py15
-rw-r--r--nova/network/linux_net.py11
-rw-r--r--nova/network/vmwareapi_net.py23
-rw-r--r--nova/quota.py23
-rw-r--r--nova/rpc.py152
-rw-r--r--nova/scheduler/api.py8
-rw-r--r--nova/scheduler/driver.py36
-rw-r--r--nova/scheduler/host_filter.py288
-rw-r--r--nova/scheduler/manager.py7
-rw-r--r--nova/scheduler/zone_manager.py20
-rw-r--r--nova/service.py83
-rw-r--r--nova/test.py50
-rw-r--r--nova/tests/api/openstack/test_flavors.py4
-rw-r--r--nova/tests/api/openstack/test_images.py24
-rw-r--r--nova/tests/api/openstack/test_limits.py121
-rw-r--r--nova/tests/api/openstack/test_server_metadata.py59
-rw-r--r--nova/tests/api/openstack/test_servers.py230
-rw-r--r--nova/tests/api/openstack/test_zones.py2
-rw-r--r--nova/tests/api/test_wsgi.py6
-rw-r--r--nova/tests/integrated/test_servers.py88
-rw-r--r--nova/tests/test_api.py19
-rw-r--r--nova/tests/test_auth.py40
-rw-r--r--nova/tests/test_cloud.py2
-rw-r--r--nova/tests/test_compute.py9
-rw-r--r--nova/tests/test_exception.py34
-rw-r--r--nova/tests/test_host_filter.py208
-rw-r--r--nova/tests/test_instance_types.py15
-rw-r--r--nova/tests/test_misc.py49
-rw-r--r--nova/tests/test_scheduler.py99
-rw-r--r--nova/tests/test_utils.py27
-rw-r--r--nova/tests/test_virt.py220
-rw-r--r--nova/tests/test_volume.py2
-rw-r--r--nova/tests/test_xenapi.py50
-rw-r--r--nova/tests/test_zones.py18
-rw-r--r--nova/utils.py188
-rw-r--r--nova/version.py6
-rw-r--r--nova/virt/fake.py5
-rw-r--r--nova/virt/hyperv.py21
-rw-r--r--nova/virt/libvirt_conn.py437
-rw-r--r--nova/virt/vmwareapi/fake.py9
-rw-r--r--nova/virt/vmwareapi/vmops.py43
-rw-r--r--nova/virt/xenapi/fake.py2
-rw-r--r--nova/virt/xenapi/vm_utils.py18
-rw-r--r--nova/virt/xenapi/vmops.py44
-rw-r--r--nova/virt/xenapi/volumeops.py6
-rw-r--r--nova/virt/xenapi_conn.py80
-rw-r--r--nova/volume/api.py7
-rw-r--r--nova/wsgi.py158
112 files changed, 3943 insertions, 1902 deletions
diff --git a/nova/CA/geninter.sh b/nova/CA/geninter.sh
index 4b7f5a55c..9b3ea3b76 100755
--- a/nova/CA/geninter.sh
+++ b/nova/CA/geninter.sh
@@ -21,7 +21,7 @@ NAME=$1
SUBJ=$2
mkdir -p projects/$NAME
cd projects/$NAME
-cp ../../openssl.cnf.tmpl openssl.cnf
+cp "$(dirname $0)/openssl.cnf.tmpl" openssl.cnf
sed -i -e s/%USERNAME%/$NAME/g openssl.cnf
mkdir -p certs crl newcerts private
openssl req -new -x509 -extensions v3_ca -keyout private/cakey.pem -out cacert.pem -days 365 -config ./openssl.cnf -batch -nodes
diff --git a/nova/CA/openssl.cnf.tmpl b/nova/CA/openssl.cnf.tmpl
index b80fadf40..f87d9f3b2 100644
--- a/nova/CA/openssl.cnf.tmpl
+++ b/nova/CA/openssl.cnf.tmpl
@@ -46,7 +46,7 @@ policy = policy_match
# RHEL 6 and Fedora 14 (using openssl-1.0.0-4.el6.x86_64 or
# openssl-1.0.0d-1.fc14.x86_64)
[ policy_match ]
-countryName = match
+countryName = supplied
stateOrProvinceName = supplied
organizationName = optional
organizationalUnitName = optional
diff --git a/nova/api/ec2/__init__.py b/nova/api/ec2/__init__.py
index a89d65a38..4a49a5a6b 100644
--- a/nova/api/ec2/__init__.py
+++ b/nova/api/ec2/__init__.py
@@ -46,8 +46,6 @@ flags.DEFINE_integer('lockout_minutes', 15,
'Number of minutes to lockout if triggered.')
flags.DEFINE_integer('lockout_window', 15,
'Number of minutes for lockout window.')
-flags.DEFINE_list('lockout_memcached_servers', None,
- 'Memcached servers or None for in process cache.')
class RequestLogging(wsgi.Middleware):
@@ -107,11 +105,11 @@ class Lockout(wsgi.Middleware):
def __init__(self, application):
"""middleware can use fake for testing."""
- if FLAGS.lockout_memcached_servers:
+ if FLAGS.memcached_servers:
import memcache
else:
from nova import fakememcache as memcache
- self.mc = memcache.Client(FLAGS.lockout_memcached_servers,
+ self.mc = memcache.Client(FLAGS.memcached_servers,
debug=0)
super(Lockout, self).__init__(application)
@@ -322,9 +320,7 @@ class Executor(wsgi.Application):
except exception.InstanceNotFound as ex:
LOG.info(_('InstanceNotFound raised: %s'), unicode(ex),
context=context)
- ec2_id = ec2utils.id_to_ec2_id(ex.instance_id)
- message = _('Instance %s not found') % ec2_id
- return self._error(req, context, type(ex).__name__, message)
+ return self._error(req, context, type(ex).__name__, ex.message)
except exception.VolumeNotFound as ex:
LOG.info(_('VolumeNotFound raised: %s'), unicode(ex),
context=context)
diff --git a/nova/api/ec2/admin.py b/nova/api/ec2/admin.py
index 6a5609d4a..ea94d9c1f 100644
--- a/nova/api/ec2/admin.py
+++ b/nova/api/ec2/admin.py
@@ -266,7 +266,7 @@ class AdminController(object):
def _vpn_for(self, context, project_id):
"""Get the VPN instance for a project ID."""
for instance in db.instance_get_all_by_project(context, project_id):
- if (instance['image_id'] == FLAGS.vpn_image_id
+ if (instance['image_id'] == str(FLAGS.vpn_image_id)
and not instance['state_description'] in
['shutting_down', 'shutdown']):
return instance
diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py
index 6daf299b9..20bfe79ef 100644
--- a/nova/api/ec2/cloud.py
+++ b/nova/api/ec2/cloud.py
@@ -49,8 +49,6 @@ flags.DECLARE('service_down_time', 'nova.scheduler.driver')
LOG = logging.getLogger("nova.api.cloud")
-InvalidInputException = exception.InvalidInputException
-
def _gen_key(context, user_id, key_name):
"""Generate a key
@@ -61,8 +59,7 @@ def _gen_key(context, user_id, key_name):
# creation before creating key_pair
try:
db.key_pair_get(context, user_id, key_name)
- raise exception.Duplicate(_("The key_pair %s already exists")
- % key_name)
+ raise exception.KeyPairExists(key_name=key_name)
except exception.NotFound:
pass
private_key, public_key, fingerprint = crypto.generate_key_pair()
@@ -159,7 +156,7 @@ class CloudController(object):
floating_ip = db.instance_get_floating_address(ctxt,
instance_ref['id'])
ec2_id = ec2utils.id_to_ec2_id(instance_ref['id'])
- image_ec2_id = self._image_ec2_id(instance_ref['image_id'], 'ami')
+ image_ec2_id = self.image_ec2_id(instance_ref['image_id'])
data = {
'user-data': base64.b64decode(instance_ref['user_data']),
'meta-data': {
@@ -187,9 +184,9 @@ class CloudController(object):
'mpi': mpi}}
for image_type in ['kernel', 'ramdisk']:
- if '%s_id' % image_type in instance_ref:
- ec2_id = self._image_ec2_id(instance_ref['%s_id' % image_type],
- self._image_type(image_type))
+ if instance_ref.get('%s_id' % image_type):
+ ec2_id = self.image_ec2_id(instance_ref['%s_id' % image_type],
+ self._image_type(image_type))
data['meta-data']['%s-id' % image_type] = ec2_id
if False: # TODO(vish): store ancestor ids
@@ -431,11 +428,11 @@ class CloudController(object):
ip_protocol = str(ip_protocol)
if ip_protocol.upper() not in ['TCP', 'UDP', 'ICMP']:
- raise InvalidInputException(_('%s is not a valid ipProtocol') %
- (ip_protocol,))
+ raise exception.InvalidIpProtocol(protocol=ip_protocol)
if ((min(from_port, to_port) < -1) or
(max(from_port, to_port) > 65535)):
- raise InvalidInputException(_('Invalid port range'))
+ raise exception.InvalidPortRange(from_port=from_port,
+ to_port=to_port)
values['protocol'] = ip_protocol
values['from_port'] = from_port
@@ -649,7 +646,7 @@ class CloudController(object):
# TODO(vish): Instance should be None at db layer instead of
# trying to lazy load, but for now we turn it into
# a dict to avoid an error.
- return {'volumeSet': [self._format_volume(context, dict(volume))]}
+ return self._format_volume(context, dict(volume))
def delete_volume(self, context, volume_id, **kwargs):
volume_id = ec2utils.ec2_id_to_id(volume_id)
@@ -739,13 +736,13 @@ class CloudController(object):
instances = self.compute_api.get_all(context, **kwargs)
for instance in instances:
if not context.is_admin:
- if instance['image_id'] == FLAGS.vpn_image_id:
+ if instance['image_id'] == str(FLAGS.vpn_image_id):
continue
i = {}
instance_id = instance['id']
ec2_id = ec2utils.id_to_ec2_id(instance_id)
i['instanceId'] = ec2_id
- i['imageId'] = self._image_ec2_id(instance['image_id'])
+ i['imageId'] = self.image_ec2_id(instance['image_id'])
i['instanceState'] = {
'code': instance['state'],
'name': instance['state_description']}
@@ -762,7 +759,9 @@ class CloudController(object):
instance['mac_address'])
i['privateDnsName'] = fixed_addr
+ i['privateIpAddress'] = fixed_addr
i['publicDnsName'] = floating_addr
+ i['ipAddress'] = floating_addr or fixed_addr
i['dnsName'] = i['publicDnsName'] or i['privateDnsName']
i['keyName'] = instance['key_name']
@@ -934,7 +933,7 @@ class CloudController(object):
return image_type
@staticmethod
- def _image_ec2_id(image_id, image_type='ami'):
+ def image_ec2_id(image_id, image_type='ami'):
"""Returns image ec2_id using id and three letter type."""
template = image_type + '-%08x'
return ec2utils.id_to_ec2_id(int(image_id), template=template)
@@ -943,25 +942,25 @@ class CloudController(object):
try:
internal_id = ec2utils.ec2_id_to_id(ec2_id)
return self.image_service.show(context, internal_id)
- except exception.NotFound:
+ except (exception.InvalidEc2Id, exception.ImageNotFound):
try:
return self.image_service.show_by_name(context, ec2_id)
except exception.NotFound:
- raise exception.NotFound(_('Image %s not found') % ec2_id)
+ raise exception.ImageNotFound(image_id=ec2_id)
def _format_image(self, image):
"""Convert from format defined by BaseImageService to S3 format."""
i = {}
image_type = self._image_type(image.get('container_format'))
- ec2_id = self._image_ec2_id(image.get('id'), image_type)
+ ec2_id = self.image_ec2_id(image.get('id'), image_type)
name = image.get('name')
i['imageId'] = ec2_id
kernel_id = image['properties'].get('kernel_id')
if kernel_id:
- i['kernelId'] = self._image_ec2_id(kernel_id, 'aki')
+ i['kernelId'] = self.image_ec2_id(kernel_id, 'aki')
ramdisk_id = image['properties'].get('ramdisk_id')
if ramdisk_id:
- i['ramdiskId'] = self._image_ec2_id(ramdisk_id, 'ari')
+ i['ramdiskId'] = self.image_ec2_id(ramdisk_id, 'ari')
i['imageOwnerId'] = image['properties'].get('owner_id')
if name:
i['imageLocation'] = "%s (%s)" % (image['properties'].
@@ -991,8 +990,7 @@ class CloudController(object):
try:
image = self._get_image(context, ec2_id)
except exception.NotFound:
- raise exception.NotFound(_('Image %s not found') %
- ec2_id)
+ raise exception.ImageNotFound(image_id=ec2_id)
images.append(image)
else:
images = self.image_service.detail(context)
@@ -1012,8 +1010,8 @@ class CloudController(object):
metadata = {'properties': {'image_location': image_location}}
image = self.image_service.create(context, metadata)
image_type = self._image_type(image.get('container_format'))
- image_id = self._image_ec2_id(image['id'],
- image_type)
+ image_id = self.image_ec2_id(image['id'],
+ image_type)
msg = _("Registered image %(image_location)s with"
" id %(image_id)s") % locals()
LOG.audit(msg, context=context)
@@ -1026,7 +1024,7 @@ class CloudController(object):
try:
image = self._get_image(context, image_id)
except exception.NotFound:
- raise exception.NotFound(_('Image %s not found') % image_id)
+ raise exception.ImageNotFound(image_id=image_id)
result = {'imageId': image_id, 'launchPermission': []}
if image['is_public']:
result['launchPermission'].append({'group': 'all'})
@@ -1049,7 +1047,7 @@ class CloudController(object):
try:
image = self._get_image(context, image_id)
except exception.NotFound:
- raise exception.NotFound(_('Image %s not found') % image_id)
+ raise exception.ImageNotFound(image_id=image_id)
internal_id = image['id']
del(image['id'])
diff --git a/nova/api/ec2/ec2utils.py b/nova/api/ec2/ec2utils.py
index 3b34f6ea5..163aa4ed2 100644
--- a/nova/api/ec2/ec2utils.py
+++ b/nova/api/ec2/ec2utils.py
@@ -24,7 +24,7 @@ def ec2_id_to_id(ec2_id):
try:
return int(ec2_id.split('-')[-1], 16)
except ValueError:
- raise exception.NotFound(_("Id %s Not Found") % ec2_id)
+ raise exception.InvalidEc2Id(ec2_id=ec2_id)
def id_to_ec2_id(instance_id, template='i-%08x'):
diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py
index 5e76a06f7..348b70d5b 100644
--- a/nova/api/openstack/__init__.py
+++ b/nova/api/openstack/__init__.py
@@ -112,9 +112,6 @@ class APIRouter(wsgi.Router):
parent_resource=dict(member_name='server',
collection_name='servers'))
- _limits = limits.LimitsController()
- mapper.resource("limit", "limits", controller=_limits)
-
super(APIRouter, self).__init__(mapper)
@@ -145,6 +142,9 @@ class APIRouterV10(APIRouter):
parent_resource=dict(member_name='server',
collection_name='servers'))
+ mapper.resource("limit", "limits",
+ controller=limits.LimitsControllerV10())
+
mapper.resource("ip", "ips", controller=ips.Controller(),
collection=dict(public='GET', private='GET'),
parent_resource=dict(member_name='server',
@@ -178,3 +178,6 @@ class APIRouterV11(APIRouter):
mapper.resource("flavor", "flavors",
controller=flavors.ControllerV11(),
collection={'detail': 'GET'})
+
+ mapper.resource("limit", "limits",
+ controller=limits.LimitsControllerV11())
diff --git a/nova/api/openstack/accounts.py b/nova/api/openstack/accounts.py
index 6e3763e47..00fdd4540 100644
--- a/nova/api/openstack/accounts.py
+++ b/nova/api/openstack/accounts.py
@@ -48,7 +48,7 @@ class Controller(common.OpenstackController):
"""We cannot depend on the db layer to check for admin access
for the auth manager, so we do it here"""
if not context.is_admin:
- raise exception.NotAuthorized(_("Not admin user."))
+ raise exception.AdminRequired()
def index(self, req):
raise faults.Fault(webob.exc.HTTPNotImplemented())
diff --git a/nova/api/openstack/common.py b/nova/api/openstack/common.py
index 0b6dc944a..32cd689ca 100644
--- a/nova/api/openstack/common.py
+++ b/nova/api/openstack/common.py
@@ -15,6 +15,7 @@
# License for the specific language governing permissions and limitations
# under the License.
+import re
from urlparse import urlparse
import webob
@@ -124,16 +125,22 @@ def get_image_id_from_image_hash(image_service, context, image_hash):
"should have numerical format") % image_id
LOG.error(msg)
raise Exception(msg)
- raise exception.NotFound(image_hash)
+ raise exception.ImageNotFound(image_id=image_hash)
def get_id_from_href(href):
"""Return the id portion of a url as an int.
- Given: http://www.foo.com/bar/123?q=4
+ Given: 'http://www.foo.com/bar/123?q=4'
+ Returns: 123
+
+ In order to support local hrefs, the href argument can be just an id:
+ Given: '123'
Returns: 123
"""
+ if re.match(r'\d+$', str(href)):
+ return int(href)
try:
return int(urlparse(href).path.split('/')[-1])
except:
diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py
index 77baf5947..34d4c27fc 100644
--- a/nova/api/openstack/images.py
+++ b/nova/api/openstack/images.py
@@ -127,7 +127,7 @@ class Controller(common.OpenstackController):
raise webob.exc.HTTPBadRequest()
image = self._compute_service.snapshot(context, server_id, image_name)
- return self.get_builder(req).build(image, detail=True)
+ return dict(image=self.get_builder(req).build(image, detail=True))
def get_builder(self, request):
"""Indicates that you must use a Controller subclass."""
diff --git a/nova/api/openstack/limits.py b/nova/api/openstack/limits.py
index 9877af191..47bc238f1 100644
--- a/nova/api/openstack/limits.py
+++ b/nova/api/openstack/limits.py
@@ -33,7 +33,7 @@ from webob.dec import wsgify
from nova import wsgi
from nova.api.openstack import common
from nova.api.openstack import faults
-from nova.wsgi import Middleware
+from nova.api.openstack.views import limits as limits_views
# Convenience constants for the limits dictionary passed to Limiter().
@@ -51,8 +51,8 @@ class LimitsController(common.OpenstackController):
_serialization_metadata = {
"application/xml": {
"attributes": {
- "limit": ["verb", "URI", "regex", "value", "unit",
- "resetTime", "remaining", "name"],
+ "limit": ["verb", "URI", "uri", "regex", "value", "unit",
+ "resetTime", "next-available", "remaining", "name"],
},
"plurals": {
"rate": "limit",
@@ -67,12 +67,21 @@ class LimitsController(common.OpenstackController):
abs_limits = {}
rate_limits = req.environ.get("nova.limits", [])
- return {
- "limits": {
- "rate": rate_limits,
- "absolute": abs_limits,
- },
- }
+ builder = self._get_view_builder(req)
+ return builder.build(rate_limits, abs_limits)
+
+ def _get_view_builder(self, req):
+ raise NotImplementedError()
+
+
+class LimitsControllerV10(LimitsController):
+ def _get_view_builder(self, req):
+ return limits_views.ViewBuilderV10()
+
+
+class LimitsControllerV11(LimitsController):
+ def _get_view_builder(self, req):
+ return limits_views.ViewBuilderV11()
class Limit(object):
@@ -186,7 +195,7 @@ DEFAULT_LIMITS = [
]
-class RateLimitingMiddleware(Middleware):
+class RateLimitingMiddleware(wsgi.Middleware):
"""
Rate-limits requests passing through this middleware. All limit information
is stored in memory for this implementation.
@@ -200,7 +209,7 @@ class RateLimitingMiddleware(Middleware):
@param application: WSGI application to wrap
@param limits: List of dictionaries describing limits
"""
- Middleware.__init__(self, application)
+ wsgi.Middleware.__init__(self, application)
self._limiter = Limiter(limits or DEFAULT_LIMITS)
@wsgify(RequestClass=wsgi.Request)
diff --git a/nova/api/openstack/server_metadata.py b/nova/api/openstack/server_metadata.py
index 5c1390b9c..fd64ee4fb 100644
--- a/nova/api/openstack/server_metadata.py
+++ b/nova/api/openstack/server_metadata.py
@@ -18,6 +18,7 @@
from webob import exc
from nova import compute
+from nova import quota
from nova import wsgi
from nova.api.openstack import common
from nova.api.openstack import faults
@@ -44,10 +45,14 @@ class Controller(common.OpenstackController):
def create(self, req, server_id):
context = req.environ['nova.context']
- body = self._deserialize(req.body, req.get_content_type())
- self.compute_api.update_or_create_instance_metadata(context,
- server_id,
- body['metadata'])
+ data = self._deserialize(req.body, req.get_content_type())
+ metadata = data.get('metadata')
+ try:
+ self.compute_api.update_or_create_instance_metadata(context,
+ server_id,
+ metadata)
+ except quota.QuotaError as error:
+ self._handle_quota_error(error)
return req.body
def update(self, req, server_id, id):
@@ -59,9 +64,13 @@ class Controller(common.OpenstackController):
if len(body) > 1:
expl = _('Request body contains too many items')
raise exc.HTTPBadRequest(explanation=expl)
- self.compute_api.update_or_create_instance_metadata(context,
- server_id,
- body)
+ try:
+ self.compute_api.update_or_create_instance_metadata(context,
+ server_id,
+ body)
+ except quota.QuotaError as error:
+ self._handle_quota_error(error)
+
return req.body
def show(self, req, server_id, id):
@@ -77,3 +86,9 @@ class Controller(common.OpenstackController):
""" Deletes an existing metadata """
context = req.environ['nova.context']
self.compute_api.delete_instance_metadata(context, server_id, id)
+
+ def _handle_quota_error(self, error):
+ """Reraise quota errors as api-specific http exceptions."""
+ if error.code == "MetadataLimitExceeded":
+ raise exc.HTTPBadRequest(explanation=error.message)
+ raise error
diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py
index f221229f0..547310613 100644
--- a/nova/api/openstack/servers.py
+++ b/nova/api/openstack/servers.py
@@ -14,33 +14,30 @@
# under the License.
import base64
-import hashlib
import traceback
from webob import exc
from xml.dom import minidom
from nova import compute
-from nova import context
from nova import exception
from nova import flags
from nova import log as logging
from nova import quota
from nova import utils
-from nova import wsgi
from nova.api.openstack import common
from nova.api.openstack import faults
import nova.api.openstack.views.addresses
import nova.api.openstack.views.flavors
+import nova.api.openstack.views.images
import nova.api.openstack.views.servers
from nova.auth import manager as auth_manager
from nova.compute import instance_types
-from nova.compute import power_state
import nova.api.openstack
from nova.scheduler import api as scheduler_api
-LOG = logging.getLogger('server')
+LOG = logging.getLogger('nova.api.openstack.servers')
FLAGS = flags.FLAGS
@@ -139,16 +136,6 @@ class Controller(common.OpenstackController):
kernel_id, ramdisk_id = self._get_kernel_ramdisk_from_image(
req, image_id)
- # Metadata is a list, not a Dictionary, because we allow duplicate keys
- # (even though JSON can't encode this)
- # In future, we may not allow duplicate keys.
- # However, the CloudServers API is not definitive on this front,
- # and we want to be compatible.
- metadata = []
- if env['server'].get('metadata'):
- for k, v in env['server']['metadata'].items():
- metadata.append({'key': k, 'value': v})
-
personality = env['server'].get('personality')
injected_files = []
if personality:
@@ -177,7 +164,7 @@ class Controller(common.OpenstackController):
display_description=name,
key_name=key_name,
key_data=key_data,
- metadata=metadata,
+ metadata=env['server'].get('metadata', {}),
injected_files=injected_files)
except quota.QuotaError as error:
self._handle_quota_error(error)
@@ -330,9 +317,6 @@ class Controller(common.OpenstackController):
return faults.Fault(exc.HTTPBadRequest())
return exc.HTTPAccepted()
- def _action_rebuild(self, input_dict, req, id):
- return faults.Fault(exc.HTTPNotImplemented())
-
def _action_resize(self, input_dict, req, id):
""" Resizes a given instance to the flavor size requested """
try:
@@ -346,18 +330,20 @@ class Controller(common.OpenstackController):
except Exception, e:
LOG.exception(_("Error in resize %s"), e)
return faults.Fault(exc.HTTPBadRequest())
- return faults.Fault(exc.HTTPAccepted())
+ return exc.HTTPAccepted()
def _action_reboot(self, input_dict, req, id):
- try:
+ if 'reboot' in input_dict and 'type' in input_dict['reboot']:
reboot_type = input_dict['reboot']['type']
- except Exception:
- raise faults.Fault(exc.HTTPNotImplemented())
+ else:
+ LOG.exception(_("Missing argument 'type' for reboot"))
+ return faults.Fault(exc.HTTPUnprocessableEntity())
try:
# TODO(gundlach): pass reboot_type, support soft reboot in
# virt driver
self.compute_api.reboot(req.environ['nova.context'], id)
- except:
+ except Exception, e:
+ LOG.exception(_("Error in reboot %s"), e)
return faults.Fault(exc.HTTPUnprocessableEntity())
return exc.HTTPAccepted()
@@ -571,9 +557,8 @@ class Controller(common.OpenstackController):
"""
image_id = image_meta['id']
if image_meta['status'] != 'active':
- raise exception.Invalid(
- _("Cannot build from image %(image_id)s, status not active") %
- locals())
+ raise exception.ImageUnacceptable(image_id=image_id,
+ reason=_("status is not active"))
if image_meta.get('container_format') != 'ami':
return None, None
@@ -581,14 +566,12 @@ class Controller(common.OpenstackController):
try:
kernel_id = image_meta['properties']['kernel_id']
except KeyError:
- raise exception.NotFound(
- _("Kernel not found for image %(image_id)s") % locals())
+ raise exception.KernelNotFoundForImage(image_id=image_id)
try:
ramdisk_id = image_meta['properties']['ramdisk_id']
except KeyError:
- raise exception.NotFound(
- _("Ramdisk not found for image %(image_id)s") % locals())
+ raise exception.RamdiskNotFoundForImage(image_id=image_id)
return kernel_id, ramdisk_id
@@ -605,19 +588,35 @@ class ControllerV10(Controller):
return nova.api.openstack.views.servers.ViewBuilderV10(
addresses_builder)
- def _get_addresses_view_builder(self, req):
- return nova.api.openstack.views.addresses.ViewBuilderV10(req)
-
def _limit_items(self, items, req):
return common.limited(items, req)
def _parse_update(self, context, server_id, inst_dict, update_dict):
if 'adminPass' in inst_dict['server']:
update_dict['admin_pass'] = inst_dict['server']['adminPass']
- try:
- self.compute_api.set_admin_password(context, server_id)
- except exception.TimeoutException:
- return exc.HTTPRequestTimeout()
+ self.compute_api.set_admin_password(context, server_id)
+
+ def _action_rebuild(self, info, request, instance_id):
+ context = request.environ['nova.context']
+ instance_id = int(instance_id)
+
+ try:
+ image_id = info["rebuild"]["imageId"]
+ except (KeyError, TypeError):
+ msg = _("Could not parse imageId from request.")
+ LOG.debug(msg)
+ return faults.Fault(exc.HTTPBadRequest(explanation=msg))
+
+ try:
+ self.compute_api.rebuild(context, instance_id, image_id)
+ except exception.BuildInProgress:
+ msg = _("Instance %d is currently being rebuilt.") % instance_id
+ LOG.debug(msg)
+ return faults.Fault(exc.HTTPConflict(explanation=msg))
+
+ response = exc.HTTPAccepted()
+ response.empty_body = True
+ return response
class ControllerV11(Controller):
@@ -639,9 +638,6 @@ class ControllerV11(Controller):
return nova.api.openstack.views.servers.ViewBuilderV11(
addresses_builder, flavor_builder, image_builder, base_url)
- def _get_addresses_view_builder(self, req):
- return nova.api.openstack.views.addresses.ViewBuilderV11(req)
-
def _action_change_password(self, input_dict, req, id):
context = req.environ['nova.context']
if (not 'changePassword' in input_dict
@@ -658,6 +654,63 @@ class ControllerV11(Controller):
def _limit_items(self, items, req):
return common.limited_by_marker(items, req)
+ def _validate_metadata(self, metadata):
+ """Ensure that we can work with the metadata given."""
+ try:
+ metadata.iteritems()
+ except AttributeError as ex:
+ msg = _("Unable to parse metadata key/value pairs.")
+ LOG.debug(msg)
+ raise faults.Fault(exc.HTTPBadRequest(explanation=msg))
+
+ def _decode_personalities(self, personalities):
+ """Decode the Base64-encoded personalities."""
+ for personality in personalities:
+ try:
+ path = personality["path"]
+ contents = personality["contents"]
+ except (KeyError, TypeError):
+ msg = _("Unable to parse personality path/contents.")
+ LOG.info(msg)
+ raise faults.Fault(exc.HTTPBadRequest(explanation=msg))
+
+ try:
+ personality["contents"] = base64.b64decode(contents)
+ except TypeError:
+ msg = _("Personality content could not be Base64 decoded.")
+ LOG.info(msg)
+ raise faults.Fault(exc.HTTPBadRequest(explanation=msg))
+
+ def _action_rebuild(self, info, request, instance_id):
+ context = request.environ['nova.context']
+ instance_id = int(instance_id)
+
+ try:
+ image_ref = info["rebuild"]["imageRef"]
+ except (KeyError, TypeError):
+ msg = _("Could not parse imageRef from request.")
+ LOG.debug(msg)
+ return faults.Fault(exc.HTTPBadRequest(explanation=msg))
+
+ image_id = common.get_id_from_href(image_ref)
+ personalities = info["rebuild"].get("personality", [])
+ metadata = info["rebuild"].get("metadata", {})
+
+ self._validate_metadata(metadata)
+ self._decode_personalities(personalities)
+
+ try:
+ self.compute_api.rebuild(context, instance_id, image_id, metadata,
+ personalities)
+ except exception.BuildInProgress:
+ msg = _("Instance %d is currently being rebuilt.") % instance_id
+ LOG.debug(msg)
+ return faults.Fault(exc.HTTPConflict(explanation=msg))
+
+ response = exc.HTTPAccepted()
+ response.empty_body = True
+ return response
+
def _get_server_admin_password(self, server):
""" Determine the admin password for a server on creation """
password = server.get('adminPass')
diff --git a/nova/api/openstack/users.py b/nova/api/openstack/users.py
index 077ccfc79..7ae4c3232 100644
--- a/nova/api/openstack/users.py
+++ b/nova/api/openstack/users.py
@@ -48,7 +48,7 @@ class Controller(common.OpenstackController):
"""We cannot depend on the db layer to check for admin access
for the auth manager, so we do it here"""
if not context.is_admin:
- raise exception.NotAuthorized(_("Not admin user"))
+ raise exception.AdminRequired()
def index(self, req):
"""Return all users in brief"""
diff --git a/nova/api/openstack/views/images.py b/nova/api/openstack/views/images.py
index 9dec8a355..2773c9c13 100644
--- a/nova/api/openstack/views/images.py
+++ b/nova/api/openstack/views/images.py
@@ -46,6 +46,14 @@ class ViewBuilder(object):
except KeyError:
image['status'] = image['status'].upper()
+ def _build_server(self, image, instance_id):
+ """Indicates that you must use a ViewBuilder subclass."""
+ raise NotImplementedError
+
+ def generate_server_ref(self, server_id):
+ """Return an href string pointing to this server."""
+ return os.path.join(self._url, "servers", str(server_id))
+
def generate_href(self, image_id):
"""Return an href string pointing to this object."""
return os.path.join(self._url, "images", str(image_id))
@@ -66,7 +74,7 @@ class ViewBuilder(object):
if "instance_id" in properties:
try:
- image["serverId"] = int(properties["instance_id"])
+ self._build_server(image, int(properties["instance_id"]))
except ValueError:
pass
@@ -85,12 +93,17 @@ class ViewBuilder(object):
class ViewBuilderV10(ViewBuilder):
"""OpenStack API v1.0 Image Builder"""
- pass
+
+ def _build_server(self, image, instance_id):
+ image["serverId"] = instance_id
class ViewBuilderV11(ViewBuilder):
"""OpenStack API v1.1 Image Builder"""
+ def _build_server(self, image, instance_id):
+ image["serverRef"] = self.generate_server_ref(instance_id)
+
def build(self, image_obj, detail=False):
"""Return a standardized image structure for display by the API."""
image = ViewBuilder.build(self, image_obj, detail)
diff --git a/nova/api/openstack/views/limits.py b/nova/api/openstack/views/limits.py
new file mode 100644
index 000000000..552db39ee
--- /dev/null
+++ b/nova/api/openstack/views/limits.py
@@ -0,0 +1,100 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2010-2011 OpenStack LLC.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import time
+
+from nova.api.openstack import common
+
+
+class ViewBuilder(object):
+ """Openstack API base limits view builder."""
+
+ def build(self, rate_limits, absolute_limits):
+ rate_limits = self._build_rate_limits(rate_limits)
+ absolute_limits = self._build_absolute_limits(absolute_limits)
+
+ output = {
+ "limits": {
+ "rate": rate_limits,
+ "absolute": absolute_limits,
+ },
+ }
+
+ return output
+
+
+class ViewBuilderV10(ViewBuilder):
+ """Openstack API v1.0 limits view builder."""
+
+ def _build_rate_limits(self, rate_limits):
+ return [self._build_rate_limit(r) for r in rate_limits]
+
+ def _build_rate_limit(self, rate_limit):
+ return {
+ "verb": rate_limit["verb"],
+ "URI": rate_limit["URI"],
+ "regex": rate_limit["regex"],
+ "value": rate_limit["value"],
+ "remaining": int(rate_limit["remaining"]),
+ "unit": rate_limit["unit"],
+ "resetTime": rate_limit["resetTime"],
+ }
+
+ def _build_absolute_limits(self, absolute_limit):
+ return {}
+
+
+class ViewBuilderV11(ViewBuilder):
+ """Openstack API v1.1 limits view builder."""
+
+ def _build_rate_limits(self, rate_limits):
+ limits = []
+ for rate_limit in rate_limits:
+ _rate_limit_key = None
+ _rate_limit = self._build_rate_limit(rate_limit)
+
+ # check for existing key
+ for limit in limits:
+ if limit["uri"] == rate_limit["URI"] and \
+ limit["regex"] == limit["regex"]:
+ _rate_limit_key = limit
+ break
+
+ # ensure we have a key if we didn't find one
+ if not _rate_limit_key:
+ _rate_limit_key = {
+ "uri": rate_limit["URI"],
+ "regex": rate_limit["regex"],
+ "limit": [],
+ }
+ limits.append(_rate_limit_key)
+
+ _rate_limit_key["limit"].append(_rate_limit)
+
+ return limits
+
+ def _build_rate_limit(self, rate_limit):
+ return {
+ "verb": rate_limit["verb"],
+ "value": rate_limit["value"],
+ "remaining": int(rate_limit["remaining"]),
+ "unit": rate_limit["unit"],
+ "next-available": rate_limit["resetTime"],
+ }
+
+ def _build_absolute_limits(self, absolute_limit):
+ return {}
diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py
index e52bfaea3..0be468edc 100644
--- a/nova/api/openstack/views/servers.py
+++ b/nova/api/openstack/views/servers.py
@@ -63,10 +63,12 @@ class ViewBuilder(object):
power_state.BLOCKED: 'ACTIVE',
power_state.SUSPENDED: 'SUSPENDED',
power_state.PAUSED: 'PAUSED',
- power_state.SHUTDOWN: 'ACTIVE',
- power_state.SHUTOFF: 'ACTIVE',
+ power_state.SHUTDOWN: 'SHUTDOWN',
+ power_state.SHUTOFF: 'SHUTOFF',
power_state.CRASHED: 'ERROR',
- power_state.FAILED: 'ERROR'}
+ power_state.FAILED: 'ERROR',
+ power_state.BUILDING: 'BUILD',
+ }
inst_dict = {
'id': int(inst['id']),
diff --git a/nova/auth/dbdriver.py b/nova/auth/dbdriver.py
index b2c580d83..a429b7812 100644
--- a/nova/auth/dbdriver.py
+++ b/nova/auth/dbdriver.py
@@ -81,7 +81,7 @@ class DbDriver(object):
user_ref = db.user_create(context.get_admin_context(), values)
return self._db_user_to_auth_user(user_ref)
except exception.Duplicate, e:
- raise exception.Duplicate(_('User %s already exists') % name)
+ raise exception.UserExists(user=name)
def _db_user_to_auth_user(self, user_ref):
return {'id': user_ref['id'],
@@ -103,9 +103,7 @@ class DbDriver(object):
"""Create a project"""
manager = db.user_get(context.get_admin_context(), manager_uid)
if not manager:
- raise exception.NotFound(_("Project can't be created because "
- "manager %s doesn't exist")
- % manager_uid)
+ raise exception.UserNotFound(user_id=manager_uid)
# description is a required attribute
if description is None:
@@ -119,9 +117,7 @@ class DbDriver(object):
for member_uid in member_uids:
member = db.user_get(context.get_admin_context(), member_uid)
if not member:
- raise exception.NotFound(_("Project can't be created "
- "because user %s doesn't exist")
- % member_uid)
+ raise exception.UserNotFound(user_id=member_uid)
members.add(member)
values = {'id': name,
@@ -132,8 +128,7 @@ class DbDriver(object):
try:
project = db.project_create(context.get_admin_context(), values)
except exception.Duplicate:
- raise exception.Duplicate(_("Project can't be created because "
- "project %s already exists") % name)
+ raise exception.ProjectExists(project=name)
for member in members:
db.project_add_member(context.get_admin_context(),
@@ -154,9 +149,7 @@ class DbDriver(object):
if manager_uid:
manager = db.user_get(context.get_admin_context(), manager_uid)
if not manager:
- raise exception.NotFound(_("Project can't be modified because "
- "manager %s doesn't exist") %
- manager_uid)
+ raise exception.UserNotFound(user_id=manager_uid)
values['project_manager'] = manager['id']
if description:
values['description'] = description
@@ -244,8 +237,8 @@ class DbDriver(object):
def _validate_user_and_project(self, user_id, project_id):
user = db.user_get(context.get_admin_context(), user_id)
if not user:
- raise exception.NotFound(_('User "%s" not found') % user_id)
+ raise exception.UserNotFound(user_id=user_id)
project = db.project_get(context.get_admin_context(), project_id)
if not project:
- raise exception.NotFound(_('Project "%s" not found') % project_id)
+ raise exception.ProjectNotFound(project_id=project_id)
return user, project
diff --git a/nova/auth/ldapdriver.py b/nova/auth/ldapdriver.py
index fcac55510..3f8432851 100644
--- a/nova/auth/ldapdriver.py
+++ b/nova/auth/ldapdriver.py
@@ -171,7 +171,7 @@ class LdapDriver(object):
def create_user(self, name, access_key, secret_key, is_admin):
"""Create a user"""
if self.__user_exists(name):
- raise exception.Duplicate(_("LDAP user %s already exists") % name)
+ raise exception.LDAPUserExists(user=name)
if FLAGS.ldap_user_modify_only:
if self.__ldap_user_exists(name):
# Retrieve user by name
@@ -202,8 +202,7 @@ class LdapDriver(object):
self.conn.modify_s(self.__uid_to_dn(name), attr)
return self.get_user(name)
else:
- raise exception.NotFound(_("LDAP object for %s doesn't exist")
- % name)
+ raise exception.LDAPUserNotFound(user_id=name)
else:
attr = [
('objectclass', ['person',
@@ -226,12 +225,9 @@ class LdapDriver(object):
description=None, member_uids=None):
"""Create a project"""
if self.__project_exists(name):
- raise exception.Duplicate(_("Project can't be created because "
- "project %s already exists") % name)
+ raise exception.ProjectExists(project=name)
if not self.__user_exists(manager_uid):
- raise exception.NotFound(_("Project can't be created because "
- "manager %s doesn't exist")
- % manager_uid)
+ raise exception.LDAPUserNotFound(user_id=manager_uid)
manager_dn = self.__uid_to_dn(manager_uid)
# description is a required attribute
if description is None:
@@ -240,9 +236,7 @@ class LdapDriver(object):
if member_uids is not None:
for member_uid in member_uids:
if not self.__user_exists(member_uid):
- raise exception.NotFound(_("Project can't be created "
- "because user %s doesn't exist")
- % member_uid)
+ raise exception.LDAPUserNotFound(user_id=member_uid)
members.append(self.__uid_to_dn(member_uid))
# always add the manager as a member because members is required
if not manager_dn in members:
@@ -265,9 +259,7 @@ class LdapDriver(object):
attr = []
if manager_uid:
if not self.__user_exists(manager_uid):
- raise exception.NotFound(_("Project can't be modified because "
- "manager %s doesn't exist")
- % manager_uid)
+ raise exception.LDAPUserNotFound(user_id=manager_uid)
manager_dn = self.__uid_to_dn(manager_uid)
attr.append((self.ldap.MOD_REPLACE, LdapDriver.project_attribute,
manager_dn))
@@ -347,7 +339,7 @@ class LdapDriver(object):
def delete_user(self, uid):
"""Delete a user"""
if not self.__user_exists(uid):
- raise exception.NotFound(_("User %s doesn't exist") % uid)
+ raise exception.LDAPUserNotFound(user_id=uid)
self.__remove_from_all(uid)
if FLAGS.ldap_user_modify_only:
# Delete attributes
@@ -471,15 +463,12 @@ class LdapDriver(object):
description, member_uids=None):
"""Create a group"""
if self.__group_exists(group_dn):
- raise exception.Duplicate(_("Group can't be created because "
- "group %s already exists") % name)
+ raise exception.LDAPGroupExists(group=name)
members = []
if member_uids is not None:
for member_uid in member_uids:
if not self.__user_exists(member_uid):
- raise exception.NotFound(_("Group can't be created "
- "because user %s doesn't exist")
- % member_uid)
+ raise exception.LDAPUserNotFound(user_id=member_uid)
members.append(self.__uid_to_dn(member_uid))
dn = self.__uid_to_dn(uid)
if not dn in members:
@@ -494,8 +483,7 @@ class LdapDriver(object):
def __is_in_group(self, uid, group_dn):
"""Check if user is in group"""
if not self.__user_exists(uid):
- raise exception.NotFound(_("User %s can't be searched in group "
- "because the user doesn't exist") % uid)
+ raise exception.LDAPUserNotFound(user_id=uid)
if not self.__group_exists(group_dn):
return False
res = self.__find_object(group_dn,
@@ -506,29 +494,23 @@ class LdapDriver(object):
def __add_to_group(self, uid, group_dn):
"""Add user to group"""
if not self.__user_exists(uid):
- raise exception.NotFound(_("User %s can't be added to the group "
- "because the user doesn't exist") % uid)
+ raise exception.LDAPUserNotFound(user_id=uid)
if not self.__group_exists(group_dn):
- raise exception.NotFound(_("The group at dn %s doesn't exist") %
- group_dn)
+ raise exception.LDAPGroupNotFound(group_id=group_dn)
if self.__is_in_group(uid, group_dn):
- raise exception.Duplicate(_("User %(uid)s is already a member of "
- "the group %(group_dn)s") % locals())
+ raise exception.LDAPMembershipExists(uid=uid, group_dn=group_dn)
attr = [(self.ldap.MOD_ADD, 'member', self.__uid_to_dn(uid))]
self.conn.modify_s(group_dn, attr)
def __remove_from_group(self, uid, group_dn):
"""Remove user from group"""
if not self.__group_exists(group_dn):
- raise exception.NotFound(_("The group at dn %s doesn't exist")
- % group_dn)
+ raise exception.LDAPGroupNotFound(group_id=group_dn)
if not self.__user_exists(uid):
- raise exception.NotFound(_("User %s can't be removed from the "
- "group because the user doesn't exist")
- % uid)
+ raise exception.LDAPUserNotFound(user_id=uid)
if not self.__is_in_group(uid, group_dn):
- raise exception.NotFound(_("User %s is not a member of the group")
- % uid)
+ raise exception.LDAPGroupMembershipNotFound(user_id=uid,
+ group_id=group_dn)
# NOTE(vish): remove user from group and any sub_groups
sub_dns = self.__find_group_dns_with_member(group_dn, uid)
for sub_dn in sub_dns:
@@ -548,9 +530,7 @@ class LdapDriver(object):
def __remove_from_all(self, uid):
"""Remove user from all roles and projects"""
if not self.__user_exists(uid):
- raise exception.NotFound(_("User %s can't be removed from all "
- "because the user doesn't exist")
- % uid)
+ raise exception.LDAPUserNotFound(user_id=uid)
role_dns = self.__find_group_dns_with_member(
FLAGS.role_project_subtree, uid)
for role_dn in role_dns:
@@ -563,8 +543,7 @@ class LdapDriver(object):
def __delete_group(self, group_dn):
"""Delete Group"""
if not self.__group_exists(group_dn):
- raise exception.NotFound(_("Group at dn %s doesn't exist")
- % group_dn)
+ raise exception.LDAPGroupNotFound(group_id=group_dn)
self.conn.delete_s(group_dn)
def __delete_roles(self, project_dn):
diff --git a/nova/auth/manager.py b/nova/auth/manager.py
index 8479c95a4..07235a2a7 100644
--- a/nova/auth/manager.py
+++ b/nova/auth/manager.py
@@ -223,6 +223,13 @@ class AuthManager(object):
if driver or not getattr(self, 'driver', None):
self.driver = utils.import_class(driver or FLAGS.auth_driver)
+ if FLAGS.memcached_servers:
+ import memcache
+ else:
+ from nova import fakememcache as memcache
+ self.mc = memcache.Client(FLAGS.memcached_servers,
+ debug=0)
+
def authenticate(self, access, signature, params, verb='GET',
server_string='127.0.0.1:8773', path='/',
check_type='ec2', headers=None):
@@ -270,8 +277,7 @@ class AuthManager(object):
LOG.debug('user: %r', user)
if user is None:
LOG.audit(_("Failed authorization for access key %s"), access_key)
- raise exception.NotFound(_('No user found for access key %s')
- % access_key)
+ raise exception.AccessKeyNotFound(access_key=access_key)
# NOTE(vish): if we stop using project name as id we need better
# logic to find a default project for user
@@ -285,8 +291,7 @@ class AuthManager(object):
uname = user.name
LOG.audit(_("failed authorization: no project named %(pjid)s"
" (user=%(uname)s)") % locals())
- raise exception.NotFound(_('No project called %s could be found')
- % project_id)
+ raise exception.ProjectNotFound(project_id=project_id)
if not self.is_admin(user) and not self.is_project_member(user,
project):
uname = user.name
@@ -295,28 +300,40 @@ class AuthManager(object):
pjid = project.id
LOG.audit(_("Failed authorization: user %(uname)s not admin"
" and not member of project %(pjname)s") % locals())
- raise exception.NotFound(_('User %(uid)s is not a member of'
- ' project %(pjid)s') % locals())
+ raise exception.ProjectMembershipNotFound(project_id=pjid,
+ user_id=uid)
if check_type == 's3':
sign = signer.Signer(user.secret.encode())
expected_signature = sign.s3_authorization(headers, verb, path)
- LOG.debug('user.secret: %s', user.secret)
- LOG.debug('expected_signature: %s', expected_signature)
- LOG.debug('signature: %s', signature)
+ LOG.debug(_('user.secret: %s'), user.secret)
+ LOG.debug(_('expected_signature: %s'), expected_signature)
+ LOG.debug(_('signature: %s'), signature)
if signature != expected_signature:
LOG.audit(_("Invalid signature for user %s"), user.name)
- raise exception.NotAuthorized(_('Signature does not match'))
+ raise exception.InvalidSignature(signature=signature,
+ user=user)
elif check_type == 'ec2':
# NOTE(vish): hmac can't handle unicode, so encode ensures that
# secret isn't unicode
expected_signature = signer.Signer(user.secret.encode()).generate(
params, verb, server_string, path)
- LOG.debug('user.secret: %s', user.secret)
- LOG.debug('expected_signature: %s', expected_signature)
- LOG.debug('signature: %s', signature)
+ LOG.debug(_('user.secret: %s'), user.secret)
+ LOG.debug(_('expected_signature: %s'), expected_signature)
+ LOG.debug(_('signature: %s'), signature)
if signature != expected_signature:
+ (addr_str, port_str) = utils.parse_server_string(server_string)
+ # If the given server_string contains port num, try without it.
+ if port_str != '':
+ host_only_signature = signer.Signer(
+ user.secret.encode()).generate(params, verb,
+ addr_str, path)
+ LOG.debug(_('host_only_signature: %s'),
+ host_only_signature)
+ if signature == host_only_signature:
+ return (user, project)
LOG.audit(_("Invalid signature for user %s"), user.name)
- raise exception.NotAuthorized(_('Signature does not match'))
+ raise exception.InvalidSignature(signature=signature,
+ user=user)
return (user, project)
def get_access_key(self, user, project):
@@ -360,6 +377,27 @@ class AuthManager(object):
if self.has_role(user, role):
return True
+ def _build_mc_key(self, user, role, project=None):
+ key_parts = ['rolecache', User.safe_id(user), str(role)]
+ if project:
+ key_parts.append(Project.safe_id(project))
+ return '-'.join(key_parts)
+
+ def _clear_mc_key(self, user, role, project=None):
+ # NOTE(anthony): it would be better to delete the key
+ self.mc.set(self._build_mc_key(user, role, project), None)
+
+ def _has_role(self, user, role, project=None):
+ mc_key = self._build_mc_key(user, role, project)
+ rslt = self.mc.get(mc_key)
+ if rslt is None:
+ with self.driver() as drv:
+ rslt = drv.has_role(user, role, project)
+ self.mc.set(mc_key, rslt)
+ return rslt
+ else:
+ return rslt
+
def has_role(self, user, role, project=None):
"""Checks existence of role for user
@@ -383,24 +421,24 @@ class AuthManager(object):
@rtype: bool
@return: True if the user has the role.
"""
- with self.driver() as drv:
- if role == 'projectmanager':
- if not project:
- raise exception.Error(_("Must specify project"))
- return self.is_project_manager(user, project)
+ if role == 'projectmanager':
+ if not project:
+ raise exception.Error(_("Must specify project"))
+ return self.is_project_manager(user, project)
+
+ global_role = self._has_role(User.safe_id(user),
+ role,
+ None)
- global_role = drv.has_role(User.safe_id(user),
- role,
- None)
- if not global_role:
- return global_role
+ if not global_role:
+ return global_role
- if not project or role in FLAGS.global_roles:
- return global_role
+ if not project or role in FLAGS.global_roles:
+ return global_role
- return drv.has_role(User.safe_id(user),
- role,
- Project.safe_id(project))
+ return self._has_role(User.safe_id(user),
+ role,
+ Project.safe_id(project))
def add_role(self, user, role, project=None):
"""Adds role for user
@@ -420,9 +458,9 @@ class AuthManager(object):
@param project: Project in which to add local role.
"""
if role not in FLAGS.allowed_roles:
- raise exception.NotFound(_("The %s role can not be found") % role)
+ raise exception.UserRoleNotFound(role_id=role)
if project is not None and role in FLAGS.global_roles:
- raise exception.NotFound(_("The %s role is global only") % role)
+ raise exception.GlobalRoleNotAllowed(role_id=role)
uid = User.safe_id(user)
pid = Project.safe_id(project)
if project:
@@ -432,6 +470,7 @@ class AuthManager(object):
LOG.audit(_("Adding sitewide role %(role)s to user %(uid)s")
% locals())
with self.driver() as drv:
+ self._clear_mc_key(uid, role, pid)
drv.add_role(uid, role, pid)
def remove_role(self, user, role, project=None):
@@ -460,6 +499,7 @@ class AuthManager(object):
LOG.audit(_("Removing sitewide role %(role)s"
" from user %(uid)s") % locals())
with self.driver() as drv:
+ self._clear_mc_key(uid, role, pid)
drv.remove_role(uid, role, pid)
@staticmethod
diff --git a/nova/cloudpipe/pipelib.py b/nova/cloudpipe/pipelib.py
index dc6f55af2..7844d31e1 100644
--- a/nova/cloudpipe/pipelib.py
+++ b/nova/cloudpipe/pipelib.py
@@ -101,12 +101,13 @@ class CloudPipe(object):
key_name = self.setup_key_pair(ctxt)
group_name = self.setup_security_group(ctxt)
+ ec2_id = self.controller.image_ec2_id(FLAGS.vpn_image_id)
reservation = self.controller.run_instances(ctxt,
user_data=self.get_encoded_zip(project_id),
max_count=1,
min_count=1,
instance_type='m1.tiny',
- image_id=FLAGS.vpn_image_id,
+ image_id=ec2_id,
key_name=key_name,
security_group=[group_name])
diff --git a/nova/compute/api.py b/nova/compute/api.py
index 48f8b7b0e..63884be97 100644
--- a/nova/compute/api.py
+++ b/nova/compute/api.py
@@ -32,6 +32,7 @@ from nova import rpc
from nova import utils
from nova import volume
from nova.compute import instance_types
+from nova.compute import power_state
from nova.scheduler import api as scheduler_api
from nova.db import base
@@ -102,19 +103,40 @@ class API(base.Base):
if len(content) > content_limit:
raise quota.QuotaError(code="OnsetFileContentLimitExceeded")
+ def _check_metadata_properties_quota(self, context, metadata={}):
+ """Enforce quota limits on metadata properties."""
+ num_metadata = len(metadata)
+ quota_metadata = quota.allowed_metadata_items(context, num_metadata)
+ if quota_metadata < num_metadata:
+ pid = context.project_id
+ msg = _("Quota exceeeded for %(pid)s, tried to set "
+ "%(num_metadata)s metadata properties") % locals()
+ LOG.warn(msg)
+ raise quota.QuotaError(msg, "MetadataLimitExceeded")
+
+ # Because metadata is stored in the DB, we hard-code the size limits
+ # In future, we may support more variable length strings, so we act
+ # as if this is quota-controlled for forwards compatibility
+ for k, v in metadata.iteritems():
+ if len(k) > 255 or len(v) > 255:
+ pid = context.project_id
+ msg = _("Quota exceeeded for %(pid)s, metadata property "
+ "key or value too long") % locals()
+ LOG.warn(msg)
+ raise quota.QuotaError(msg, "MetadataLimitExceeded")
+
def create(self, context, instance_type,
image_id, kernel_id=None, ramdisk_id=None,
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, metadata=[],
+ availability_zone=None, user_data=None, metadata={},
injected_files=None):
"""Create the number and type of instances requested.
Verifies that quota and other arguments are valid.
"""
-
if not instance_type:
instance_type = instance_types.get_default_instance_type()
@@ -128,30 +150,7 @@ class API(base.Base):
"run %s more instances of this type.") %
num_instances, "InstanceLimitExceeded")
- num_metadata = len(metadata)
- quota_metadata = quota.allowed_metadata_items(context, num_metadata)
- if quota_metadata < num_metadata:
- pid = context.project_id
- msg = (_("Quota exceeeded for %(pid)s,"
- " tried to set %(num_metadata)s metadata properties")
- % locals())
- LOG.warn(msg)
- raise quota.QuotaError(msg, "MetadataLimitExceeded")
-
- # Because metadata is stored in the DB, we hard-code the size limits
- # In future, we may support more variable length strings, so we act
- # as if this is quota-controlled for forwards compatibility
- for metadata_item in metadata:
- k = metadata_item['key']
- v = metadata_item['value']
- if len(k) > 255 or len(v) > 255:
- pid = context.project_id
- msg = (_("Quota exceeeded for %(pid)s,"
- " metadata property key or value too long")
- % locals())
- LOG.warn(msg)
- raise quota.QuotaError(msg, "MetadataLimitExceeded")
-
+ self._check_metadata_properties_quota(context, metadata)
self._check_injected_file_quota(context, injected_files)
image = self.image_service.show(context, image_id)
@@ -483,6 +482,17 @@ class API(base.Base):
"""Generic handler for RPC calls to the scheduler."""
rpc.cast(context, FLAGS.scheduler_topic, args)
+ def _find_host(self, context, instance_id):
+ """Find the host associated with an instance."""
+ for attempts in xrange(10):
+ instance = self.get(context, instance_id)
+ host = instance["host"]
+ if host:
+ return host
+ time.sleep(1)
+ raise exception.Error(_("Unable to find host for Instance %s")
+ % instance_id)
+
def snapshot(self, context, instance_id, name):
"""Snapshot the given instance.
@@ -503,14 +513,41 @@ class API(base.Base):
"""Reboot the given instance."""
self._cast_compute_message('reboot_instance', context, instance_id)
+ def rebuild(self, context, instance_id, image_id, metadata=None,
+ files_to_inject=None):
+ """Rebuild the given instance with the provided metadata."""
+ instance = db.api.instance_get(context, instance_id)
+
+ if instance["state"] == power_state.BUILDING:
+ msg = _("Instance already building")
+ raise exception.BuildInProgress(msg)
+
+ metadata = metadata or {}
+ self._check_metadata_properties_quota(context, metadata)
+
+ files_to_inject = files_to_inject or []
+ self._check_injected_file_quota(context, files_to_inject)
+
+ self.db.instance_update(context, instance_id, {"metadata": metadata})
+
+ rebuild_params = {
+ "image_id": image_id,
+ "injected_files": files_to_inject,
+ }
+
+ self._cast_compute_message('rebuild_instance',
+ context,
+ instance_id,
+ params=rebuild_params)
+
def revert_resize(self, context, instance_id):
"""Reverts a resize, deleting the 'new' instance in the process."""
context = context.elevated()
migration_ref = self.db.migration_get_by_instance_and_status(context,
instance_id, 'finished')
if not migration_ref:
- raise exception.NotFound(_("No finished migrations found for "
- "instance"))
+ raise exception.MigrationNotFoundByStatus(instance_id=instance_id,
+ status='finished')
params = {'migration_id': migration_ref['id']}
self._cast_compute_message('revert_resize', context, instance_id,
@@ -524,8 +561,8 @@ class API(base.Base):
migration_ref = self.db.migration_get_by_instance_and_status(context,
instance_id, 'finished')
if not migration_ref:
- raise exception.NotFound(_("No finished migrations found for "
- "instance"))
+ raise exception.MigrationNotFoundByStatus(instance_id=instance_id,
+ status='finished')
instance_ref = self.db.instance_get(context, instance_id)
params = {'migration_id': migration_ref['id']}
self._cast_compute_message('confirm_resize', context, instance_id,
@@ -609,8 +646,12 @@ class API(base.Base):
def set_admin_password(self, context, instance_id, password=None):
"""Set the root/admin password for the given instance."""
- self._cast_compute_message(
- 'set_admin_password', context, instance_id, password)
+ host = self._find_host(context, instance_id)
+
+ rpc.cast(context,
+ self.db.queue_get_for(context, FLAGS.compute_topic, host),
+ {"method": "set_admin_password",
+ "args": {"instance_id": instance_id, "new_pass": password}})
def inject_file(self, context, instance_id):
"""Write a file to the given instance."""
@@ -722,5 +763,8 @@ class API(base.Base):
def update_or_create_instance_metadata(self, context, instance_id,
metadata):
"""Updates or creates instance metadata."""
+ combined_metadata = self.get_instance_metadata(context, instance_id)
+ combined_metadata.update(metadata)
+ self._check_metadata_properties_quota(context, combined_metadata)
self.db.instance_metadata_update_or_create(context, instance_id,
metadata)
diff --git a/nova/compute/instance_types.py b/nova/compute/instance_types.py
index 98b4425c8..1275a6fdd 100644
--- a/nova/compute/instance_types.py
+++ b/nova/compute/instance_types.py
@@ -37,11 +37,11 @@ def create(name, memory, vcpus, local_gb, flavorid, swap=0,
try:
int(option)
except ValueError:
- raise exception.InvalidInputException(
- _("create arguments must be positive integers"))
+ raise exception.InvalidInput(reason=_("create arguments must "
+ "be positive integers"))
if (int(memory) <= 0) or (int(vcpus) <= 0) or (int(local_gb) < 0):
- raise exception.InvalidInputException(
- _("create arguments must be positive integers"))
+ raise exception.InvalidInput(reason=_("create arguments must "
+ "be positive integers"))
try:
db.instance_type_create(
@@ -56,13 +56,15 @@ def create(name, memory, vcpus, local_gb, flavorid, swap=0,
rxtx_cap=rxtx_cap))
except exception.DBError, e:
LOG.exception(_('DB error: %s') % e)
- raise exception.ApiError(_("Cannot create instance type: %s") % name)
+ raise exception.ApiError(_("Cannot create instance_type with "
+ "name %(name)s and flavorid %(flavorid)s")
+ % locals())
def destroy(name):
"""Marks instance types as deleted."""
if name is None:
- raise exception.InvalidInputException(_("No instance type specified"))
+ raise exception.InvalidInstanceType(instance_type=name)
else:
try:
db.instance_type_destroy(context.get_admin_context(), name)
@@ -74,7 +76,7 @@ def destroy(name):
def purge(name):
"""Removes instance types from database."""
if name is None:
- raise exception.InvalidInputException(_("No instance type specified"))
+ raise exception.InvalidInstanceType(instance_type=name)
else:
try:
db.instance_type_purge(context.get_admin_context(), name)
diff --git a/nova/compute/manager.py b/nova/compute/manager.py
index 39d7af9c1..556b3b3b9 100644
--- a/nova/compute/manager.py
+++ b/nova/compute/manager.py
@@ -17,8 +17,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-"""
-Handles all processes relating to instances (guest vms).
+"""Handles all processes relating to instances (guest vms).
The :py:class:`ComputeManager` class is a :py:class:`nova.manager.Manager` that
handles RPC calls relating to creating instances. It is responsible for
@@ -33,15 +32,15 @@ terminating it.
by :func:`nova.utils.import_object`
:volume_manager: Name of class that handles persistent storage, loaded by
:func:`nova.utils.import_object`
+
"""
import datetime
import os
-import random
-import string
import socket
import sys
import tempfile
+import time
import functools
from eventlet import greenthread
@@ -50,11 +49,14 @@ from nova import exception
from nova import flags
from nova import log as logging
from nova import manager
+from nova import network
from nova import rpc
from nova import utils
+from nova import volume
from nova.compute import power_state
from nova.virt import driver
+
FLAGS = flags.FLAGS
flags.DEFINE_string('instances_path', '$state_path/instances',
'where instances are stored on disk')
@@ -73,20 +75,17 @@ flags.DEFINE_integer('live_migration_retry_count', 30,
flags.DEFINE_integer("rescue_timeout", 0,
"Automatically unrescue an instance after N seconds."
" Set to 0 to disable.")
+flags.DEFINE_bool('auto_assign_floating_ip', False,
+ 'Autoassigning floating ip to VM')
+
LOG = logging.getLogger('nova.compute.manager')
def checks_instance_lock(function):
- """
- decorator used for preventing action against locked instances
- unless, of course, you happen to be admin
-
- """
-
+ """Decorator to prevent action against locked instances for non-admins."""
@functools.wraps(function)
def decorated_function(self, context, instance_id, *args, **kwargs):
-
LOG.info(_("check_instance_lock: decorating: |%s|"), function,
context=context)
LOG.info(_("check_instance_lock: arguments: |%(self)s| |%(context)s|"
@@ -112,7 +111,6 @@ def checks_instance_lock(function):
class ComputeManager(manager.SchedulerDependentManager):
-
"""Manages the running instances from creation to destruction."""
def __init__(self, compute_driver=None, *args, **kwargs):
@@ -132,37 +130,55 @@ class ComputeManager(manager.SchedulerDependentManager):
self.network_manager = utils.import_object(FLAGS.network_manager)
self.volume_manager = utils.import_object(FLAGS.volume_manager)
+ self.network_api = network.API()
+ self._last_host_check = 0
super(ComputeManager, self).__init__(service_name="compute",
*args, **kwargs)
def init_host(self):
- """Do any initialization that needs to be run if this is a
- standalone service.
- """
+ """Initialization for a standalone compute service."""
self.driver.init_host(host=self.host)
- def _update_state(self, context, instance_id):
+ def _update_state(self, context, instance_id, state=None):
"""Update the state of an instance from the driver info."""
- # FIXME(ja): include other fields from state?
instance_ref = self.db.instance_get(context, instance_id)
- try:
- info = self.driver.get_info(instance_ref['name'])
- state = info['state']
- except exception.NotFound:
- state = power_state.FAILED
+
+ if state is None:
+ try:
+ info = self.driver.get_info(instance_ref['name'])
+ except exception.NotFound:
+ info = None
+
+ if info is not None:
+ state = info['state']
+ else:
+ state = power_state.FAILED
+
self.db.instance_set_state(context, instance_id, state)
+ def _update_launched_at(self, context, instance_id, launched_at=None):
+ """Update the launched_at parameter of the given instance."""
+ data = {'launched_at': launched_at or datetime.datetime.utcnow()}
+ self.db.instance_update(context, instance_id, data)
+
+ def _update_image_id(self, context, instance_id, image_id):
+ """Update the image_id for the given instance."""
+ data = {'image_id': image_id}
+ self.db.instance_update(context, instance_id, data)
+
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."""
+ """Retrieves the console host for a project on this host.
+
+ Currently this is just set in the flags for each compute host.
+
+ """
#TODO(mdragon): perhaps make this variable by console_type?
return self.db.queue_get_for(context,
FLAGS.console_topic,
FLAGS.console_host)
def get_network_topic(self, context, **kwargs):
- """Retrieves the network host for a project on this host"""
+ """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
# it can pas messages instead of checking the db
@@ -179,15 +195,23 @@ class ComputeManager(manager.SchedulerDependentManager):
return self.driver.get_console_pool_info(console_type)
@exception.wrap_exception
- def refresh_security_group_rules(self, context,
- security_group_id, **kwargs):
- """This call passes straight through to the virtualization driver."""
+ def refresh_security_group_rules(self, context, security_group_id,
+ **kwargs):
+ """Tell the virtualization driver to refresh security group rules.
+
+ 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):
- """This call passes straight through to the virtualization driver."""
+ """Tell the virtualization driver to refresh security group members.
+
+ Passes straight through to the virtualization driver.
+
+ """
return self.driver.refresh_security_group_members(security_group_id)
@exception.wrap_exception
@@ -209,7 +233,7 @@ class ComputeManager(manager.SchedulerDependentManager):
power_state.NOSTATE,
'networking')
- is_vpn = instance_ref['image_id'] == FLAGS.vpn_image_id
+ is_vpn = instance_ref['image_id'] == str(FLAGS.vpn_image_id)
# NOTE(vish): This could be a cast because we don't do anything
# with the address currently, but I'm leaving it as
# a call to ensure that network setup completes. We
@@ -225,31 +249,36 @@ class ComputeManager(manager.SchedulerDependentManager):
instance_id)
# TODO(vish) check to make sure the availability zone matches
- self.db.instance_set_state(context,
- instance_id,
- power_state.NOSTATE,
- 'spawning')
+ self._update_state(context, instance_id, power_state.BUILDING)
try:
self.driver.spawn(instance_ref)
- now = datetime.datetime.utcnow()
- self.db.instance_update(context,
- instance_id,
- {'launched_at': now})
- except Exception: # pylint: disable=W0702
- LOG.exception(_("Instance '%s' failed to spawn. Is virtualization"
- " enabled in the BIOS?"), instance_id,
- context=context)
- self.db.instance_set_state(context,
- instance_id,
- power_state.SHUTDOWN)
-
+ except Exception as ex: # pylint: disable=W0702
+ msg = _("Instance '%(instance_id)s' failed to spawn. Is "
+ "virtualization enabled in the BIOS? Details: "
+ "%(ex)s") % locals()
+ LOG.exception(msg)
+
+ if not FLAGS.stub_network and FLAGS.auto_assign_floating_ip:
+ public_ip = self.network_api.allocate_floating_ip(context)
+
+ self.db.floating_ip_set_auto_assigned(context, public_ip)
+ fixed_ip = self.db.fixed_ip_get_by_address(context, address)
+ floating_ip = self.db.floating_ip_get_by_address(context,
+ public_ip)
+
+ self.network_api.associate_floating_ip(context,
+ floating_ip,
+ fixed_ip,
+ affect_auto_assigned=True)
+
+ self._update_launched_at(context, instance_id)
self._update_state(context, instance_id)
@exception.wrap_exception
@checks_instance_lock
def terminate_instance(self, context, instance_id):
- """Terminate an instance on this machine."""
+ """Terminate an instance on this host."""
context = context.elevated()
instance_ref = self.db.instance_get(context, instance_id)
LOG.audit(_("Terminating instance %s"), instance_id, context=context)
@@ -264,13 +293,17 @@ class ComputeManager(manager.SchedulerDependentManager):
# NOTE(vish): Right now we don't really care if the ip is
# disassociated. We may need to worry about
# checking this later.
- network_topic = self.db.queue_get_for(context,
- FLAGS.network_topic,
- floating_ip['host'])
- rpc.cast(context,
- network_topic,
- {"method": "disassociate_floating_ip",
- "args": {"floating_address": address}})
+ self.network_api.disassociate_floating_ip(context,
+ address,
+ True)
+ if (FLAGS.auto_assign_floating_ip
+ and floating_ip.get('auto_assigned')):
+ LOG.debug(_("Deallocating floating ip %s"),
+ floating_ip['address'],
+ context=context)
+ self.network_api.release_floating_ip(context,
+ address,
+ True)
address = fixed_ip['address']
if address:
@@ -296,8 +329,35 @@ class ComputeManager(manager.SchedulerDependentManager):
@exception.wrap_exception
@checks_instance_lock
+ def rebuild_instance(self, context, instance_id, image_id):
+ """Destroy and re-make this instance.
+
+ A 'rebuild' effectively purges all existing data from the system and
+ remakes the VM with given 'metadata' and 'personalities'.
+
+ :param context: `nova.RequestContext` object
+ :param instance_id: Instance identifier (integer)
+ :param image_id: Image identifier (integer)
+ """
+ context = context.elevated()
+
+ instance_ref = self.db.instance_get(context, instance_id)
+ LOG.audit(_("Rebuilding instance %s"), instance_id, context=context)
+
+ self._update_state(context, instance_id, power_state.BUILDING)
+
+ self.driver.destroy(instance_ref)
+ instance_ref.image_id = image_id
+ self.driver.spawn(instance_ref)
+
+ self._update_image_id(context, instance_id, image_id)
+ self._update_launched_at(context, instance_id)
+ self._update_state(context, instance_id)
+
+ @exception.wrap_exception
+ @checks_instance_lock
def reboot_instance(self, context, instance_id):
- """Reboot an instance on this server."""
+ """Reboot an instance on this host."""
context = context.elevated()
self._update_state(context, instance_id)
instance_ref = self.db.instance_get(context, instance_id)
@@ -321,7 +381,7 @@ class ComputeManager(manager.SchedulerDependentManager):
@exception.wrap_exception
def snapshot_instance(self, context, instance_id, image_id):
- """Snapshot an instance on this server."""
+ """Snapshot an instance on this host."""
context = context.elevated()
instance_ref = self.db.instance_get(context, instance_id)
@@ -344,28 +404,37 @@ class ComputeManager(manager.SchedulerDependentManager):
@exception.wrap_exception
@checks_instance_lock
def set_admin_password(self, context, instance_id, new_pass=None):
- """Set the root/admin password for an instance on this server."""
+ """Set the root/admin password for an instance on this host."""
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 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 = utils.generate_password(FLAGS.password_length)
- self.driver.set_admin_password(instance_ref, new_pass)
- self._update_state(context, instance_id)
+
+ while True:
+ 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:
+ time.sleep(5)
+ continue
+ else:
+ try:
+ self.driver.set_admin_password(instance_ref, new_pass)
+ LOG.audit(_("Instance %s: Root password set"),
+ instance_ref["name"])
+ break
+ except Exception, e:
+ # Catch all here because this could be anything.
+ LOG.exception(e)
+ continue
@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"""
+ """Write a file to the specified path in an instance on this host."""
context = context.elevated()
instance_ref = self.db.instance_get(context, instance_id)
instance_id = instance_ref['id']
@@ -383,44 +452,34 @@ class ComputeManager(manager.SchedulerDependentManager):
@exception.wrap_exception
@checks_instance_lock
def rescue_instance(self, context, instance_id):
- """Rescue an instance on this server."""
+ """Rescue an instance on this host."""
context = context.elevated()
instance_ref = self.db.instance_get(context, instance_id)
LOG.audit(_('instance %s: rescuing'), instance_id, context=context)
- self.db.instance_set_state(
- context,
- instance_id,
- power_state.NOSTATE,
- 'rescuing')
+ self.db.instance_set_state(context,
+ instance_id,
+ power_state.NOSTATE,
+ 'rescuing')
self.network_manager.setup_compute_network(context, instance_id)
- self.driver.rescue(
- instance_ref,
- lambda result: self._update_state_callback(
- self,
- context,
- instance_id,
- result))
+ _update_state = lambda result: self._update_state_callback(
+ self, context, instance_id, result)
+ self.driver.rescue(instance_ref, _update_state)
self._update_state(context, instance_id)
@exception.wrap_exception
@checks_instance_lock
def unrescue_instance(self, context, instance_id):
- """Rescue an instance on this server."""
+ """Rescue an instance on this host."""
context = context.elevated()
instance_ref = self.db.instance_get(context, instance_id)
LOG.audit(_('instance %s: unrescuing'), instance_id, context=context)
- self.db.instance_set_state(
- context,
- instance_id,
- power_state.NOSTATE,
- 'unrescuing')
- self.driver.unrescue(
- instance_ref,
- lambda result: self._update_state_callback(
- self,
- context,
- instance_id,
- result))
+ self.db.instance_set_state(context,
+ instance_id,
+ power_state.NOSTATE,
+ 'unrescuing')
+ _update_state = lambda result: self._update_state_callback(
+ self, context, instance_id, result)
+ self.driver.unrescue(instance_ref, _update_state)
self._update_state(context, instance_id)
@staticmethod
@@ -431,18 +490,20 @@ class ComputeManager(manager.SchedulerDependentManager):
@exception.wrap_exception
@checks_instance_lock
def confirm_resize(self, context, instance_id, migration_id):
- """Destroys the source instance"""
+ """Destroys the source instance."""
context = context.elevated()
instance_ref = self.db.instance_get(context, instance_id)
- migration_ref = self.db.migration_get(context, migration_id)
self.driver.destroy(instance_ref)
@exception.wrap_exception
@checks_instance_lock
def revert_resize(self, context, instance_id, migration_id):
- """Destroys the new instance on the destination machine,
- reverts the model changes, and powers on the old
- instance on the source machine"""
+ """Destroys the new instance on the destination machine.
+
+ Reverts the model changes, and powers on the old instance on the
+ source machine.
+
+ """
instance_ref = self.db.instance_get(context, instance_id)
migration_ref = self.db.migration_get(context, migration_id)
@@ -459,9 +520,12 @@ class ComputeManager(manager.SchedulerDependentManager):
@exception.wrap_exception
@checks_instance_lock
def finish_revert_resize(self, context, instance_id, migration_id):
- """Finishes the second half of reverting a resize, powering back on
- the source instance and reverting the resized attributes in the
- database"""
+ """Finishes the second half of reverting a resize.
+
+ Power back on the source instance and revert the resized attributes
+ in the database.
+
+ """
instance_ref = self.db.instance_get(context, instance_id)
migration_ref = self.db.migration_get(context, migration_id)
instance_type = self.db.instance_type_get_by_flavor_id(context,
@@ -481,8 +545,11 @@ class ComputeManager(manager.SchedulerDependentManager):
@exception.wrap_exception
@checks_instance_lock
def prep_resize(self, context, instance_id, flavor_id):
- """Initiates the process of moving a running instance to another
- host, possibly changing the RAM and disk size in the process"""
+ """Initiates the process of moving a running instance to another host.
+
+ Possibly changes the RAM and disk size in the process.
+
+ """
context = context.elevated()
instance_ref = self.db.instance_get(context, instance_id)
if instance_ref['host'] == FLAGS.host:
@@ -514,34 +581,38 @@ class ComputeManager(manager.SchedulerDependentManager):
@exception.wrap_exception
@checks_instance_lock
def resize_instance(self, context, instance_id, migration_id):
- """Starts the migration of a running instance to another host"""
+ """Starts the migration of a running instance to another host."""
migration_ref = self.db.migration_get(context, migration_id)
instance_ref = self.db.instance_get(context, instance_id)
- self.db.migration_update(context, migration_id,
- {'status': 'migrating', })
-
- disk_info = self.driver.migrate_disk_and_power_off(instance_ref,
- migration_ref['dest_host'])
- self.db.migration_update(context, migration_id,
- {'status': 'post-migrating', })
-
- service = self.db.service_get_by_host_and_topic(context,
- migration_ref['dest_compute'], FLAGS.compute_topic)
- topic = self.db.queue_get_for(context, FLAGS.compute_topic,
- migration_ref['dest_compute'])
- rpc.cast(context, topic,
- {'method': 'finish_resize',
- 'args': {
- 'migration_id': migration_id,
- 'instance_id': instance_id,
- 'disk_info': disk_info, },
- })
+ self.db.migration_update(context,
+ migration_id,
+ {'status': 'migrating'})
+
+ disk_info = self.driver.migrate_disk_and_power_off(
+ instance_ref, migration_ref['dest_host'])
+ self.db.migration_update(context,
+ migration_id,
+ {'status': 'post-migrating'})
+
+ service = self.db.service_get_by_host_and_topic(
+ context, migration_ref['dest_compute'], FLAGS.compute_topic)
+ topic = self.db.queue_get_for(context,
+ FLAGS.compute_topic,
+ migration_ref['dest_compute'])
+ rpc.cast(context, topic, {'method': 'finish_resize',
+ 'args': {'migration_id': migration_id,
+ 'instance_id': instance_id,
+ 'disk_info': disk_info}})
@exception.wrap_exception
@checks_instance_lock
def finish_resize(self, context, instance_id, migration_id, disk_info):
- """Completes the migration process by setting up the newly transferred
- disk and turning on the instance on its new host machine"""
+ """Completes the migration process.
+
+ Sets up the newly transferred disk and turns on the instance at its
+ new host machine.
+
+ """
migration_ref = self.db.migration_get(context, migration_id)
instance_ref = self.db.instance_get(context,
migration_ref['instance_id'])
@@ -566,7 +637,7 @@ class ComputeManager(manager.SchedulerDependentManager):
@exception.wrap_exception
@checks_instance_lock
def pause_instance(self, context, instance_id):
- """Pause an instance on this server."""
+ """Pause an instance on this host."""
context = context.elevated()
instance_ref = self.db.instance_get(context, instance_id)
LOG.audit(_('instance %s: pausing'), instance_id, context=context)
@@ -583,7 +654,7 @@ class ComputeManager(manager.SchedulerDependentManager):
@exception.wrap_exception
@checks_instance_lock
def unpause_instance(self, context, instance_id):
- """Unpause a paused instance on this server."""
+ """Unpause a paused instance on this host."""
context = context.elevated()
instance_ref = self.db.instance_get(context, instance_id)
LOG.audit(_('instance %s: unpausing'), instance_id, context=context)
@@ -599,7 +670,7 @@ class ComputeManager(manager.SchedulerDependentManager):
@exception.wrap_exception
def get_diagnostics(self, context, instance_id):
- """Retrieve diagnostics for an instance on this server."""
+ """Retrieve diagnostics for an instance on this host."""
instance_ref = self.db.instance_get(context, instance_id)
if instance_ref["state"] == power_state.RUNNING:
@@ -610,10 +681,7 @@ class ComputeManager(manager.SchedulerDependentManager):
@exception.wrap_exception
@checks_instance_lock
def suspend_instance(self, context, instance_id):
- """
- suspend the instance with instance_id
-
- """
+ """Suspend the given instance."""
context = context.elevated()
instance_ref = self.db.instance_get(context, instance_id)
LOG.audit(_('instance %s: suspending'), instance_id, context=context)
@@ -629,10 +697,7 @@ class ComputeManager(manager.SchedulerDependentManager):
@exception.wrap_exception
@checks_instance_lock
def resume_instance(self, context, instance_id):
- """
- resume the suspended instance with instance_id
-
- """
+ """Resume the given suspended instance."""
context = context.elevated()
instance_ref = self.db.instance_get(context, instance_id)
LOG.audit(_('instance %s: resuming'), instance_id, context=context)
@@ -647,34 +712,23 @@ class ComputeManager(manager.SchedulerDependentManager):
@exception.wrap_exception
def lock_instance(self, context, instance_id):
- """
- lock the instance with instance_id
-
- """
+ """Lock the given instance."""
context = context.elevated()
- instance_ref = self.db.instance_get(context, instance_id)
LOG.debug(_('instance %s: locking'), instance_id, context=context)
self.db.instance_update(context, instance_id, {'locked': True})
@exception.wrap_exception
def unlock_instance(self, context, instance_id):
- """
- unlock the instance with instance_id
-
- """
+ """Unlock the given instance."""
context = context.elevated()
- instance_ref = self.db.instance_get(context, instance_id)
LOG.debug(_('instance %s: unlocking'), instance_id, context=context)
self.db.instance_update(context, instance_id, {'locked': False})
@exception.wrap_exception
def get_lock(self, context, instance_id):
- """
- return the boolean state of (instance with instance_id)'s lock
-
- """
+ """Return the boolean state of the given instance's lock."""
context = context.elevated()
LOG.debug(_('instance %s: getting locked state'), instance_id,
context=context)
@@ -683,10 +737,7 @@ class ComputeManager(manager.SchedulerDependentManager):
@checks_instance_lock
def reset_network(self, context, instance_id):
- """
- Reset networking on the instance.
-
- """
+ """Reset networking on the given instance."""
context = context.elevated()
instance_ref = self.db.instance_get(context, instance_id)
LOG.debug(_('instance %s: reset network'), instance_id,
@@ -695,10 +746,7 @@ class ComputeManager(manager.SchedulerDependentManager):
@checks_instance_lock
def inject_network_info(self, context, instance_id):
- """
- Inject network info for the instance.
-
- """
+ """Inject network info for the given instance."""
context = context.elevated()
instance_ref = self.db.instance_get(context, instance_id)
LOG.debug(_('instance %s: inject network info'), instance_id,
@@ -707,29 +755,28 @@ class ComputeManager(manager.SchedulerDependentManager):
@exception.wrap_exception
def get_console_output(self, context, instance_id):
- """Send the console output for an instance."""
+ """Send the console output for the given instance."""
context = context.elevated()
instance_ref = self.db.instance_get(context, instance_id)
LOG.audit(_("Get console output for instance %s"), instance_id,
context=context)
- return self.driver.get_console_output(instance_ref)
+ output = self.driver.get_console_output(instance_ref)
+ return output.decode('utf-8', 'replace').encode('ascii', 'replace')
@exception.wrap_exception
def get_ajax_console(self, context, instance_id):
- """Return connection information for an ajax console"""
+ """Return connection information for an ajax console."""
context = context.elevated()
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)
@exception.wrap_exception
def get_vnc_console(self, context, instance_id):
- """Return connection information for an vnc console."""
+ """Return connection information for a vnc console."""
context = context.elevated()
LOG.debug(_("instance %s: getting vnc console"), instance_id)
instance_ref = self.db.instance_get(context, instance_id)
-
return self.driver.get_vnc_console(instance_ref)
@checks_instance_lock
@@ -781,9 +828,17 @@ class ComputeManager(manager.SchedulerDependentManager):
self.db.volume_detached(context, volume_id)
return True
+ def remove_volume(self, context, volume_id):
+ """Remove volume on compute host.
+
+ :param context: security context
+ :param volume_id: volume ID
+ """
+ self.volume_manager.remove_compute_volume(context, volume_id)
+
@exception.wrap_exception
def compare_cpu(self, context, cpu_info):
- """Checks the host cpu is compatible to a cpu given by xml.
+ """Checks that the host cpu is compatible with a cpu given by xml.
:param context: security context
:param cpu_info: json string obtained from virConnect.getCapabilities
@@ -804,7 +859,6 @@ class ComputeManager(manager.SchedulerDependentManager):
:returns: tmpfile name(basename)
"""
-
dirpath = FLAGS.instances_path
fd, tmp_file = tempfile.mkstemp(dir=dirpath)
LOG.debug(_("Creating tmpfile %s to notify to other "
@@ -821,10 +875,9 @@ class ComputeManager(manager.SchedulerDependentManager):
:param filename: confirm existence of FLAGS.instances_path/thisfile
"""
-
tmp_file = os.path.join(FLAGS.instances_path, filename)
if not os.path.exists(tmp_file):
- raise exception.NotFound(_('%s not found') % tmp_file)
+ raise exception.FileNotFound(file_path=tmp_file)
@exception.wrap_exception
def cleanup_shared_storage_test_file(self, context, filename):
@@ -834,7 +887,6 @@ class ComputeManager(manager.SchedulerDependentManager):
:param filename: remove existence of FLAGS.instances_path/thisfile
"""
-
tmp_file = os.path.join(FLAGS.instances_path, filename)
os.remove(tmp_file)
@@ -846,7 +898,6 @@ class ComputeManager(manager.SchedulerDependentManager):
:returns: See driver.update_available_resource()
"""
-
return self.driver.update_available_resource(context, self.host)
def pre_live_migration(self, context, instance_id, time=None):
@@ -856,7 +907,6 @@ class ComputeManager(manager.SchedulerDependentManager):
:param instance_id: nova.db.sqlalchemy.models.Instance.Id
"""
-
if not time:
time = greenthread
@@ -867,8 +917,7 @@ class ComputeManager(manager.SchedulerDependentManager):
# Getting fixed ips
fixed_ip = self.db.instance_get_fixed_address(context, instance_id)
if not fixed_ip:
- msg = _("%(instance_id)s(%(ec2_id)s) does not have fixed_ip.")
- raise exception.NotFound(msg % locals())
+ raise exception.NoFixedIpsFoundForInstance(instance_id=instance_id)
# If any volume is mounted, prepare here.
if not instance_ref['volumes']:
@@ -915,7 +964,6 @@ class ComputeManager(manager.SchedulerDependentManager):
:param dest: destination host
"""
-
# Get instance for error handling.
instance_ref = self.db.instance_get(context, instance_id)
i_name = instance_ref.name
@@ -1006,17 +1054,15 @@ class ComputeManager(manager.SchedulerDependentManager):
"Domain not found: no domain with matching name.\" "
"This error can be safely ignored."))
- def recover_live_migration(self, ctxt, instance_ref, host=None):
+ def recover_live_migration(self, ctxt, instance_ref, host=None, dest=None):
"""Recovers Instance/volume state from migrating -> running.
:param ctxt: security context
:param instance_id: nova.db.sqlalchemy.models.Instance.Id
- :param host:
- DB column value is updated by this hostname.
- if none, the host instance currently running is selected.
+ :param host: DB column value is updated by this hostname.
+ If none, the host instance currently running is selected.
"""
-
if not host:
host = instance_ref['host']
@@ -1026,8 +1072,13 @@ class ComputeManager(manager.SchedulerDependentManager):
'state': power_state.RUNNING,
'host': host})
- for volume in instance_ref['volumes']:
- self.db.volume_update(ctxt, volume['id'], {'status': 'in-use'})
+ if dest:
+ volume_api = volume.API()
+ for volume_ref in instance_ref['volumes']:
+ volume_id = volume_ref['id']
+ self.db.volume_update(ctxt, volume_id, {'status': 'in-use'})
+ if dest:
+ volume_api.remove_from_compute(ctxt, volume_id, dest)
def periodic_tasks(self, context=None):
"""Tasks to be run at a periodic interval."""
@@ -1044,6 +1095,13 @@ class ComputeManager(manager.SchedulerDependentManager):
error_list.append(ex)
try:
+ self._report_driver_status()
+ except Exception as ex:
+ LOG.warning(_("Error during report_driver_status(): %s"),
+ unicode(ex))
+ error_list.append(ex)
+
+ try:
self._poll_instance_states(context)
except Exception as ex:
LOG.warning(_("Error during instance poll: %s"),
@@ -1052,6 +1110,16 @@ class ComputeManager(manager.SchedulerDependentManager):
return error_list
+ def _report_driver_status(self):
+ curr_time = time.time()
+ if curr_time - self._last_host_check > FLAGS.host_state_interval:
+ self._last_host_check = curr_time
+ LOG.info(_("Updating host status"))
+ # This will grab info about the host and queue it
+ # to be sent to the Schedulers.
+ self.update_service_capabilities(
+ self.driver.get_host_stats(refresh=True))
+
def _poll_instance_states(self, context):
vm_instances = self.driver.list_instances_detail()
vm_instances = dict((vm.name, vm) for vm in vm_instances)
@@ -1069,8 +1137,7 @@ class ComputeManager(manager.SchedulerDependentManager):
if vm_instance is None:
# NOTE(justinsb): We have to be very careful here, because a
# concurrent operation could be in progress (e.g. a spawn)
- if db_state == power_state.NOSTATE:
- # Assume that NOSTATE => spawning
+ if db_state == power_state.BUILDING:
# TODO(justinsb): This does mean that if we crash during a
# spawn, the machine will never leave the spawning state,
# but this is just the way nova is; this function isn't
@@ -1101,9 +1168,7 @@ class ComputeManager(manager.SchedulerDependentManager):
if vm_state != db_state:
LOG.info(_("DB/VM state mismatch. Changing state from "
"'%(db_state)s' to '%(vm_state)s'") % locals())
- self.db.instance_set_state(context,
- db_instance['id'],
- vm_state)
+ self._update_state(context, db_instance['id'], vm_state)
# NOTE(justinsb): We no longer auto-remove SHUTOFF instances
# It's quite hard to get them back when we do.
diff --git a/nova/compute/power_state.py b/nova/compute/power_state.py
index ef013b2ef..c468fe6b3 100644
--- a/nova/compute/power_state.py
+++ b/nova/compute/power_state.py
@@ -30,20 +30,23 @@ SHUTOFF = 0x05
CRASHED = 0x06
SUSPENDED = 0x07
FAILED = 0x08
+BUILDING = 0x09
# TODO(justinsb): Power state really needs to be a proper class,
# so that we're not locked into the libvirt status codes and can put mapping
# logic here rather than spread throughout the code
_STATE_MAP = {
- NOSTATE: 'pending',
- RUNNING: 'running',
- BLOCKED: 'blocked',
- PAUSED: 'paused',
- SHUTDOWN: 'shutdown',
- SHUTOFF: 'shutdown',
- CRASHED: 'crashed',
- SUSPENDED: 'suspended',
- FAILED: 'failed to spawn'}
+ NOSTATE: 'pending',
+ RUNNING: 'running',
+ BLOCKED: 'blocked',
+ PAUSED: 'paused',
+ SHUTDOWN: 'shutdown',
+ SHUTOFF: 'shutdown',
+ CRASHED: 'crashed',
+ SUSPENDED: 'suspended',
+ FAILED: 'failed to spawn',
+ BUILDING: 'building',
+}
def name(code):
diff --git a/nova/console/api.py b/nova/console/api.py
index 3850d2c44..137ddcaac 100644
--- a/nova/console/api.py
+++ b/nova/console/api.py
@@ -15,23 +15,19 @@
# License for the specific language governing permissions and limitations
# under the License.
-"""
-Handles ConsoleProxy API requests
-"""
+"""Handles ConsoleProxy API requests."""
from nova import exception
-from nova.db import base
-
-
from nova import flags
from nova import rpc
+from nova.db import base
FLAGS = flags.FLAGS
class API(base.Base):
- """API for spining up or down console proxy connections"""
+ """API for spinning up or down console proxy connections."""
def __init__(self, **kwargs):
super(API, self).__init__(**kwargs)
@@ -51,8 +47,8 @@ class API(base.Base):
self.db.queue_get_for(context,
FLAGS.console_topic,
pool['host']),
- {"method": "remove_console",
- "args": {"console_id": console['id']}})
+ {'method': 'remove_console',
+ 'args': {'console_id': console['id']}})
def create_console(self, context, instance_id):
instance = self.db.instance_get(context, instance_id)
@@ -63,13 +59,12 @@ class API(base.Base):
# here.
rpc.cast(context,
self._get_console_topic(context, instance['host']),
- {"method": "add_console",
- "args": {"instance_id": instance_id}})
+ {'method': 'add_console',
+ 'args': {'instance_id': instance_id}})
def _get_console_topic(self, context, instance_host):
topic = self.db.queue_get_for(context,
FLAGS.compute_topic,
instance_host)
- return rpc.call(context,
- topic,
- {"method": "get_console_topic", "args": {'fake': 1}})
+ return rpc.call(context, topic, {'method': 'get_console_topic',
+ 'args': {'fake': 1}})
diff --git a/nova/console/fake.py b/nova/console/fake.py
index 7a90d5221..e2eb886f8 100644
--- a/nova/console/fake.py
+++ b/nova/console/fake.py
@@ -15,9 +15,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-"""
-Fake ConsoleProxy driver for tests.
-"""
+"""Fake ConsoleProxy driver for tests."""
from nova import exception
@@ -27,32 +25,32 @@ class FakeConsoleProxy(object):
@property
def console_type(self):
- return "fake"
+ return 'fake'
def setup_console(self, context, console):
- """Sets up actual proxies"""
+ """Sets up actual proxies."""
pass
def teardown_console(self, context, console):
- """Tears down actual proxies"""
+ """Tears down actual proxies."""
pass
def init_host(self):
- """Start up any config'ed consoles on start"""
+ """Start up any config'ed consoles on start."""
pass
def generate_password(self, length=8):
- """Returns random console password"""
- return "fakepass"
+ """Returns random console password."""
+ return 'fakepass'
def get_port(self, context):
- """get available port for consoles that need one"""
+ """Get available port for consoles that need one."""
return 5999
def fix_pool_password(self, password):
- """Trim password to length, and any other massaging"""
+ """Trim password to length, and any other massaging."""
return password
def fix_console_password(self, password):
- """Trim password to length, and any other massaging"""
+ """Trim password to length, and any other massaging."""
return password
diff --git a/nova/console/manager.py b/nova/console/manager.py
index bfa571ea9..e0db21666 100644
--- a/nova/console/manager.py
+++ b/nova/console/manager.py
@@ -15,9 +15,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-"""
-Console Proxy Service
-"""
+"""Console Proxy Service."""
import functools
import socket
@@ -29,6 +27,7 @@ from nova import manager
from nova import rpc
from nova import utils
+
FLAGS = flags.FLAGS
flags.DEFINE_string('console_driver',
'nova.console.xvp.XVPConsoleProxy',
@@ -41,9 +40,11 @@ flags.DEFINE_string('console_public_hostname',
class ConsoleProxyManager(manager.Manager):
+ """Sets up and tears down any console proxy connections.
+
+ Needed for accessing instance consoles securely.
- """ Sets up and tears down any proxy connections needed for accessing
- instance consoles securely"""
+ """
def __init__(self, console_driver=None, *args, **kwargs):
if not console_driver:
@@ -67,7 +68,7 @@ class ConsoleProxyManager(manager.Manager):
pool['id'],
instance_id)
except exception.NotFound:
- logging.debug(_("Adding console"))
+ logging.debug(_('Adding console'))
if not password:
password = utils.generate_password(8)
if not port:
@@ -115,8 +116,8 @@ class ConsoleProxyManager(manager.Manager):
self.db.queue_get_for(context,
FLAGS.compute_topic,
instance_host),
- {"method": "get_console_pool_info",
- "args": {"console_type": console_type}})
+ {'method': 'get_console_pool_info',
+ 'args': {'console_type': console_type}})
pool_info['password'] = self.driver.fix_pool_password(
pool_info['password'])
pool_info['host'] = self.host
diff --git a/nova/console/vmrc.py b/nova/console/vmrc.py
index 521da289f..cc8b0cdf5 100644
--- a/nova/console/vmrc.py
+++ b/nova/console/vmrc.py
@@ -15,9 +15,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-"""
-VMRC console drivers.
-"""
+"""VMRC console drivers."""
import base64
import json
@@ -27,6 +25,8 @@ from nova import flags
from nova import log as logging
from nova.virt.vmwareapi import vim_util
+
+FLAGS = flags.FLAGS
flags.DEFINE_integer('console_vmrc_port',
443,
"port for VMware VMRC connections")
@@ -34,8 +34,6 @@ flags.DEFINE_integer('console_vmrc_error_retries',
10,
"number of retries for retrieving VMRC information")
-FLAGS = flags.FLAGS
-
class VMRCConsole(object):
"""VMRC console driver with ESX credentials."""
@@ -69,34 +67,33 @@ class VMRCConsole(object):
return password
def generate_password(self, vim_session, pool, instance_name):
- """
- Returns VMRC Connection credentials.
+ """Returns VMRC Connection credentials.
Return string is of the form '<VM PATH>:<ESX Username>@<ESX Password>'.
+
"""
username, password = pool['username'], pool['password']
- vms = vim_session._call_method(vim_util, "get_objects",
- "VirtualMachine", ["name", "config.files.vmPathName"])
+ vms = vim_session._call_method(vim_util, 'get_objects',
+ 'VirtualMachine', ['name', 'config.files.vmPathName'])
vm_ds_path_name = None
vm_ref = None
for vm in vms:
vm_name = None
ds_path_name = None
for prop in vm.propSet:
- if prop.name == "name":
+ if prop.name == 'name':
vm_name = prop.val
- elif prop.name == "config.files.vmPathName":
+ elif prop.name == 'config.files.vmPathName':
ds_path_name = prop.val
if vm_name == instance_name:
vm_ref = vm.obj
vm_ds_path_name = ds_path_name
break
if vm_ref is None:
- raise exception.NotFound(_("instance - %s not present") %
- instance_name)
- json_data = json.dumps({"vm_id": vm_ds_path_name,
- "username": username,
- "password": password})
+ raise exception.InstanceNotFound(instance_id=instance_name)
+ json_data = json.dumps({'vm_id': vm_ds_path_name,
+ 'username': username,
+ 'password': password})
return base64.b64encode(json_data)
def is_otp(self):
@@ -115,28 +112,27 @@ class VMRCSessionConsole(VMRCConsole):
return 'vmrc+session'
def generate_password(self, vim_session, pool, instance_name):
- """
- Returns a VMRC Session.
+ """Returns a VMRC Session.
Return string is of the form '<VM MOID>:<VMRC Ticket>'.
+
"""
- vms = vim_session._call_method(vim_util, "get_objects",
- "VirtualMachine", ["name"])
- vm_ref = None
+ vms = vim_session._call_method(vim_util, 'get_objects',
+ 'VirtualMachine', ['name'])
+ vm_ref = NoneV
for vm in vms:
if vm.propSet[0].val == instance_name:
vm_ref = vm.obj
if vm_ref is None:
- raise exception.NotFound(_("instance - %s not present") %
- instance_name)
+ raise exception.InstanceNotFound(instance_id=instance_name)
virtual_machine_ticket = \
vim_session._call_method(
vim_session._get_vim(),
- "AcquireCloneTicket",
+ 'AcquireCloneTicket',
vim_session._get_vim().get_service_content().sessionManager)
- json_data = json.dumps({"vm_id": str(vm_ref.value),
- "username": virtual_machine_ticket,
- "password": virtual_machine_ticket})
+ json_data = json.dumps({'vm_id': str(vm_ref.value),
+ 'username': virtual_machine_ticket,
+ 'password': virtual_machine_ticket})
return base64.b64encode(json_data)
def is_otp(self):
diff --git a/nova/console/vmrc_manager.py b/nova/console/vmrc_manager.py
index 09beac7a0..acecc1075 100644
--- a/nova/console/vmrc_manager.py
+++ b/nova/console/vmrc_manager.py
@@ -15,9 +15,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-"""
-VMRC Console Manager.
-"""
+"""VMRC Console Manager."""
from nova import exception
from nova import flags
@@ -25,24 +23,21 @@ from nova import log as logging
from nova import manager
from nova import rpc
from nova import utils
-from nova.virt.vmwareapi_conn import VMWareAPISession
+from nova.virt import vmwareapi_conn
+
LOG = logging.getLogger("nova.console.vmrc_manager")
+
FLAGS = flags.FLAGS
-flags.DEFINE_string('console_public_hostname',
- '',
+flags.DEFINE_string('console_public_hostname', '',
'Publicly visible name for this console host')
-flags.DEFINE_string('console_driver',
- 'nova.console.vmrc.VMRCConsole',
+flags.DEFINE_string('console_driver', 'nova.console.vmrc.VMRCConsole',
'Driver to use for the console')
class ConsoleVMRCManager(manager.Manager):
-
- """
- Manager to handle VMRC connections needed for accessing instance consoles.
- """
+ """Manager to handle VMRC connections for accessing instance consoles."""
def __init__(self, console_driver=None, *args, **kwargs):
self.driver = utils.import_object(FLAGS.console_driver)
@@ -56,16 +51,17 @@ class ConsoleVMRCManager(manager.Manager):
"""Get VIM session for the pool specified."""
vim_session = None
if pool['id'] not in self.sessions.keys():
- vim_session = VMWareAPISession(pool['address'],
- pool['username'],
- pool['password'],
- FLAGS.console_vmrc_error_retries)
+ vim_session = vmwareapi_conn.VMWareAPISession(
+ pool['address'],
+ pool['username'],
+ pool['password'],
+ FLAGS.console_vmrc_error_retries)
self.sessions[pool['id']] = vim_session
return self.sessions[pool['id']]
def _generate_console(self, context, pool, name, instance_id, instance):
"""Sets up console for the instance."""
- LOG.debug(_("Adding console"))
+ LOG.debug(_('Adding console'))
password = self.driver.generate_password(
self._get_vim_session(pool),
@@ -84,9 +80,10 @@ class ConsoleVMRCManager(manager.Manager):
@exception.wrap_exception
def add_console(self, context, instance_id, password=None,
port=None, **kwargs):
- """
- Adds a console for the instance. If it is one time password, then we
- generate new console credentials.
+ """Adds a console for the instance.
+
+ If it is one time password, then we generate new console credentials.
+
"""
instance = self.db.instance_get(context, instance_id)
host = instance['host']
@@ -97,19 +94,17 @@ class ConsoleVMRCManager(manager.Manager):
pool['id'],
instance_id)
if self.driver.is_otp():
- console = self._generate_console(
- context,
- pool,
- name,
- instance_id,
- instance)
+ console = self._generate_console(context,
+ pool,
+ name,
+ instance_id,
+ instance)
except exception.NotFound:
- console = self._generate_console(
- context,
- pool,
- name,
- instance_id,
- instance)
+ console = self._generate_console(context,
+ pool,
+ name,
+ instance_id,
+ instance)
return console['id']
@exception.wrap_exception
@@ -118,13 +113,11 @@ class ConsoleVMRCManager(manager.Manager):
try:
console = self.db.console_get(context, console_id)
except exception.NotFound:
- LOG.debug(_("Tried to remove non-existent console "
- "%(console_id)s.") %
- {'console_id': console_id})
+ LOG.debug(_('Tried to remove non-existent console '
+ '%(console_id)s.') % {'console_id': console_id})
return
- LOG.debug(_("Removing console "
- "%(console_id)s.") %
- {'console_id': console_id})
+ LOG.debug(_('Removing console '
+ '%(console_id)s.') % {'console_id': console_id})
self.db.console_delete(context, console_id)
self.driver.teardown_console(context, console)
@@ -139,11 +132,11 @@ class ConsoleVMRCManager(manager.Manager):
console_type)
except exception.NotFound:
pool_info = rpc.call(context,
- self.db.queue_get_for(context,
- FLAGS.compute_topic,
- instance_host),
- {"method": "get_console_pool_info",
- "args": {"console_type": console_type}})
+ self.db.queue_get_for(context,
+ FLAGS.compute_topic,
+ instance_host),
+ {'method': 'get_console_pool_info',
+ 'args': {'console_type': console_type}})
pool_info['password'] = self.driver.fix_pool_password(
pool_info['password'])
pool_info['host'] = self.host
diff --git a/nova/console/xvp.py b/nova/console/xvp.py
index 0cedfbb13..3cd287183 100644
--- a/nova/console/xvp.py
+++ b/nova/console/xvp.py
@@ -15,16 +15,14 @@
# License for the specific language governing permissions and limitations
# under the License.
-"""
-XVP (Xenserver VNC Proxy) driver.
-"""
+"""XVP (Xenserver VNC Proxy) driver."""
import fcntl
import os
import signal
import subprocess
-from Cheetah.Template import Template
+from Cheetah import Template
from nova import context
from nova import db
@@ -33,6 +31,8 @@ from nova import flags
from nova import log as logging
from nova import utils
+
+FLAGS = flags.FLAGS
flags.DEFINE_string('console_xvp_conf_template',
utils.abspath('console/xvp.conf.template'),
'XVP conf template')
@@ -47,12 +47,11 @@ flags.DEFINE_string('console_xvp_log',
'XVP log file')
flags.DEFINE_integer('console_xvp_multiplex_port',
5900,
- "port for XVP to multiplex VNC connections on")
-FLAGS = flags.FLAGS
+ 'port for XVP to multiplex VNC connections on')
class XVPConsoleProxy(object):
- """Sets up XVP config, and manages xvp daemon"""
+ """Sets up XVP config, and manages XVP daemon."""
def __init__(self):
self.xvpconf_template = open(FLAGS.console_xvp_conf_template).read()
@@ -61,50 +60,51 @@ class XVPConsoleProxy(object):
@property
def console_type(self):
- return "vnc+xvp"
+ return 'vnc+xvp'
def get_port(self, context):
- """get available port for consoles that need one"""
+ """Get available port for consoles that need one."""
#TODO(mdragon): implement port selection for non multiplex ports,
# we are not using that, but someone else may want
# it.
return FLAGS.console_xvp_multiplex_port
def setup_console(self, context, console):
- """Sets up actual proxies"""
+ """Sets up actual proxies."""
self._rebuild_xvp_conf(context.elevated())
def teardown_console(self, context, console):
- """Tears down actual proxies"""
+ """Tears down actual proxies."""
self._rebuild_xvp_conf(context.elevated())
def init_host(self):
- """Start up any config'ed consoles on start"""
+ """Start up any config'ed consoles on start."""
ctxt = context.get_admin_context()
self._rebuild_xvp_conf(ctxt)
def fix_pool_password(self, password):
- """Trim password to length, and encode"""
+ """Trim password to length, and encode."""
return self._xvp_encrypt(password, is_pool_password=True)
def fix_console_password(self, password):
- """Trim password to length, and encode"""
+ """Trim password to length, and encode."""
return self._xvp_encrypt(password)
def _rebuild_xvp_conf(self, context):
- logging.debug(_("Rebuilding xvp conf"))
+ logging.debug(_('Rebuilding xvp conf'))
pools = [pool for pool in
db.console_pool_get_all_by_host_type(context, self.host,
self.console_type)
if pool['consoles']]
if not pools:
- logging.debug("No console pools!")
+ logging.debug('No console pools!')
self._xvp_stop()
return
conf_data = {'multiplex_port': FLAGS.console_xvp_multiplex_port,
'pools': pools,
'pass_encode': self.fix_console_password}
- config = str(Template(self.xvpconf_template, searchList=[conf_data]))
+ config = str(Template.Template(self.xvpconf_template,
+ searchList=[conf_data]))
self._write_conf(config)
self._xvp_restart()
@@ -114,7 +114,7 @@ class XVPConsoleProxy(object):
cfile.write(config)
def _xvp_stop(self):
- logging.debug(_("Stopping xvp"))
+ logging.debug(_('Stopping xvp'))
pid = self._xvp_pid()
if not pid:
return
@@ -127,19 +127,19 @@ class XVPConsoleProxy(object):
def _xvp_start(self):
if self._xvp_check_running():
return
- logging.debug(_("Starting xvp"))
+ logging.debug(_('Starting xvp'))
try:
utils.execute('xvp',
'-p', FLAGS.console_xvp_pid,
'-c', FLAGS.console_xvp_conf,
'-l', FLAGS.console_xvp_log)
except exception.ProcessExecutionError, err:
- logging.error(_("Error starting xvp: %s") % err)
+ logging.error(_('Error starting xvp: %s') % err)
def _xvp_restart(self):
- logging.debug(_("Restarting xvp"))
+ logging.debug(_('Restarting xvp'))
if not self._xvp_check_running():
- logging.debug(_("xvp not running..."))
+ logging.debug(_('xvp not running...'))
self._xvp_start()
else:
pid = self._xvp_pid()
@@ -178,7 +178,9 @@ class XVPConsoleProxy(object):
Note that xvp's obfuscation should not be considered 'real' encryption.
It simply DES encrypts the passwords with static keys plainly viewable
- in the xvp source code."""
+ in the xvp source code.
+
+ """
maxlen = 8
flag = '-e'
if is_pool_password:
diff --git a/nova/context.py b/nova/context.py
index 0256bf448..c113f7ea7 100644
--- a/nova/context.py
+++ b/nova/context.py
@@ -16,9 +16,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-"""
-RequestContext: context for requests that persist through all of nova.
-"""
+"""RequestContext: context for requests that persist through all of nova."""
import datetime
import random
@@ -28,6 +26,12 @@ from nova import utils
class RequestContext(object):
+ """Security context and request information.
+
+ Represents the user taking a given action within the system.
+
+ """
+
def __init__(self, user, project, is_admin=None, read_deleted=False,
remote_address=None, timestamp=None, request_id=None):
if hasattr(user, 'id'):
diff --git a/nova/crypto.py b/nova/crypto.py
index 605be2a32..14b9cbef6 100644
--- a/nova/crypto.py
+++ b/nova/crypto.py
@@ -15,10 +15,11 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
-"""
-Wrappers around standard crypto data elements.
+
+"""Wrappers around standard crypto data elements.
Includes root and intermediate CAs, SSH key_pairs and x509 certificates.
+
"""
import base64
@@ -43,6 +44,8 @@ from nova import log as logging
LOG = logging.getLogger("nova.crypto")
+
+
FLAGS = flags.FLAGS
flags.DEFINE_string('ca_file', 'cacert.pem', _('Filename of root CA'))
flags.DEFINE_string('key_file',
@@ -90,13 +93,13 @@ def key_path(project_id=None):
def fetch_ca(project_id=None, chain=True):
if not FLAGS.use_project_ca:
project_id = None
- buffer = ""
+ buffer = ''
if project_id:
- with open(ca_path(project_id), "r") as cafile:
+ with open(ca_path(project_id), 'r') as cafile:
buffer += cafile.read()
if not chain:
return buffer
- with open(ca_path(None), "r") as cafile:
+ with open(ca_path(None), 'r') as cafile:
buffer += cafile.read()
return buffer
@@ -143,7 +146,7 @@ def ssl_pub_to_ssh_pub(ssl_public_key, name='root', suffix='nova'):
def revoke_cert(project_id, file_name):
- """Revoke a cert by file name"""
+ """Revoke a cert by file name."""
start = os.getcwd()
os.chdir(ca_folder(project_id))
# NOTE(vish): potential race condition here
@@ -155,14 +158,14 @@ def revoke_cert(project_id, file_name):
def revoke_certs_by_user(user_id):
- """Revoke all user certs"""
+ """Revoke all user certs."""
admin = context.get_admin_context()
for cert in db.certificate_get_all_by_user(admin, user_id):
revoke_cert(cert['project_id'], cert['file_name'])
def revoke_certs_by_project(project_id):
- """Revoke all project certs"""
+ """Revoke all project certs."""
# NOTE(vish): This is somewhat useless because we can just shut down
# the vpn.
admin = context.get_admin_context()
@@ -171,29 +174,29 @@ def revoke_certs_by_project(project_id):
def revoke_certs_by_user_and_project(user_id, project_id):
- """Revoke certs for user in project"""
+ """Revoke certs for user in project."""
admin = context.get_admin_context()
for cert in db.certificate_get_all_by_user(admin, user_id, project_id):
revoke_cert(cert['project_id'], cert['file_name'])
def _project_cert_subject(project_id):
- """Helper to generate user cert subject"""
+ """Helper to generate user cert subject."""
return FLAGS.project_cert_subject % (project_id, utils.isotime())
def _vpn_cert_subject(project_id):
- """Helper to generate user cert subject"""
+ """Helper to generate user cert subject."""
return FLAGS.vpn_cert_subject % (project_id, utils.isotime())
def _user_cert_subject(user_id, project_id):
- """Helper to generate user cert subject"""
+ """Helper to generate user cert subject."""
return FLAGS.user_cert_subject % (project_id, user_id, utils.isotime())
def generate_x509_cert(user_id, project_id, bits=1024):
- """Generate and sign a cert for user in project"""
+ """Generate and sign a cert for user in project."""
subject = _user_cert_subject(user_id, project_id)
tmpdir = tempfile.mkdtemp()
keyfile = os.path.abspath(os.path.join(tmpdir, 'temp.key'))
@@ -205,7 +208,7 @@ def generate_x509_cert(user_id, project_id, bits=1024):
csr = open(csrfile).read()
shutil.rmtree(tmpdir)
(serial, signed_csr) = sign_csr(csr, project_id)
- fname = os.path.join(ca_folder(project_id), "newcerts/%s.pem" % serial)
+ fname = os.path.join(ca_folder(project_id), 'newcerts/%s.pem' % serial)
cert = {'user_id': user_id,
'project_id': project_id,
'file_name': fname}
@@ -227,8 +230,8 @@ def _ensure_project_folder(project_id):
def generate_vpn_files(project_id):
project_folder = ca_folder(project_id)
- csr_fn = os.path.join(project_folder, "server.csr")
- crt_fn = os.path.join(project_folder, "server.crt")
+ csr_fn = os.path.join(project_folder, 'server.csr')
+ crt_fn = os.path.join(project_folder, 'server.crt')
genvpn_sh_path = os.path.join(os.path.dirname(__file__),
'CA',
@@ -241,10 +244,10 @@ def generate_vpn_files(project_id):
# TODO(vish): the shell scripts could all be done in python
utils.execute('sh', genvpn_sh_path,
project_id, _vpn_cert_subject(project_id))
- with open(csr_fn, "r") as csrfile:
+ with open(csr_fn, 'r') as csrfile:
csr_text = csrfile.read()
(serial, signed_csr) = sign_csr(csr_text, project_id)
- with open(crt_fn, "w") as crtfile:
+ with open(crt_fn, 'w') as crtfile:
crtfile.write(signed_csr)
os.chdir(start)
@@ -261,12 +264,12 @@ def sign_csr(csr_text, project_id=None):
def _sign_csr(csr_text, ca_folder):
tmpfolder = tempfile.mkdtemp()
- inbound = os.path.join(tmpfolder, "inbound.csr")
- outbound = os.path.join(tmpfolder, "outbound.csr")
- csrfile = open(inbound, "w")
+ inbound = os.path.join(tmpfolder, 'inbound.csr')
+ outbound = os.path.join(tmpfolder, 'outbound.csr')
+ csrfile = open(inbound, 'w')
csrfile.write(csr_text)
csrfile.close()
- LOG.debug(_("Flags path: %s"), ca_folder)
+ LOG.debug(_('Flags path: %s'), ca_folder)
start = os.getcwd()
# Change working dir to CA
if not os.path.exists(ca_folder):
@@ -276,13 +279,13 @@ def _sign_csr(csr_text, ca_folder):
'./openssl.cnf', '-infiles', inbound)
out, _err = utils.execute('openssl', 'x509', '-in', outbound,
'-serial', '-noout')
- serial = string.strip(out.rpartition("=")[2])
+ serial = string.strip(out.rpartition('=')[2])
os.chdir(start)
- with open(outbound, "r") as crtfile:
+ with open(outbound, 'r') as crtfile:
return (serial, crtfile.read())
-def mkreq(bits, subject="foo", ca=0):
+def mkreq(bits, subject='foo', ca=0):
pk = M2Crypto.EVP.PKey()
req = M2Crypto.X509.Request()
rsa = M2Crypto.RSA.gen_key(bits, 65537, callback=lambda: None)
@@ -314,7 +317,7 @@ def mkcacert(subject='nova', years=1):
cert.set_not_before(now)
cert.set_not_after(nowPlusYear)
issuer = M2Crypto.X509.X509_Name()
- issuer.C = "US"
+ issuer.C = 'US'
issuer.CN = subject
cert.set_issuer(issuer)
cert.set_pubkey(pkey)
@@ -352,13 +355,15 @@ def mkcacert(subject='nova', years=1):
# http://code.google.com/p/boto
def compute_md5(fp):
- """
+ """Compute an md5 hash.
+
:type fp: file
:param fp: File pointer to the file to MD5 hash. The file pointer will be
reset to the beginning of the file before the method returns.
:rtype: tuple
- :return: the hex digest version of the MD5 hash
+ :returns: the hex digest version of the MD5 hash
+
"""
m = hashlib.md5()
fp.seek(0)
diff --git a/nova/db/api.py b/nova/db/api.py
index 9fc4b8c0a..57e585a9c 100644
--- a/nova/db/api.py
+++ b/nova/db/api.py
@@ -15,8 +15,8 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
-"""
-Defines interface for DB access.
+
+"""Defines interface for DB access.
The underlying driver is loaded as a :class:`LazyPluggable`.
@@ -30,6 +30,7 @@ The underlying driver is loaded as a :class:`LazyPluggable`.
:enable_new_services: when adding a new service to the database, is it in the
pool of available hardware (Default: True)
+
"""
from nova import exception
@@ -88,7 +89,7 @@ def service_get(context, service_id):
def service_get_by_host_and_topic(context, host, topic):
- """Get a service by host it's on and topic it listens to"""
+ """Get a service by host it's on and topic it listens to."""
return IMPL.service_get_by_host_and_topic(context, host, topic)
@@ -115,7 +116,7 @@ def service_get_all_compute_by_host(context, host):
def service_get_all_compute_sorted(context):
"""Get all compute services sorted by instance count.
- Returns a list of (Service, instance_count) tuples.
+ :returns: a list of (Service, instance_count) tuples.
"""
return IMPL.service_get_all_compute_sorted(context)
@@ -124,7 +125,7 @@ def service_get_all_compute_sorted(context):
def service_get_all_network_sorted(context):
"""Get all network services sorted by network count.
- Returns a list of (Service, network_count) tuples.
+ :returns: a list of (Service, network_count) tuples.
"""
return IMPL.service_get_all_network_sorted(context)
@@ -133,7 +134,7 @@ def service_get_all_network_sorted(context):
def service_get_all_volume_sorted(context):
"""Get all volume services sorted by volume count.
- Returns a list of (Service, volume_count) tuples.
+ :returns: a list of (Service, volume_count) tuples.
"""
return IMPL.service_get_all_volume_sorted(context)
@@ -243,7 +244,7 @@ def floating_ip_count_by_project(context, project_id):
def floating_ip_deallocate(context, address):
- """Deallocate an floating ip by address"""
+ """Deallocate an floating ip by address."""
return IMPL.floating_ip_deallocate(context, address)
@@ -255,7 +256,7 @@ def floating_ip_destroy(context, address):
def floating_ip_disassociate(context, address):
"""Disassociate an floating ip from a fixed ip by address.
- Returns the address of the existing fixed ip.
+ :returns: the address of the existing fixed ip.
"""
return IMPL.floating_ip_disassociate(context, address)
@@ -293,25 +294,30 @@ def floating_ip_update(context, address, values):
return IMPL.floating_ip_update(context, address, values)
+def floating_ip_set_auto_assigned(context, address):
+ """Set auto_assigned flag to floating ip"""
+ return IMPL.floating_ip_set_auto_assigned(context, address)
+
####################
+
def migration_update(context, id, values):
- """Update a migration instance"""
+ """Update a migration instance."""
return IMPL.migration_update(context, id, values)
def migration_create(context, values):
- """Create a migration record"""
+ """Create a migration record."""
return IMPL.migration_create(context, values)
def migration_get(context, migration_id):
- """Finds a migration by the id"""
+ """Finds a migration by the id."""
return IMPL.migration_get(context, migration_id)
def migration_get_by_instance_and_status(context, instance_id, status):
- """Finds a migration by the instance id its migrating"""
+ """Finds a migration by the instance id its migrating."""
return IMPL.migration_get_by_instance_and_status(context, instance_id,
status)
@@ -457,11 +463,6 @@ def instance_get_project_vpn(context, project_id):
return IMPL.instance_get_project_vpn(context, project_id)
-def instance_is_vpn(context, instance_id):
- """True if instance is a vpn."""
- return IMPL.instance_is_vpn(context, instance_id)
-
-
def instance_set_state(context, instance_id, state, description=None):
"""Set the state of an instance."""
return IMPL.instance_set_state(context, instance_id, state, description)
@@ -581,7 +582,9 @@ def network_create_safe(context, values):
def network_delete_safe(context, network_id):
"""Delete network with key network_id.
+
This method assumes that the network is not associated with any project
+
"""
return IMPL.network_delete_safe(context, network_id)
@@ -676,7 +679,6 @@ def project_get_network(context, project_id, associate=True):
network if one is not found, otherwise it returns None.
"""
-
return IMPL.project_get_network(context, project_id, associate)
@@ -724,7 +726,9 @@ def iscsi_target_create_safe(context, values):
The device is not returned. If the create violates the unique
constraints because the iscsi_target and host already exist,
- no exception is raised."""
+ no exception is raised.
+
+ """
return IMPL.iscsi_target_create_safe(context, values)
@@ -1089,10 +1093,7 @@ def project_delete(context, project_id):
def host_get_networks(context, host):
- """Return all networks for which the given host is the designated
- network host.
-
- """
+ """All networks for which the given host is the network host."""
return IMPL.host_get_networks(context, host)
@@ -1154,38 +1155,40 @@ def console_get(context, console_id, instance_id=None):
def instance_type_create(context, values):
- """Create a new instance type"""
+ """Create a new instance type."""
return IMPL.instance_type_create(context, values)
def instance_type_get_all(context, inactive=False):
- """Get all instance types"""
+ """Get all instance types."""
return IMPL.instance_type_get_all(context, inactive)
def instance_type_get_by_id(context, id):
- """Get instance type by id"""
+ """Get instance type by id."""
return IMPL.instance_type_get_by_id(context, id)
def instance_type_get_by_name(context, name):
- """Get instance type by name"""
+ """Get instance type by name."""
return IMPL.instance_type_get_by_name(context, name)
def instance_type_get_by_flavor_id(context, id):
- """Get instance type by name"""
+ """Get instance type by name."""
return IMPL.instance_type_get_by_flavor_id(context, id)
def instance_type_destroy(context, name):
- """Delete a instance type"""
+ """Delete a instance type."""
return IMPL.instance_type_destroy(context, name)
def instance_type_purge(context, name):
- """Purges (removes) an instance type from DB
- Use instance_type_destroy for most cases
+ """Purges (removes) an instance type from DB.
+
+ Use instance_type_destroy for most cases
+
"""
return IMPL.instance_type_purge(context, name)
@@ -1222,15 +1225,15 @@ def zone_get_all(context):
def instance_metadata_get(context, instance_id):
- """Get all metadata for an instance"""
+ """Get all metadata for an instance."""
return IMPL.instance_metadata_get(context, instance_id)
def instance_metadata_delete(context, instance_id, key):
- """Delete the given metadata item"""
+ """Delete the given metadata item."""
IMPL.instance_metadata_delete(context, instance_id, key)
def instance_metadata_update_or_create(context, instance_id, metadata):
- """Create or update instance metadata"""
+ """Create or update instance metadata."""
IMPL.instance_metadata_update_or_create(context, instance_id, metadata)
diff --git a/nova/db/base.py b/nova/db/base.py
index a0f2180c6..a0d055d5b 100644
--- a/nova/db/base.py
+++ b/nova/db/base.py
@@ -16,20 +16,20 @@
# License for the specific language governing permissions and limitations
# under the License.
-"""
-Base class for classes that need modular database access.
-"""
+"""Base class for classes that need modular database access."""
from nova import utils
from nova import flags
+
FLAGS = flags.FLAGS
flags.DEFINE_string('db_driver', 'nova.db.api',
'driver to use for database access')
class Base(object):
- """DB driver is injected in the init method"""
+ """DB driver is injected in the init method."""
+
def __init__(self, db_driver=None):
if not db_driver:
db_driver = FLAGS.db_driver
diff --git a/nova/db/migration.py b/nova/db/migration.py
index e54b90cd8..ccd06cffe 100644
--- a/nova/db/migration.py
+++ b/nova/db/migration.py
@@ -15,11 +15,13 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
+
"""Database setup and migration commands."""
from nova import flags
from nova import utils
+
FLAGS = flags.FLAGS
flags.DECLARE('db_backend', 'nova.db.api')
diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py
index 059a22cb9..7302f25b0 100644
--- a/nova/db/sqlalchemy/api.py
+++ b/nova/db/sqlalchemy/api.py
@@ -94,7 +94,7 @@ def require_admin_context(f):
"""
def wrapper(*args, **kwargs):
if not is_admin_context(args[0]):
- raise exception.NotAuthorized()
+ raise exception.AdminRequired()
return f(*args, **kwargs)
return wrapper
@@ -105,7 +105,7 @@ def require_context(f):
"""
def wrapper(*args, **kwargs):
if not is_admin_context(args[0]) and not is_user_context(args[0]):
- raise exception.NotAuthorized()
+ raise exception.AdminRequired()
return f(*args, **kwargs)
return wrapper
@@ -137,7 +137,7 @@ def service_get(context, service_id, session=None):
first()
if not result:
- raise exception.NotFound(_('No service for id %s') % service_id)
+ raise exception.ServiceNotFound(service_id=service_id)
return result
@@ -196,8 +196,7 @@ def service_get_all_compute_by_host(context, host):
all()
if not result:
- raise exception.NotFound(_("%s does not exist or is not "
- "a compute node.") % host)
+ raise exception.ComputeHostNotFound(host=host)
return result
@@ -284,8 +283,7 @@ def service_get_by_args(context, host, binary):
filter_by(deleted=can_read_deleted(context)).\
first()
if not result:
- raise exception.NotFound(_('No service for %(host)s, %(binary)s')
- % locals())
+ raise exception.HostBinaryNotFound(host=host, binary=binary)
return result
@@ -323,7 +321,7 @@ def compute_node_get(context, compute_id, session=None):
first()
if not result:
- raise exception.NotFound(_('No computeNode for id %s') % compute_id)
+ raise exception.ComputeHostNotFound(host=compute_id)
return result
@@ -359,7 +357,7 @@ def certificate_get(context, certificate_id, session=None):
first()
if not result:
- raise exception.NotFound('No certificate for id %s' % certificate_id)
+ raise exception.CertificateNotFound(certificate_id=certificate_id)
return result
@@ -461,6 +459,7 @@ def floating_ip_count_by_project(context, project_id):
session = get_session()
return session.query(models.FloatingIp).\
filter_by(project_id=project_id).\
+ filter_by(auto_assigned=False).\
filter_by(deleted=False).\
count()
@@ -489,6 +488,7 @@ def floating_ip_deallocate(context, address):
address,
session=session)
floating_ip_ref['project_id'] = None
+ floating_ip_ref['auto_assigned'] = False
floating_ip_ref.save(session=session)
@@ -522,6 +522,17 @@ def floating_ip_disassociate(context, address):
return fixed_ip_address
+@require_context
+def floating_ip_set_auto_assigned(context, address):
+ session = get_session()
+ with session.begin():
+ floating_ip_ref = floating_ip_get_by_address(context,
+ address,
+ session=session)
+ floating_ip_ref.auto_assigned = True
+ floating_ip_ref.save(session=session)
+
+
@require_admin_context
def floating_ip_get_all(context):
session = get_session()
@@ -548,6 +559,7 @@ def floating_ip_get_all_by_project(context, project_id):
return session.query(models.FloatingIp).\
options(joinedload_all('fixed_ip.instance')).\
filter_by(project_id=project_id).\
+ filter_by(auto_assigned=False).\
filter_by(deleted=False).\
all()
@@ -564,7 +576,7 @@ def floating_ip_get_by_address(context, address, session=None):
filter_by(deleted=can_read_deleted(context)).\
first()
if not result:
- raise exception.NotFound('No floating ip for address %s' % address)
+ raise exception.FloatingIpNotFound(fixed_ip=address)
return result
@@ -672,7 +684,7 @@ def fixed_ip_get_all(context, session=None):
session = get_session()
result = session.query(models.FixedIp).all()
if not result:
- raise exception.NotFound(_('No fixed ips defined'))
+ raise exception.NoFloatingIpsDefined()
return result
@@ -688,7 +700,7 @@ def fixed_ip_get_all_by_host(context, host=None):
all()
if not result:
- raise exception.NotFound(_('No fixed ips for this host defined'))
+ raise exception.NoFloatingIpsDefinedForHost(host=host)
return result
@@ -704,7 +716,7 @@ def fixed_ip_get_by_address(context, address, session=None):
options(joinedload('instance')).\
first()
if not result:
- raise exception.NotFound(_('No floating ip for address %s') % address)
+ raise exception.FloatingIpNotFound(fixed_ip=address)
if is_user_context(context):
authorize_project_context(context, result.instance.project_id)
@@ -725,7 +737,7 @@ def fixed_ip_get_all_by_instance(context, instance_id):
filter_by(instance_id=instance_id).\
filter_by(deleted=False)
if not rv:
- raise exception.NotFound(_('No address for instance %s') % instance_id)
+ raise exception.NoFloatingIpsFoundForInstance(instance_id=instance_id)
return rv
@@ -770,9 +782,10 @@ def instance_create(context, values):
metadata = values.get('metadata')
metadata_refs = []
if metadata:
- for metadata_item in metadata:
+ for k, v in metadata.iteritems():
metadata_ref = models.InstanceMetadata()
- metadata_ref.update(metadata_item)
+ metadata_ref['key'] = k
+ metadata_ref['value'] = v
metadata_refs.append(metadata_ref)
values['metadata'] = metadata_refs
@@ -803,17 +816,17 @@ def instance_destroy(context, instance_id):
with session.begin():
session.query(models.Instance).\
filter_by(id=instance_id).\
- update({'deleted': 1,
+ update({'deleted': True,
'deleted_at': datetime.datetime.utcnow(),
'updated_at': literal_column('updated_at')})
session.query(models.SecurityGroupInstanceAssociation).\
filter_by(instance_id=instance_id).\
- update({'deleted': 1,
+ update({'deleted': True,
'deleted_at': datetime.datetime.utcnow(),
'updated_at': literal_column('updated_at')})
session.query(models.InstanceMetadata).\
filter_by(instance_id=instance_id).\
- update({'deleted': 1,
+ update({'deleted': True,
'deleted_at': datetime.datetime.utcnow(),
'updated_at': literal_column('updated_at')})
@@ -847,9 +860,7 @@ def instance_get(context, instance_id, session=None):
filter_by(deleted=False).\
first()
if not result:
- raise exception.InstanceNotFound(_('Instance %s not found')
- % instance_id,
- instance_id)
+ raise exception.InstanceNotFound(instance_id=instance_id)
return result
@@ -940,7 +951,7 @@ def instance_get_project_vpn(context, project_id):
options(joinedload('security_groups')).\
options(joinedload('instance_type')).\
filter_by(project_id=project_id).\
- filter_by(image_id=FLAGS.vpn_image_id).\
+ filter_by(image_id=str(FLAGS.vpn_image_id)).\
filter_by(deleted=can_read_deleted(context)).\
first()
@@ -980,13 +991,6 @@ def instance_get_floating_address(context, instance_id):
@require_admin_context
-def instance_is_vpn(context, instance_id):
- # TODO(vish): Move this into image code somewhere
- instance_ref = instance_get(context, instance_id)
- return instance_ref['image_id'] == FLAGS.vpn_image_id
-
-
-@require_admin_context
def instance_set_state(context, instance_id, state, description=None):
# TODO(devcamcar): Move this out of models and into driver
from nova.compute import power_state
@@ -1125,8 +1129,7 @@ def key_pair_get(context, user_id, name, session=None):
filter_by(deleted=can_read_deleted(context)).\
first()
if not result:
- raise exception.NotFound(_('no keypair for user %(user_id)s,'
- ' name %(name)s') % locals())
+ raise exception.KeypairNotFound(user_id=user_id, name=name)
return result
@@ -1252,7 +1255,7 @@ def network_get(context, network_id, session=None):
filter_by(deleted=False).\
first()
if not result:
- raise exception.NotFound(_('No network for id %s') % network_id)
+ raise exception.NetworkNotFound(network_id=network_id)
return result
@@ -1262,7 +1265,7 @@ def network_get_all(context):
session = get_session()
result = session.query(models.Network)
if not result:
- raise exception.NotFound(_('No networks defined'))
+ raise exception.NoNetworksFound()
return result
@@ -1291,7 +1294,7 @@ def network_get_by_bridge(context, bridge):
first()
if not result:
- raise exception.NotFound(_('No network for bridge %s') % bridge)
+ raise exception.NetworkNotFoundForBridge(bridge=bridge)
return result
@@ -1302,8 +1305,7 @@ def network_get_by_cidr(context, cidr):
filter_by(cidr=cidr).first()
if not result:
- raise exception.NotFound(_('Network with cidr %s does not exist') %
- cidr)
+ raise exception.NetworkNotFoundForCidr(cidr=cidr)
return result
@@ -1317,7 +1319,7 @@ def network_get_by_instance(_context, instance_id):
filter_by(deleted=False).\
first()
if not rv:
- raise exception.NotFound(_('No network for instance %s') % instance_id)
+ raise exception.NetworkNotFoundForInstance(instance_id=instance_id)
return rv
@@ -1330,7 +1332,7 @@ def network_get_all_by_instance(_context, instance_id):
filter_by(instance_id=instance_id).\
filter_by(deleted=False)
if not rv:
- raise exception.NotFound(_('No network for instance %s') % instance_id)
+ raise exception.NetworkNotFoundForInstance(instance_id=instance_id)
return rv
@@ -1344,7 +1346,7 @@ def network_set_host(context, network_id, host_id):
with_lockmode('update').\
first()
if not network_ref:
- raise exception.NotFound(_('No network for id %s') % network_id)
+ raise exception.NetworkNotFound(network_id=network_id)
# NOTE(vish): if with_lockmode isn't supported, as in sqlite,
# then this has concurrency issues
@@ -1469,7 +1471,7 @@ def auth_token_get(context, token_hash, session=None):
filter_by(deleted=can_read_deleted(context)).\
first()
if not tk:
- raise exception.NotFound(_('Token %s does not exist') % token_hash)
+ raise exception.AuthTokenNotFound(token=token_hash)
return tk
@@ -1503,7 +1505,7 @@ def quota_get(context, project_id, session=None):
filter_by(deleted=can_read_deleted(context)).\
first()
if not result:
- raise exception.NotFound(_('No quota for project_id %s') % project_id)
+ raise exception.ProjectQuotaNotFound(project_id=project_id)
return result
@@ -1658,8 +1660,7 @@ def volume_get(context, volume_id, session=None):
filter_by(deleted=False).\
first()
if not result:
- raise exception.VolumeNotFound(_('Volume %s not found') % volume_id,
- volume_id)
+ raise exception.VolumeNotFound(volume_id=volume_id)
return result
@@ -1691,7 +1692,7 @@ def volume_get_all_by_instance(context, instance_id):
filter_by(deleted=False).\
all()
if not result:
- raise exception.NotFound(_('No volume for instance %s') % instance_id)
+ raise exception.VolumeNotFoundForInstance(instance_id=instance_id)
return result
@@ -1716,8 +1717,7 @@ def volume_get_instance(context, volume_id):
options(joinedload('instance')).\
first()
if not result:
- raise exception.VolumeNotFound(_('Volume %s not found') % volume_id,
- volume_id)
+ raise exception.VolumeNotFound(volume_id=volume_id)
return result.instance
@@ -1729,8 +1729,7 @@ def volume_get_shelf_and_blade(context, volume_id):
filter_by(volume_id=volume_id).\
first()
if not result:
- raise exception.NotFound(_('No export device found for volume %s') %
- volume_id)
+ raise exception.ExportDeviceNotFoundForVolume(volume_id=volume_id)
return (result.shelf_id, result.blade_id)
@@ -1742,8 +1741,7 @@ def volume_get_iscsi_target_num(context, volume_id):
filter_by(volume_id=volume_id).\
first()
if not result:
- raise exception.NotFound(_('No target id found for volume %s') %
- volume_id)
+ raise exception.ISCSITargetNotFoundForVolume(volume_id=volume_id)
return result.target_num
@@ -1800,8 +1798,7 @@ def snapshot_get(context, snapshot_id, session=None):
filter_by(deleted=False).\
first()
if not result:
- raise exception.SnapshotNotFound(_('Snapshot %s not found') % snapshot_id,
- snapshot_id)
+ raise exception.SnapshotNotFound(snapshot_id=snapshot_id)
return result
@@ -1864,8 +1861,8 @@ def security_group_get(context, security_group_id, session=None):
options(joinedload_all('rules')).\
first()
if not result:
- raise exception.NotFound(_("No security group with id %s") %
- security_group_id)
+ raise exception.SecurityGroupNotFound(
+ security_group_id=security_group_id)
return result
@@ -1880,9 +1877,8 @@ def security_group_get_by_name(context, project_id, group_name):
options(joinedload_all('instances')).\
first()
if not result:
- raise exception.NotFound(
- _('No security group named %(group_name)s'
- ' for project: %(project_id)s') % locals())
+ raise exception.SecurityGroupNotFoundForProject(project_id=project_id,
+ security_group_id=group_name)
return result
@@ -1983,8 +1979,8 @@ def security_group_rule_get(context, security_group_rule_id, session=None):
filter_by(id=security_group_rule_id).\
first()
if not result:
- raise exception.NotFound(_("No secuity group rule with id %s") %
- security_group_rule_id)
+ raise exception.SecurityGroupNotFoundForRule(
+ rule_id=security_group_rule_id)
return result
@@ -2057,7 +2053,7 @@ def user_get(context, id, session=None):
first()
if not result:
- raise exception.NotFound(_('No user for id %s') % id)
+ raise exception.UserNotFound(user_id=id)
return result
@@ -2073,7 +2069,7 @@ def user_get_by_access_key(context, access_key, session=None):
first()
if not result:
- raise exception.NotFound(_('No user for access key %s') % access_key)
+ raise exception.AccessKeyNotFound(access_key=access_key)
return result
@@ -2138,7 +2134,7 @@ def project_get(context, id, session=None):
first()
if not result:
- raise exception.NotFound(_("No project with id %s") % id)
+ raise exception.ProjectNotFound(project_id=id)
return result
@@ -2159,7 +2155,7 @@ def project_get_by_user(context, user_id):
options(joinedload_all('projects')).\
first()
if not user:
- raise exception.NotFound(_('Invalid user_id %s') % user_id)
+ raise exception.UserNotFound(user_id=user_id)
return user.projects
@@ -2299,8 +2295,7 @@ def migration_get(context, id, session=None):
result = session.query(models.Migration).\
filter_by(id=id).first()
if not result:
- raise exception.NotFound(_("No migration found with id %s")
- % id)
+ raise exception.MigrationNotFound(migration_id=id)
return result
@@ -2311,8 +2306,8 @@ def migration_get_by_instance_and_status(context, instance_id, status):
filter_by(instance_id=instance_id).\
filter_by(status=status).first()
if not result:
- raise exception.NotFound(_("No migration found for instance "
- "%(instance_id)s with status %(status)s") % locals())
+ raise exception.MigrationNotFoundByStatus(instance_id=instance_id,
+ status=status)
return result
@@ -2333,8 +2328,7 @@ def console_pool_get(context, pool_id):
filter_by(id=pool_id).\
first()
if not result:
- raise exception.NotFound(_("No console pool with id %(pool_id)s")
- % locals())
+ raise exception.ConsolePoolNotFound(pool_id=pool_id)
return result
@@ -2350,9 +2344,9 @@ def console_pool_get_by_host_type(context, compute_host, host,
options(joinedload('consoles')).\
first()
if not result:
- raise exception.NotFound(_('No console pool of type %(console_type)s '
- 'for compute host %(compute_host)s '
- 'on proxy host %(host)s') % locals())
+ raise exception.ConsolePoolNotFoundForHostType(host=host,
+ console_type=console_type,
+ compute_host=compute_host)
return result
@@ -2390,8 +2384,8 @@ def console_get_by_pool_instance(context, pool_id, instance_id):
options(joinedload('pool')).\
first()
if not result:
- raise exception.NotFound(_('No console for instance %(instance_id)s '
- 'in pool %(pool_id)s') % locals())
+ raise exception.ConsoleNotFoundInPoolForInstance(pool_id=pool_id,
+ instance_id=instance_id)
return result
@@ -2412,9 +2406,11 @@ def console_get(context, console_id, instance_id=None):
query = query.filter_by(instance_id=instance_id)
result = query.options(joinedload('pool')).first()
if not result:
- idesc = (_("on instance %s") % instance_id) if instance_id else ""
- raise exception.NotFound(_("No console with id %(console_id)s"
- " %(idesc)s") % locals())
+ if instance_id:
+ raise exception.ConsoleNotFoundForInstance(console_id=console_id,
+ instance_id=instance_id)
+ else:
+ raise exception.ConsoleNotFound(console_id=console_id)
return result
@@ -2453,7 +2449,7 @@ def instance_type_get_all(context, inactive=False):
inst_dict[i['name']] = dict(i)
return inst_dict
else:
- raise exception.NotFound
+ raise exception.NoInstanceTypesFound()
@require_context
@@ -2464,7 +2460,7 @@ def instance_type_get_by_id(context, id):
filter_by(id=id).\
first()
if not inst_type:
- raise exception.NotFound(_("No instance type with id %s") % id)
+ raise exception.InstanceTypeNotFound(instance_type=id)
else:
return dict(inst_type)
@@ -2477,7 +2473,7 @@ def instance_type_get_by_name(context, name):
filter_by(name=name).\
first()
if not inst_type:
- raise exception.NotFound(_("No instance type with name %s") % name)
+ raise exception.InstanceTypeNotFoundByName(instance_type_name=name)
else:
return dict(inst_type)
@@ -2490,7 +2486,7 @@ def instance_type_get_by_flavor_id(context, id):
filter_by(flavorid=int(id)).\
first()
if not inst_type:
- raise exception.NotFound(_("No flavor with flavorid %s") % id)
+ raise exception.FlavorNotFound(flavor_id=id)
else:
return dict(inst_type)
@@ -2503,7 +2499,7 @@ def instance_type_destroy(context, name):
filter_by(name=name)
records = instance_type_ref.update(dict(deleted=True))
if records == 0:
- raise exception.NotFound
+ raise exception.InstanceTypeNotFoundByName(instance_type_name=name)
else:
return instance_type_ref
@@ -2518,7 +2514,7 @@ def instance_type_purge(context, name):
filter_by(name=name)
records = instance_type_ref.delete()
if records == 0:
- raise exception.NotFound
+ raise exception.InstanceTypeNotFoundByName(instance_type_name=name)
else:
return instance_type_ref
@@ -2539,7 +2535,7 @@ def zone_update(context, zone_id, values):
session = get_session()
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())
+ raise exception.ZoneNotFound(zone_id=zone_id)
zone.update(values)
zone.save()
return zone
@@ -2559,7 +2555,7 @@ 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())
+ raise exception.ZoneNotFound(zone_id=zone_id)
return result
@@ -2593,7 +2589,7 @@ def instance_metadata_delete(context, instance_id, key):
filter_by(instance_id=instance_id).\
filter_by(key=key).\
filter_by(deleted=False).\
- update({'deleted': 1,
+ update({'deleted': True,
'deleted_at': datetime.datetime.utcnow(),
'updated_at': literal_column('updated_at')})
@@ -2609,8 +2605,8 @@ def instance_metadata_get_item(context, instance_id, key):
first()
if not meta_result:
- raise exception.NotFound(_('Invalid metadata key for instance %s') %
- instance_id)
+ raise exception.InstanceMetadataNotFound(metadata_key=key,
+ instance_id=instance_id)
return meta_result
diff --git a/nova/db/sqlalchemy/migrate_repo/versions/001_austin.py b/nova/db/sqlalchemy/migrate_repo/versions/001_austin.py
index 9e7ab3554..63bbaccc1 100644
--- a/nova/db/sqlalchemy/migrate_repo/versions/001_austin.py
+++ b/nova/db/sqlalchemy/migrate_repo/versions/001_austin.py
@@ -17,15 +17,13 @@
# under the License.
## Table code mostly autogenerated by genmodel.py
-from sqlalchemy import *
-from migrate import *
-
+from sqlalchemy import Boolean, Column, DateTime, ForeignKey
+from sqlalchemy import ForeignKeyConstraint, Integer, MetaData, String
+from sqlalchemy import Table, Text
from nova import log as logging
-
meta = MetaData()
-
auth_tokens = Table('auth_tokens', meta,
Column('created_at', DateTime(timezone=False)),
Column('updated_at', DateTime(timezone=False)),
diff --git a/nova/db/sqlalchemy/migrate_repo/versions/002_bexar.py b/nova/db/sqlalchemy/migrate_repo/versions/002_bexar.py
index 413536a59..9bb8a8ada 100644
--- a/nova/db/sqlalchemy/migrate_repo/versions/002_bexar.py
+++ b/nova/db/sqlalchemy/migrate_repo/versions/002_bexar.py
@@ -16,15 +16,12 @@
# License for the specific language governing permissions and limitations
# under the License.
-from sqlalchemy import *
-from migrate import *
-
+from sqlalchemy import Boolean, Column, DateTime, ForeignKey
+from sqlalchemy import Integer, MetaData, String, Table, Text
from nova import log as logging
-
meta = MetaData()
-
# Just for the ForeignKey and column creation to succeed, these are not the
# actual definitions of instances or services.
instances = Table('instances', meta,
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
index 5ba7910f1..8e0de4d2b 100644
--- 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
@@ -15,15 +15,10 @@
# License for the specific language governing permissions and limitations
# under the License.
-from sqlalchemy import *
-from migrate import *
-
-from nova import log as logging
-
+from sqlalchemy import Column, Integer, MetaData, String, Table
meta = MetaData()
-
networks = Table('networks', meta,
Column('id', Integer(), primary_key=True, nullable=False),
)
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
index ade981687..0abea374c 100644
--- a/nova/db/sqlalchemy/migrate_repo/versions/004_add_zone_tables.py
+++ b/nova/db/sqlalchemy/migrate_repo/versions/004_add_zone_tables.py
@@ -13,15 +13,12 @@
# License for the specific language governing permissions and limitations
# under the License.
-from sqlalchemy import *
-from migrate import *
-
+from sqlalchemy import Boolean, Column, DateTime, Integer
+from sqlalchemy import MetaData, String, Table
from nova import log as logging
-
meta = MetaData()
-
#
# New Tables
#
diff --git a/nova/db/sqlalchemy/migrate_repo/versions/005_add_instance_metadata.py b/nova/db/sqlalchemy/migrate_repo/versions/005_add_instance_metadata.py
index 4cb07e0d8..a1a86e3b4 100644
--- a/nova/db/sqlalchemy/migrate_repo/versions/005_add_instance_metadata.py
+++ b/nova/db/sqlalchemy/migrate_repo/versions/005_add_instance_metadata.py
@@ -15,15 +15,12 @@
# License for the specific language governing permissions and limitations
# under the License.
-from sqlalchemy import *
-from migrate import *
-
+from sqlalchemy import Boolean, Column, DateTime, ForeignKey, Integer
+from sqlalchemy import MetaData, String, Table
from nova import log as logging
-
meta = MetaData()
-
# Just for the ForeignKey and column creation to succeed, these are not the
# actual definitions of instances or services.
instances = Table('instances', meta,
diff --git a/nova/db/sqlalchemy/migrate_repo/versions/006_add_provider_data_to_volumes.py b/nova/db/sqlalchemy/migrate_repo/versions/006_add_provider_data_to_volumes.py
index 705fc8ff3..4627d3332 100644
--- a/nova/db/sqlalchemy/migrate_repo/versions/006_add_provider_data_to_volumes.py
+++ b/nova/db/sqlalchemy/migrate_repo/versions/006_add_provider_data_to_volumes.py
@@ -15,11 +15,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-from sqlalchemy import *
-from migrate import *
-
-from nova import log as logging
-
+from sqlalchemy import Column, Integer, MetaData, String, Table
meta = MetaData()
diff --git a/nova/db/sqlalchemy/migrate_repo/versions/007_add_ipv6_to_fixed_ips.py b/nova/db/sqlalchemy/migrate_repo/versions/007_add_ipv6_to_fixed_ips.py
index 427934d53..6f2668040 100644
--- a/nova/db/sqlalchemy/migrate_repo/versions/007_add_ipv6_to_fixed_ips.py
+++ b/nova/db/sqlalchemy/migrate_repo/versions/007_add_ipv6_to_fixed_ips.py
@@ -13,15 +13,10 @@
# License for the specific language governing permissions and limitations
# under the License.
-from sqlalchemy import *
-from migrate import *
-
-from nova import log as logging
-
+from sqlalchemy import Column, Integer, MetaData, String, Table
meta = MetaData()
-
# Table stub-definitions
# Just for the ForeignKey and column creation to succeed, these are not the
# actual definitions of instances or services.
diff --git a/nova/db/sqlalchemy/migrate_repo/versions/008_add_instance_types.py b/nova/db/sqlalchemy/migrate_repo/versions/008_add_instance_types.py
index 5e2cb69d9..63999f6ff 100644
--- a/nova/db/sqlalchemy/migrate_repo/versions/008_add_instance_types.py
+++ b/nova/db/sqlalchemy/migrate_repo/versions/008_add_instance_types.py
@@ -13,15 +13,10 @@
# License for the specific language governing permissions and limitations
# under the License.
-from sqlalchemy import *
-from migrate import *
-
-from nova import api
-from nova import db
+from sqlalchemy import Boolean, Column, DateTime, Integer
+from sqlalchemy import MetaData, String, Table
from nova import log as logging
-import datetime
-
meta = MetaData()
diff --git a/nova/db/sqlalchemy/migrate_repo/versions/009_add_instance_migrations.py b/nova/db/sqlalchemy/migrate_repo/versions/009_add_instance_migrations.py
index 4fda525f1..0f2d0079a 100644
--- a/nova/db/sqlalchemy/migrate_repo/versions/009_add_instance_migrations.py
+++ b/nova/db/sqlalchemy/migrate_repo/versions/009_add_instance_migrations.py
@@ -15,12 +15,10 @@
# License for the specific language governing permissions and limitations
# under the License.from sqlalchemy import *
-from sqlalchemy import *
-from migrate import *
-
+from sqlalchemy import Boolean, Column, DateTime, ForeignKey, Integer
+from sqlalchemy import MetaData, String, Table
from nova import log as logging
-
meta = MetaData()
# Just for the ForeignKey and column creation to succeed, these are not the
diff --git a/nova/db/sqlalchemy/migrate_repo/versions/010_add_os_type_to_instances.py b/nova/db/sqlalchemy/migrate_repo/versions/010_add_os_type_to_instances.py
index eb3066894..a5b80586e 100644
--- a/nova/db/sqlalchemy/migrate_repo/versions/010_add_os_type_to_instances.py
+++ b/nova/db/sqlalchemy/migrate_repo/versions/010_add_os_type_to_instances.py
@@ -14,12 +14,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-from sqlalchemy import *
-from sqlalchemy.sql import text
-from migrate import *
-
-from nova import log as logging
-
+from sqlalchemy import Column, Integer, MetaData, String, Table
meta = MetaData()
diff --git a/nova/db/sqlalchemy/migrate_repo/versions/011_live_migration.py b/nova/db/sqlalchemy/migrate_repo/versions/011_live_migration.py
index 23ccccb4e..b2b0256d2 100644
--- a/nova/db/sqlalchemy/migrate_repo/versions/011_live_migration.py
+++ b/nova/db/sqlalchemy/migrate_repo/versions/011_live_migration.py
@@ -16,10 +16,9 @@
# License for the specific language governing permissions and limitations
# under the License.
-from migrate import *
+from sqlalchemy import Boolean, Column, DateTime, Integer, MetaData
+from sqlalchemy import Table, Text
from nova import log as logging
-from sqlalchemy import *
-
meta = MetaData()
diff --git a/nova/db/sqlalchemy/migrate_repo/versions/012_add_ipv6_flatmanager.py b/nova/db/sqlalchemy/migrate_repo/versions/012_add_ipv6_flatmanager.py
index e87085668..10d250522 100644
--- a/nova/db/sqlalchemy/migrate_repo/versions/012_add_ipv6_flatmanager.py
+++ b/nova/db/sqlalchemy/migrate_repo/versions/012_add_ipv6_flatmanager.py
@@ -13,15 +13,11 @@
# License for the specific language governing permissions and limitations
# under the License.
-from sqlalchemy import *
-from migrate import *
-
-from nova import log as logging
-
+from sqlalchemy import Boolean, Column, DateTime, ForeignKey, Integer
+from sqlalchemy import MetaData, String, Table
meta = MetaData()
-
# Table stub-definitions
# Just for the ForeignKey and column creation to succeed, these are not the
# actual definitions of instances or services.
diff --git a/nova/db/sqlalchemy/migrate_repo/versions/013_add_flavors_to_migrations.py b/nova/db/sqlalchemy/migrate_repo/versions/013_add_flavors_to_migrations.py
index 3fb92e85c..7246839b7 100644
--- a/nova/db/sqlalchemy/migrate_repo/versions/013_add_flavors_to_migrations.py
+++ b/nova/db/sqlalchemy/migrate_repo/versions/013_add_flavors_to_migrations.py
@@ -15,11 +15,7 @@
# License for the specific language governing permissions and limitations
# under the License.from sqlalchemy import *
-from sqlalchemy import *
-from migrate import *
-
-from nova import log as logging
-
+from sqlalchemy import Column, Integer, MetaData, Table
meta = MetaData()
diff --git a/nova/db/sqlalchemy/migrate_repo/versions/014_add_instance_type_id_to_instances.py b/nova/db/sqlalchemy/migrate_repo/versions/014_add_instance_type_id_to_instances.py
index b12a0a801..62216be12 100644
--- a/nova/db/sqlalchemy/migrate_repo/versions/014_add_instance_type_id_to_instances.py
+++ b/nova/db/sqlalchemy/migrate_repo/versions/014_add_instance_type_id_to_instances.py
@@ -14,16 +14,11 @@
# License for the specific language governing permissions and limitations
# under the License.
-from sqlalchemy import *
-from sqlalchemy.sql import text
-from migrate import *
-
+from sqlalchemy import Column, Integer, MetaData, String, Table
#from nova import log as logging
-
meta = MetaData()
-
c_instance_type = Column('instance_type',
String(length=255, convert_unicode=False,
assert_unicode=None, unicode_error=None,
@@ -54,10 +49,12 @@ def upgrade(migrate_engine):
instances.create_column(c_instance_type_id)
+ type_names = {}
recs = migrate_engine.execute(instance_types.select())
for row in recs:
- type_id = row[0]
- type_name = row[1]
+ type_names[row[0]] = row[1]
+
+ for type_id, type_name in type_names.iteritems():
migrate_engine.execute(instances.update()\
.where(instances.c.instance_type == type_name)\
.values(instance_type_id=type_id))
diff --git a/nova/db/sqlalchemy/migrate_repo/versions/015_add_auto_assign_to_floating_ips.py b/nova/db/sqlalchemy/migrate_repo/versions/015_add_auto_assign_to_floating_ips.py
new file mode 100644
index 000000000..375760c84
--- /dev/null
+++ b/nova/db/sqlalchemy/migrate_repo/versions/015_add_auto_assign_to_floating_ips.py
@@ -0,0 +1,35 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2011 OpenStack LLC.
+# Copyright 2011 Grid Dynamics
+#
+# 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 Boolean, Column, MetaData, Table
+
+meta = MetaData()
+
+c_auto_assigned = Column('auto_assigned', Boolean, default=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
+
+ floating_ips = Table('floating_ips',
+ meta,
+ autoload=True,
+ autoload_with=migrate_engine)
+
+ floating_ips.create_column(c_auto_assigned)
diff --git a/nova/db/sqlalchemy/migrate_repo/versions/015_add_volume_snapshot_support.py b/nova/db/sqlalchemy/migrate_repo/versions/016_add_volume_snapshot_support.py
index 288f63e72..288f63e72 100644
--- a/nova/db/sqlalchemy/migrate_repo/versions/015_add_volume_snapshot_support.py
+++ b/nova/db/sqlalchemy/migrate_repo/versions/016_add_volume_snapshot_support.py
diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py
index 9abe4d9ae..2e0ead5f9 100644
--- a/nova/db/sqlalchemy/models.py
+++ b/nova/db/sqlalchemy/models.py
@@ -616,6 +616,7 @@ class FloatingIp(BASE, NovaBase):
'FloatingIp.deleted == False)')
project_id = Column(String(255))
host = Column(String(255)) # , ForeignKey('hosts.id'))
+ auto_assigned = Column(Boolean, default=False, nullable=False)
class ConsolePool(BASE, NovaBase):
diff --git a/nova/exception.py b/nova/exception.py
index 7adc3d007..39620ccc1 100644
--- a/nova/exception.py
+++ b/nova/exception.py
@@ -16,94 +16,55 @@
# License for the specific language governing permissions and limitations
# under the License.
-"""
-Nova base exception handling, including decorator for re-raising
-Nova-type exceptions. SHOULD include dedicated exception logging.
+"""Nova base exception handling.
+
+Includes decorator for re-raising Nova-type exceptions.
+
+SHOULD include dedicated exception logging.
+
"""
from nova import log as logging
+
+
LOG = logging.getLogger('nova.exception')
class ProcessExecutionError(IOError):
-
def __init__(self, stdout=None, stderr=None, exit_code=None, cmd=None,
description=None):
if description is None:
- description = _("Unexpected error while running command.")
+ description = _('Unexpected error while running command.')
if exit_code is None:
exit_code = '-'
- message = _("%(description)s\nCommand: %(cmd)s\n"
- "Exit code: %(exit_code)s\nStdout: %(stdout)r\n"
- "Stderr: %(stderr)r") % locals()
+ message = _('%(description)s\nCommand: %(cmd)s\n'
+ 'Exit code: %(exit_code)s\nStdout: %(stdout)r\n'
+ 'Stderr: %(stderr)r') % locals()
IOError.__init__(self, message)
class Error(Exception):
-
def __init__(self, message=None):
super(Error, self).__init__(message)
class ApiError(Error):
- def __init__(self, message='Unknown', code='ApiError'):
- self.message = message
+ def __init__(self, message='Unknown', code=None):
+ self.msg = message
self.code = code
- super(ApiError, self).__init__('%s: %s' % (code, message))
+ if code:
+ outstr = '%s: %s' % (code, message)
+ else:
+ outstr = '%s' % message
+ super(ApiError, self).__init__(outstr)
-class NotFound(Error):
- pass
-
-
-class InstanceNotFound(NotFound):
- def __init__(self, message, instance_id):
- self.instance_id = instance_id
- super(InstanceNotFound, self).__init__(message)
-
-
-class VolumeNotFound(NotFound):
- def __init__(self, message, volume_id):
- self.volume_id = volume_id
- super(VolumeNotFound, self).__init__(message)
-
-
-class SnapshotNotFound(NotFound):
- def __init__(self, message, snapshot_id):
- self.snapshot_id = snapshot_id
- super(SnapshotNotFound, self).__init__(message)
-
-
-class Duplicate(Error):
- pass
-
-
-class NotAuthorized(Error):
- pass
-
-
-class NotEmpty(Error):
- pass
-
-
-class Invalid(Error):
- pass
-
-
-class InvalidInputException(Error):
- pass
-
-
-class InvalidContentType(Error):
- pass
-
-
-class TimeoutException(Error):
+class BuildInProgress(Error):
pass
class DBError(Error):
- """Wraps an implementation specific exception"""
+ """Wraps an implementation specific exception."""
def __init__(self, inner_exception):
self.inner_exception = inner_exception
super(DBError, self).__init__(str(inner_exception))
@@ -114,7 +75,7 @@ def wrap_db_error(f):
try:
return f(*args, **kwargs)
except Exception, e:
- LOG.exception(_('DB exception wrapped'))
+ LOG.exception(_('DB exception wrapped.'))
raise DBError(e)
return _wrap
_wrap.func_name = f.func_name
@@ -133,3 +94,465 @@ def wrap_exception(f):
raise
_wrap.func_name = f.func_name
return _wrap
+
+
+class NovaException(Exception):
+ """Base Nova Exception
+
+ To correctly use this class, inherit from it and define
+ a 'message' property. That message will get printf'd
+ with the keyword arguments provided to the constructor.
+
+ """
+ message = _("An unknown exception occurred.")
+
+ def __init__(self, **kwargs):
+ try:
+ self._error_string = self.message % kwargs
+
+ except Exception:
+ # at least get the core message out if something happened
+ self._error_string = self.message
+
+ def __str__(self):
+ return self._error_string
+
+
+class NotAuthorized(NovaException):
+ message = _("Not authorized.")
+
+ def __init__(self, *args, **kwargs):
+ super(NotFound, self).__init__(**kwargs)
+
+
+class AdminRequired(NotAuthorized):
+ message = _("User does not have admin privileges")
+
+
+class Invalid(NovaException):
+ message = _("Unacceptable parameters.")
+
+
+class InvalidSignature(Invalid):
+ message = _("Invalid signature %(signature)s for user %(user)s.")
+
+
+class InvalidInput(Invalid):
+ message = _("Invalid input received") + ": %(reason)s"
+
+
+class InvalidInstanceType(Invalid):
+ message = _("Invalid instance type %(instance_type)s.")
+
+
+class InvalidPortRange(Invalid):
+ message = _("Invalid port range %(from_port)s:%(to_port)s.")
+
+
+class InvalidIpProtocol(Invalid):
+ message = _("Invalid IP protocol %(protocol)s.")
+
+
+class InvalidContentType(Invalid):
+ message = _("Invalid content type %(content_type)s.")
+
+
+class InstanceNotRunning(Invalid):
+ message = _("Instance %(instance_id)s is not running.")
+
+
+class InstanceNotSuspended(Invalid):
+ message = _("Instance %(instance_id)s is not suspended.")
+
+
+class InstanceNotInRescueMode(Invalid):
+ message = _("Instance %(instance_id)s is not in rescue mode")
+
+
+class InstanceSuspendFailure(Invalid):
+ message = _("Failed to suspend instance") + ": %(reason)s"
+
+
+class InstanceResumeFailure(Invalid):
+ message = _("Failed to resume server") + ": %(reason)s."
+
+
+class InstanceRebootFailure(Invalid):
+ message = _("Failed to reboot instance") + ": %(reason)s"
+
+
+class ServiceUnavailable(Invalid):
+ message = _("Service is unavailable at this time.")
+
+
+class VolumeServiceUnavailable(ServiceUnavailable):
+ message = _("Volume service is unavailable at this time.")
+
+
+class ComputeServiceUnavailable(ServiceUnavailable):
+ message = _("Compute service is unavailable at this time.")
+
+
+class UnableToMigrateToSelf(Invalid):
+ message = _("Unable to migrate instance (%(instance_id)s) "
+ "to current host (%(host)s).")
+
+
+class SourceHostUnavailable(Invalid):
+ message = _("Original compute host is unavailable at this time.")
+
+
+class InvalidHypervisorType(Invalid):
+ message = _("The supplied hypervisor type of is invalid.")
+
+
+class DestinationHypervisorTooOld(Invalid):
+ message = _("The instance requires a newer hypervisor version than "
+ "has been provided.")
+
+
+class InvalidDevicePath(Invalid):
+ message = _("The supplied device path (%(path)s) is invalid.")
+
+
+class InvalidCPUInfo(Invalid):
+ message = _("Unacceptable CPU info") + ": %(reason)s"
+
+
+class InvalidVLANTag(Invalid):
+ message = _("VLAN tag is not appropriate for the port group "
+ "%(bridge)s. Expected VLAN tag is %(tag)s, "
+ "but the one associated with the port group is %(pgroup)s.")
+
+
+class InvalidVLANPortGroup(Invalid):
+ message = _("vSwitch which contains the port group %(bridge)s is "
+ "not associated with the desired physical adapter. "
+ "Expected vSwitch is %(expected)s, but the one associated "
+ "is %(actual)s.")
+
+
+class InvalidDiskFormat(Invalid):
+ message = _("Disk format %(disk_format)s is not acceptable")
+
+
+class ImageUnacceptable(Invalid):
+ message = _("Image %(image_id)s is unacceptable") + ": %(reason)s"
+
+
+class InstanceUnacceptable(Invalid):
+ message = _("Instance %(instance_id)s is unacceptable") + ": %(reason)s"
+
+
+class InvalidEc2Id(Invalid):
+ message = _("Ec2 id %(ec2_id)s is unacceptable.")
+
+
+class NotFound(NovaException):
+ message = _("Resource could not be found.")
+
+ def __init__(self, *args, **kwargs):
+ super(NotFound, self).__init__(**kwargs)
+
+
+class InstanceNotFound(NotFound):
+ message = _("Instance %(instance_id)s could not be found.")
+
+
+class VolumeNotFound(NotFound):
+ message = _("Volume %(volume_id)s could not be found.")
+
+
+class VolumeNotFoundForInstance(VolumeNotFound):
+ message = _("Volume not found for instance %(instance_id)s.")
+
+
+class SnapshotNotFound(NotFound):
+ message = _("Snapshot %(snapshot_id)s could not be found.")
+
+
+class ExportDeviceNotFoundForVolume(NotFound):
+ message = _("No export device found for volume %(volume_id)s.")
+
+
+class ISCSITargetNotFoundForVolume(NotFound):
+ message = _("No target id found for volume %(volume_id)s.")
+
+
+class DiskNotFound(NotFound):
+ message = _("No disk at %(location)s")
+
+
+class ImageNotFound(NotFound):
+ message = _("Image %(image_id)s could not be found.")
+
+
+class KernelNotFoundForImage(ImageNotFound):
+ message = _("Kernel not found for image %(image_id)s.")
+
+
+class RamdiskNotFoundForImage(ImageNotFound):
+ message = _("Ramdisk not found for image %(image_id)s.")
+
+
+class UserNotFound(NotFound):
+ message = _("User %(user_id)s could not be found.")
+
+
+class ProjectNotFound(NotFound):
+ message = _("Project %(project_id)s could not be found.")
+
+
+class ProjectMembershipNotFound(NotFound):
+ message = _("User %(user_id)s is not a member of project %(project_id)s.")
+
+
+class UserRoleNotFound(NotFound):
+ message = _("Role %(role_id)s could not be found.")
+
+
+class StorageRepositoryNotFound(NotFound):
+ message = _("Cannot find SR to read/write VDI.")
+
+
+class NetworkNotFound(NotFound):
+ message = _("Network %(network_id)s could not be found.")
+
+
+class NetworkNotFoundForBridge(NetworkNotFound):
+ message = _("Network could not be found for bridge %(bridge)s")
+
+
+class NetworkNotFoundForCidr(NetworkNotFound):
+ message = _("Network could not be found with cidr %(cidr)s.")
+
+
+class NetworkNotFoundForInstance(NetworkNotFound):
+ message = _("Network could not be found for instance %(instance_id)s.")
+
+
+class NoNetworksFound(NotFound):
+ message = _("No networks defined.")
+
+
+class DatastoreNotFound(NotFound):
+ message = _("Could not find the datastore reference(s) which the VM uses.")
+
+
+class NoFixedIpsFoundForInstance(NotFound):
+ message = _("Instance %(instance_id)s has zero fixed ips.")
+
+
+class FloatingIpNotFound(NotFound):
+ message = _("Floating ip not found for fixed address %(fixed_ip)s.")
+
+
+class NoFloatingIpsDefined(NotFound):
+ message = _("Zero floating ips could be found.")
+
+
+class NoFloatingIpsDefinedForHost(NoFloatingIpsDefined):
+ message = _("Zero floating ips defined for host %(host)s.")
+
+
+class NoFloatingIpsDefinedForInstance(NoFloatingIpsDefined):
+ message = _("Zero floating ips defined for instance %(instance_id)s.")
+
+
+class KeypairNotFound(NotFound):
+ message = _("Keypair %(keypair_name)s not found for user %(user_id)s")
+
+
+class CertificateNotFound(NotFound):
+ message = _("Certificate %(certificate_id)s not found.")
+
+
+class ServiceNotFound(NotFound):
+ message = _("Service %(service_id)s could not be found.")
+
+
+class HostNotFound(NotFound):
+ message = _("Host %(host)s could not be found.")
+
+
+class ComputeHostNotFound(HostNotFound):
+ message = _("Compute host %(host)s could not be found.")
+
+
+class HostBinaryNotFound(NotFound):
+ message = _("Could not find binary %(binary)s on host %(host)s.")
+
+
+class AuthTokenNotFound(NotFound):
+ message = _("Auth token %(token)s could not be found.")
+
+
+class AccessKeyNotFound(NotFound):
+ message = _("Access Key %(access_key)s could not be found.")
+
+
+class QuotaNotFound(NotFound):
+ message = _("Quota could not be found")
+
+
+class ProjectQuotaNotFound(QuotaNotFound):
+ message = _("Quota for project %(project_id)s could not be found.")
+
+
+class SecurityGroupNotFound(NotFound):
+ message = _("Security group %(security_group_id)s not found.")
+
+
+class SecurityGroupNotFoundForProject(SecurityGroupNotFound):
+ message = _("Security group %(security_group_id)s not found "
+ "for project %(project_id)s.")
+
+
+class SecurityGroupNotFoundForRule(SecurityGroupNotFound):
+ message = _("Security group with rule %(rule_id)s not found.")
+
+
+class MigrationNotFound(NotFound):
+ message = _("Migration %(migration_id)s could not be found.")
+
+
+class MigrationNotFoundByStatus(MigrationNotFound):
+ message = _("Migration not found for instance %(instance_id)s "
+ "with status %(status)s.")
+
+
+class ConsolePoolNotFound(NotFound):
+ message = _("Console pool %(pool_id)s could not be found.")
+
+
+class ConsolePoolNotFoundForHostType(NotFound):
+ message = _("Console pool of type %(console_type)s "
+ "for compute host %(compute_host)s "
+ "on proxy host %(host)s not found.")
+
+
+class ConsoleNotFound(NotFound):
+ message = _("Console %(console_id)s could not be found.")
+
+
+class ConsoleNotFoundForInstance(ConsoleNotFound):
+ message = _("Console for instance %(instance_id)s could not be found.")
+
+
+class ConsoleNotFoundInPoolForInstance(ConsoleNotFound):
+ message = _("Console for instance %(instance_id)s "
+ "in pool %(pool_id)s could not be found.")
+
+
+class NoInstanceTypesFound(NotFound):
+ message = _("Zero instance types found.")
+
+
+class InstanceTypeNotFound(NotFound):
+ message = _("Instance type %(instance_type_id)s could not be found.")
+
+
+class InstanceTypeNotFoundByName(InstanceTypeNotFound):
+ message = _("Instance type with name %(instance_type_name)s "
+ "could not be found.")
+
+
+class FlavorNotFound(NotFound):
+ message = _("Flavor %(flavor_id)s could not be found.")
+
+
+class ZoneNotFound(NotFound):
+ message = _("Zone %(zone_id)s could not be found.")
+
+
+class SchedulerHostFilterDriverNotFound(NotFound):
+ message = _("Scheduler Host Filter Driver %(driver_name)s could"
+ " not be found.")
+
+
+class InstanceMetadataNotFound(NotFound):
+ message = _("Instance %(instance_id)s has no metadata with "
+ "key %(metadata_key)s.")
+
+
+class LDAPObjectNotFound(NotFound):
+ message = _("LDAP object could not be found")
+
+
+class LDAPUserNotFound(LDAPObjectNotFound):
+ message = _("LDAP user %(user_id)s could not be found.")
+
+
+class LDAPGroupNotFound(LDAPObjectNotFound):
+ message = _("LDAP group %(group_id)s could not be found.")
+
+
+class LDAPGroupMembershipNotFound(NotFound):
+ message = _("LDAP user %(user_id)s is not a member of group %(group_id)s.")
+
+
+class FileNotFound(NotFound):
+ message = _("File %(file_path)s could not be found.")
+
+
+class NoFilesFound(NotFound):
+ message = _("Zero files could be found.")
+
+
+class SwitchNotFoundForNetworkAdapter(NotFound):
+ message = _("Virtual switch associated with the "
+ "network adapter %(adapter)s not found.")
+
+
+class NetworkAdapterNotFound(NotFound):
+ message = _("Network adapter %(adapter)s could not be found.")
+
+
+class ClassNotFound(NotFound):
+ message = _("Class %(class_name)s could not be found")
+
+
+class NotAllowed(NovaException):
+ message = _("Action not allowed.")
+
+
+class GlobalRoleNotAllowed(NotAllowed):
+ message = _("Unable to use global role %(role_id)s")
+
+
+#TODO(bcwaldon): EOL this exception!
+class Duplicate(NovaException):
+ pass
+
+
+class KeyPairExists(Duplicate):
+ message = _("Key pair %(key_name)s already exists.")
+
+
+class UserExists(Duplicate):
+ message = _("User %(user)s already exists.")
+
+
+class LDAPUserExists(UserExists):
+ message = _("LDAP user %(user)s already exists.")
+
+
+class LDAPGroupExists(Duplicate):
+ message = _("LDAP group %(group)s already exists.")
+
+
+class LDAPMembershipExists(Duplicate):
+ message = _("User %(uid)s is already a member of "
+ "the group %(group_dn)s")
+
+
+class ProjectExists(Duplicate):
+ message = _("Project %(project)s already exists.")
+
+
+class InstanceExists(Duplicate):
+ message = _("Instance %(name)s already exists.")
+
+
+class MigrationError(NovaException):
+ message = _("Migration error") + ": %(reason)s"
diff --git a/nova/fakememcache.py b/nova/fakememcache.py
index 67f46dbdc..e4f238aa9 100644
--- a/nova/fakememcache.py
+++ b/nova/fakememcache.py
@@ -18,14 +18,14 @@
"""Super simple fake memcache client."""
-import utils
+from nova import utils
class Client(object):
"""Replicates a tiny subset of memcached client interface."""
def __init__(self, *args, **kwargs):
- """Ignores the passed in args"""
+ """Ignores the passed in args."""
self.cache = {}
def get(self, key):
diff --git a/nova/flags.py b/nova/flags.py
index f011ab383..519793643 100644
--- a/nova/flags.py
+++ b/nova/flags.py
@@ -16,9 +16,13 @@
# License for the specific language governing permissions and limitations
# under the License.
-"""
+"""Command-line flag library.
+
+Wraps gflags.
+
Package-level global flags are defined here, the rest are defined
where they're used.
+
"""
import getopt
@@ -145,10 +149,12 @@ class FlagValues(gflags.FlagValues):
class StrWrapper(object):
- """Wrapper around FlagValues objects
+ """Wrapper around FlagValues objects.
Wraps FlagValues objects for string.Template so that we're
- sure to return strings."""
+ sure to return strings.
+
+ """
def __init__(self, context_objs):
self.context_objs = context_objs
@@ -169,6 +175,7 @@ def _GetCallingModule():
We generally use this function to get the name of the module calling a
DEFINE_foo... function.
+
"""
# Walk down the stack to find the first globals dict that's not ours.
for depth in range(1, sys.getrecursionlimit()):
@@ -192,6 +199,7 @@ def __GetModuleName(globals_dict):
Returns:
A string (the name of the module) or None (if the module could not
be identified.
+
"""
for name, module in sys.modules.iteritems():
if getattr(module, '__dict__', None) is globals_dict:
@@ -316,7 +324,7 @@ DEFINE_string('null_kernel', 'nokernel',
'kernel image that indicates not to use a kernel,'
' but to use a raw disk image instead')
-DEFINE_string('vpn_image_id', 'ami-cloudpipe', 'AMI for cloudpipe vpn server')
+DEFINE_integer('vpn_image_id', 0, 'integer id for cloudpipe vpn server')
DEFINE_string('vpn_key_suffix',
'-vpn',
'Suffix to add to project name for vpn key and secgroups')
@@ -326,7 +334,7 @@ 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('lock_path', os.path.join(os.path.dirname(__file__), '../'),
- "Directory for lock files")
+ 'Directory for lock files')
DEFINE_string('logdir', None, 'output to a per-service log file in named '
'directory')
@@ -361,6 +369,9 @@ DEFINE_string('host', socket.gethostname(),
DEFINE_string('node_availability_zone', 'nova',
'availability zone of this node')
+DEFINE_list('memcached_servers', None,
+ 'Memcached servers or None for in process cache.')
+
DEFINE_string('zone_name', 'nova', 'name of this zone')
DEFINE_list('zone_capabilities',
['hypervisor=xenserver;kvm', 'os=linux;windows'],
diff --git a/nova/image/fake.py b/nova/image/fake.py
index e02b4127e..b400b2adb 100644
--- a/nova/image/fake.py
+++ b/nova/image/fake.py
@@ -14,6 +14,7 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
+
"""Implementation of an fake image service"""
import copy
@@ -69,14 +70,14 @@ class FakeImageService(service.BaseImageService):
image = self.images.get(image_id)
if image:
return copy.deepcopy(image)
- LOG.warn("Unable to find image id %s. Have images: %s",
+ LOG.warn('Unable to find image id %s. Have images: %s',
image_id, self.images)
- raise exception.NotFound
+ raise exception.ImageNotFound(image_id=image_id)
def create(self, context, data):
"""Store the image data and return the new image id.
- :raises Duplicate if the image already exist.
+ :raises: Duplicate if the image already exist.
"""
image_id = int(data['id'])
@@ -88,24 +89,24 @@ class FakeImageService(service.BaseImageService):
def update(self, context, image_id, data):
"""Replace the contents of the given image with the new data.
- :raises NotFound if the image does not exist.
+ :raises: ImageNotFound if the image does not exist.
"""
image_id = int(image_id)
if not self.images.get(image_id):
- raise exception.NotFound
+ raise exception.ImageNotFound(image_id=image_id)
self.images[image_id] = copy.deepcopy(data)
def delete(self, context, image_id):
"""Delete the given image.
- :raises NotFound if the image does not exist.
+ :raises: ImageNotFound if the image does not exist.
"""
image_id = int(image_id)
removed = self.images.pop(image_id, None)
if not removed:
- raise exception.NotFound
+ raise exception.ImageNotFound(image_id=image_id)
def delete_all(self):
"""Clears out all images."""
diff --git a/nova/image/glance.py b/nova/image/glance.py
index 1a80bb2af..193e37273 100644
--- a/nova/image/glance.py
+++ b/nova/image/glance.py
@@ -14,6 +14,7 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
+
"""Implementation of an image service that uses Glance as the backend"""
from __future__ import absolute_import
@@ -31,16 +32,18 @@ from nova.image import service
LOG = logging.getLogger('nova.image.glance')
+
FLAGS = flags.FLAGS
+
GlanceClient = utils.import_class('glance.client.Client')
class GlanceImageService(service.BaseImageService):
"""Provides storage and retrieval of disk image objects within Glance."""
- GLANCE_ONLY_ATTRS = ["size", "location", "disk_format",
- "container_format"]
+ GLANCE_ONLY_ATTRS = ['size', 'location', 'disk_format',
+ 'container_format']
# NOTE(sirp): Overriding to use _translate_to_service provided by
# BaseImageService
@@ -56,9 +59,7 @@ class GlanceImageService(service.BaseImageService):
self.client = client
def index(self, context):
- """
- Calls out to Glance for a list of images available
- """
+ """Calls out to Glance for a list of images available."""
# NOTE(sirp): We need to use `get_images_detailed` and not
# `get_images` here because we need `is_public` and `properties`
# included so we can filter by user
@@ -71,9 +72,7 @@ class GlanceImageService(service.BaseImageService):
return filtered
def detail(self, context):
- """
- Calls out to Glance for a list of detailed image information
- """
+ """Calls out to Glance for a list of detailed image information."""
filtered = []
image_metas = self.client.get_images_detailed()
for image_meta in image_metas:
@@ -83,40 +82,34 @@ class GlanceImageService(service.BaseImageService):
return filtered
def show(self, context, image_id):
- """
- Returns a dict containing image data for the given opaque image id.
- """
+ """Returns a dict with image data for the given opaque image id."""
try:
image_meta = self.client.get_image_meta(image_id)
except glance_exception.NotFound:
- raise exception.NotFound
+ raise exception.ImageNotFound(image_id=image_id)
if not self._is_image_available(context, image_meta):
- raise exception.NotFound
+ raise exception.ImageNotFound(image_id=image_id)
base_image_meta = self._translate_to_base(image_meta)
return base_image_meta
def show_by_name(self, context, name):
- """
- Returns a dict containing image data for the given name.
- """
+ """Returns a dict containing image data for the given name."""
# TODO(vish): replace this with more efficient call when glance
# supports it.
image_metas = self.detail(context)
for image_meta in image_metas:
if name == image_meta.get('name'):
return image_meta
- raise exception.NotFound
+ raise exception.ImageNotFound(image_id=name)
def get(self, context, image_id, data):
- """
- Calls out to Glance for metadata and data and writes data.
- """
+ """Calls out to Glance for metadata and data and writes data."""
try:
image_meta, image_chunks = self.client.get_image(image_id)
except glance_exception.NotFound:
- raise exception.NotFound
+ raise exception.ImageNotFound(image_id=image_id)
for chunk in image_chunks:
data.write(chunk)
@@ -125,16 +118,16 @@ class GlanceImageService(service.BaseImageService):
return base_image_meta
def create(self, context, image_meta, data=None):
- """
- Store the image data and return the new image id.
+ """Store the image data and return the new image id.
+
+ :raises: AlreadyExists if the image already exist.
- :raises AlreadyExists if the image already exist.
"""
# Translate Base -> Service
- LOG.debug(_("Creating image in Glance. Metadata passed in %s"),
+ LOG.debug(_('Creating image in Glance. Metadata passed in %s'),
image_meta)
sent_service_image_meta = self._translate_to_service(image_meta)
- LOG.debug(_("Metadata after formatting for Glance %s"),
+ LOG.debug(_('Metadata after formatting for Glance %s'),
sent_service_image_meta)
recv_service_image_meta = self.client.add_image(
@@ -142,50 +135,47 @@ class GlanceImageService(service.BaseImageService):
# Translate Service -> Base
base_image_meta = self._translate_to_base(recv_service_image_meta)
- LOG.debug(_("Metadata returned from Glance formatted for Base %s"),
+ LOG.debug(_('Metadata returned from Glance formatted for Base %s'),
base_image_meta)
return base_image_meta
def update(self, context, image_id, image_meta, data=None):
"""Replace the contents of the given image with the new data.
- :raises NotFound if the image does not exist.
+ :raises: ImageNotFound if the image does not exist.
+
"""
# NOTE(vish): show is to check if image is available
self.show(context, image_id)
try:
image_meta = self.client.update_image(image_id, image_meta, data)
except glance_exception.NotFound:
- raise exception.NotFound
+ raise exception.ImageNotFound(image_id=image_id)
base_image_meta = self._translate_to_base(image_meta)
return base_image_meta
def delete(self, context, image_id):
- """
- Delete the given image.
+ """Delete the given image.
+
+ :raises: ImageNotFound if the image does not exist.
- :raises NotFound if the image does not exist.
"""
# NOTE(vish): show is to check if image is available
self.show(context, image_id)
try:
result = self.client.delete_image(image_id)
except glance_exception.NotFound:
- raise exception.NotFound
+ raise exception.ImageNotFound(image_id=image_id)
return result
def delete_all(self):
- """
- Clears out all images
- """
+ """Clears out all images."""
pass
@classmethod
def _translate_to_base(cls, image_meta):
- """Overriding the base translation to handle conversion to datetime
- objects
- """
+ """Override translation to handle conversion to datetime objects."""
image_meta = service.BaseImageService._propertify_metadata(
image_meta, cls.SERVICE_IMAGE_ATTRS)
image_meta = _convert_timestamps_to_datetimes(image_meta)
@@ -194,9 +184,7 @@ class GlanceImageService(service.BaseImageService):
# utility functions
def _convert_timestamps_to_datetimes(image_meta):
- """
- Returns image with known timestamp fields converted to datetime objects
- """
+ """Returns image with timestamp fields converted to datetime objects."""
for attr in ['created_at', 'updated_at', 'deleted_at']:
if image_meta.get(attr):
image_meta[attr] = _parse_glance_iso8601_timestamp(
@@ -205,10 +193,8 @@ def _convert_timestamps_to_datetimes(image_meta):
def _parse_glance_iso8601_timestamp(timestamp):
- """
- Parse a subset of iso8601 timestamps into datetime objects
- """
- iso_formats = ["%Y-%m-%dT%H:%M:%S.%f", "%Y-%m-%dT%H:%M:%S"]
+ """Parse a subset of iso8601 timestamps into datetime objects."""
+ iso_formats = ['%Y-%m-%dT%H:%M:%S.%f', '%Y-%m-%dT%H:%M:%S']
for iso_format in iso_formats:
try:
@@ -216,5 +202,5 @@ def _parse_glance_iso8601_timestamp(timestamp):
except ValueError:
pass
- raise ValueError(_("%(timestamp)s does not follow any of the "
- "signatures: %(ISO_FORMATS)s") % locals())
+ raise ValueError(_('%(timestamp)s does not follow any of the '
+ 'signatures: %(ISO_FORMATS)s') % locals())
diff --git a/nova/image/local.py b/nova/image/local.py
index fa5e93346..918180bae 100644
--- a/nova/image/local.py
+++ b/nova/image/local.py
@@ -23,14 +23,15 @@ import shutil
from nova import exception
from nova import flags
from nova import log as logging
-from nova.image import service
from nova import utils
+from nova.image import service
FLAGS = flags.FLAGS
flags.DEFINE_string('images_path', '$state_path/images',
'path to decrypted images')
+
LOG = logging.getLogger('nova.image.local')
@@ -56,9 +57,8 @@ class LocalImageService(service.BaseImageService):
try:
unhexed_image_id = int(image_dir, 16)
except ValueError:
- LOG.error(
- _("%s is not in correct directory naming format"\
- % image_dir))
+ LOG.error(_('%s is not in correct directory naming format')
+ % image_dir)
else:
images.append(unhexed_image_id)
return images
@@ -86,10 +86,10 @@ class LocalImageService(service.BaseImageService):
with open(self._path_to(image_id)) as metadata_file:
image_meta = json.load(metadata_file)
if not self._is_image_available(context, image_meta):
- raise exception.NotFound
+ raise exception.ImageNotFound(image_id=image_id)
return image_meta
except (IOError, ValueError):
- raise exception.NotFound
+ raise exception.ImageNotFound(image_id=image_id)
def show_by_name(self, context, name):
"""Returns a dict containing image data for the given name."""
@@ -102,7 +102,7 @@ class LocalImageService(service.BaseImageService):
image = cantidate
break
if image is None:
- raise exception.NotFound
+ raise exception.ImageNotFound(image_id=name)
return image
def get(self, context, image_id, data):
@@ -113,7 +113,7 @@ class LocalImageService(service.BaseImageService):
with open(self._path_to(image_id, 'image')) as image_file:
shutil.copyfileobj(image_file, data)
except (IOError, ValueError):
- raise exception.NotFound
+ raise exception.ImageNotFound(image_id=image_id)
return metadata
def create(self, context, metadata, data=None):
@@ -143,12 +143,13 @@ class LocalImageService(service.BaseImageService):
with open(self._path_to(image_id), 'w') as metadata_file:
json.dump(metadata, metadata_file)
except (IOError, ValueError):
- raise exception.NotFound
+ raise exception.ImageNotFound(image_id=image_id)
return metadata
def delete(self, context, image_id):
"""Delete the given image.
- Raises NotFound if the image does not exist.
+
+ :raises: ImageNotFound if the image does not exist.
"""
# NOTE(vish): show is to check if image is available
@@ -156,7 +157,7 @@ class LocalImageService(service.BaseImageService):
try:
shutil.rmtree(self._path_to(image_id, None))
except (IOError, ValueError):
- raise exception.NotFound
+ raise exception.ImageNotFound(image_id=image_id)
def delete_all(self):
"""Clears out all images in local directory."""
diff --git a/nova/image/s3.py b/nova/image/s3.py
index 2a02d4674..c38c58d95 100644
--- a/nova/image/s3.py
+++ b/nova/image/s3.py
@@ -16,13 +16,9 @@
# License for the specific language governing permissions and limitations
# under the License.
-"""
-Proxy AMI-related calls from the cloud controller, to the running
-objectstore service.
-"""
+"""Proxy AMI-related calls from cloud controller to objectstore service."""
import binascii
-import eventlet
import os
import shutil
import tarfile
@@ -30,6 +26,7 @@ import tempfile
from xml.etree import ElementTree
import boto.s3.connection
+import eventlet
from nova import crypto
from nova import exception
@@ -46,7 +43,8 @@ flags.DEFINE_string('image_decryption_dir', '/tmp',
class S3ImageService(service.BaseImageService):
- """Wraps an existing image service to support s3 based register"""
+ """Wraps an existing image service to support s3 based register."""
+
def __init__(self, service=None, *args, **kwargs):
if service is None:
service = utils.import_object(FLAGS.image_service)
@@ -54,7 +52,11 @@ class S3ImageService(service.BaseImageService):
self.service.__init__(*args, **kwargs)
def create(self, context, metadata, data=None):
- """metadata['properties'] should contain image_location"""
+ """Create an image.
+
+ metadata['properties'] should contain image_location.
+
+ """
image = self._s3_create(context, metadata)
return image
@@ -100,12 +102,12 @@ class S3ImageService(service.BaseImageService):
return local_filename
def _s3_create(self, context, metadata):
- """Gets a manifext from s3 and makes an image"""
+ """Gets a manifext from s3 and makes an image."""
image_path = tempfile.mkdtemp(dir=FLAGS.image_decryption_dir)
image_location = metadata['properties']['image_location']
- bucket_name = image_location.split("/")[0]
+ bucket_name = image_location.split('/')[0]
manifest_path = image_location[len(bucket_name) + 1:]
bucket = self._conn(context).get_bucket(bucket_name)
key = bucket.get_key(manifest_path)
@@ -116,7 +118,7 @@ class S3ImageService(service.BaseImageService):
image_type = 'machine'
try:
- kernel_id = manifest.find("machine_configuration/kernel_id").text
+ kernel_id = manifest.find('machine_configuration/kernel_id').text
if kernel_id == 'true':
image_format = 'aki'
image_type = 'kernel'
@@ -125,7 +127,7 @@ class S3ImageService(service.BaseImageService):
kernel_id = None
try:
- ramdisk_id = manifest.find("machine_configuration/ramdisk_id").text
+ ramdisk_id = manifest.find('machine_configuration/ramdisk_id').text
if ramdisk_id == 'true':
image_format = 'ari'
image_type = 'ramdisk'
@@ -134,7 +136,7 @@ class S3ImageService(service.BaseImageService):
ramdisk_id = None
try:
- arch = manifest.find("machine_configuration/architecture").text
+ arch = manifest.find('machine_configuration/architecture').text
except Exception:
arch = 'x86_64'
@@ -160,7 +162,7 @@ class S3ImageService(service.BaseImageService):
def delayed_create():
"""This handles the fetching and decrypting of the part files."""
parts = []
- for fn_element in manifest.find("image").getiterator("filename"):
+ for fn_element in manifest.find('image').getiterator('filename'):
part = self._download_file(bucket, fn_element.text, image_path)
parts.append(part)
@@ -174,9 +176,9 @@ class S3ImageService(service.BaseImageService):
metadata['properties']['image_state'] = 'decrypting'
self.service.update(context, image_id, metadata)
- hex_key = manifest.find("image/ec2_encrypted_key").text
+ hex_key = manifest.find('image/ec2_encrypted_key').text
encrypted_key = binascii.a2b_hex(hex_key)
- hex_iv = manifest.find("image/ec2_encrypted_iv").text
+ hex_iv = manifest.find('image/ec2_encrypted_iv').text
encrypted_iv = binascii.a2b_hex(hex_iv)
# FIXME(vish): grab key from common service so this can run on
@@ -214,7 +216,7 @@ class S3ImageService(service.BaseImageService):
process_input=encrypted_key,
check_exit_code=False)
if err:
- raise exception.Error(_("Failed to decrypt private key: %s")
+ raise exception.Error(_('Failed to decrypt private key: %s')
% err)
iv, err = utils.execute('openssl',
'rsautl',
@@ -223,8 +225,8 @@ class S3ImageService(service.BaseImageService):
process_input=encrypted_iv,
check_exit_code=False)
if err:
- raise exception.Error(_("Failed to decrypt initialization "
- "vector: %s") % err)
+ raise exception.Error(_('Failed to decrypt initialization '
+ 'vector: %s') % err)
_out, err = utils.execute('openssl', 'enc',
'-d', '-aes-128-cbc',
@@ -234,14 +236,14 @@ class S3ImageService(service.BaseImageService):
'-out', '%s' % (decrypted_filename,),
check_exit_code=False)
if err:
- raise exception.Error(_("Failed to decrypt image file "
- "%(image_file)s: %(err)s") %
+ raise exception.Error(_('Failed to decrypt image file '
+ '%(image_file)s: %(err)s') %
{'image_file': encrypted_filename,
'err': err})
@staticmethod
def _untarzip_image(path, filename):
- tar_file = tarfile.open(filename, "r|gz")
+ tar_file = tarfile.open(filename, 'r|gz')
tar_file.extractall(path)
image_file = tar_file.getnames()[0]
tar_file.close()
diff --git a/nova/image/service.py b/nova/image/service.py
index fddc72409..ab6749049 100644
--- a/nova/image/service.py
+++ b/nova/image/service.py
@@ -20,7 +20,7 @@ from nova import utils
class BaseImageService(object):
- """Base class for providing image search and retrieval services
+ """Base class for providing image search and retrieval services.
ImageService exposes two concepts of metadata:
@@ -35,7 +35,9 @@ class BaseImageService(object):
This means that ImageServices will return BASE_IMAGE_ATTRS as keys in the
metadata dict, all other attributes will be returned as keys in the nested
'properties' dict.
+
"""
+
BASE_IMAGE_ATTRS = ['id', 'name', 'created_at', 'updated_at',
'deleted_at', 'deleted', 'status', 'is_public']
@@ -45,23 +47,18 @@ class BaseImageService(object):
SERVICE_IMAGE_ATTRS = []
def index(self, context):
- """
- Returns a sequence of mappings of id and name information about
- images.
+ """List images.
- :rtype: array
- :retval: a sequence of mappings with the following signature
- {'id': opaque id of image, 'name': name of image}
+ :returns: a sequence of mappings with the following signature
+ {'id': opaque id of image, 'name': name of image}
"""
raise NotImplementedError
def detail(self, context):
- """
- Returns a sequence of mappings of detailed information about images.
+ """Detailed information about an images.
- :rtype: array
- :retval: a sequence of mappings with the following signature
+ :returns: a sequence of mappings with the following signature
{'id': opaque id of image,
'name': name of image,
'created_at': creation datetime object,
@@ -77,15 +74,14 @@ class BaseImageService(object):
NotImplementedError, in which case Nova will emulate this method
with repeated calls to show() for each image received from the
index() method.
+
"""
raise NotImplementedError
def show(self, context, image_id):
- """
- Returns a dict containing image metadata for the given opaque image id.
-
- :retval a mapping with the following signature:
+ """Detailed information about an image.
+ :returns: a mapping with the following signature:
{'id': opaque id of image,
'name': name of image,
'created_at': creation datetime object,
@@ -96,54 +92,56 @@ class BaseImageService(object):
'is_public': boolean indicating if image is public
}, ...
- :raises NotFound if the image does not exist
+ :raises: NotFound if the image does not exist
+
"""
raise NotImplementedError
def get(self, context, data):
- """
- Returns a dict containing image metadata and writes image data to data.
+ """Get an image.
:param data: a file-like object to hold binary image data
+ :returns: a dict containing image metadata, writes image data to data.
+ :raises: NotFound if the image does not exist
- :raises NotFound if the image does not exist
"""
raise NotImplementedError
def create(self, context, metadata, data=None):
- """
- Store the image metadata and data and return the new image metadata.
+ """Store the image metadata and data.
- :raises AlreadyExists if the image already exist.
+ :returns: the new image metadata.
+ :raises: AlreadyExists if the image already exist.
"""
raise NotImplementedError
def update(self, context, image_id, metadata, data=None):
- """Update the given image metadata and data and return the metadata
+ """Update the given image metadata and data and return the metadata.
- :raises NotFound if the image does not exist.
+ :raises: NotFound if the image does not exist.
"""
raise NotImplementedError
def delete(self, context, image_id):
- """
- Delete the given image.
+ """Delete the given image.
- :raises NotFound if the image does not exist.
+ :raises: NotFound if the image does not exist.
"""
raise NotImplementedError
@staticmethod
def _is_image_available(context, image_meta):
- """
+ """Check image availability.
+
Images are always available if they are public or if the user is an
admin.
Otherwise, we filter by project_id (if present) and then fall-back to
images owned by user.
+
"""
# FIXME(sirp): We should be filtering by user_id on the Glance side
# for security; however, we can't do that until we get authn/authz
@@ -169,29 +167,32 @@ class BaseImageService(object):
This is used by subclasses to expose only a metadata dictionary that
is the same across ImageService implementations.
+
"""
return cls._propertify_metadata(metadata, cls.BASE_IMAGE_ATTRS)
@classmethod
def _translate_to_service(cls, metadata):
- """Return a metadata dictionary that is usable by the ImageService
- subclass.
+ """Return a metadata dict that is usable by the ImageService subclass.
As an example, Glance has additional attributes (like 'location'); the
BaseImageService considers these properties, but we need to translate
these back to first-class attrs for sending to Glance. This method
handles this by allowing you to specify the attributes an ImageService
considers first-class.
+
"""
if not cls.SERVICE_IMAGE_ATTRS:
- raise NotImplementedError(_("Cannot use this without specifying "
- "SERVICE_IMAGE_ATTRS for subclass"))
+ raise NotImplementedError(_('Cannot use this without specifying '
+ 'SERVICE_IMAGE_ATTRS for subclass'))
return cls._propertify_metadata(metadata, cls.SERVICE_IMAGE_ATTRS)
@staticmethod
def _propertify_metadata(metadata, keys):
- """Return a dict with any unrecognized keys placed in the nested
- 'properties' dict.
+ """Move unknown keys to a nested 'properties' dict.
+
+ :returns: a new dict with the keys moved.
+
"""
flattened = utils.flatten_dict(metadata)
attributes, properties = utils.partition_dict(flattened, keys)
diff --git a/nova/log.py b/nova/log.py
index ea94be194..096279f7c 100644
--- a/nova/log.py
+++ b/nova/log.py
@@ -16,16 +16,15 @@
# License for the specific language governing permissions and limitations
# under the License.
-"""
-Nova logging handler.
+"""Nova logging handler.
This module adds to logging functionality by adding the option to specify
a context object when calling the various log methods. If the context object
is not specified, default formatting is used.
It also allows setting of formatting information through flags.
-"""
+"""
import cStringIO
import inspect
@@ -41,34 +40,28 @@ from nova import version
FLAGS = flags.FLAGS
-
flags.DEFINE_string('logging_context_format_string',
'%(asctime)s %(levelname)s %(name)s '
'[%(request_id)s %(user)s '
'%(project)s] %(message)s',
'format string to use for log messages with context')
-
flags.DEFINE_string('logging_default_format_string',
'%(asctime)s %(levelname)s %(name)s [-] '
'%(message)s',
'format string to use for log messages without context')
-
flags.DEFINE_string('logging_debug_format_suffix',
'from (pid=%(process)d) %(funcName)s'
' %(pathname)s:%(lineno)d',
'data to append to log format when level is DEBUG')
-
flags.DEFINE_string('logging_exception_prefix',
'(%(name)s): TRACE: ',
'prefix each line of exception output with this format')
-
flags.DEFINE_list('default_log_levels',
['amqplib=WARN',
'sqlalchemy=WARN',
'boto=WARN',
'eventlet.wsgi.server=WARN'],
'list of logger=LEVEL pairs')
-
flags.DEFINE_bool('use_syslog', False, 'output to syslog')
flags.DEFINE_string('logfile', None, 'output to named file')
@@ -83,6 +76,8 @@ WARN = logging.WARN
INFO = logging.INFO
DEBUG = logging.DEBUG
NOTSET = logging.NOTSET
+
+
# methods
getLogger = logging.getLogger
debug = logging.debug
@@ -93,6 +88,8 @@ error = logging.error
exception = logging.exception
critical = logging.critical
log = logging.log
+
+
# handlers
StreamHandler = logging.StreamHandler
WatchedFileHandler = logging.handlers.WatchedFileHandler
@@ -127,17 +124,18 @@ def _get_log_file_path(binary=None):
class NovaLogger(logging.Logger):
- """
- NovaLogger manages request context and formatting.
+ """NovaLogger manages request context and formatting.
This becomes the class that is instanciated by logging.getLogger.
+
"""
+
def __init__(self, name, level=NOTSET):
logging.Logger.__init__(self, name, level)
self.setup_from_flags()
def setup_from_flags(self):
- """Setup logger from flags"""
+ """Setup logger from flags."""
level = NOTSET
for pair in FLAGS.default_log_levels:
logger, _sep, level_name = pair.partition('=')
@@ -148,7 +146,7 @@ class NovaLogger(logging.Logger):
self.setLevel(level)
def _log(self, level, msg, args, exc_info=None, extra=None, context=None):
- """Extract context from any log call"""
+ """Extract context from any log call."""
if not extra:
extra = {}
if context:
@@ -157,17 +155,17 @@ class NovaLogger(logging.Logger):
return logging.Logger._log(self, level, msg, args, exc_info, extra)
def addHandler(self, handler):
- """Each handler gets our custom formatter"""
+ """Each handler gets our custom formatter."""
handler.setFormatter(_formatter)
return logging.Logger.addHandler(self, handler)
def audit(self, msg, *args, **kwargs):
- """Shortcut for our AUDIT level"""
+ """Shortcut for our AUDIT level."""
if self.isEnabledFor(AUDIT):
self._log(AUDIT, msg, args, **kwargs)
def exception(self, msg, *args, **kwargs):
- """Logging.exception doesn't handle kwargs, so breaks context"""
+ """Logging.exception doesn't handle kwargs, so breaks context."""
if not kwargs.get('exc_info'):
kwargs['exc_info'] = 1
self.error(msg, *args, **kwargs)
@@ -181,14 +179,13 @@ class NovaLogger(logging.Logger):
for k in env.keys():
if not isinstance(env[k], str):
env.pop(k)
- message = "Environment: %s" % json.dumps(env)
+ message = 'Environment: %s' % json.dumps(env)
kwargs.pop('exc_info')
self.error(message, **kwargs)
class NovaFormatter(logging.Formatter):
- """
- A nova.context.RequestContext aware formatter configured through flags.
+ """A nova.context.RequestContext aware formatter configured through flags.
The flags used to set format strings are: logging_context_foramt_string
and logging_default_format_string. You can also specify
@@ -197,10 +194,11 @@ class NovaFormatter(logging.Formatter):
For information about what variables are available for the formatter see:
http://docs.python.org/library/logging.html#formatter
+
"""
def format(self, record):
- """Uses contextstring if request_id is set, otherwise default"""
+ """Uses contextstring if request_id is set, otherwise default."""
if record.__dict__.get('request_id', None):
self._fmt = FLAGS.logging_context_format_string
else:
@@ -214,20 +212,21 @@ class NovaFormatter(logging.Formatter):
return logging.Formatter.format(self, record)
def formatException(self, exc_info, record=None):
- """Format exception output with FLAGS.logging_exception_prefix"""
+ """Format exception output with FLAGS.logging_exception_prefix."""
if not record:
return logging.Formatter.formatException(self, exc_info)
stringbuffer = cStringIO.StringIO()
traceback.print_exception(exc_info[0], exc_info[1], exc_info[2],
None, stringbuffer)
- lines = stringbuffer.getvalue().split("\n")
+ lines = stringbuffer.getvalue().split('\n')
stringbuffer.close()
formatted_lines = []
for line in lines:
pl = FLAGS.logging_exception_prefix % record.__dict__
- fl = "%s%s" % (pl, line)
+ fl = '%s%s' % (pl, line)
formatted_lines.append(fl)
- return "\n".join(formatted_lines)
+ return '\n'.join(formatted_lines)
+
_formatter = NovaFormatter()
@@ -241,7 +240,7 @@ class NovaRootLogger(NovaLogger):
NovaLogger.__init__(self, name, level)
def setup_from_flags(self):
- """Setup logger from flags"""
+ """Setup logger from flags."""
global _filelog
if FLAGS.use_syslog:
self.syslog = SysLogHandler(address='/dev/log')
diff --git a/nova/manager.py b/nova/manager.py
index 804a50479..34338ac04 100644
--- a/nova/manager.py
+++ b/nova/manager.py
@@ -16,7 +16,8 @@
# License for the specific language governing permissions and limitations
# under the License.
-"""
+"""Base Manager class.
+
Managers are responsible for a certain aspect of the sytem. It is a logical
grouping of code relating to a portion of the system. In general other
components should be using the manager to make changes to the components that
@@ -49,16 +50,19 @@ Managers will often provide methods for initial setup of a host or periodic
tasksto a wrapping service.
This module provides Manager, a base class for managers.
+
"""
-from nova import utils
from nova import flags
from nova import log as logging
+from nova import utils
from nova.db import base
from nova.scheduler import api
+
FLAGS = flags.FLAGS
+
LOG = logging.getLogger('nova.manager')
@@ -70,23 +74,29 @@ class Manager(base.Base):
super(Manager, self).__init__(db_driver)
def periodic_tasks(self, context=None):
- """Tasks to be run at a periodic interval"""
+ """Tasks to be run at a periodic interval."""
pass
def init_host(self):
- """Do any initialization that needs to be run if this is a standalone
- service. Child classes should override this method."""
+ """Handle initialization if this is a standalone service.
+
+ Child classes should override this method.
+
+ """
pass
class SchedulerDependentManager(Manager):
"""Periodically send capability updates to the Scheduler services.
- Services that need to update the Scheduler of their capabilities
- should derive from this class. Otherwise they can derive from
- manager.Manager directly. Updates are only sent after
- update_service_capabilities is called with non-None values."""
- def __init__(self, host=None, db_driver=None, service_name="undefined"):
+ Services that need to update the Scheduler of their capabilities
+ should derive from this class. Otherwise they can derive from
+ manager.Manager directly. Updates are only sent after
+ update_service_capabilities is called with non-None values.
+
+ """
+
+ def __init__(self, host=None, db_driver=None, service_name='undefined'):
self.last_capabilities = None
self.service_name = service_name
super(SchedulerDependentManager, self).__init__(host, db_driver)
@@ -96,9 +106,9 @@ class SchedulerDependentManager(Manager):
self.last_capabilities = capabilities
def periodic_tasks(self, context=None):
- """Pass data back to the scheduler at a periodic interval"""
+ """Pass data back to the scheduler at a periodic interval."""
if self.last_capabilities:
- LOG.debug(_("Notifying Schedulers of capabilities ..."))
+ LOG.debug(_('Notifying Schedulers of capabilities ...'))
api.update_service_capabilities(context, self.service_name,
self.host, self.last_capabilities)
diff --git a/nova/network/api.py b/nova/network/api.py
index c56e3062b..1d8193b28 100644
--- a/nova/network/api.py
+++ b/nova/network/api.py
@@ -51,8 +51,11 @@ class API(base.Base):
{"method": "allocate_floating_ip",
"args": {"project_id": context.project_id}})
- def release_floating_ip(self, context, address):
+ def release_floating_ip(self, context, address,
+ affect_auto_assigned=False):
floating_ip = self.db.floating_ip_get_by_address(context, address)
+ if not affect_auto_assigned and floating_ip.get('auto_assigned'):
+ return
# NOTE(vish): We don't know which network host should get the ip
# when we deallocate, so just send it to any one. This
# will probably need to move into a network supervisor
@@ -62,10 +65,13 @@ class API(base.Base):
{"method": "deallocate_floating_ip",
"args": {"floating_address": floating_ip['address']}})
- def associate_floating_ip(self, context, floating_ip, fixed_ip):
+ def associate_floating_ip(self, context, floating_ip, fixed_ip,
+ affect_auto_assigned=False):
if isinstance(fixed_ip, str) or isinstance(fixed_ip, unicode):
fixed_ip = self.db.fixed_ip_get_by_address(context, fixed_ip)
floating_ip = self.db.floating_ip_get_by_address(context, floating_ip)
+ if not affect_auto_assigned and floating_ip.get('auto_assigned'):
+ return
# Check if the floating ip address is allocated
if floating_ip['project_id'] is None:
raise exception.ApiError(_("Address (%s) is not allocated") %
@@ -90,8 +96,11 @@ class API(base.Base):
"args": {"floating_address": floating_ip['address'],
"fixed_address": fixed_ip['address']}})
- def disassociate_floating_ip(self, context, address):
+ def disassociate_floating_ip(self, context, address,
+ affect_auto_assigned=False):
floating_ip = self.db.floating_ip_get_by_address(context, address)
+ if not affect_auto_assigned and floating_ip.get('auto_assigned'):
+ return
if not floating_ip.get('fixed_ip'):
raise exception.ApiError('Address is not associated.')
# NOTE(vish): Get the topic from the host name of the network of
diff --git a/nova/network/linux_net.py b/nova/network/linux_net.py
index ec5579dee..b50a4b4ea 100644
--- a/nova/network/linux_net.py
+++ b/nova/network/linux_net.py
@@ -56,13 +56,12 @@ flags.DEFINE_string('input_chain', 'INPUT',
'chain to add nova_input to')
flags.DEFINE_integer('dhcp_lease_time', 120,
'Lifetime of a DHCP lease')
-
flags.DEFINE_string('dns_server', None,
'if set, uses specific dns server for dnsmasq')
flags.DEFINE_string('dmz_cidr', '10.128.0.0/24',
'dmz range that should be accepted')
-
-
+flags.DEFINE_string('dnsmasq_config_file', "",
+ 'Override the default dnsmasq settings with this file')
binary_name = os.path.basename(inspect.stack()[-1][1])
@@ -407,6 +406,10 @@ def ensure_vlan_forward(public_ip, port, private_ip):
"-d %s -p udp "
"--dport %s -j DNAT --to %s:1194" %
(public_ip, port, private_ip))
+ iptables_manager.ipv4['nat'].add_rule("OUTPUT",
+ "-d %s -p udp "
+ "--dport %s -j DNAT --to %s:1194" %
+ (public_ip, port, private_ip))
iptables_manager.apply()
@@ -678,7 +681,7 @@ def _dnsmasq_cmd(net):
cmd = ['sudo', '-E', 'dnsmasq',
'--strict-order',
'--bind-interfaces',
- '--conf-file=',
+ '--conf-file=%s' % FLAGS.dnsmasq_config_file,
'--domain=%s' % FLAGS.dhcp_domain,
'--pid-file=%s' % _dhcp_file(net['bridge'], 'pid'),
'--listen-address=%s' % net['gateway'],
diff --git a/nova/network/vmwareapi_net.py b/nova/network/vmwareapi_net.py
index 93e6584f0..9b2db7b8f 100644
--- a/nova/network/vmwareapi_net.py
+++ b/nova/network/vmwareapi_net.py
@@ -52,16 +52,13 @@ def ensure_vlan_bridge(vlan_num, bridge, net_attrs=None):
# Check if the vlan_interface physical network adapter exists on the host
if not network_utils.check_if_vlan_interface_exists(session,
vlan_interface):
- raise exception.NotFound(_("There is no physical network adapter with "
- "the name %s on the ESX host") % vlan_interface)
+ raise exception.NetworkAdapterNotFound(adapter=vlan_interface)
# Get the vSwitch associated with the Physical Adapter
vswitch_associated = network_utils.get_vswitch_for_vlan_interface(
session, vlan_interface)
if vswitch_associated is None:
- raise exception.NotFound(_("There is no virtual switch associated "
- "with the physical network adapter with name %s") %
- vlan_interface)
+ raise exception.SwicthNotFoundForNetworkAdapter(adapter=vlan_interface)
# Check whether bridge already exists and retrieve the the ref of the
# network whose name_label is "bridge"
network_ref = network_utils.get_network_with_the_name(session, bridge)
@@ -75,17 +72,13 @@ def ensure_vlan_bridge(vlan_num, bridge, net_attrs=None):
pg_vlanid, pg_vswitch = \
network_utils.get_vlanid_and_vswitch_for_portgroup(session, bridge)
- # Check if the vsiwtch associated is proper
+ # Check if the vswitch associated is proper
if pg_vswitch != vswitch_associated:
- raise exception.Invalid(_("vSwitch which contains the port group "
- "%(bridge)s is not associated with the desired "
- "physical adapter. Expected vSwitch is "
- "%(vswitch_associated)s, but the one associated"
- " is %(pg_vswitch)s") % locals())
+ raise exception.InvalidVLANPortGroup(bridge=bridge,
+ expected=vswitch_associated,
+ actual=pg_vswitch)
# Check if the vlan id is proper for the port group
if pg_vlanid != vlan_num:
- raise exception.Invalid(_("VLAN tag is not appropriate for the "
- "port group %(bridge)s. Expected VLAN tag is "
- "%(vlan_num)s, but the one associated with the "
- "port group is %(pg_vlanid)s") % locals())
+ raise exception.InvalidVLANTag(bridge=bridge, tag=vlan_num,
+ pgroup=pg_vlanid)
diff --git a/nova/quota.py b/nova/quota.py
index 2b24c0b5b..d8b5d9a93 100644
--- a/nova/quota.py
+++ b/nova/quota.py
@@ -15,16 +15,15 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
-"""
-Quotas for instances, volumes, and floating ips
-"""
+
+"""Quotas for instances, volumes, and floating ips."""
from nova import db
from nova import exception
from nova import flags
-FLAGS = flags.FLAGS
+FLAGS = flags.FLAGS
flags.DEFINE_integer('quota_instances', 10,
'number of instances allowed per project')
flags.DEFINE_integer('quota_cores', 20,
@@ -64,7 +63,7 @@ def get_quota(context, project_id):
def allowed_instances(context, num_instances, instance_type):
- """Check quota and return min(num_instances, allowed_instances)"""
+ """Check quota and return min(num_instances, allowed_instances)."""
project_id = context.project_id
context = context.elevated()
used_instances, used_cores = db.instance_data_get_for_project(context,
@@ -79,7 +78,7 @@ def allowed_instances(context, num_instances, instance_type):
def allowed_volumes(context, num_volumes, size):
- """Check quota and return min(num_volumes, allowed_volumes)"""
+ """Check quota and return min(num_volumes, allowed_volumes)."""
project_id = context.project_id
context = context.elevated()
used_volumes, used_gigabytes = db.volume_data_get_for_project(context,
@@ -95,7 +94,7 @@ def allowed_volumes(context, num_volumes, size):
def allowed_floating_ips(context, num_floating_ips):
- """Check quota and return min(num_floating_ips, allowed_floating_ips)"""
+ """Check quota and return min(num_floating_ips, allowed_floating_ips)."""
project_id = context.project_id
context = context.elevated()
used_floating_ips = db.floating_ip_count_by_project(context, project_id)
@@ -105,7 +104,7 @@ def allowed_floating_ips(context, num_floating_ips):
def allowed_metadata_items(context, num_metadata_items):
- """Check quota; return min(num_metadata_items,allowed_metadata_items)"""
+ """Check quota; return min(num_metadata_items,allowed_metadata_items)."""
project_id = context.project_id
context = context.elevated()
quota = get_quota(context, project_id)
@@ -114,20 +113,20 @@ def allowed_metadata_items(context, num_metadata_items):
def allowed_injected_files(context):
- """Return the number of injected files allowed"""
+ """Return the number of injected files allowed."""
return FLAGS.quota_max_injected_files
def allowed_injected_file_content_bytes(context):
- """Return the number of bytes allowed per injected file content"""
+ """Return the number of bytes allowed per injected file content."""
return FLAGS.quota_max_injected_file_content_bytes
def allowed_injected_file_path_bytes(context):
- """Return the number of bytes allowed in an injected file path"""
+ """Return the number of bytes allowed in an injected file path."""
return FLAGS.quota_max_injected_file_path_bytes
class QuotaError(exception.ApiError):
- """Quota Exceeeded"""
+ """Quota Exceeeded."""
pass
diff --git a/nova/rpc.py b/nova/rpc.py
index b610cdf9b..2116f22c3 100644
--- a/nova/rpc.py
+++ b/nova/rpc.py
@@ -16,9 +16,12 @@
# License for the specific language governing permissions and limitations
# under the License.
-"""
-AMQP-based RPC. Queues have consumers and publishers.
+"""AMQP-based RPC.
+
+Queues have consumers and publishers.
+
No fan-out support yet.
+
"""
import json
@@ -40,17 +43,19 @@ from nova import log as logging
from nova import utils
-FLAGS = flags.FLAGS
LOG = logging.getLogger('nova.rpc')
+
+FLAGS = flags.FLAGS
flags.DEFINE_integer('rpc_thread_pool_size', 1024, 'Size of RPC thread pool')
class Connection(carrot_connection.BrokerConnection):
- """Connection instance object"""
+ """Connection instance object."""
+
@classmethod
def instance(cls, new=True):
- """Returns the instance"""
+ """Returns the instance."""
if new or not hasattr(cls, '_instance'):
params = dict(hostname=FLAGS.rabbit_host,
port=FLAGS.rabbit_port,
@@ -71,9 +76,11 @@ class Connection(carrot_connection.BrokerConnection):
@classmethod
def recreate(cls):
- """Recreates the connection instance
+ """Recreates the connection instance.
+
+ This is necessary to recover from some network errors/disconnects.
- This is necessary to recover from some network errors/disconnects"""
+ """
try:
del cls._instance
except AttributeError, e:
@@ -84,10 +91,12 @@ class Connection(carrot_connection.BrokerConnection):
class Consumer(messaging.Consumer):
- """Consumer base class
+ """Consumer base class.
+
+ Contains methods for connecting the fetch method to async loops.
- Contains methods for connecting the fetch method to async loops
"""
+
def __init__(self, *args, **kwargs):
for i in xrange(FLAGS.rabbit_max_retries):
if i > 0:
@@ -100,19 +109,18 @@ class Consumer(messaging.Consumer):
fl_host = FLAGS.rabbit_host
fl_port = FLAGS.rabbit_port
fl_intv = FLAGS.rabbit_retry_interval
- LOG.error(_("AMQP server on %(fl_host)s:%(fl_port)d is"
- " unreachable: %(e)s. Trying again in %(fl_intv)d"
- " seconds.")
- % locals())
+ LOG.error(_('AMQP server on %(fl_host)s:%(fl_port)d is'
+ ' unreachable: %(e)s. Trying again in %(fl_intv)d'
+ ' seconds.') % locals())
self.failed_connection = True
if self.failed_connection:
- LOG.error(_("Unable to connect to AMQP server "
- "after %d tries. Shutting down."),
+ LOG.error(_('Unable to connect to AMQP server '
+ 'after %d tries. Shutting down.'),
FLAGS.rabbit_max_retries)
sys.exit(1)
def fetch(self, no_ack=None, auto_ack=None, enable_callbacks=False):
- """Wraps the parent fetch with some logic for failed connections"""
+ """Wraps the parent fetch with some logic for failed connection."""
# TODO(vish): the logic for failed connections and logging should be
# refactored into some sort of connection manager object
try:
@@ -125,14 +133,14 @@ class Consumer(messaging.Consumer):
self.declare()
super(Consumer, self).fetch(no_ack, auto_ack, enable_callbacks)
if self.failed_connection:
- LOG.error(_("Reconnected to queue"))
+ LOG.error(_('Reconnected to queue'))
self.failed_connection = False
# NOTE(vish): This is catching all errors because we really don't
# want exceptions to be logged 10 times a second if some
# persistent failure occurs.
except Exception, e: # pylint: disable=W0703
if not self.failed_connection:
- LOG.exception(_("Failed to fetch message from queue: %s" % e))
+ LOG.exception(_('Failed to fetch message from queue: %s' % e))
self.failed_connection = True
def attach_to_eventlet(self):
@@ -143,8 +151,9 @@ class Consumer(messaging.Consumer):
class AdapterConsumer(Consumer):
- """Calls methods on a proxy object based on method and args"""
- def __init__(self, connection=None, topic="broadcast", proxy=None):
+ """Calls methods on a proxy object based on method and args."""
+
+ 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)
@@ -156,13 +165,14 @@ class AdapterConsumer(Consumer):
@exception.wrap_exception
def _receive(self, message_data, message):
- """Magically looks for a method on the proxy object and calls it
+ """Magically looks for a method on the proxy object and calls it.
Message data should be a dictionary with two keys:
method: string representing the method to call
args: dictionary of arg: value
Example: {'method': 'echo', 'args': {'value': 42}}
+
"""
LOG.debug(_('received %s') % message_data)
msg_id = message_data.pop('_msg_id', None)
@@ -189,22 +199,23 @@ class AdapterConsumer(Consumer):
if msg_id:
msg_reply(msg_id, rval, None)
except Exception as e:
- logging.exception("Exception during message handling")
+ logging.exception('Exception during message handling')
if msg_id:
msg_reply(msg_id, None, sys.exc_info())
return
class Publisher(messaging.Publisher):
- """Publisher base class"""
+ """Publisher base class."""
pass
class TopicAdapterConsumer(AdapterConsumer):
- """Consumes messages on a specific topic"""
- exchange_type = "topic"
+ """Consumes messages on a specific topic."""
+
+ exchange_type = 'topic'
- def __init__(self, connection=None, topic="broadcast", proxy=None):
+ def __init__(self, connection=None, topic='broadcast', proxy=None):
self.queue = topic
self.routing_key = topic
self.exchange = FLAGS.control_exchange
@@ -214,27 +225,29 @@ class TopicAdapterConsumer(AdapterConsumer):
class FanoutAdapterConsumer(AdapterConsumer):
- """Consumes messages from a fanout exchange"""
- exchange_type = "fanout"
+ """Consumes messages from a fanout exchange."""
- def __init__(self, connection=None, topic="broadcast", proxy=None):
- self.exchange = "%s_fanout" % topic
+ exchange_type = 'fanout'
+
+ def __init__(self, connection=None, topic='broadcast', proxy=None):
+ self.exchange = '%s_fanout' % topic
self.routing_key = topic
unique = uuid.uuid4().hex
- self.queue = "%s_fanout_%s" % (topic, unique)
+ self.queue = '%s_fanout_%s' % (topic, unique)
self.durable = False
- LOG.info(_("Created '%(exchange)s' fanout exchange "
- "with '%(key)s' routing key"),
- dict(exchange=self.exchange, key=self.routing_key))
+ LOG.info(_('Created "%(exchange)s" fanout exchange '
+ 'with "%(key)s" routing key'),
+ dict(exchange=self.exchange, key=self.routing_key))
super(FanoutAdapterConsumer, self).__init__(connection=connection,
topic=topic, proxy=proxy)
class TopicPublisher(Publisher):
- """Publishes messages on a specific topic"""
- exchange_type = "topic"
+ """Publishes messages on a specific topic."""
+
+ exchange_type = 'topic'
- def __init__(self, connection=None, topic="broadcast"):
+ def __init__(self, connection=None, topic='broadcast'):
self.routing_key = topic
self.exchange = FLAGS.control_exchange
self.durable = False
@@ -243,20 +256,22 @@ class TopicPublisher(Publisher):
class FanoutPublisher(Publisher):
"""Publishes messages to a fanout exchange."""
- exchange_type = "fanout"
+
+ exchange_type = 'fanout'
def __init__(self, topic, connection=None):
- self.exchange = "%s_fanout" % topic
- self.queue = "%s_fanout" % topic
+ self.exchange = '%s_fanout' % topic
+ self.queue = '%s_fanout' % topic
self.durable = False
- LOG.info(_("Creating '%(exchange)s' fanout exchange"),
- dict(exchange=self.exchange))
+ LOG.info(_('Creating "%(exchange)s" fanout exchange'),
+ dict(exchange=self.exchange))
super(FanoutPublisher, self).__init__(connection=connection)
class DirectConsumer(Consumer):
- """Consumes messages directly on a channel specified by msg_id"""
- exchange_type = "direct"
+ """Consumes messages directly on a channel specified by msg_id."""
+
+ exchange_type = 'direct'
def __init__(self, connection=None, msg_id=None):
self.queue = msg_id
@@ -268,8 +283,9 @@ class DirectConsumer(Consumer):
class DirectPublisher(Publisher):
- """Publishes messages directly on a channel specified by msg_id"""
- exchange_type = "direct"
+ """Publishes messages directly on a channel specified by msg_id."""
+
+ exchange_type = 'direct'
def __init__(self, connection=None, msg_id=None):
self.routing_key = msg_id
@@ -279,9 +295,9 @@ class DirectPublisher(Publisher):
def msg_reply(msg_id, reply=None, failure=None):
- """Sends a reply or an error on the channel signified by msg_id
+ """Sends a reply or an error on the channel signified by msg_id.
- failure should be a sys.exc_info() tuple.
+ Failure should be a sys.exc_info() tuple.
"""
if failure:
@@ -303,17 +319,20 @@ def msg_reply(msg_id, reply=None, failure=None):
class RemoteError(exception.Error):
- """Signifies that a remote class has raised an exception
+ """Signifies that a remote class has raised an exception.
Containes a string representation of the type of the original exception,
the value of the original exception, and the traceback. These are
sent to the parent as a joined string so printing the exception
- contains all of the relevent info."""
+ contains all of the relevent info.
+
+ """
+
def __init__(self, exc_type, value, traceback):
self.exc_type = exc_type
self.value = value
self.traceback = traceback
- super(RemoteError, self).__init__("%s %s\n%s" % (exc_type,
+ super(RemoteError, self).__init__('%s %s\n%s' % (exc_type,
value,
traceback))
@@ -339,6 +358,7 @@ def _pack_context(msg, context):
context out into a bunch of separate keys. If we want to support
more arguments in rabbit messages, we may want to do the same
for args at some point.
+
"""
context = dict([('_context_%s' % key, value)
for (key, value) in context.to_dict().iteritems()])
@@ -346,11 +366,11 @@ def _pack_context(msg, context):
def call(context, topic, msg):
- """Sends a message on a topic and wait for a response"""
- LOG.debug(_("Making asynchronous call on %s ..."), topic)
+ """Sends a message on a topic and wait for a response."""
+ LOG.debug(_('Making asynchronous call on %s ...'), topic)
msg_id = uuid.uuid4().hex
msg.update({'_msg_id': msg_id})
- LOG.debug(_("MSG_ID is %s") % (msg_id))
+ LOG.debug(_('MSG_ID is %s') % (msg_id))
_pack_context(msg, context)
class WaitMessage(object):
@@ -387,8 +407,8 @@ def call(context, topic, msg):
def cast(context, topic, msg):
- """Sends a message on a topic without waiting for a response"""
- LOG.debug(_("Making asynchronous cast on %s..."), topic)
+ """Sends a message on a topic without waiting for a response."""
+ LOG.debug(_('Making asynchronous cast on %s...'), topic)
_pack_context(msg, context)
conn = Connection.instance()
publisher = TopicPublisher(connection=conn, topic=topic)
@@ -397,8 +417,8 @@ def cast(context, topic, msg):
def fanout_cast(context, topic, msg):
- """Sends a message on a fanout exchange without waiting for a response"""
- LOG.debug(_("Making asynchronous fanout cast..."))
+ """Sends a message on a fanout exchange without waiting for a response."""
+ LOG.debug(_('Making asynchronous fanout cast...'))
_pack_context(msg, context)
conn = Connection.instance()
publisher = FanoutPublisher(topic, connection=conn)
@@ -407,14 +427,14 @@ def fanout_cast(context, topic, msg):
def generic_response(message_data, message):
- """Logs a result and exits"""
+ """Logs a result and exits."""
LOG.debug(_('response %s'), message_data)
message.ack()
sys.exit(0)
def send_message(topic, message, wait=True):
- """Sends a message for testing"""
+ """Sends a message for testing."""
msg_id = uuid.uuid4().hex
message.update({'_msg_id': msg_id})
LOG.debug(_('topic is %s'), topic)
@@ -425,14 +445,14 @@ def send_message(topic, message, wait=True):
queue=msg_id,
exchange=msg_id,
auto_delete=True,
- exchange_type="direct",
+ exchange_type='direct',
routing_key=msg_id)
consumer.register_callback(generic_response)
publisher = messaging.Publisher(connection=Connection.instance(),
exchange=FLAGS.control_exchange,
durable=False,
- exchange_type="topic",
+ exchange_type='topic',
routing_key=topic)
publisher.send(message)
publisher.close()
@@ -441,8 +461,8 @@ def send_message(topic, message, wait=True):
consumer.wait()
-if __name__ == "__main__":
- # NOTE(vish): you can send messages from the command line using
- # topic and a json sting representing a dictionary
- # for the method
+if __name__ == '__main__':
+ # You can send messages from the command line using
+ # topic and a json string representing a dictionary
+ # for the method
send_message(sys.argv[1], json.loads(sys.argv[2]))
diff --git a/nova/scheduler/api.py b/nova/scheduler/api.py
index 6bb3bf3cd..816ae5513 100644
--- a/nova/scheduler/api.py
+++ b/nova/scheduler/api.py
@@ -76,11 +76,9 @@ def zone_update(context, zone_id, data):
return db.zone_update(context, zone_id, data)
-def get_zone_capabilities(context, service=None):
- """Returns a dict of key, value capabilities for this zone,
- or for a particular class of services running in this zone."""
- return _call_scheduler('get_zone_capabilities', context=context,
- params=dict(service=service))
+def get_zone_capabilities(context):
+ """Returns a dict of key, value capabilities for this zone."""
+ return _call_scheduler('get_zone_capabilities', context=context)
def update_service_capabilities(context, service_name, host, capabilities):
diff --git a/nova/scheduler/driver.py b/nova/scheduler/driver.py
index ce05d9f6a..2094e3565 100644
--- a/nova/scheduler/driver.py
+++ b/nova/scheduler/driver.py
@@ -129,15 +129,14 @@ class Scheduler(object):
if (power_state.RUNNING != instance_ref['state'] or \
'running' != instance_ref['state_description']):
ec2_id = instance_ref['hostname']
- raise exception.Invalid(_('Instance(%s) is not running') % ec2_id)
+ raise exception.InstanceNotRunning(instance_id=ec2_id)
# Checing volume node is running when any volumes are mounted
# to the instance.
if len(instance_ref['volumes']) != 0:
services = db.service_get_all_by_topic(context, 'volume')
if len(services) < 1 or not self.service_is_up(services[0]):
- raise exception.Invalid(_("volume node is not alive"
- "(time synchronize problem?)"))
+ raise exception.VolumeServiceUnavailable()
# Checking src host exists and compute node
src = instance_ref['host']
@@ -145,8 +144,7 @@ class Scheduler(object):
# Checking src host is alive.
if not self.service_is_up(services[0]):
- raise exception.Invalid(_("%s is not alive(time "
- "synchronize problem?)") % src)
+ raise exception.ComputeServiceUnavailable(host=src)
def _live_migration_dest_check(self, context, instance_ref, dest):
"""Live migration check routine (for destination host).
@@ -163,17 +161,15 @@ class Scheduler(object):
# Checking dest host is alive.
if not self.service_is_up(dservice_ref):
- raise exception.Invalid(_("%s is not alive(time "
- "synchronize problem?)") % dest)
+ raise exception.ComputeServiceUnavailable(host=dest)
# Checking whether The host where instance is running
# and dest is not same.
src = instance_ref['host']
if dest == src:
ec2_id = instance_ref['hostname']
- raise exception.Invalid(_("%(dest)s is where %(ec2_id)s is "
- "running now. choose other host.")
- % locals())
+ raise exception.UnableToMigrateToSelf(instance_id=ec2_id,
+ host=dest)
# Checking dst host still has enough capacities.
self.assert_compute_node_has_enough_resources(context,
@@ -204,26 +200,20 @@ class Scheduler(object):
oservice_refs = db.service_get_all_compute_by_host(context,
instance_ref['launched_on'])
except exception.NotFound:
- raise exception.Invalid(_("host %s where instance was launched "
- "does not exist.")
- % instance_ref['launched_on'])
+ raise exception.SourceHostUnavailable()
oservice_ref = oservice_refs[0]['compute_node'][0]
# Checking hypervisor is same.
orig_hypervisor = oservice_ref['hypervisor_type']
dest_hypervisor = dservice_ref['hypervisor_type']
if orig_hypervisor != dest_hypervisor:
- raise exception.Invalid(_("Different hypervisor type"
- "(%(orig_hypervisor)s->"
- "%(dest_hypervisor)s)')" % locals()))
+ raise exception.InvalidHypervisorType()
# Checkng hypervisor version.
orig_hypervisor = oservice_ref['hypervisor_version']
dest_hypervisor = dservice_ref['hypervisor_version']
if orig_hypervisor > dest_hypervisor:
- raise exception.Invalid(_("Older hypervisor version"
- "(%(orig_hypervisor)s->"
- "%(dest_hypervisor)s)") % locals())
+ raise exception.DestinationHypervisorTooOld()
# Checking cpuinfo.
try:
@@ -265,11 +255,9 @@ class Scheduler(object):
mem_avail = mem_total - mem_used
mem_inst = instance_ref['memory_mb']
if mem_avail <= mem_inst:
- raise exception.NotEmpty(_("Unable to migrate %(ec2_id)s "
- "to destination: %(dest)s "
- "(host:%(mem_avail)s "
- "<= instance:%(mem_inst)s)")
- % locals())
+ reason = _("Unable to migrate %(ec2_id)s to destination: %(dest)s "
+ "(host:%(mem_avail)s <= instance:%(mem_inst)s)")
+ raise exception.MigrationError(reason=reason % locals())
def mounted_on_same_shared_storage(self, context, instance_ref, dest):
"""Check if the src and dest host mount same shared storage.
diff --git a/nova/scheduler/host_filter.py b/nova/scheduler/host_filter.py
new file mode 100644
index 000000000..483f3225c
--- /dev/null
+++ b/nova/scheduler/host_filter.py
@@ -0,0 +1,288 @@
+# Copyright (c) 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.
+
+"""
+Host Filter is a driver mechanism for requesting instance resources.
+Three drivers are included: AllHosts, Flavor & JSON. AllHosts just
+returns the full, unfiltered list of hosts. Flavor is a hard coded
+matching mechanism based on flavor criteria and JSON is an ad-hoc
+filter grammar.
+
+Why JSON? The requests for instances may come in through the
+REST interface from a user or a parent Zone.
+Currently Flavors and/or InstanceTypes are used for
+specifing the type of instance desired. Specific Nova users have
+noted a need for a more expressive way of specifying instances.
+Since we don't want to get into building full DSL this is a simple
+form as an example of how this could be done. In reality, most
+consumers will use the more rigid filters such as FlavorFilter.
+
+Note: These are "required" capability filters. These capabilities
+used must be present or the host will be excluded. The hosts
+returned are then weighed by the Weighted Scheduler. Weights
+can take the more esoteric factors into consideration (such as
+server affinity and customer separation).
+"""
+
+import json
+
+from nova import exception
+from nova import flags
+from nova import log as logging
+from nova import utils
+
+LOG = logging.getLogger('nova.scheduler.host_filter')
+
+FLAGS = flags.FLAGS
+flags.DEFINE_string('default_host_filter_driver',
+ 'nova.scheduler.host_filter.AllHostsFilter',
+ 'Which driver to use for filtering hosts.')
+
+
+class HostFilter(object):
+ """Base class for host filter drivers."""
+
+ def instance_type_to_filter(self, instance_type):
+ """Convert instance_type into a filter for most common use-case."""
+ raise NotImplementedError()
+
+ def filter_hosts(self, zone_manager, query):
+ """Return a list of hosts that fulfill the filter."""
+ raise NotImplementedError()
+
+ def _full_name(self):
+ """module.classname of the filter driver"""
+ return "%s.%s" % (self.__module__, self.__class__.__name__)
+
+
+class AllHostsFilter(HostFilter):
+ """NOP host filter driver. Returns all hosts in ZoneManager.
+ This essentially does what the old Scheduler+Chance used
+ to give us."""
+
+ def instance_type_to_filter(self, instance_type):
+ """Return anything to prevent base-class from raising
+ exception."""
+ return (self._full_name(), instance_type)
+
+ def filter_hosts(self, zone_manager, query):
+ """Return a list of hosts from ZoneManager list."""
+ return [(host, services)
+ for host, services in zone_manager.service_states.iteritems()]
+
+
+class FlavorFilter(HostFilter):
+ """HostFilter driver hard-coded to work with flavors."""
+
+ def instance_type_to_filter(self, instance_type):
+ """Use instance_type to filter hosts."""
+ return (self._full_name(), instance_type)
+
+ def filter_hosts(self, zone_manager, query):
+ """Return a list of hosts that can create instance_type."""
+ instance_type = query
+ selected_hosts = []
+ for host, services in zone_manager.service_states.iteritems():
+ capabilities = services.get('compute', {})
+ host_ram_mb = capabilities['host_memory_free']
+ disk_bytes = capabilities['disk_available']
+ if host_ram_mb >= instance_type['memory_mb'] and \
+ disk_bytes >= instance_type['local_gb']:
+ selected_hosts.append((host, capabilities))
+ return selected_hosts
+
+#host entries (currently) are like:
+# {'host_name-description': 'Default install of XenServer',
+# 'host_hostname': 'xs-mini',
+# 'host_memory_total': 8244539392,
+# 'host_memory_overhead': 184225792,
+# 'host_memory_free': 3868327936,
+# 'host_memory_free_computed': 3840843776},
+# 'host_other-config': {},
+# 'host_ip_address': '192.168.1.109',
+# 'host_cpu_info': {},
+# 'disk_available': 32954957824,
+# 'disk_total': 50394562560,
+# 'disk_used': 17439604736},
+# 'host_uuid': 'cedb9b39-9388-41df-8891-c5c9a0c0fe5f',
+# 'host_name-label': 'xs-mini'}
+
+# instance_type table has:
+#name = Column(String(255), unique=True)
+#memory_mb = Column(Integer)
+#vcpus = Column(Integer)
+#local_gb = Column(Integer)
+#flavorid = Column(Integer, unique=True)
+#swap = Column(Integer, nullable=False, default=0)
+#rxtx_quota = Column(Integer, nullable=False, default=0)
+#rxtx_cap = Column(Integer, nullable=False, default=0)
+
+
+class JsonFilter(HostFilter):
+ """Host Filter driver to allow simple JSON-based grammar for
+ selecting hosts."""
+
+ def _equals(self, args):
+ """First term is == all the other terms."""
+ if len(args) < 2:
+ return False
+ lhs = args[0]
+ for rhs in args[1:]:
+ if lhs != rhs:
+ return False
+ return True
+
+ def _less_than(self, args):
+ """First term is < all the other terms."""
+ if len(args) < 2:
+ return False
+ lhs = args[0]
+ for rhs in args[1:]:
+ if lhs >= rhs:
+ return False
+ return True
+
+ def _greater_than(self, args):
+ """First term is > all the other terms."""
+ if len(args) < 2:
+ return False
+ lhs = args[0]
+ for rhs in args[1:]:
+ if lhs <= rhs:
+ return False
+ return True
+
+ def _in(self, args):
+ """First term is in set of remaining terms"""
+ if len(args) < 2:
+ return False
+ return args[0] in args[1:]
+
+ def _less_than_equal(self, args):
+ """First term is <= all the other terms."""
+ if len(args) < 2:
+ return False
+ lhs = args[0]
+ for rhs in args[1:]:
+ if lhs > rhs:
+ return False
+ return True
+
+ def _greater_than_equal(self, args):
+ """First term is >= all the other terms."""
+ if len(args) < 2:
+ return False
+ lhs = args[0]
+ for rhs in args[1:]:
+ if lhs < rhs:
+ return False
+ return True
+
+ def _not(self, args):
+ """Flip each of the arguments."""
+ if len(args) == 0:
+ return False
+ return [not arg for arg in args]
+
+ def _or(self, args):
+ """True if any arg is True."""
+ return True in args
+
+ def _and(self, args):
+ """True if all args are True."""
+ return False not in args
+
+ commands = {
+ '=': _equals,
+ '<': _less_than,
+ '>': _greater_than,
+ 'in': _in,
+ '<=': _less_than_equal,
+ '>=': _greater_than_equal,
+ 'not': _not,
+ 'or': _or,
+ 'and': _and,
+ }
+
+ def instance_type_to_filter(self, instance_type):
+ """Convert instance_type into JSON filter object."""
+ required_ram = instance_type['memory_mb']
+ required_disk = instance_type['local_gb']
+ query = ['and',
+ ['>=', '$compute.host_memory_free', required_ram],
+ ['>=', '$compute.disk_available', required_disk]
+ ]
+ return (self._full_name(), json.dumps(query))
+
+ def _parse_string(self, string, host, services):
+ """Strings prefixed with $ are capability lookups in the
+ form '$service.capability[.subcap*]'"""
+ if not string:
+ return None
+ if string[0] != '$':
+ return string
+
+ path = string[1:].split('.')
+ for item in path:
+ services = services.get(item, None)
+ if not services:
+ return None
+ return services
+
+ def _process_filter(self, zone_manager, query, host, services):
+ """Recursively parse the query structure."""
+ if len(query) == 0:
+ return True
+ cmd = query[0]
+ method = self.commands[cmd] # Let exception fly.
+ cooked_args = []
+ for arg in query[1:]:
+ if isinstance(arg, list):
+ arg = self._process_filter(zone_manager, arg, host, services)
+ elif isinstance(arg, basestring):
+ arg = self._parse_string(arg, host, services)
+ if arg != None:
+ cooked_args.append(arg)
+ result = method(self, cooked_args)
+ return result
+
+ def filter_hosts(self, zone_manager, query):
+ """Return a list of hosts that can fulfill filter."""
+ expanded = json.loads(query)
+ hosts = []
+ for host, services in zone_manager.service_states.iteritems():
+ r = self._process_filter(zone_manager, expanded, host, services)
+ if isinstance(r, list):
+ r = True in r
+ if r:
+ hosts.append((host, services))
+ return hosts
+
+
+DRIVERS = [AllHostsFilter, FlavorFilter, JsonFilter]
+
+
+def choose_driver(driver_name=None):
+ """Since the caller may specify which driver to use we need
+ to have an authoritative list of what is permissible. This
+ function checks the driver name against a predefined set
+ of acceptable drivers."""
+
+ if not driver_name:
+ driver_name = FLAGS.default_host_filter_driver
+ for driver in DRIVERS:
+ if "%s.%s" % (driver.__module__, driver.__name__) == driver_name:
+ return driver()
+ raise exception.SchedulerHostFilterDriverNotFound(driver_name=driver_name)
diff --git a/nova/scheduler/manager.py b/nova/scheduler/manager.py
index 7d62cfc4e..55cd7208b 100644
--- a/nova/scheduler/manager.py
+++ b/nova/scheduler/manager.py
@@ -60,10 +60,9 @@ class SchedulerManager(manager.Manager):
"""Get a list of zones from the ZoneManager."""
return self.zone_manager.get_zone_list()
- def get_zone_capabilities(self, context=None, service=None):
- """Get the normalized set of capabilites for this zone,
- or for a particular service."""
- return self.zone_manager.get_zone_capabilities(context, service)
+ def get_zone_capabilities(self, context=None):
+ """Get the normalized set of capabilites for this zone."""
+ return self.zone_manager.get_zone_capabilities(context)
def update_service_capabilities(self, context=None, service_name=None,
host=None, capabilities={}):
diff --git a/nova/scheduler/zone_manager.py b/nova/scheduler/zone_manager.py
index 198f9d4cc..3ddf6f3c3 100644
--- a/nova/scheduler/zone_manager.py
+++ b/nova/scheduler/zone_manager.py
@@ -106,28 +106,26 @@ class ZoneManager(object):
def __init__(self):
self.last_zone_db_check = datetime.min
self.zone_states = {} # { <zone_id> : ZoneState }
- self.service_states = {} # { <service> : { <host> : { cap k : v }}}
+ self.service_states = {} # { <host> : { <service> : { cap k : v }}}
self.green_pool = greenpool.GreenPool()
def get_zone_list(self):
"""Return the list of zones we know about."""
return [zone.to_dict() for zone in self.zone_states.values()]
- def get_zone_capabilities(self, context, service=None):
+ def get_zone_capabilities(self, context):
"""Roll up all the individual host info to generic 'service'
capabilities. Each capability is aggregated into
<cap>_min and <cap>_max values."""
- service_dict = self.service_states
- if service:
- service_dict = {service: self.service_states.get(service, {})}
+ hosts_dict = self.service_states
# TODO(sandy) - be smarter about fabricating this structure.
# But it's likely to change once we understand what the Best-Match
# code will need better.
combined = {} # { <service>_<cap> : (min, max), ... }
- for service_name, host_dict in service_dict.iteritems():
- for host, caps_dict in host_dict.iteritems():
- for cap, value in caps_dict.iteritems():
+ for host, host_dict in hosts_dict.iteritems():
+ for service_name, service_dict in host_dict.iteritems():
+ for cap, value in service_dict.iteritems():
key = "%s_%s" % (service_name, cap)
min_value, max_value = combined.get(key, (value, value))
min_value = min(min_value, value)
@@ -171,6 +169,6 @@ class ZoneManager(object):
"""Update the per-service capabilities based on this notification."""
logging.debug(_("Received %(service_name)s service update from "
"%(host)s: %(capabilities)s") % locals())
- service_caps = self.service_states.get(service_name, {})
- service_caps[host] = capabilities
- self.service_states[service_name] = service_caps
+ service_caps = self.service_states.get(host, {})
+ service_caps[service_name] = capabilities
+ self.service_states[host] = service_caps
diff --git a/nova/service.py b/nova/service.py
index 47c0b96c0..2532b9df2 100644
--- a/nova/service.py
+++ b/nova/service.py
@@ -17,9 +17,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-"""
-Generic Node baseclass for all workers that run on hosts
-"""
+"""Generic Node baseclass for all workers that run on hosts."""
import inspect
import os
@@ -30,13 +28,11 @@ from eventlet import event
from eventlet import greenthread
from eventlet import greenpool
-from sqlalchemy.exc import OperationalError
-
from nova import context
from nova import db
from nova import exception
-from nova import log as logging
from nova import flags
+from nova import log as logging
from nova import rpc
from nova import utils
from nova import version
@@ -79,7 +75,7 @@ class Service(object):
def start(self):
vcs_string = version.version_string_with_vcs()
- logging.audit(_("Starting %(topic)s node (version %(vcs_string)s)"),
+ logging.audit(_('Starting %(topic)s node (version %(vcs_string)s)'),
{'topic': self.topic, 'vcs_string': vcs_string})
self.manager.init_host()
self.model_disconnected = False
@@ -140,29 +136,24 @@ class Service(object):
return getattr(manager, key)
@classmethod
- def create(cls,
- host=None,
- binary=None,
- topic=None,
- manager=None,
- report_interval=None,
- periodic_interval=None):
+ def create(cls, host=None, binary=None, topic=None, manager=None,
+ report_interval=None, periodic_interval=None):
"""Instantiates class and passes back application object.
- Args:
- host, defaults to FLAGS.host
- binary, defaults to basename of executable
- topic, defaults to bin_name - "nova-" part
- manager, defaults to FLAGS.<topic>_manager
- report_interval, defaults to FLAGS.report_interval
- periodic_interval, defaults to FLAGS.periodic_interval
+ :param host: defaults to FLAGS.host
+ :param binary: defaults to basename of executable
+ :param topic: defaults to bin_name - 'nova-' part
+ :param manager: defaults to FLAGS.<topic>_manager
+ :param report_interval: defaults to FLAGS.report_interval
+ :param periodic_interval: defaults to FLAGS.periodic_interval
+
"""
if not host:
host = FLAGS.host
if not binary:
binary = os.path.basename(inspect.stack()[-1][1])
if not topic:
- topic = binary.rpartition("nova-")[2]
+ topic = binary.rpartition('nova-')[2]
if not manager:
manager = FLAGS.get('%s_manager' % topic, None)
if not report_interval:
@@ -175,12 +166,12 @@ class Service(object):
return service_obj
def kill(self):
- """Destroy the service object in the datastore"""
+ """Destroy the service object in the datastore."""
self.stop()
try:
db.service_destroy(context.get_admin_context(), self.service_id)
except exception.NotFound:
- logging.warn(_("Service killed that has no database entry"))
+ logging.warn(_('Service killed that has no database entry'))
def stop(self):
for x in self.timers:
@@ -198,7 +189,7 @@ class Service(object):
pass
def periodic_tasks(self):
- """Tasks to be run at a periodic interval"""
+ """Tasks to be run at a periodic interval."""
self.manager.periodic_tasks(context.get_admin_context())
def report_state(self):
@@ -208,8 +199,8 @@ class Service(object):
try:
service_ref = db.service_get(ctxt, self.service_id)
except exception.NotFound:
- logging.debug(_("The service database object disappeared, "
- "Recreating it."))
+ logging.debug(_('The service database object disappeared, '
+ 'Recreating it.'))
self._create_service_ref(ctxt)
service_ref = db.service_get(ctxt, self.service_id)
@@ -218,23 +209,24 @@ class Service(object):
{'report_count': service_ref['report_count'] + 1})
# TODO(termie): make this pattern be more elegant.
- if getattr(self, "model_disconnected", False):
+ if getattr(self, 'model_disconnected', False):
self.model_disconnected = False
- logging.error(_("Recovered model server connection!"))
+ logging.error(_('Recovered model server connection!'))
# TODO(vish): this should probably only catch connection errors
except Exception: # pylint: disable=W0702
- if not getattr(self, "model_disconnected", False):
+ if not getattr(self, 'model_disconnected', False):
self.model_disconnected = True
- logging.exception(_("model server went away"))
+ logging.exception(_('model server went away'))
class WsgiService(object):
"""Base class for WSGI based services.
For each api you define, you must also define these flags:
- :<api>_listen: The address on which to listen
- :<api>_listen_port: The port on which to listen
+ :<api>_listen: The address on which to listen
+ :<api>_listen_port: The port on which to listen
+
"""
def __init__(self, conf, apis):
@@ -250,13 +242,14 @@ class WsgiService(object):
class ApiService(WsgiService):
- """Class for our nova-api service"""
+ """Class for our nova-api service."""
+
@classmethod
def create(cls, conf=None):
if not conf:
conf = wsgi.paste_config_file(FLAGS.api_paste_config)
if not conf:
- message = (_("No paste configuration found for: %s"),
+ message = (_('No paste configuration found for: %s'),
FLAGS.api_paste_config)
raise exception.Error(message)
api_endpoints = ['ec2', 'osapi']
@@ -280,11 +273,11 @@ def serve(*services):
FLAGS.ParseNewFlags()
name = '_'.join(x.binary for x in services)
- logging.debug(_("Serving %s"), name)
- logging.debug(_("Full set of FLAGS:"))
+ logging.debug(_('Serving %s'), name)
+ logging.debug(_('Full set of FLAGS:'))
for flag in FLAGS:
flag_get = FLAGS.get(flag, None)
- logging.debug("%(flag)s : %(flag_get)s" % locals())
+ logging.debug('%(flag)s : %(flag_get)s' % locals())
for x in services:
x.start()
@@ -315,20 +308,20 @@ def serve_wsgi(cls, conf=None):
def _run_wsgi(paste_config_file, apis):
- logging.debug(_("Using paste.deploy config at: %s"), paste_config_file)
+ logging.debug(_('Using paste.deploy config at: %s'), paste_config_file)
apps = []
for api in apis:
config = wsgi.load_paste_configuration(paste_config_file, api)
if config is None:
- logging.debug(_("No paste configuration for app: %s"), api)
+ logging.debug(_('No paste configuration for app: %s'), api)
continue
- logging.debug(_("App Config: %(api)s\n%(config)r") % locals())
- logging.info(_("Running %s API"), api)
+ logging.debug(_('App Config: %(api)s\n%(config)r') % locals())
+ logging.info(_('Running %s API'), api)
app = wsgi.load_paste_app(paste_config_file, api)
- apps.append((app, getattr(FLAGS, "%s_listen_port" % api),
- getattr(FLAGS, "%s_listen" % api)))
+ apps.append((app, getattr(FLAGS, '%s_listen_port' % api),
+ getattr(FLAGS, '%s_listen' % api)))
if len(apps) == 0:
- logging.error(_("No known API applications configured in %s."),
+ logging.error(_('No known API applications configured in %s.'),
paste_config_file)
return
diff --git a/nova/test.py b/nova/test.py
index 3b608520a..4deb2a175 100644
--- a/nova/test.py
+++ b/nova/test.py
@@ -16,12 +16,12 @@
# License for the specific language governing permissions and limitations
# under the License.
-"""
-Base classes for our unit tests.
-Allows overriding of flags for use of fakes,
-and some black magic for inline callbacks.
-"""
+"""Base classes for our unit tests.
+Allows overriding of flags for use of fakes, and some black magic for
+inline callbacks.
+
+"""
import datetime
import functools
@@ -52,9 +52,9 @@ flags.DEFINE_bool('fake_tests', True,
def skip_if_fake(func):
- """Decorator that skips a test if running in fake mode"""
+ """Decorator that skips a test if running in fake mode."""
def _skipper(*args, **kw):
- """Wrapped skipper function"""
+ """Wrapped skipper function."""
if FLAGS.fake_tests:
raise unittest.SkipTest('Test cannot be run in fake mode')
else:
@@ -63,9 +63,10 @@ def skip_if_fake(func):
class TestCase(unittest.TestCase):
- """Test case base class for all unit tests"""
+ """Test case base class for all unit tests."""
+
def setUp(self):
- """Run before each test method to initialize test environment"""
+ """Run before each test method to initialize test environment."""
super(TestCase, self).setUp()
# NOTE(vish): We need a better method for creating fixtures for tests
# now that we have some required db setup for the system
@@ -86,8 +87,7 @@ class TestCase(unittest.TestCase):
self._original_flags = FLAGS.FlagValuesDict()
def tearDown(self):
- """Runs after each test method to finalize/tear down test
- environment."""
+ """Runs after each test method to tear down test environment."""
try:
self.mox.UnsetStubs()
self.stubs.UnsetAll()
@@ -121,7 +121,7 @@ class TestCase(unittest.TestCase):
pass
def flags(self, **kw):
- """Override flag variables for a test"""
+ """Override flag variables for a test."""
for k, v in kw.iteritems():
if k in self.flag_overrides:
self.reset_flags()
@@ -131,7 +131,11 @@ class TestCase(unittest.TestCase):
setattr(FLAGS, k, v)
def reset_flags(self):
- """Resets all flag variables for the test. Runs after each test"""
+ """Resets all flag variables for the test.
+
+ Runs after each test.
+
+ """
FLAGS.Reset()
for k, v in self._original_flags.iteritems():
setattr(FLAGS, k, v)
@@ -158,7 +162,6 @@ class TestCase(unittest.TestCase):
def _monkey_patch_wsgi(self):
"""Allow us to kill servers spawned by wsgi.Server."""
- # TODO(termie): change these patterns to use functools
self.original_start = wsgi.Server.start
@functools.wraps(self.original_start)
@@ -189,12 +192,13 @@ class TestCase(unittest.TestCase):
If you don't care (or don't know) a given value, you can specify
the string DONTCARE as the value. This will cause that dict-item
to be skipped.
+
"""
def raise_assertion(msg):
d1str = str(d1)
d2str = str(d2)
- base_msg = ("Dictionaries do not match. %(msg)s d1: %(d1str)s "
- "d2: %(d2str)s" % locals())
+ base_msg = ('Dictionaries do not match. %(msg)s d1: %(d1str)s '
+ 'd2: %(d2str)s' % locals())
raise AssertionError(base_msg)
d1keys = set(d1.keys())
@@ -202,8 +206,8 @@ class TestCase(unittest.TestCase):
if d1keys != d2keys:
d1only = d1keys - d2keys
d2only = d2keys - d1keys
- raise_assertion("Keys in d1 and not d2: %(d1only)s. "
- "Keys in d2 and not d1: %(d2only)s" % locals())
+ raise_assertion('Keys in d1 and not d2: %(d1only)s. '
+ 'Keys in d2 and not d1: %(d2only)s' % locals())
for key in d1keys:
d1value = d1[key]
@@ -217,19 +221,19 @@ class TestCase(unittest.TestCase):
"d2['%(key)s']=%(d2value)s" % locals())
def assertDictListMatch(self, L1, L2):
- """Assert a list of dicts are equivalent"""
+ """Assert a list of dicts are equivalent."""
def raise_assertion(msg):
L1str = str(L1)
L2str = str(L2)
- base_msg = ("List of dictionaries do not match: %(msg)s "
- "L1: %(L1str)s L2: %(L2str)s" % locals())
+ base_msg = ('List of dictionaries do not match: %(msg)s '
+ 'L1: %(L1str)s L2: %(L2str)s' % locals())
raise AssertionError(base_msg)
L1count = len(L1)
L2count = len(L2)
if L1count != L2count:
- raise_assertion("Length mismatch: len(L1)=%(L1count)d != "
- "len(L2)=%(L2count)d" % locals())
+ raise_assertion('Length mismatch: len(L1)=%(L1count)d != '
+ 'len(L2)=%(L2count)d' % locals())
for d1, d2 in zip(L1, L2):
self.assertDictMatch(d1, d2)
diff --git a/nova/tests/api/openstack/test_flavors.py b/nova/tests/api/openstack/test_flavors.py
index 954d72adf..d1c62e454 100644
--- a/nova/tests/api/openstack/test_flavors.py
+++ b/nova/tests/api/openstack/test_flavors.py
@@ -47,8 +47,8 @@ def return_instance_types(context, num=2):
return instance_types
-def return_instance_type_not_found(context, flavorid):
- raise exception.NotFound()
+def return_instance_type_not_found(context, flavor_id):
+ raise exception.InstanceTypeNotFound(flavor_id=flavor_id)
class FlavorsTest(test.TestCase):
diff --git a/nova/tests/api/openstack/test_images.py b/nova/tests/api/openstack/test_images.py
index ae86d0686..2c329f920 100644
--- a/nova/tests/api/openstack/test_images.py
+++ b/nova/tests/api/openstack/test_images.py
@@ -75,6 +75,18 @@ class _BaseImageServiceTests(test.TestCase):
self.context,
'bad image id')
+ def test_create_and_show_non_existing_image_by_name(self):
+ fixture = self._make_fixture('test image')
+ num_images = len(self.service.index(self.context))
+
+ image_id = self.service.create(self.context, fixture)['id']
+
+ self.assertNotEquals(None, image_id)
+ self.assertRaises(exception.ImageNotFound,
+ self.service.show_by_name,
+ self.context,
+ 'bad image id')
+
def test_update(self):
fixture = self._make_fixture('test image')
image_id = self.service.create(self.context, fixture)['id']
@@ -538,7 +550,8 @@ class ImageControllerWithGlanceServiceTest(test.TestCase):
},
{
'id': 127,
- 'name': 'killed backup', 'serverId': 42,
+ 'name': 'killed backup',
+ 'serverId': 42,
'updated': self.NOW_API_FORMAT,
'created': self.NOW_API_FORMAT,
'status': 'FAILED',
@@ -584,7 +597,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase):
{
'id': 124,
'name': 'queued backup',
- 'serverId': 42,
+ 'serverRef': "http://localhost/v1.1/servers/42",
'updated': self.NOW_API_FORMAT,
'created': self.NOW_API_FORMAT,
'status': 'QUEUED',
@@ -606,7 +619,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase):
{
'id': 125,
'name': 'saving backup',
- 'serverId': 42,
+ 'serverRef': "http://localhost/v1.1/servers/42",
'updated': self.NOW_API_FORMAT,
'created': self.NOW_API_FORMAT,
'status': 'SAVING',
@@ -629,7 +642,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase):
{
'id': 126,
'name': 'active backup',
- 'serverId': 42,
+ 'serverRef': "http://localhost/v1.1/servers/42",
'updated': self.NOW_API_FORMAT,
'created': self.NOW_API_FORMAT,
'status': 'ACTIVE',
@@ -650,7 +663,8 @@ class ImageControllerWithGlanceServiceTest(test.TestCase):
},
{
'id': 127,
- 'name': 'killed backup', 'serverId': 42,
+ 'name': 'killed backup',
+ 'serverRef': "http://localhost/v1.1/servers/42",
'updated': self.NOW_API_FORMAT,
'created': self.NOW_API_FORMAT,
'status': 'FAILED',
diff --git a/nova/tests/api/openstack/test_limits.py b/nova/tests/api/openstack/test_limits.py
index df367005d..45bd4d501 100644
--- a/nova/tests/api/openstack/test_limits.py
+++ b/nova/tests/api/openstack/test_limits.py
@@ -28,15 +28,14 @@ import webob
from xml.dom.minidom import parseString
from nova.api.openstack import limits
-from nova.api.openstack.limits import Limit
TEST_LIMITS = [
- Limit("GET", "/delayed", "^/delayed", 1, limits.PER_MINUTE),
- Limit("POST", "*", ".*", 7, limits.PER_MINUTE),
- Limit("POST", "/servers", "^/servers", 3, limits.PER_MINUTE),
- Limit("PUT", "*", "", 10, limits.PER_MINUTE),
- Limit("PUT", "/servers", "^/servers", 5, limits.PER_MINUTE),
+ limits.Limit("GET", "/delayed", "^/delayed", 1, limits.PER_MINUTE),
+ limits.Limit("POST", "*", ".*", 7, limits.PER_MINUTE),
+ limits.Limit("POST", "/servers", "^/servers", 3, limits.PER_MINUTE),
+ limits.Limit("PUT", "*", "", 10, limits.PER_MINUTE),
+ limits.Limit("PUT", "/servers", "^/servers", 5, limits.PER_MINUTE),
]
@@ -58,15 +57,15 @@ class BaseLimitTestSuite(unittest.TestCase):
return self.time
-class LimitsControllerTest(BaseLimitTestSuite):
+class LimitsControllerV10Test(BaseLimitTestSuite):
"""
- Tests for `limits.LimitsController` class.
+ Tests for `limits.LimitsControllerV10` class.
"""
def setUp(self):
"""Run before each test."""
BaseLimitTestSuite.setUp(self)
- self.controller = limits.LimitsController()
+ self.controller = limits.LimitsControllerV10()
def _get_index_request(self, accept_header="application/json"):
"""Helper to set routing arguments."""
@@ -81,8 +80,8 @@ class LimitsControllerTest(BaseLimitTestSuite):
def _populate_limits(self, request):
"""Put limit info into a request."""
_limits = [
- Limit("GET", "*", ".*", 10, 60).display(),
- Limit("POST", "*", ".*", 5, 60 * 60).display(),
+ limits.Limit("GET", "*", ".*", 10, 60).display(),
+ limits.Limit("POST", "*", ".*", 5, 60 * 60).display(),
]
request.environ["nova.limits"] = _limits
return request
@@ -171,6 +170,100 @@ class LimitsControllerTest(BaseLimitTestSuite):
self.assertEqual(expected.toxml(), body.toxml())
+class LimitsControllerV11Test(BaseLimitTestSuite):
+ """
+ Tests for `limits.LimitsControllerV11` class.
+ """
+
+ def setUp(self):
+ """Run before each test."""
+ BaseLimitTestSuite.setUp(self)
+ self.controller = limits.LimitsControllerV11()
+
+ def _get_index_request(self, accept_header="application/json"):
+ """Helper to set routing arguments."""
+ request = webob.Request.blank("/")
+ request.accept = accept_header
+ request.environ["wsgiorg.routing_args"] = (None, {
+ "action": "index",
+ "controller": "",
+ })
+ return request
+
+ def _populate_limits(self, request):
+ """Put limit info into a request."""
+ _limits = [
+ limits.Limit("GET", "*", ".*", 10, 60).display(),
+ limits.Limit("POST", "*", ".*", 5, 60 * 60).display(),
+ limits.Limit("GET", "changes-since*", "changes-since",
+ 5, 60).display(),
+ ]
+ request.environ["nova.limits"] = _limits
+ return request
+
+ def test_empty_index_json(self):
+ """Test getting empty limit details in JSON."""
+ request = self._get_index_request()
+ response = request.get_response(self.controller)
+ expected = {
+ "limits": {
+ "rate": [],
+ "absolute": {},
+ },
+ }
+ body = json.loads(response.body)
+ self.assertEqual(expected, body)
+
+ def test_index_json(self):
+ """Test getting limit details in JSON."""
+ request = self._get_index_request()
+ request = self._populate_limits(request)
+ response = request.get_response(self.controller)
+ expected = {
+ "limits": {
+ "rate": [
+ {
+ "regex": ".*",
+ "uri": "*",
+ "limit": [
+ {
+ "verb": "GET",
+ "next-available": 0,
+ "unit": "MINUTE",
+ "value": 10,
+ "remaining": 10,
+ },
+ {
+ "verb": "POST",
+ "next-available": 0,
+ "unit": "HOUR",
+ "value": 5,
+ "remaining": 5,
+ },
+ ],
+ },
+ {
+ "regex": "changes-since",
+ "uri": "changes-since*",
+ "limit": [
+ {
+ "verb": "GET",
+ "next-available": 0,
+ "unit": "MINUTE",
+ "value": 5,
+ "remaining": 5,
+ },
+ ],
+ },
+
+ ],
+ "absolute": {},
+ },
+ }
+ body = json.loads(response.body)
+ self.assertEqual(expected, body)
+
+
class LimitMiddlewareTest(BaseLimitTestSuite):
"""
Tests for the `limits.RateLimitingMiddleware` class.
@@ -185,7 +278,7 @@ class LimitMiddlewareTest(BaseLimitTestSuite):
"""Prepare middleware for use through fake WSGI app."""
BaseLimitTestSuite.setUp(self)
_limits = [
- Limit("GET", "*", ".*", 1, 60),
+ limits.Limit("GET", "*", ".*", 1, 60),
]
self.app = limits.RateLimitingMiddleware(self._empty_app, _limits)
@@ -238,7 +331,7 @@ class LimitTest(BaseLimitTestSuite):
def test_GET_no_delay(self):
"""Test a limit handles 1 GET per second."""
- limit = Limit("GET", "*", ".*", 1, 1)
+ limit = limits.Limit("GET", "*", ".*", 1, 1)
delay = limit("GET", "/anything")
self.assertEqual(None, delay)
self.assertEqual(0, limit.next_request)
@@ -246,7 +339,7 @@ class LimitTest(BaseLimitTestSuite):
def test_GET_delay(self):
"""Test two calls to 1 GET per second limit."""
- limit = Limit("GET", "*", ".*", 1, 1)
+ limit = limits.Limit("GET", "*", ".*", 1, 1)
delay = limit("GET", "/anything")
self.assertEqual(None, delay)
diff --git a/nova/tests/api/openstack/test_server_metadata.py b/nova/tests/api/openstack/test_server_metadata.py
index 680ff3e2c..c4d1d4fd8 100644
--- a/nova/tests/api/openstack/test_server_metadata.py
+++ b/nova/tests/api/openstack/test_server_metadata.py
@@ -21,11 +21,19 @@ import unittest
import webob
+from nova import flags
from nova.api import openstack
from nova.tests.api.openstack import fakes
import nova.wsgi
+FLAGS = flags.FLAGS
+
+
+def return_create_instance_metadata_max(context, server_id, metadata):
+ return stub_max_server_metadata()
+
+
def return_create_instance_metadata(context, server_id, metadata):
return stub_server_metadata()
@@ -52,6 +60,13 @@ def stub_server_metadata():
return metadata
+def stub_max_server_metadata():
+ metadata = {"metadata": {}}
+ for num in range(FLAGS.quota_metadata_items):
+ metadata['metadata']['key%i' % num] = "blah"
+ return metadata
+
+
class ServerMetaDataTest(unittest.TestCase):
def setUp(self):
@@ -68,7 +83,7 @@ class ServerMetaDataTest(unittest.TestCase):
def test_index(self):
self.stubs.Set(nova.db.api, 'instance_metadata_get',
- return_server_metadata)
+ return_server_metadata)
req = webob.Request.blank('/v1.1/servers/1/meta')
req.environ['api.version'] = '1.1'
res = req.get_response(fakes.wsgi_app())
@@ -78,7 +93,7 @@ class ServerMetaDataTest(unittest.TestCase):
def test_index_no_data(self):
self.stubs.Set(nova.db.api, 'instance_metadata_get',
- return_empty_server_metadata)
+ return_empty_server_metadata)
req = webob.Request.blank('/v1.1/servers/1/meta')
req.environ['api.version'] = '1.1'
res = req.get_response(fakes.wsgi_app())
@@ -88,7 +103,7 @@ class ServerMetaDataTest(unittest.TestCase):
def test_show(self):
self.stubs.Set(nova.db.api, 'instance_metadata_get',
- return_server_metadata)
+ return_server_metadata)
req = webob.Request.blank('/v1.1/servers/1/meta/key5')
req.environ['api.version'] = '1.1'
res = req.get_response(fakes.wsgi_app())
@@ -98,7 +113,7 @@ class ServerMetaDataTest(unittest.TestCase):
def test_show_meta_not_found(self):
self.stubs.Set(nova.db.api, 'instance_metadata_get',
- return_empty_server_metadata)
+ return_empty_server_metadata)
req = webob.Request.blank('/v1.1/servers/1/meta/key6')
req.environ['api.version'] = '1.1'
res = req.get_response(fakes.wsgi_app())
@@ -107,7 +122,7 @@ class ServerMetaDataTest(unittest.TestCase):
def test_delete(self):
self.stubs.Set(nova.db.api, 'instance_metadata_delete',
- delete_server_metadata)
+ delete_server_metadata)
req = webob.Request.blank('/v1.1/servers/1/meta/key5')
req.environ['api.version'] = '1.1'
req.method = 'DELETE'
@@ -116,7 +131,7 @@ class ServerMetaDataTest(unittest.TestCase):
def test_create(self):
self.stubs.Set(nova.db.api, 'instance_metadata_update_or_create',
- return_create_instance_metadata)
+ return_create_instance_metadata)
req = webob.Request.blank('/v1.1/servers/1/meta')
req.environ['api.version'] = '1.1'
req.method = 'POST'
@@ -129,7 +144,7 @@ class ServerMetaDataTest(unittest.TestCase):
def test_update_item(self):
self.stubs.Set(nova.db.api, 'instance_metadata_update_or_create',
- return_create_instance_metadata)
+ return_create_instance_metadata)
req = webob.Request.blank('/v1.1/servers/1/meta/key1')
req.environ['api.version'] = '1.1'
req.method = 'PUT'
@@ -142,7 +157,7 @@ class ServerMetaDataTest(unittest.TestCase):
def test_update_item_too_many_keys(self):
self.stubs.Set(nova.db.api, 'instance_metadata_update_or_create',
- return_create_instance_metadata)
+ return_create_instance_metadata)
req = webob.Request.blank('/v1.1/servers/1/meta/key1')
req.environ['api.version'] = '1.1'
req.method = 'PUT'
@@ -153,7 +168,7 @@ class ServerMetaDataTest(unittest.TestCase):
def test_update_item_body_uri_mismatch(self):
self.stubs.Set(nova.db.api, 'instance_metadata_update_or_create',
- return_create_instance_metadata)
+ return_create_instance_metadata)
req = webob.Request.blank('/v1.1/servers/1/meta/bad')
req.environ['api.version'] = '1.1'
req.method = 'PUT'
@@ -161,3 +176,29 @@ class ServerMetaDataTest(unittest.TestCase):
req.headers["content-type"] = "application/json"
res = req.get_response(fakes.wsgi_app())
self.assertEqual(400, res.status_int)
+
+ def test_too_many_metadata_items_on_create(self):
+ self.stubs.Set(nova.db.api, 'instance_metadata_update_or_create',
+ return_create_instance_metadata)
+ data = {"metadata": {}}
+ for num in range(FLAGS.quota_metadata_items + 1):
+ data['metadata']['key%i' % num] = "blah"
+ json_string = str(data).replace("\'", "\"")
+ req = webob.Request.blank('/v1.1/servers/1/meta')
+ req.environ['api.version'] = '1.1'
+ req.method = 'POST'
+ req.body = json_string
+ req.headers["content-type"] = "application/json"
+ res = req.get_response(fakes.wsgi_app())
+ self.assertEqual(400, res.status_int)
+
+ def test_to_many_metadata_items_on_update_item(self):
+ self.stubs.Set(nova.db.api, 'instance_metadata_update_or_create',
+ return_create_instance_metadata_max)
+ req = webob.Request.blank('/v1.1/servers/1/meta/key1')
+ req.environ['api.version'] = '1.1'
+ req.method = 'PUT'
+ req.body = '{"a new key": "a new value"}'
+ req.headers["content-type"] = "application/json"
+ res = req.get_response(fakes.wsgi_app())
+ self.assertEqual(400, res.status_int)
diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py
index 556046e9d..89edece42 100644
--- a/nova/tests/api/openstack/test_servers.py
+++ b/nova/tests/api/openstack/test_servers.py
@@ -33,6 +33,7 @@ import nova.api.openstack
from nova.api.openstack import servers
import nova.compute.api
from nova.compute import instance_types
+from nova.compute import power_state
import nova.db.api
from nova.db.sqlalchemy.models import Instance
from nova.db.sqlalchemy.models import InstanceMetadata
@@ -56,6 +57,12 @@ def return_server_with_addresses(private, public):
return _return_server
+def return_server_with_power_state(power_state):
+ def _return_server(context, id):
+ return stub_instance(id, power_state=power_state)
+ return _return_server
+
+
def return_servers(context, user_id=1):
return [stub_instance(i, user_id) for i in xrange(5)]
@@ -73,7 +80,7 @@ def instance_address(context, instance_id):
def stub_instance(id, user_id=1, private_address=None, public_addresses=None,
- host=None):
+ host=None, power_state=0):
metadata = []
metadata.append(InstanceMetadata(key='seq', value=id))
@@ -96,7 +103,7 @@ def stub_instance(id, user_id=1, private_address=None, public_addresses=None,
"launch_index": 0,
"key_name": "",
"key_data": "",
- "state": 0,
+ "state": power_state,
"state_description": "",
"memory_mb": 0,
"vcpus": 0,
@@ -127,6 +134,10 @@ def fake_compute_api(cls, req, id):
return True
+def find_host(self, context, instance_id):
+ return "nova"
+
+
class ServersTest(test.TestCase):
def setUp(self):
@@ -466,6 +477,7 @@ class ServersTest(test.TestCase):
"_get_kernel_ramdisk_from_image", kernel_ramdisk_mapping)
self.stubs.Set(nova.api.openstack.common,
"get_image_id_from_image_hash", image_id_from_hash)
+ self.stubs.Set(nova.compute.api.API, "_find_host", find_host)
def _test_create_instance_helper(self):
self._setup_for_create_instance()
@@ -613,6 +625,33 @@ class ServersTest(test.TestCase):
res = req.get_response(fakes.wsgi_app())
self.assertEqual(res.status_int, 400)
+ def test_create_instance_v11_local_href(self):
+ self._setup_for_create_instance()
+
+ imageRef = 'http://localhost/v1.1/images/2'
+ imageRefLocal = '2'
+ flavorRef = 'http://localhost/v1.1/flavors/3'
+ body = {
+ 'server': {
+ 'name': 'server_test',
+ 'imageRef': imageRefLocal,
+ 'flavorRef': flavorRef,
+ },
+ }
+
+ req = webob.Request.blank('/v1.1/servers')
+ req.method = 'POST'
+ req.body = json.dumps(body)
+ req.headers["content-type"] = "application/json"
+
+ res = req.get_response(fakes.wsgi_app())
+
+ server = json.loads(res.body)['server']
+ self.assertEqual(1, server['id'])
+ self.assertEqual(flavorRef, server['flavorRef'])
+ self.assertEqual(imageRef, server['imageRef'])
+ self.assertEqual(res.status_int, 200)
+
def test_create_instance_with_admin_pass_v10(self):
self._setup_for_create_instance()
@@ -733,6 +772,7 @@ class ServersTest(test.TestCase):
self.stubs.Set(nova.db.api, 'instance_update',
server_update)
+ self.stubs.Set(nova.compute.api.API, "_find_host", find_host)
req = webob.Request.blank('/v1.0/servers/1')
req.method = 'PUT'
@@ -1024,15 +1064,175 @@ class ServersTest(test.TestCase):
req.body = json.dumps(body)
res = req.get_response(fakes.wsgi_app())
- def test_server_rebuild(self):
- body = dict(server=dict(
- name='server_test', imageId=2, flavorId=2, metadata={},
- personality={}))
+ def test_server_rebuild_accepted(self):
+ body = {
+ "rebuild": {
+ "imageId": 2,
+ },
+ }
+
+ req = webob.Request.blank('/v1.0/servers/1/action')
+ req.method = 'POST'
+ req.content_type = 'application/json'
+ req.body = json.dumps(body)
+
+ res = req.get_response(fakes.wsgi_app())
+ self.assertEqual(res.status_int, 202)
+ self.assertEqual(res.body, "")
+
+ def test_server_rebuild_rejected_when_building(self):
+ body = {
+ "rebuild": {
+ "imageId": 2,
+ },
+ }
+
+ state = power_state.BUILDING
+ new_return_server = return_server_with_power_state(state)
+ self.stubs.Set(nova.db.api, 'instance_get', new_return_server)
+
+ req = webob.Request.blank('/v1.0/servers/1/action')
+ req.method = 'POST'
+ req.content_type = 'application/json'
+ req.body = json.dumps(body)
+
+ res = req.get_response(fakes.wsgi_app())
+ self.assertEqual(res.status_int, 409)
+
+ def test_server_rebuild_bad_entity(self):
+ body = {
+ "rebuild": {
+ },
+ }
+
req = webob.Request.blank('/v1.0/servers/1/action')
req.method = 'POST'
req.content_type = 'application/json'
req.body = json.dumps(body)
+
+ res = req.get_response(fakes.wsgi_app())
+ self.assertEqual(res.status_int, 400)
+
+ def test_server_rebuild_accepted_minimum_v11(self):
+ body = {
+ "rebuild": {
+ "imageRef": "http://localhost/images/2",
+ },
+ }
+
+ req = webob.Request.blank('/v1.1/servers/1/action')
+ req.method = 'POST'
+ req.content_type = 'application/json'
+ req.body = json.dumps(body)
+
+ res = req.get_response(fakes.wsgi_app())
+ self.assertEqual(res.status_int, 202)
+
+ def test_server_rebuild_rejected_when_building_v11(self):
+ body = {
+ "rebuild": {
+ "imageRef": "http://localhost/images/2",
+ },
+ }
+
+ state = power_state.BUILDING
+ new_return_server = return_server_with_power_state(state)
+ self.stubs.Set(nova.db.api, 'instance_get', new_return_server)
+
+ req = webob.Request.blank('/v1.1/servers/1/action')
+ req.method = 'POST'
+ req.content_type = 'application/json'
+ req.body = json.dumps(body)
+
+ res = req.get_response(fakes.wsgi_app())
+ self.assertEqual(res.status_int, 409)
+
+ def test_server_rebuild_accepted_with_metadata_v11(self):
+ body = {
+ "rebuild": {
+ "imageRef": "http://localhost/images/2",
+ "metadata": {
+ "new": "metadata",
+ },
+ },
+ }
+
+ req = webob.Request.blank('/v1.1/servers/1/action')
+ req.method = 'POST'
+ req.content_type = 'application/json'
+ req.body = json.dumps(body)
+
+ res = req.get_response(fakes.wsgi_app())
+ self.assertEqual(res.status_int, 202)
+
+ def test_server_rebuild_accepted_with_bad_metadata_v11(self):
+ body = {
+ "rebuild": {
+ "imageRef": "http://localhost/images/2",
+ "metadata": "stack",
+ },
+ }
+
+ req = webob.Request.blank('/v1.1/servers/1/action')
+ req.method = 'POST'
+ req.content_type = 'application/json'
+ req.body = json.dumps(body)
+
+ res = req.get_response(fakes.wsgi_app())
+ self.assertEqual(res.status_int, 400)
+
+ def test_server_rebuild_bad_entity_v11(self):
+ body = {
+ "rebuild": {
+ "imageId": 2,
+ },
+ }
+
+ req = webob.Request.blank('/v1.1/servers/1/action')
+ req.method = 'POST'
+ req.content_type = 'application/json'
+ req.body = json.dumps(body)
+
+ res = req.get_response(fakes.wsgi_app())
+ self.assertEqual(res.status_int, 400)
+
+ def test_server_rebuild_bad_personality_v11(self):
+ body = {
+ "rebuild": {
+ "imageRef": "http://localhost/images/2",
+ "personality": [{
+ "path": "/path/to/file",
+ "contents": "INVALID b64",
+ }]
+ },
+ }
+
+ req = webob.Request.blank('/v1.1/servers/1/action')
+ req.method = 'POST'
+ req.content_type = 'application/json'
+ req.body = json.dumps(body)
+
+ res = req.get_response(fakes.wsgi_app())
+ self.assertEqual(res.status_int, 400)
+
+ def test_server_rebuild_personality_v11(self):
+ body = {
+ "rebuild": {
+ "imageRef": "http://localhost/images/2",
+ "personality": [{
+ "path": "/path/to/file",
+ "contents": base64.b64encode("Test String"),
+ }]
+ },
+ }
+
+ req = webob.Request.blank('/v1.1/servers/1/action')
+ req.method = 'POST'
+ req.content_type = 'application/json'
+ req.body = json.dumps(body)
+
res = req.get_response(fakes.wsgi_app())
+ self.assertEqual(res.status_int, 202)
def test_delete_server_instance(self):
req = webob.Request.blank('/v1.0/servers/1')
@@ -1155,6 +1355,24 @@ class ServersTest(test.TestCase):
res = req.get_response(fakes.wsgi_app())
self.assertEqual(res.status_int, 400)
+ def test_shutdown_status(self):
+ new_server = return_server_with_power_state(power_state.SHUTDOWN)
+ self.stubs.Set(nova.db.api, 'instance_get', new_server)
+ req = webob.Request.blank('/v1.0/servers/1')
+ res = req.get_response(fakes.wsgi_app())
+ self.assertEqual(res.status_int, 200)
+ res_dict = json.loads(res.body)
+ self.assertEqual(res_dict['server']['status'], 'SHUTDOWN')
+
+ def test_shutoff_status(self):
+ new_server = return_server_with_power_state(power_state.SHUTOFF)
+ self.stubs.Set(nova.db.api, 'instance_get', new_server)
+ req = webob.Request.blank('/v1.0/servers/1')
+ res = req.get_response(fakes.wsgi_app())
+ self.assertEqual(res.status_int, 200)
+ res_dict = json.loads(res.body)
+ self.assertEqual(res_dict['server']['status'], 'SHUTOFF')
+
class TestServerCreateRequestXMLDeserializer(unittest.TestCase):
diff --git a/nova/tests/api/openstack/test_zones.py b/nova/tests/api/openstack/test_zones.py
index a3f191aaa..5d5799b59 100644
--- a/nova/tests/api/openstack/test_zones.py
+++ b/nova/tests/api/openstack/test_zones.py
@@ -75,7 +75,7 @@ def zone_get_all_db(context):
]
-def zone_capabilities(method, context, params):
+def zone_capabilities(method, context):
return dict()
diff --git a/nova/tests/api/test_wsgi.py b/nova/tests/api/test_wsgi.py
index 1ecdd1cfb..5820ecdc2 100644
--- a/nova/tests/api/test_wsgi.py
+++ b/nova/tests/api/test_wsgi.py
@@ -136,6 +136,12 @@ class RequestTest(test.TestCase):
request.body = "asdf<br />"
self.assertRaises(webob.exc.HTTPBadRequest, request.get_content_type)
+ def test_request_content_type_with_charset(self):
+ request = wsgi.Request.blank('/tests/123')
+ request.headers["Content-Type"] = "application/json; charset=UTF-8"
+ result = request.get_content_type()
+ self.assertEqual(result, "application/json")
+
def test_content_type_from_accept_xml(self):
request = wsgi.Request.blank('/tests/123')
request.headers["Accept"] = "application/xml"
diff --git a/nova/tests/integrated/test_servers.py b/nova/tests/integrated/test_servers.py
index 749ea8955..e89d0100a 100644
--- a/nova/tests/integrated/test_servers.py
+++ b/nova/tests/integrated/test_servers.py
@@ -134,50 +134,50 @@ class ServersTest(integrated_helpers._IntegratedTestBase):
# Should be gone
self.assertFalse(found_server)
-# TODO(justinsb): Enable this unit test when the metadata bug is fixed
-# def test_create_server_with_metadata(self):
-# """Creates a server with metadata"""
-#
-# # Build the server data gradually, checking errors along the way
-# server = self._build_minimal_create_server_request()
-#
-# for metadata_count in range(30):
-# metadata = {}
-# for i in range(metadata_count):
-# metadata['key_%s' % i] = 'value_%s' % i
-# server['metadata'] = metadata
-#
-# post = {'server': server}
-# created_server = self.api.post_server(post)
-# LOG.debug("created_server: %s" % created_server)
-# self.assertTrue(created_server['id'])
-# created_server_id = created_server['id']
-# # Reenable when bug fixed
-# # self.assertEqual(metadata, created_server.get('metadata'))
-#
-# # Check it's there
-# found_server = self.api.get_server(created_server_id)
-# self.assertEqual(created_server_id, found_server['id'])
-# self.assertEqual(metadata, found_server.get('metadata'))
-#
-# # The server should also be in the all-servers details list
-# servers = self.api.get_servers(detail=True)
-# server_map = dict((server['id'], server) for server in servers)
-# found_server = server_map.get(created_server_id)
-# self.assertTrue(found_server)
-# # Details do include metadata
-# self.assertEqual(metadata, found_server.get('metadata'))
-#
-# # The server should also be in the all-servers summary list
-# servers = self.api.get_servers(detail=False)
-# server_map = dict((server['id'], server) for server in servers)
-# found_server = server_map.get(created_server_id)
-# self.assertTrue(found_server)
-# # Summary should not include metadata
-# self.assertFalse(found_server.get('metadata'))
-#
-# # Cleanup
-# self._delete_server(created_server_id)
+ def test_create_server_with_metadata(self):
+ """Creates a server with metadata."""
+
+ # Build the server data gradually, checking errors along the way
+ server = self._build_minimal_create_server_request()
+
+ metadata = {}
+ for i in range(30):
+ metadata['key_%s' % i] = 'value_%s' % i
+
+ server['metadata'] = metadata
+
+ post = {'server': server}
+ created_server = self.api.post_server(post)
+ LOG.debug("created_server: %s" % created_server)
+ self.assertTrue(created_server['id'])
+ created_server_id = created_server['id']
+
+ # Reenable when bug fixed
+ self.assertEqual(metadata, created_server.get('metadata'))
+ # Check it's there
+
+ found_server = self.api.get_server(created_server_id)
+ self.assertEqual(created_server_id, found_server['id'])
+ self.assertEqual(metadata, found_server.get('metadata'))
+
+ # The server should also be in the all-servers details list
+ servers = self.api.get_servers(detail=True)
+ server_map = dict((server['id'], server) for server in servers)
+ found_server = server_map.get(created_server_id)
+ self.assertTrue(found_server)
+ # Details do include metadata
+ self.assertEqual(metadata, found_server.get('metadata'))
+
+ # The server should also be in the all-servers summary list
+ servers = self.api.get_servers(detail=False)
+ server_map = dict((server['id'], server) for server in servers)
+ found_server = server_map.get(created_server_id)
+ self.assertTrue(found_server)
+ # Summary should not include metadata
+ self.assertFalse(found_server.get('metadata'))
+
+ # Cleanup
+ self._delete_server(created_server_id)
if __name__ == "__main__":
diff --git a/nova/tests/test_api.py b/nova/tests/test_api.py
index fa0e56597..97f401b87 100644
--- a/nova/tests/test_api.py
+++ b/nova/tests/test_api.py
@@ -28,10 +28,12 @@ import StringIO
import webob
from nova import context
+from nova import exception
from nova import test
from nova.api import ec2
-from nova.api.ec2 import cloud
from nova.api.ec2 import apirequest
+from nova.api.ec2 import cloud
+from nova.api.ec2 import ec2utils
from nova.auth import manager
@@ -101,6 +103,21 @@ class XmlConversionTestCase(test.TestCase):
self.assertEqual(conv('-0'), 0)
+class Ec2utilsTestCase(test.TestCase):
+ def test_ec2_id_to_id(self):
+ self.assertEqual(ec2utils.ec2_id_to_id('i-0000001e'), 30)
+ self.assertEqual(ec2utils.ec2_id_to_id('ami-1d'), 29)
+
+ def test_bad_ec2_id(self):
+ self.assertRaises(exception.InvalidEc2Id,
+ ec2utils.ec2_id_to_id,
+ 'badone')
+
+ def test_id_to_ec2_id(self):
+ self.assertEqual(ec2utils.id_to_ec2_id(30), 'i-0000001e')
+ self.assertEqual(ec2utils.id_to_ec2_id(29, 'ami-%08x'), 'ami-0000001d')
+
+
class ApiEc2TestCase(test.TestCase):
"""Unit test for the cloud controller on an EC2 API"""
def setUp(self):
diff --git a/nova/tests/test_auth.py b/nova/tests/test_auth.py
index f8a1b1564..f02dd94b7 100644
--- a/nova/tests/test_auth.py
+++ b/nova/tests/test_auth.py
@@ -101,9 +101,43 @@ class _AuthManagerBaseTestCase(test.TestCase):
self.assertEqual('private-party', u.access)
def test_004_signature_is_valid(self):
- #self.assertTrue(self.manager.authenticate(**boto.generate_url ...? ))
- pass
- #raise NotImplementedError
+ with user_generator(self.manager, name='admin', secret='admin',
+ access='admin'):
+ with project_generator(self.manager, name="admin",
+ manager_user='admin'):
+ accesskey = 'admin:admin'
+ expected_result = (self.manager.get_user('admin'),
+ self.manager.get_project('admin'))
+ # captured sig and query string using boto 1.9b/euca2ools 1.2
+ sig = 'd67Wzd9Bwz8xid9QU+lzWXcF2Y3tRicYABPJgrqfrwM='
+ auth_params = {'AWSAccessKeyId': 'admin:admin',
+ 'Action': 'DescribeAvailabilityZones',
+ 'SignatureMethod': 'HmacSHA256',
+ 'SignatureVersion': '2',
+ 'Timestamp': '2011-04-22T11:29:29',
+ 'Version': '2009-11-30'}
+ self.assertTrue(expected_result, self.manager.authenticate(
+ accesskey,
+ sig,
+ auth_params,
+ 'GET',
+ '127.0.0.1:8773',
+ '/services/Cloud/'))
+ # captured sig and query string using RightAWS 1.10.0
+ sig = 'ECYLU6xdFG0ZqRVhQybPJQNJ5W4B9n8fGs6+/fuGD2c='
+ auth_params = {'AWSAccessKeyId': 'admin:admin',
+ 'Action': 'DescribeAvailabilityZones',
+ 'SignatureMethod': 'HmacSHA256',
+ 'SignatureVersion': '2',
+ 'Timestamp': '2011-04-22T11:29:49.000Z',
+ 'Version': '2008-12-01'}
+ self.assertTrue(expected_result, self.manager.authenticate(
+ accesskey,
+ sig,
+ auth_params,
+ 'GET',
+ '127.0.0.1',
+ '/services/Cloud'))
def test_005_can_get_credentials(self):
return
diff --git a/nova/tests/test_cloud.py b/nova/tests/test_cloud.py
index c45bdd12c..f271c03f2 100644
--- a/nova/tests/test_cloud.py
+++ b/nova/tests/test_cloud.py
@@ -290,7 +290,7 @@ class CloudTestCase(test.TestCase):
instance_id = rv['instancesSet'][0]['instanceId']
output = self.cloud.get_console_output(context=self.context,
instance_id=[instance_id])
- self.assertEquals(b64decode(output['output']), 'FAKE CONSOLE OUTPUT')
+ self.assertEquals(b64decode(output['output']), 'FAKE CONSOLE?OUTPUT')
# TODO(soren): We need this until we can stop polling in the rpc code
# for unit tests.
greenthread.sleep(0.3)
diff --git a/nova/tests/test_compute.py b/nova/tests/test_compute.py
index 393110791..55e7ae0c4 100644
--- a/nova/tests/test_compute.py
+++ b/nova/tests/test_compute.py
@@ -21,6 +21,7 @@ Tests For Compute
import datetime
import mox
+import stubout
from nova import compute
from nova import context
@@ -52,6 +53,10 @@ class FakeTime(object):
self.counter += t
+def nop_report_driver_status(self):
+ pass
+
+
class ComputeTestCase(test.TestCase):
"""Test case for compute"""
def setUp(self):
@@ -649,6 +654,10 @@ class ComputeTestCase(test.TestCase):
def test_run_kill_vm(self):
"""Detect when a vm is terminated behind the scenes"""
+ self.stubs = stubout.StubOutForTesting()
+ self.stubs.Set(compute_manager.ComputeManager,
+ '_report_driver_status', nop_report_driver_status)
+
instance_id = self._create_instance()
self.compute.run_instance(self.context, instance_id)
diff --git a/nova/tests/test_exception.py b/nova/tests/test_exception.py
new file mode 100644
index 000000000..4d3b9cc73
--- /dev/null
+++ b/nova/tests/test_exception.py
@@ -0,0 +1,34 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2010 United States Government as represented by the
+# Administrator of the National Aeronautics and Space Administration.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from nova import test
+from nova import exception
+
+
+class ApiErrorTestCase(test.TestCase):
+ def test_return_valid_error(self):
+ # without 'code' arg
+ err = exception.ApiError('fake error')
+ self.assertEqual(err.__str__(), 'fake error')
+ self.assertEqual(err.code, None)
+ self.assertEqual(err.msg, 'fake error')
+ # with 'code' arg
+ err = exception.ApiError('fake error', 'blah code')
+ self.assertEqual(err.__str__(), 'blah code: fake error')
+ self.assertEqual(err.code, 'blah code')
+ self.assertEqual(err.msg, 'fake error')
diff --git a/nova/tests/test_host_filter.py b/nova/tests/test_host_filter.py
new file mode 100644
index 000000000..c029d41e6
--- /dev/null
+++ b/nova/tests/test_host_filter.py
@@ -0,0 +1,208 @@
+# 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.
+"""
+Tests For Scheduler Host Filter Drivers.
+"""
+
+import json
+
+from nova import exception
+from nova import flags
+from nova import test
+from nova.scheduler import host_filter
+
+FLAGS = flags.FLAGS
+
+
+class FakeZoneManager:
+ pass
+
+
+class HostFilterTestCase(test.TestCase):
+ """Test case for host filter drivers."""
+
+ def _host_caps(self, multiplier):
+ # Returns host capabilities in the following way:
+ # host1 = memory:free 10 (100max)
+ # disk:available 100 (1000max)
+ # hostN = memory:free 10 + 10N
+ # disk:available 100 + 100N
+ # in other words: hostN has more resources than host0
+ # which means ... don't go above 10 hosts.
+ return {'host_name-description': 'XenServer %s' % multiplier,
+ 'host_hostname': 'xs-%s' % multiplier,
+ 'host_memory_total': 100,
+ 'host_memory_overhead': 10,
+ 'host_memory_free': 10 + multiplier * 10,
+ 'host_memory_free-computed': 10 + multiplier * 10,
+ 'host_other-config': {},
+ 'host_ip_address': '192.168.1.%d' % (100 + multiplier),
+ 'host_cpu_info': {},
+ 'disk_available': 100 + multiplier * 100,
+ 'disk_total': 1000,
+ 'disk_used': 0,
+ 'host_uuid': 'xxx-%d' % multiplier,
+ 'host_name-label': 'xs-%s' % multiplier}
+
+ def setUp(self):
+ self.old_flag = FLAGS.default_host_filter_driver
+ FLAGS.default_host_filter_driver = \
+ 'nova.scheduler.host_filter.AllHostsFilter'
+ self.instance_type = dict(name='tiny',
+ memory_mb=50,
+ vcpus=10,
+ local_gb=500,
+ flavorid=1,
+ swap=500,
+ rxtx_quota=30000,
+ rxtx_cap=200)
+
+ self.zone_manager = FakeZoneManager()
+ states = {}
+ for x in xrange(10):
+ states['host%02d' % (x + 1)] = {'compute': self._host_caps(x)}
+ self.zone_manager.service_states = states
+
+ def tearDown(self):
+ FLAGS.default_host_filter_driver = self.old_flag
+
+ def test_choose_driver(self):
+ # Test default driver ...
+ driver = host_filter.choose_driver()
+ self.assertEquals(driver._full_name(),
+ 'nova.scheduler.host_filter.AllHostsFilter')
+ # Test valid driver ...
+ driver = host_filter.choose_driver(
+ 'nova.scheduler.host_filter.FlavorFilter')
+ self.assertEquals(driver._full_name(),
+ 'nova.scheduler.host_filter.FlavorFilter')
+ # Test invalid driver ...
+ try:
+ host_filter.choose_driver('does not exist')
+ self.fail("Should not find driver")
+ except exception.SchedulerHostFilterDriverNotFound:
+ pass
+
+ def test_all_host_driver(self):
+ driver = host_filter.AllHostsFilter()
+ cooked = driver.instance_type_to_filter(self.instance_type)
+ hosts = driver.filter_hosts(self.zone_manager, cooked)
+ self.assertEquals(10, len(hosts))
+ for host, capabilities in hosts:
+ self.assertTrue(host.startswith('host'))
+
+ def test_flavor_driver(self):
+ driver = host_filter.FlavorFilter()
+ # filter all hosts that can support 50 ram and 500 disk
+ name, cooked = driver.instance_type_to_filter(self.instance_type)
+ self.assertEquals('nova.scheduler.host_filter.FlavorFilter', name)
+ hosts = driver.filter_hosts(self.zone_manager, cooked)
+ self.assertEquals(6, len(hosts))
+ just_hosts = [host for host, caps in hosts]
+ just_hosts.sort()
+ self.assertEquals('host05', just_hosts[0])
+ self.assertEquals('host10', just_hosts[5])
+
+ def test_json_driver(self):
+ driver = host_filter.JsonFilter()
+ # filter all hosts that can support 50 ram and 500 disk
+ name, cooked = driver.instance_type_to_filter(self.instance_type)
+ self.assertEquals('nova.scheduler.host_filter.JsonFilter', name)
+ hosts = driver.filter_hosts(self.zone_manager, cooked)
+ self.assertEquals(6, len(hosts))
+ just_hosts = [host for host, caps in hosts]
+ just_hosts.sort()
+ self.assertEquals('host05', just_hosts[0])
+ self.assertEquals('host10', just_hosts[5])
+
+ # Try some custom queries
+
+ raw = ['or',
+ ['and',
+ ['<', '$compute.host_memory_free', 30],
+ ['<', '$compute.disk_available', 300]
+ ],
+ ['and',
+ ['>', '$compute.host_memory_free', 70],
+ ['>', '$compute.disk_available', 700]
+ ]
+ ]
+ cooked = json.dumps(raw)
+ hosts = driver.filter_hosts(self.zone_manager, cooked)
+
+ self.assertEquals(5, len(hosts))
+ just_hosts = [host for host, caps in hosts]
+ just_hosts.sort()
+ for index, host in zip([1, 2, 8, 9, 10], just_hosts):
+ self.assertEquals('host%02d' % index, host)
+
+ raw = ['not',
+ ['=', '$compute.host_memory_free', 30],
+ ]
+ cooked = json.dumps(raw)
+ hosts = driver.filter_hosts(self.zone_manager, cooked)
+
+ self.assertEquals(9, len(hosts))
+ just_hosts = [host for host, caps in hosts]
+ just_hosts.sort()
+ for index, host in zip([1, 2, 4, 5, 6, 7, 8, 9, 10], just_hosts):
+ self.assertEquals('host%02d' % index, host)
+
+ raw = ['in', '$compute.host_memory_free', 20, 40, 60, 80, 100]
+ cooked = json.dumps(raw)
+ hosts = driver.filter_hosts(self.zone_manager, cooked)
+
+ self.assertEquals(5, len(hosts))
+ just_hosts = [host for host, caps in hosts]
+ just_hosts.sort()
+ for index, host in zip([2, 4, 6, 8, 10], just_hosts):
+ self.assertEquals('host%02d' % index, host)
+
+ # Try some bogus input ...
+ raw = ['unknown command', ]
+ cooked = json.dumps(raw)
+ try:
+ driver.filter_hosts(self.zone_manager, cooked)
+ self.fail("Should give KeyError")
+ except KeyError, e:
+ pass
+
+ self.assertTrue(driver.filter_hosts(self.zone_manager, json.dumps([])))
+ self.assertTrue(driver.filter_hosts(self.zone_manager, json.dumps({})))
+ self.assertTrue(driver.filter_hosts(self.zone_manager, json.dumps(
+ ['not', True, False, True, False]
+ )))
+
+ try:
+ driver.filter_hosts(self.zone_manager, json.dumps(
+ 'not', True, False, True, False
+ ))
+ self.fail("Should give KeyError")
+ except KeyError, e:
+ pass
+
+ self.assertFalse(driver.filter_hosts(self.zone_manager, json.dumps(
+ ['=', '$foo', 100]
+ )))
+ self.assertFalse(driver.filter_hosts(self.zone_manager, json.dumps(
+ ['=', '$.....', 100]
+ )))
+ self.assertFalse(driver.filter_hosts(self.zone_manager, json.dumps(
+ ['>', ['and', ['or', ['not', ['<', ['>=', ['<=', ['in', ]]]]]]]]
+ )))
+
+ self.assertFalse(driver.filter_hosts(self.zone_manager, json.dumps(
+ ['=', {}, ['>', '$missing....foo']]
+ )))
diff --git a/nova/tests/test_instance_types.py b/nova/tests/test_instance_types.py
index 5d6d5e1f4..ef271518c 100644
--- a/nova/tests/test_instance_types.py
+++ b/nova/tests/test_instance_types.py
@@ -75,16 +75,25 @@ class InstanceTypeTestCase(test.TestCase):
def test_invalid_create_args_should_fail(self):
"""Ensures that instance type creation fails with invalid args"""
self.assertRaises(
- exception.InvalidInputException,
+ exception.InvalidInput,
instance_types.create, self.name, 0, 1, 120, self.flavorid)
self.assertRaises(
- exception.InvalidInputException,
+ exception.InvalidInput,
instance_types.create, self.name, 256, -1, 120, self.flavorid)
self.assertRaises(
- exception.InvalidInputException,
+ exception.InvalidInput,
instance_types.create, self.name, 256, 1, "aa", self.flavorid)
def test_non_existant_inst_type_shouldnt_delete(self):
"""Ensures that instance type creation fails with invalid args"""
self.assertRaises(exception.ApiError,
instance_types.destroy, "sfsfsdfdfs")
+
+ def test_repeated_inst_types_should_raise_api_error(self):
+ """Ensures that instance duplicates raises ApiError"""
+ new_name = self.name + "dup"
+ instance_types.create(new_name, 256, 1, 120, self.flavorid + 1)
+ instance_types.destroy(new_name)
+ self.assertRaises(
+ exception.ApiError,
+ instance_types.create, new_name, 256, 1, 120, self.flavorid)
diff --git a/nova/tests/test_misc.py b/nova/tests/test_misc.py
index 4e17e1ce0..cf8f4c05e 100644
--- a/nova/tests/test_misc.py
+++ b/nova/tests/test_misc.py
@@ -29,11 +29,12 @@ from nova.utils import parse_mailmap, str_dict_replace
class ProjectTestCase(test.TestCase):
def test_authors_up_to_date(self):
topdir = os.path.normpath(os.path.dirname(__file__) + '/../../')
- if os.path.exists(os.path.join(topdir, '.bzr')):
- contributors = set()
-
- mailmap = parse_mailmap(os.path.join(topdir, '.mailmap'))
+ missing = set()
+ contributors = set()
+ mailmap = parse_mailmap(os.path.join(topdir, '.mailmap'))
+ authors_file = open(os.path.join(topdir, 'Authors'), 'r').read()
+ if os.path.exists(os.path.join(topdir, '.bzr')):
import bzrlib.workingtree
tree = bzrlib.workingtree.WorkingTree.open(topdir)
tree.lock_read()
@@ -47,22 +48,36 @@ class ProjectTestCase(test.TestCase):
for r in revs:
for author in r.get_apparent_authors():
email = author.split(' ')[-1]
- contributors.add(str_dict_replace(email, mailmap))
+ contributors.add(str_dict_replace(email,
+ mailmap))
+ finally:
+ tree.unlock()
- authors_file = open(os.path.join(topdir, 'Authors'),
- 'r').read()
+ elif os.path.exists(os.path.join(topdir, '.git')):
+ import git
+ repo = git.Repo(topdir)
+ for commit in repo.head.commit.iter_parents():
+ email = commit.author.email
+ if email is None:
+ email = commit.author.name
+ if 'nova-core' in email:
+ continue
+ if email.split(' ')[-1] == '<>':
+ email = email.split(' ')[-2]
+ email = '<' + email + '>'
+ contributors.add(str_dict_replace(email, mailmap))
- missing = set()
- for contributor in contributors:
- if contributor == 'nova-core':
- continue
- if not contributor in authors_file:
- missing.add(contributor)
+ else:
+ return
- self.assertTrue(len(missing) == 0,
- '%r not listed in Authors' % missing)
- finally:
- tree.unlock()
+ for contributor in contributors:
+ if contributor == 'nova-core':
+ continue
+ if not contributor in authors_file:
+ missing.add(contributor)
+
+ self.assertTrue(len(missing) == 0,
+ '%r not listed in Authors' % missing)
class LockTestCase(test.TestCase):
diff --git a/nova/tests/test_scheduler.py b/nova/tests/test_scheduler.py
index 51d987288..968ef9d6c 100644
--- a/nova/tests/test_scheduler.py
+++ b/nova/tests/test_scheduler.py
@@ -120,12 +120,11 @@ class SchedulerTestCase(test.TestCase):
dest = 'dummydest'
ctxt = context.get_admin_context()
- try:
- scheduler.show_host_resources(ctxt, dest)
- except exception.NotFound, e:
- c1 = (e.message.find(_("does not exist or is not a "
- "compute node.")) >= 0)
- self.assertTrue(c1)
+ self.assertRaises(exception.NotFound, scheduler.show_host_resources,
+ ctxt, dest)
+ #TODO(bcwaldon): reimplement this functionality
+ #c1 = (e.message.find(_("does not exist or is not a "
+ # "compute node.")) >= 0)
def _dic_is_equal(self, dic1, dic2, keys=None):
"""Compares 2 dictionary contents(Helper method)"""
@@ -698,14 +697,10 @@ class SimpleDriverTestCase(test.TestCase):
'topic': 'volume', 'report_count': 0}
s_ref = db.service_create(self.context, dic)
- try:
- self.scheduler.driver.schedule_live_migration(self.context,
- instance_id,
- i_ref['host'])
- except exception.Invalid, e:
- c = (e.message.find('volume node is not alive') >= 0)
+ self.assertRaises(exception.VolumeServiceUnavailable,
+ self.scheduler.driver.schedule_live_migration,
+ self.context, instance_id, i_ref['host'])
- self.assertTrue(c)
db.instance_destroy(self.context, instance_id)
db.service_destroy(self.context, s_ref['id'])
db.volume_destroy(self.context, v_ref['id'])
@@ -718,13 +713,10 @@ class SimpleDriverTestCase(test.TestCase):
s_ref = self._create_compute_service(created_at=t, updated_at=t,
host=i_ref['host'])
- try:
- self.scheduler.driver._live_migration_src_check(self.context,
- i_ref)
- except exception.Invalid, e:
- c = (e.message.find('is not alive') >= 0)
+ self.assertRaises(exception.ComputeServiceUnavailable,
+ self.scheduler.driver._live_migration_src_check,
+ self.context, i_ref)
- self.assertTrue(c)
db.instance_destroy(self.context, instance_id)
db.service_destroy(self.context, s_ref['id'])
@@ -749,14 +741,10 @@ class SimpleDriverTestCase(test.TestCase):
s_ref = self._create_compute_service(created_at=t, updated_at=t,
host=i_ref['host'])
- try:
- self.scheduler.driver._live_migration_dest_check(self.context,
- i_ref,
- i_ref['host'])
- except exception.Invalid, e:
- c = (e.message.find('is not alive') >= 0)
+ self.assertRaises(exception.ComputeServiceUnavailable,
+ self.scheduler.driver._live_migration_dest_check,
+ self.context, i_ref, i_ref['host'])
- self.assertTrue(c)
db.instance_destroy(self.context, instance_id)
db.service_destroy(self.context, s_ref['id'])
@@ -766,14 +754,10 @@ class SimpleDriverTestCase(test.TestCase):
i_ref = db.instance_get(self.context, instance_id)
s_ref = self._create_compute_service(host=i_ref['host'])
- try:
- self.scheduler.driver._live_migration_dest_check(self.context,
- i_ref,
- i_ref['host'])
- except exception.Invalid, e:
- c = (e.message.find('choose other host') >= 0)
+ self.assertRaises(exception.UnableToMigrateToSelf,
+ self.scheduler.driver._live_migration_dest_check,
+ self.context, i_ref, i_ref['host'])
- self.assertTrue(c)
db.instance_destroy(self.context, instance_id)
db.service_destroy(self.context, s_ref['id'])
@@ -784,14 +768,10 @@ class SimpleDriverTestCase(test.TestCase):
s_ref = self._create_compute_service(host='somewhere',
memory_mb_used=12)
- try:
- self.scheduler.driver._live_migration_dest_check(self.context,
- i_ref,
- 'somewhere')
- except exception.NotEmpty, e:
- c = (e.message.find('Unable to migrate') >= 0)
+ self.assertRaises(exception.MigrationError,
+ self.scheduler.driver._live_migration_dest_check,
+ self.context, i_ref, 'somewhere')
- self.assertTrue(c)
db.instance_destroy(self.context, instance_id)
db.service_destroy(self.context, s_ref['id'])
@@ -837,14 +817,10 @@ class SimpleDriverTestCase(test.TestCase):
"args": {'filename': fpath}})
self.mox.ReplayAll()
- try:
- self.scheduler.driver._live_migration_common_check(self.context,
- i_ref,
- dest)
- except exception.Invalid, e:
- c = (e.message.find('does not exist') >= 0)
+ self.assertRaises(exception.SourceHostUnavailable,
+ self.scheduler.driver._live_migration_common_check,
+ self.context, i_ref, dest)
- self.assertTrue(c)
db.instance_destroy(self.context, instance_id)
db.service_destroy(self.context, s_ref['id'])
@@ -865,14 +841,10 @@ class SimpleDriverTestCase(test.TestCase):
driver.mounted_on_same_shared_storage(mox.IgnoreArg(), i_ref, dest)
self.mox.ReplayAll()
- try:
- self.scheduler.driver._live_migration_common_check(self.context,
- i_ref,
- dest)
- except exception.Invalid, e:
- c = (e.message.find(_('Different hypervisor type')) >= 0)
+ self.assertRaises(exception.InvalidHypervisorType,
+ self.scheduler.driver._live_migration_common_check,
+ self.context, i_ref, dest)
- self.assertTrue(c)
db.instance_destroy(self.context, instance_id)
db.service_destroy(self.context, s_ref['id'])
db.service_destroy(self.context, s_ref2['id'])
@@ -895,14 +867,10 @@ class SimpleDriverTestCase(test.TestCase):
driver.mounted_on_same_shared_storage(mox.IgnoreArg(), i_ref, dest)
self.mox.ReplayAll()
- try:
- self.scheduler.driver._live_migration_common_check(self.context,
- i_ref,
- dest)
- except exception.Invalid, e:
- c = (e.message.find(_('Older hypervisor version')) >= 0)
+ self.assertRaises(exception.DestinationHypervisorTooOld,
+ self.scheduler.driver._live_migration_common_check,
+ self.context, i_ref, dest)
- self.assertTrue(c)
db.instance_destroy(self.context, instance_id)
db.service_destroy(self.context, s_ref['id'])
db.service_destroy(self.context, s_ref2['id'])
@@ -968,7 +936,7 @@ class FakeRerouteCompute(api.reroute_compute):
def go_boom(self, context, instance):
- raise exception.InstanceNotFound("boom message", instance)
+ raise exception.InstanceNotFound(instance_id=instance)
def found_instance(self, context, instance):
@@ -1017,11 +985,8 @@ class ZoneRedirectTest(test.TestCase):
def test_routing_flags(self):
FLAGS.enable_zone_routing = False
decorator = FakeRerouteCompute("foo")
- try:
- result = decorator(go_boom)(None, None, 1)
- self.assertFail(_("Should have thrown exception."))
- except exception.InstanceNotFound, e:
- self.assertEquals(e.message, 'boom message')
+ self.assertRaises(exception.InstanceNotFound, decorator(go_boom),
+ None, None, 1)
def test_get_collection_context_and_id(self):
decorator = api.reroute_compute("foo")
diff --git a/nova/tests/test_utils.py b/nova/tests/test_utils.py
index e08d229b0..8f7e83c3e 100644
--- a/nova/tests/test_utils.py
+++ b/nova/tests/test_utils.py
@@ -17,9 +17,9 @@
import os
import tempfile
+from nova import exception
from nova import test
from nova import utils
-from nova import exception
class ExecuteTestCase(test.TestCase):
@@ -250,3 +250,28 @@ class GetFromPathTestCase(test.TestCase):
input = {'a': [1, 2, {'b': 'b_1'}]}
self.assertEquals([1, 2, {'b': 'b_1'}], f(input, "a"))
self.assertEquals(['b_1'], f(input, "a/b"))
+
+
+class GenericUtilsTestCase(test.TestCase):
+ def test_parse_server_string(self):
+ result = utils.parse_server_string('::1')
+ self.assertEqual(('::1', ''), result)
+ result = utils.parse_server_string('[::1]:8773')
+ self.assertEqual(('::1', '8773'), result)
+ result = utils.parse_server_string('2001:db8::192.168.1.1')
+ self.assertEqual(('2001:db8::192.168.1.1', ''), result)
+ result = utils.parse_server_string('[2001:db8::192.168.1.1]:8773')
+ self.assertEqual(('2001:db8::192.168.1.1', '8773'), result)
+ result = utils.parse_server_string('192.168.1.1')
+ self.assertEqual(('192.168.1.1', ''), result)
+ result = utils.parse_server_string('192.168.1.2:8773')
+ self.assertEqual(('192.168.1.2', '8773'), result)
+ result = utils.parse_server_string('192.168.1.3')
+ self.assertEqual(('192.168.1.3', ''), result)
+ result = utils.parse_server_string('www.example.com:8443')
+ self.assertEqual(('www.example.com', '8443'), result)
+ result = utils.parse_server_string('www.example.com')
+ self.assertEqual(('www.example.com', ''), result)
+ # error case
+ result = utils.parse_server_string('www.exa:mple.com:8443')
+ self.assertEqual(('', ''), result)
diff --git a/nova/tests/test_virt.py b/nova/tests/test_virt.py
index aeaea91c7..1311ba361 100644
--- a/nova/tests/test_virt.py
+++ b/nova/tests/test_virt.py
@@ -31,9 +31,7 @@ from nova import test
from nova import utils
from nova.api.ec2 import cloud
from nova.auth import manager
-from nova.compute import manager as compute_manager
from nova.compute import power_state
-from nova.db.sqlalchemy import models
from nova.virt import libvirt_conn
libvirt = None
@@ -46,6 +44,27 @@ def _concurrency(wait, done, target):
done.send()
+def _create_network_info(count=1, ipv6=None):
+ if ipv6 is None:
+ ipv6 = FLAGS.use_ipv6
+ fake = 'fake'
+ fake_ip = '0.0.0.0/0'
+ fake_ip_2 = '0.0.0.1/0'
+ fake_ip_3 = '0.0.0.1/0'
+ network = {'gateway': fake,
+ 'gateway_v6': fake,
+ 'bridge': fake,
+ 'cidr': fake_ip,
+ 'cidr_v6': fake_ip}
+ mapping = {'mac': fake,
+ 'ips': [{'ip': fake_ip}, {'ip': fake_ip}]}
+ if ipv6:
+ mapping['ip6s'] = [{'ip': fake_ip},
+ {'ip': fake_ip_2},
+ {'ip': fake_ip_3}]
+ return [(network, mapping) for x in xrange(0, count)]
+
+
class CacheConcurrencyTestCase(test.TestCase):
def setUp(self):
super(CacheConcurrencyTestCase, self).setUp()
@@ -194,6 +213,37 @@ class LibvirtConnTestCase(test.TestCase):
return db.service_create(context.get_admin_context(), service_ref)
+ def test_preparing_xml_info(self):
+ conn = libvirt_conn.LibvirtConnection(True)
+ instance_ref = db.instance_create(self.context, self.test_instance)
+
+ result = conn._prepare_xml_info(instance_ref, False)
+ self.assertFalse(result['nics'])
+
+ result = conn._prepare_xml_info(instance_ref, False,
+ _create_network_info())
+ self.assertTrue(len(result['nics']) == 1)
+
+ result = conn._prepare_xml_info(instance_ref, False,
+ _create_network_info(2))
+ self.assertTrue(len(result['nics']) == 2)
+
+ def test_get_nic_for_xml_v4(self):
+ conn = libvirt_conn.LibvirtConnection(True)
+ network, mapping = _create_network_info()[0]
+ self.flags(use_ipv6=False)
+ params = conn._get_nic_for_xml(network, mapping)['extra_params']
+ self.assertTrue(params.find('PROJNETV6') == -1)
+ self.assertTrue(params.find('PROJMASKV6') == -1)
+
+ def test_get_nic_for_xml_v6(self):
+ conn = libvirt_conn.LibvirtConnection(True)
+ network, mapping = _create_network_info()[0]
+ self.flags(use_ipv6=True)
+ params = conn._get_nic_for_xml(network, mapping)['extra_params']
+ self.assertTrue(params.find('PROJNETV6') > -1)
+ self.assertTrue(params.find('PROJMASKV6') > -1)
+
def test_xml_and_uri_no_ramdisk_no_kernel(self):
instance_data = dict(self.test_instance)
self._check_xml_and_uri(instance_data,
@@ -229,6 +279,22 @@ class LibvirtConnTestCase(test.TestCase):
instance_data = dict(self.test_instance)
self._check_xml_and_container(instance_data)
+ def test_multi_nic(self):
+ instance_data = dict(self.test_instance)
+ network_info = _create_network_info(2)
+ conn = libvirt_conn.LibvirtConnection(True)
+ instance_ref = db.instance_create(self.context, instance_data)
+ xml = conn.to_xml(instance_ref, False, network_info)
+ tree = xml_to_tree(xml)
+ interfaces = tree.findall("./devices/interface")
+ self.assertEquals(len(interfaces), 2)
+ parameters = interfaces[0].findall('./filterref/parameter')
+ self.assertEquals(interfaces[0].get('type'), 'bridge')
+ self.assertEquals(parameters[0].get('name'), 'IP')
+ self.assertEquals(parameters[0].get('value'), '0.0.0.0/0')
+ self.assertEquals(parameters[1].get('name'), 'DHCPSERVER')
+ self.assertEquals(parameters[1].get('value'), 'fake')
+
def _check_xml_and_container(self, instance):
user_context = context.RequestContext(project=self.project,
user=self.user)
@@ -327,19 +393,13 @@ class LibvirtConnTestCase(test.TestCase):
check = (lambda t: t.find('./os/initrd'), None)
check_list.append(check)
+ parameter = './devices/interface/filterref/parameter'
common_checks = [
(lambda t: t.find('.').tag, 'domain'),
- (lambda t: t.find(
- './devices/interface/filterref/parameter').get('name'), 'IP'),
- (lambda t: t.find(
- './devices/interface/filterref/parameter').get(
- 'value'), '10.11.12.13'),
- (lambda t: t.findall(
- './devices/interface/filterref/parameter')[1].get(
- 'name'), 'DHCPSERVER'),
- (lambda t: t.findall(
- './devices/interface/filterref/parameter')[1].get(
- 'value'), '10.0.0.1'),
+ (lambda t: t.find(parameter).get('name'), 'IP'),
+ (lambda t: t.find(parameter).get('value'), '10.11.12.13'),
+ (lambda t: t.findall(parameter)[1].get('name'), 'DHCPSERVER'),
+ (lambda t: t.findall(parameter)[1].get('value'), '10.0.0.1'),
(lambda t: t.find('./devices/serial/source').get(
'path').split('/')[1], 'console.log'),
(lambda t: t.find('./memory').text, '2097152')]
@@ -451,7 +511,7 @@ class LibvirtConnTestCase(test.TestCase):
self.mox.ReplayAll()
conn = libvirt_conn.LibvirtConnection(False)
- self.assertRaises(exception.Invalid,
+ self.assertRaises(exception.ComputeServiceUnavailable,
conn.update_available_resource,
self.context, 'dummy')
@@ -549,6 +609,48 @@ class LibvirtConnTestCase(test.TestCase):
db.volume_destroy(self.context, volume_ref['id'])
db.instance_destroy(self.context, instance_ref['id'])
+ def test_spawn_with_network_info(self):
+ # Skip if non-libvirt environment
+ if not self.lazy_load_library_exists():
+ return
+
+ # Preparing mocks
+ def fake_none(self, instance):
+ return
+
+ self.create_fake_libvirt_mock()
+ instance = db.instance_create(self.context, self.test_instance)
+
+ # Start test
+ self.mox.ReplayAll()
+ conn = libvirt_conn.LibvirtConnection(False)
+ conn.firewall_driver.setattr('setup_basic_filtering', fake_none)
+ conn.firewall_driver.setattr('prepare_instance_filter', fake_none)
+
+ network = db.project_get_network(context.get_admin_context(),
+ self.project.id)
+ ip_dict = {'ip': self.test_ip,
+ 'netmask': network['netmask'],
+ 'enabled': '1'}
+ mapping = {'label': network['label'],
+ 'gateway': network['gateway'],
+ 'mac': instance['mac_address'],
+ 'dns': [network['dns']],
+ 'ips': [ip_dict]}
+ network_info = [(network, mapping)]
+
+ try:
+ conn.spawn(instance, network_info)
+ except Exception, e:
+ count = (0 <= e.message.find('Unexpected method call'))
+
+ self.assertTrue(count)
+
+ def test_get_host_ip_addr(self):
+ conn = libvirt_conn.LibvirtConnection(False)
+ ip = conn.get_host_ip_addr()
+ self.assertEquals(ip, FLAGS.my_ip)
+
def tearDown(self):
self.manager.delete_project(self.project)
self.manager.delete_user(self.user)
@@ -614,11 +716,15 @@ class IptablesFirewallTestCase(test.TestCase):
'# Completed on Tue Jan 18 23:47:56 2011',
]
+ def _create_instance_ref(self):
+ return db.instance_create(self.context,
+ {'user_id': 'fake',
+ 'project_id': 'fake',
+ 'mac_address': '56:12:12:12:12:12',
+ 'instance_type_id': 1})
+
def test_static_filters(self):
- instance_ref = db.instance_create(self.context,
- {'user_id': 'fake',
- 'project_id': 'fake',
- 'mac_address': '56:12:12:12:12:12'})
+ instance_ref = self._create_instance_ref()
ip = '10.11.12.13'
network_ref = db.project_get_network(self.context,
@@ -729,6 +835,40 @@ class IptablesFirewallTestCase(test.TestCase):
"TCP port 80/81 acceptance rule wasn't added")
db.instance_destroy(admin_ctxt, instance_ref['id'])
+ def test_filters_for_instance_with_ip_v6(self):
+ self.flags(use_ipv6=True)
+ network_info = _create_network_info()
+ rulesv4, rulesv6 = self.fw._filters_for_instance("fake", network_info)
+ self.assertEquals(len(rulesv4), 2)
+ self.assertEquals(len(rulesv6), 3)
+
+ def test_filters_for_instance_without_ip_v6(self):
+ self.flags(use_ipv6=False)
+ network_info = _create_network_info()
+ rulesv4, rulesv6 = self.fw._filters_for_instance("fake", network_info)
+ self.assertEquals(len(rulesv4), 2)
+ self.assertEquals(len(rulesv6), 0)
+
+ def multinic_iptables_test(self):
+ ipv4_rules_per_network = 2
+ ipv6_rules_per_network = 3
+ networks_count = 5
+ instance_ref = self._create_instance_ref()
+ network_info = _create_network_info(networks_count)
+ ipv4_len = len(self.fw.iptables.ipv4['filter'].rules)
+ ipv6_len = len(self.fw.iptables.ipv6['filter'].rules)
+ inst_ipv4, inst_ipv6 = self.fw.instance_rules(instance_ref,
+ network_info)
+ self.fw.add_filters_for_instance(instance_ref, network_info)
+ ipv4 = self.fw.iptables.ipv4['filter'].rules
+ ipv6 = self.fw.iptables.ipv6['filter'].rules
+ ipv4_network_rules = len(ipv4) - len(inst_ipv4) - ipv4_len
+ ipv6_network_rules = len(ipv6) - len(inst_ipv6) - ipv6_len
+ self.assertEquals(ipv4_network_rules,
+ ipv4_rules_per_network * networks_count)
+ self.assertEquals(ipv6_network_rules,
+ ipv6_rules_per_network * networks_count)
+
class NWFilterTestCase(test.TestCase):
def setUp(self):
@@ -810,6 +950,28 @@ class NWFilterTestCase(test.TestCase):
return db.security_group_get_by_name(self.context, 'fake', 'testgroup')
+ def _create_instance(self):
+ return db.instance_create(self.context,
+ {'user_id': 'fake',
+ 'project_id': 'fake',
+ 'mac_address': '00:A0:C9:14:C8:29',
+ 'instance_type_id': 1})
+
+ def _create_instance_type(self, params={}):
+ """Create a test instance"""
+ context = self.context.elevated()
+ inst = {}
+ inst['name'] = 'm1.small'
+ inst['memory_mb'] = '1024'
+ inst['vcpus'] = '1'
+ inst['local_gb'] = '20'
+ inst['flavorid'] = '1'
+ inst['swap'] = '2048'
+ inst['rxtx_quota'] = 100
+ inst['rxtx_cap'] = 200
+ inst.update(params)
+ return db.instance_type_create(context, inst)['id']
+
def test_creates_base_rule_first(self):
# These come pre-defined by libvirt
self.defined_filters = ['no-mac-spoofing',
@@ -838,24 +1000,18 @@ class NWFilterTestCase(test.TestCase):
self.fake_libvirt_connection.nwfilterDefineXML = _filterDefineXMLMock
- instance_ref = db.instance_create(self.context,
- {'user_id': 'fake',
- 'project_id': 'fake',
- 'mac_address': '00:A0:C9:14:C8:29'})
+ instance_ref = self._create_instance()
inst_id = instance_ref['id']
ip = '10.11.12.13'
- network_ref = db.project_get_network(self.context,
- 'fake')
-
- fixed_ip = {'address': ip,
- 'network_id': network_ref['id']}
+ network_ref = db.project_get_network(self.context, 'fake')
+ fixed_ip = {'address': ip, 'network_id': network_ref['id']}
admin_ctxt = context.get_admin_context()
db.fixed_ip_create(admin_ctxt, fixed_ip)
db.fixed_ip_update(admin_ctxt, ip, {'allocated': True,
- 'instance_id': instance_ref['id']})
+ 'instance_id': inst_id})
def _ensure_all_called():
instance_filter = 'nova-instance-%s-%s' % (instance_ref['name'],
@@ -881,3 +1037,11 @@ class NWFilterTestCase(test.TestCase):
_ensure_all_called()
self.teardown_security_group()
db.instance_destroy(admin_ctxt, instance_ref['id'])
+
+ def test_create_network_filters(self):
+ instance_ref = self._create_instance()
+ network_info = _create_network_info(3)
+ result = self.fw._create_network_filters(instance_ref,
+ network_info,
+ "fake")
+ self.assertEquals(len(result), 3)
diff --git a/nova/tests/test_volume.py b/nova/tests/test_volume.py
index e9d8289aa..236d12434 100644
--- a/nova/tests/test_volume.py
+++ b/nova/tests/test_volume.py
@@ -142,7 +142,7 @@ class VolumeTestCase(test.TestCase):
self.assertEqual(vol['status'], "available")
self.volume.delete_volume(self.context, volume_id)
- self.assertRaises(exception.Error,
+ self.assertRaises(exception.VolumeNotFound,
db.volume_get,
self.context,
volume_id)
diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py
index 375480a2e..6072f5455 100644
--- a/nova/tests/test_xenapi.py
+++ b/nova/tests/test_xenapi.py
@@ -17,6 +17,7 @@
"""Test suite for XenAPI."""
import functools
+import json
import os
import re
import stubout
@@ -665,3 +666,52 @@ class XenAPIDetermineDiskImageTestCase(test.TestCase):
self.fake_instance.image_id = glance_stubs.FakeGlance.IMAGE_VHD
self.fake_instance.kernel_id = None
self.assert_disk_type(vm_utils.ImageType.DISK_VHD)
+
+
+class FakeXenApi(object):
+ """Fake XenApi for testing HostState."""
+
+ class FakeSR(object):
+ def get_record(self, ref):
+ return {'virtual_allocation': 10000,
+ 'physical_utilisation': 20000}
+
+ SR = FakeSR()
+
+
+class FakeSession(object):
+ """Fake Session class for HostState testing."""
+
+ def async_call_plugin(self, *args):
+ return None
+
+ def wait_for_task(self, *args):
+ vm = {'total': 10,
+ 'overhead': 20,
+ 'free': 30,
+ 'free-computed': 40}
+ return json.dumps({'host_memory': vm})
+
+ def get_xenapi(self):
+ return FakeXenApi()
+
+
+class HostStateTestCase(test.TestCase):
+ """Tests HostState, which holds metrics from XenServer that get
+ reported back to the Schedulers."""
+
+ def _fake_safe_find_sr(self, session):
+ """None SR ref since we're ignoring it in FakeSR."""
+ return None
+
+ def test_host_state(self):
+ self.stubs = stubout.StubOutForTesting()
+ self.stubs.Set(vm_utils, 'safe_find_sr', self._fake_safe_find_sr)
+ host_state = xenapi_conn.HostState(FakeSession())
+ stats = host_state._stats
+ self.assertEquals(stats['disk_total'], 10000)
+ self.assertEquals(stats['disk_used'], 20000)
+ self.assertEquals(stats['host_memory_total'], 10)
+ self.assertEquals(stats['host_memory_overhead'], 20)
+ self.assertEquals(stats['host_memory_free'], 30)
+ self.assertEquals(stats['host_memory_free_computed'], 40)
diff --git a/nova/tests/test_zones.py b/nova/tests/test_zones.py
index 688dc704d..e132809dc 100644
--- a/nova/tests/test_zones.py
+++ b/nova/tests/test_zones.py
@@ -78,38 +78,32 @@ class ZoneManagerTestCase(test.TestCase):
def test_service_capabilities(self):
zm = zone_manager.ZoneManager()
- caps = zm.get_zone_capabilities(self, None)
+ caps = zm.get_zone_capabilities(None)
self.assertEquals(caps, {})
zm.update_service_capabilities("svc1", "host1", dict(a=1, b=2))
- caps = zm.get_zone_capabilities(self, None)
+ caps = zm.get_zone_capabilities(None)
self.assertEquals(caps, dict(svc1_a=(1, 1), svc1_b=(2, 2)))
zm.update_service_capabilities("svc1", "host1", dict(a=2, b=3))
- caps = zm.get_zone_capabilities(self, None)
+ caps = zm.get_zone_capabilities(None)
self.assertEquals(caps, dict(svc1_a=(2, 2), svc1_b=(3, 3)))
zm.update_service_capabilities("svc1", "host2", dict(a=20, b=30))
- caps = zm.get_zone_capabilities(self, None)
+ caps = zm.get_zone_capabilities(None)
self.assertEquals(caps, dict(svc1_a=(2, 20), svc1_b=(3, 30)))
zm.update_service_capabilities("svc10", "host1", dict(a=99, b=99))
- caps = zm.get_zone_capabilities(self, None)
+ caps = zm.get_zone_capabilities(None)
self.assertEquals(caps, dict(svc1_a=(2, 20), svc1_b=(3, 30),
svc10_a=(99, 99), svc10_b=(99, 99)))
zm.update_service_capabilities("svc1", "host3", dict(c=5))
- caps = zm.get_zone_capabilities(self, None)
+ caps = zm.get_zone_capabilities(None)
self.assertEquals(caps, dict(svc1_a=(2, 20), svc1_b=(3, 30),
svc1_c=(5, 5), svc10_a=(99, 99),
svc10_b=(99, 99)))
- caps = zm.get_zone_capabilities(self, 'svc1')
- self.assertEquals(caps, dict(svc1_a=(2, 20), svc1_b=(3, 30),
- svc1_c=(5, 5)))
- caps = zm.get_zone_capabilities(self, 'svc10')
- self.assertEquals(caps, dict(svc10_a=(99, 99), svc10_b=(99, 99)))
-
def test_refresh_from_db_replace_existing(self):
zm = zone_manager.ZoneManager()
zone_state = zone_manager.ZoneState()
diff --git a/nova/utils.py b/nova/utils.py
index 76cba1a08..b55e83e5a 100644
--- a/nova/utils.py
+++ b/nova/utils.py
@@ -17,9 +17,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-"""
-System-level utilities and helper functions.
-"""
+"""Utilities and helper functions."""
import base64
import datetime
@@ -43,9 +41,8 @@ from eventlet import event
from eventlet import greenthread
from eventlet import semaphore
from eventlet.green import subprocess
-None
+
from nova import exception
-from nova.exception import ProcessExecutionError
from nova import flags
from nova import log as logging
@@ -56,18 +53,18 @@ FLAGS = flags.FLAGS
def import_class(import_str):
- """Returns a class from a string including module and class"""
+ """Returns a class from a string including module and class."""
mod_str, _sep, class_str = import_str.rpartition('.')
try:
__import__(mod_str)
return getattr(sys.modules[mod_str], class_str)
except (ImportError, ValueError, AttributeError), exc:
LOG.debug(_('Inner Exception: %s'), exc)
- raise exception.NotFound(_('Class %s cannot be found') % class_str)
+ raise exception.ClassNotFound(class_name=class_str)
def import_object(import_str):
- """Returns an object including a module or module and class"""
+ """Returns an object including a module or module and class."""
try:
__import__(import_str)
return sys.modules[import_str]
@@ -99,11 +96,12 @@ def vpn_ping(address, port, timeout=0.05, session_id=None):
cli_id = 64 bit identifier
? = unknown, probably flags/padding
bit 9 was 1 and the rest were 0 in testing
+
"""
if session_id is None:
session_id = random.randint(0, 0xffffffffffffffff)
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
- data = struct.pack("!BQxxxxxx", 0x38, session_id)
+ data = struct.pack('!BQxxxxxx', 0x38, session_id)
sock.sendto(data, (address, port))
sock.settimeout(timeout)
try:
@@ -112,7 +110,7 @@ def vpn_ping(address, port, timeout=0.05, session_id=None):
return False
finally:
sock.close()
- fmt = "!BQxxxxxQxxxx"
+ fmt = '!BQxxxxxQxxxx'
if len(received) != struct.calcsize(fmt):
print struct.calcsize(fmt)
return False
@@ -122,15 +120,8 @@ def vpn_ping(address, port, timeout=0.05, session_id=None):
def fetchfile(url, target):
- LOG.debug(_("Fetching %s") % url)
-# c = pycurl.Curl()
-# fp = open(target, "wb")
-# c.setopt(c.URL, url)
-# c.setopt(c.WRITEDATA, fp)
-# c.perform()
-# c.close()
-# fp.close()
- execute("curl", "--fail", url, "-o", target)
+ LOG.debug(_('Fetching %s') % url)
+ execute('curl', '--fail', url, '-o', target)
def execute(*cmd, **kwargs):
@@ -147,7 +138,7 @@ def execute(*cmd, **kwargs):
while attempts > 0:
attempts -= 1
try:
- LOG.debug(_("Running cmd (subprocess): %s"), ' '.join(cmd))
+ LOG.debug(_('Running cmd (subprocess): %s'), ' '.join(cmd))
env = os.environ.copy()
if addl_env:
env.update(addl_env)
@@ -163,20 +154,21 @@ def execute(*cmd, **kwargs):
result = obj.communicate()
obj.stdin.close()
if obj.returncode:
- LOG.debug(_("Result was %s") % obj.returncode)
+ LOG.debug(_('Result was %s') % obj.returncode)
if type(check_exit_code) == types.IntType \
and obj.returncode != check_exit_code:
(stdout, stderr) = result
- raise ProcessExecutionError(exit_code=obj.returncode,
- stdout=stdout,
- stderr=stderr,
- cmd=' '.join(cmd))
+ raise exception.ProcessExecutionError(
+ exit_code=obj.returncode,
+ stdout=stdout,
+ stderr=stderr,
+ cmd=' '.join(cmd))
return result
- except ProcessExecutionError:
+ except exception.ProcessExecutionError:
if not attempts:
raise
else:
- LOG.debug(_("%r failed. Retrying."), cmd)
+ LOG.debug(_('%r failed. Retrying.'), cmd)
if delay_on_retry:
greenthread.sleep(random.randint(20, 200) / 100.0)
finally:
@@ -188,13 +180,13 @@ def execute(*cmd, **kwargs):
def ssh_execute(ssh, cmd, process_input=None,
addl_env=None, check_exit_code=True):
- LOG.debug(_("Running cmd (SSH): %s"), ' '.join(cmd))
+ LOG.debug(_('Running cmd (SSH): %s'), ' '.join(cmd))
if addl_env:
- raise exception.Error("Environment not supported over SSH")
+ raise exception.Error(_('Environment not supported over SSH'))
if process_input:
# This is (probably) fixable if we need it...
- raise exception.Error("process_input not supported over SSH")
+ raise exception.Error(_('process_input not supported over SSH'))
stdin_stream, stdout_stream, stderr_stream = ssh.exec_command(cmd)
channel = stdout_stream.channel
@@ -212,7 +204,7 @@ def ssh_execute(ssh, cmd, process_input=None,
# exit_status == -1 if no exit code was returned
if exit_status != -1:
- LOG.debug(_("Result was %s") % exit_status)
+ LOG.debug(_('Result was %s') % exit_status)
if check_exit_code and exit_status != 0:
raise exception.ProcessExecutionError(exit_code=exit_status,
stdout=stdout,
@@ -240,9 +232,12 @@ def default_flagfile(filename='nova.conf'):
# turn relative filename into an absolute path
script_dir = os.path.dirname(inspect.stack()[-1][1])
filename = os.path.abspath(os.path.join(script_dir, filename))
- if os.path.exists(filename):
- flagfile = ['--flagfile=%s' % filename]
- sys.argv = sys.argv[:1] + flagfile + sys.argv[1:]
+ if not os.path.exists(filename):
+ filename = "./nova.conf"
+ if not os.path.exists(filename):
+ filename = '/etc/nova/nova.conf'
+ flagfile = ['--flagfile=%s' % filename]
+ sys.argv = sys.argv[:1] + flagfile + sys.argv[1:]
def debug(arg):
@@ -251,7 +246,7 @@ def debug(arg):
def runthis(prompt, *cmd, **kwargs):
- LOG.debug(_("Running %s"), (" ".join(cmd)))
+ LOG.debug(_('Running %s'), (' '.join(cmd)))
rv, err = execute(*cmd, **kwargs)
@@ -266,48 +261,49 @@ def generate_mac():
random.randint(0x00, 0x7f),
random.randint(0x00, 0xff),
random.randint(0x00, 0xff)]
- return ':'.join(map(lambda x: "%02x" % x, mac))
+ return ':'.join(map(lambda x: '%02x' % x, mac))
# Default symbols to use for passwords. Avoids visually confusing characters.
# ~6 bits per symbol
-DEFAULT_PASSWORD_SYMBOLS = ("23456789" # Removed: 0,1
- "ABCDEFGHJKLMNPQRSTUVWXYZ" # Removed: I, O
- "abcdefghijkmnopqrstuvwxyz") # Removed: l
+DEFAULT_PASSWORD_SYMBOLS = ('23456789' # Removed: 0,1
+ 'ABCDEFGHJKLMNPQRSTUVWXYZ' # Removed: I, O
+ 'abcdefghijkmnopqrstuvwxyz') # Removed: l
# ~5 bits per symbol
-EASIER_PASSWORD_SYMBOLS = ("23456789" # Removed: 0, 1
- "ABCDEFGHJKLMNPQRSTUVWXYZ") # Removed: I, O
+EASIER_PASSWORD_SYMBOLS = ('23456789' # Removed: 0, 1
+ 'ABCDEFGHJKLMNPQRSTUVWXYZ') # Removed: I, O
def generate_password(length=20, symbols=DEFAULT_PASSWORD_SYMBOLS):
"""Generate a random password from the supplied symbols.
Believed to be reasonably secure (with a reasonable password length!)
+
"""
r = random.SystemRandom()
- return "".join([r.choice(symbols) for _i in xrange(length)])
+ return ''.join([r.choice(symbols) for _i in xrange(length)])
def last_octet(address):
- return int(address.split(".")[-1])
+ return int(address.split('.')[-1])
def get_my_linklocal(interface):
try:
- if_str = execute("ip", "-f", "inet6", "-o", "addr", "show", interface)
- condition = "\s+inet6\s+([0-9a-f:]+)/\d+\s+scope\s+link"
+ if_str = execute('ip', '-f', 'inet6', '-o', 'addr', 'show', interface)
+ condition = '\s+inet6\s+([0-9a-f:]+)/\d+\s+scope\s+link'
links = [re.search(condition, x) for x in if_str[0].split('\n')]
address = [w.group(1) for w in links if w is not None]
if address[0] is not None:
return address[0]
else:
- raise exception.Error(_("Link Local address is not found.:%s")
+ raise exception.Error(_('Link Local address is not found.:%s')
% if_str)
except Exception as ex:
raise exception.Error(_("Couldn't get Link Local IP of %(interface)s"
- " :%(ex)s") % locals())
+ " :%(ex)s") % locals())
def to_global_ipv6(prefix, mac):
@@ -319,15 +315,15 @@ def to_global_ipv6(prefix, mac):
return (mac64_addr ^ netaddr.IPAddress('::0200:0:0:0') | maskIP).\
format()
except TypeError:
- raise TypeError(_("Bad mac for to_global_ipv6: %s") % mac)
+ raise TypeError(_('Bad mac for to_global_ipv6: %s') % mac)
def to_mac(ipv6_address):
address = netaddr.IPAddress(ipv6_address)
- mask1 = netaddr.IPAddress("::ffff:ffff:ffff:ffff")
- mask2 = netaddr.IPAddress("::0200:0:0:0")
+ mask1 = netaddr.IPAddress('::ffff:ffff:ffff:ffff')
+ mask2 = netaddr.IPAddress('::0200:0:0:0')
mac64 = netaddr.EUI(int(address & mask1 ^ mask2)).words
- return ":".join(["%02x" % i for i in mac64[0:3] + mac64[5:8]])
+ return ':'.join(['%02x' % i for i in mac64[0:3] + mac64[5:8]])
def utcnow():
@@ -341,7 +337,7 @@ utcnow.override_time = None
def is_older_than(before, seconds):
- """Return True if before is older than seconds"""
+ """Return True if before is older than seconds."""
return utcnow() - before > datetime.timedelta(seconds=seconds)
@@ -379,7 +375,7 @@ def isotime(at=None):
def parse_isotime(timestr):
- """Turn an iso formatted time back into a datetime"""
+ """Turn an iso formatted time back into a datetime."""
return datetime.datetime.strptime(timestr, TIME_FORMAT)
@@ -433,16 +429,19 @@ class LazyPluggable(object):
class LoopingCallDone(Exception):
- """The poll-function passed to LoopingCall can raise this exception to
+ """Exception to break out and stop a LoopingCall.
+
+ The poll-function passed to LoopingCall can raise this exception to
break out of the loop normally. This is somewhat analogous to
StopIteration.
An optional return-value can be included as the argument to the exception;
this return-value will be returned by LoopingCall.wait()
+
"""
def __init__(self, retvalue=True):
- """:param retvalue: Value that LoopingCall.wait() should return"""
+ """:param retvalue: Value that LoopingCall.wait() should return."""
self.retvalue = retvalue
@@ -493,7 +492,7 @@ def xhtml_escape(value):
http://github.com/facebook/tornado/blob/master/tornado/escape.py
"""
- return saxutils.escape(value, {'"': "&quot;"})
+ return saxutils.escape(value, {'"': '&quot;'})
def utf8(value):
@@ -504,7 +503,7 @@ def utf8(value):
"""
if isinstance(value, unicode):
- return value.encode("utf-8")
+ return value.encode('utf-8')
assert isinstance(value, str)
return value
@@ -554,7 +553,7 @@ class _NoopContextManager(object):
def synchronized(name, external=False):
- """Synchronization decorator
+ """Synchronization decorator.
Decorating a method like so:
@synchronized('mylock')
@@ -578,6 +577,7 @@ def synchronized(name, external=False):
multiple processes. This means that if two different workers both run a
a method decorated with @synchronized('mylock', external=True), only one
of them will execute at a time.
+
"""
def wrap(f):
@@ -590,13 +590,13 @@ def synchronized(name, external=False):
_semaphores[name] = semaphore.Semaphore()
sem = _semaphores[name]
LOG.debug(_('Attempting to grab semaphore "%(lock)s" for method '
- '"%(method)s"...' % {"lock": name,
- "method": f.__name__}))
+ '"%(method)s"...' % {'lock': name,
+ 'method': f.__name__}))
with sem:
if external:
LOG.debug(_('Attempting to grab file lock "%(lock)s" for '
'method "%(method)s"...' %
- {"lock": name, "method": f.__name__}))
+ {'lock': name, 'method': f.__name__}))
lock_file_path = os.path.join(FLAGS.lock_path,
'nova-%s.lock' % name)
lock = lockfile.FileLock(lock_file_path)
@@ -617,21 +617,23 @@ def synchronized(name, external=False):
def get_from_path(items, path):
- """ Returns a list of items matching the specified path. Takes an
- XPath-like expression e.g. prop1/prop2/prop3, and for each item in items,
- looks up items[prop1][prop2][prop3]. Like XPath, if any of the
+ """Returns a list of items matching the specified path.
+
+ Takes an XPath-like expression e.g. prop1/prop2/prop3, and for each item
+ in items, looks up items[prop1][prop2][prop3]. Like XPath, if any of the
intermediate results are lists it will treat each list item individually.
A 'None' in items or any child expressions will be ignored, this function
will not throw because of None (anywhere) in items. The returned list
- will contain no None values."""
+ will contain no None values.
+ """
if path is None:
- raise exception.Error("Invalid mini_xpath")
+ raise exception.Error('Invalid mini_xpath')
- (first_token, sep, remainder) = path.partition("/")
+ (first_token, sep, remainder) = path.partition('/')
- if first_token == "":
- raise exception.Error("Invalid mini_xpath")
+ if first_token == '':
+ raise exception.Error('Invalid mini_xpath')
results = []
@@ -645,7 +647,7 @@ def get_from_path(items, path):
for item in items:
if item is None:
continue
- get_method = getattr(item, "get", None)
+ get_method = getattr(item, 'get', None)
if get_method is None:
continue
child = get_method(first_token)
@@ -666,7 +668,7 @@ def get_from_path(items, path):
def flatten_dict(dict_, flattened=None):
- """Recursively flatten a nested dictionary"""
+ """Recursively flatten a nested dictionary."""
flattened = flattened or {}
for key, value in dict_.iteritems():
if hasattr(value, 'iteritems'):
@@ -677,9 +679,7 @@ def flatten_dict(dict_, flattened=None):
def partition_dict(dict_, keys):
- """Return two dicts, one containing only `keys` the other containing
- everything but `keys`
- """
+ """Return two dicts, one with `keys` the other with everything else."""
intersection = {}
difference = {}
for key, value in dict_.iteritems():
@@ -691,9 +691,7 @@ def partition_dict(dict_, keys):
def map_dict_keys(dict_, key_map):
- """Return a dictionary in which the dictionaries keys are mapped to
- new keys.
- """
+ """Return a dict in which the dictionaries keys are mapped to new keys."""
mapped = {}
for key, value in dict_.iteritems():
mapped_key = key_map[key] if key in key_map else key
@@ -702,15 +700,45 @@ def map_dict_keys(dict_, key_map):
def subset_dict(dict_, keys):
- """Return a dict that only contains a subset of keys"""
+ """Return a dict that only contains a subset of keys."""
subset = partition_dict(dict_, keys)[0]
return subset
def check_isinstance(obj, cls):
- """Checks that obj is of type cls, and lets PyLint infer types"""
+ """Checks that obj is of type cls, and lets PyLint infer types."""
if isinstance(obj, cls):
return obj
- raise Exception(_("Expected object of type: %s") % (str(cls)))
+ raise Exception(_('Expected object of type: %s') % (str(cls)))
# TODO(justinsb): Can we make this better??
return cls() # Ugly PyLint hack
+
+
+def parse_server_string(server_str):
+ """
+ Parses the given server_string and returns a list of host and port.
+ If it's not a combination of host part and port, the port element
+ is a null string. If the input is invalid expression, return a null
+ list.
+ """
+ try:
+ # First of all, exclude pure IPv6 address (w/o port).
+ if netaddr.valid_ipv6(server_str):
+ return (server_str, '')
+
+ # Next, check if this is IPv6 address with a port number combination.
+ if server_str.find("]:") != -1:
+ (address, port) = server_str.replace('[', '', 1).split(']:')
+ return (address, port)
+
+ # Third, check if this is a combination of an address and a port
+ if server_str.find(':') == -1:
+ return (server_str, '')
+
+ # This must be a combination of an address and a port
+ (address, port) = server_str.split(':')
+ return (address, port)
+
+ except:
+ LOG.debug(_('Invalid server_string: %s' % server_str))
+ return ('', '')
diff --git a/nova/version.py b/nova/version.py
index c43d12cf8..1f8d08e8c 100644
--- a/nova/version.py
+++ b/nova/version.py
@@ -21,9 +21,9 @@ except ImportError:
'revision_id': 'LOCALREVISION',
'revno': 0}
+
NOVA_VERSION = ['2011', '3']
YEAR, COUNT = NOVA_VERSION
-
FINAL = False # This becomes true at Release Candidate time
@@ -39,8 +39,8 @@ def version_string():
def vcs_version_string():
- return "%s:%s" % (version_info['branch_nick'], version_info['revision_id'])
+ return '%s:%s' % (version_info['branch_nick'], version_info['revision_id'])
def version_string_with_vcs():
- return "%s-%s" % (canonical_version_string(), vcs_version_string())
+ return '%s-%s' % (canonical_version_string(), vcs_version_string())
diff --git a/nova/virt/fake.py b/nova/virt/fake.py
index c3d5230df..5ac376e46 100644
--- a/nova/virt/fake.py
+++ b/nova/virt/fake.py
@@ -288,8 +288,7 @@ class FakeConnection(driver.ComputeDriver):
knowledge of the instance
"""
if instance_name not in self.instances:
- raise exception.NotFound(_("Instance %s Not Found")
- % instance_name)
+ raise exception.InstanceNotFound(instance_id=instance_name)
i = self.instances[instance_name]
return {'state': i.state,
'max_mem': 0,
@@ -368,7 +367,7 @@ class FakeConnection(driver.ComputeDriver):
return [0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L]
def get_console_output(self, instance):
- return 'FAKE CONSOLE OUTPUT'
+ return 'FAKE CONSOLE\xffOUTPUT'
def get_ajax_console(self, instance):
return {'token': 'FAKETOKEN',
diff --git a/nova/virt/hyperv.py b/nova/virt/hyperv.py
index 13f403a66..1142e97a4 100644
--- a/nova/virt/hyperv.py
+++ b/nova/virt/hyperv.py
@@ -143,8 +143,7 @@ class HyperVConnection(driver.ComputeDriver):
""" Create a new VM and start it."""
vm = self._lookup(instance.name)
if vm is not None:
- raise exception.Duplicate(_('Attempt to create duplicate vm %s') %
- instance.name)
+ raise exception.InstanceExists(name=instance.name)
user = manager.AuthManager().get_user(instance['user_id'])
project = manager.AuthManager().get_project(instance['project_id'])
@@ -368,7 +367,7 @@ class HyperVConnection(driver.ComputeDriver):
"""Reboot the specified instance."""
vm = self._lookup(instance.name)
if vm is None:
- raise exception.NotFound('instance not present %s' % instance.name)
+ raise exception.InstanceNotFound(instance_id=instance.id)
self._set_vm_state(instance.name, 'Reboot')
def destroy(self, instance):
@@ -412,7 +411,7 @@ class HyperVConnection(driver.ComputeDriver):
"""Get information about the VM"""
vm = self._lookup(instance_id)
if vm is None:
- raise exception.NotFound('instance not present %s' % instance_id)
+ raise exception.InstanceNotFound(instance_id=instance_id)
vm = self._conn.Msvm_ComputerSystem(ElementName=instance_id)[0]
vs_man_svc = self._conn.Msvm_VirtualSystemManagementService()[0]
vmsettings = vm.associators(
@@ -474,14 +473,12 @@ class HyperVConnection(driver.ComputeDriver):
def attach_volume(self, instance_name, device_path, mountpoint):
vm = self._lookup(instance_name)
if vm is None:
- raise exception.NotFound('Cannot attach volume to missing %s vm'
- % instance_name)
+ raise exception.InstanceNotFound(instance_id=instance_name)
def detach_volume(self, instance_name, mountpoint):
vm = self._lookup(instance_name)
if vm is None:
- raise exception.NotFound('Cannot detach volume from missing %s '
- % instance_name)
+ raise exception.InstanceNotFound(instance_id=instance_name)
def poll_rescued_instances(self, timeout):
pass
@@ -489,3 +486,11 @@ class HyperVConnection(driver.ComputeDriver):
def update_available_resource(self, ctxt, host):
"""This method is supported only by libvirt."""
return
+
+ def update_host_status(self):
+ """See xenapi_conn.py implementation."""
+ pass
+
+ def get_host_stats(self, refresh=False):
+ """See xenapi_conn.py implementation."""
+ pass
diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py
index d212be3c9..555e44ce2 100644
--- a/nova/virt/libvirt_conn.py
+++ b/nova/virt/libvirt_conn.py
@@ -154,8 +154,8 @@ def _get_net_and_prefixlen(cidr):
def _get_ip_version(cidr):
- net = IPy.IP(cidr)
- return int(net.version())
+ net = IPy.IP(cidr)
+ return int(net.version())
def _get_network_info(instance):
@@ -165,9 +165,10 @@ def _get_network_info(instance):
ip_addresses = db.fixed_ip_get_all_by_instance(admin_context,
instance['id'])
-
networks = db.network_get_all_by_instance(admin_context,
instance['id'])
+ flavor = db.instance_type_get_by_id(admin_context,
+ instance['instance_type_id'])
network_info = []
for network in networks:
@@ -191,7 +192,9 @@ def _get_network_info(instance):
mapping = {
'label': network['label'],
'gateway': network['gateway'],
+ 'broadcast': network['broadcast'],
'mac': instance['mac_address'],
+ 'rxtx_cap': flavor['rxtx_cap'],
'dns': [network['dns']],
'ips': [ip_dict(ip) for ip in network_ips]}
@@ -309,19 +312,10 @@ class LibvirtConnection(driver.ComputeDriver):
def destroy(self, instance, cleanup=True):
instance_name = instance['name']
- # TODO(justinsb): Refactor all lookupByName calls for error-handling
try:
- virt_dom = self._conn.lookupByName(instance_name)
- except libvirt.libvirtError as e:
- errcode = e.get_error_code()
- if errcode == libvirt.VIR_ERR_NO_DOMAIN:
- virt_dom = None
- else:
- LOG.warning(_("Error from libvirt during lookup of "
- "%(instance_name)s. Code=%(errcode)s "
- "Error=%(e)s") %
- locals())
- raise
+ virt_dom = self._lookup_by_name(instance_name)
+ except exception.NotFound:
+ virt_dom = None
# If the instance is already terminated, we're still happy
# Otherwise, destroy it
@@ -359,28 +353,19 @@ class LibvirtConnection(driver.ComputeDriver):
locals())
raise
- # We'll save this for when we do shutdown,
- # instead of destroy - but destroy returns immediately
- timer = utils.LoopingCall(f=None)
+ def _wait_for_destroy():
+ """Called at an interval until the VM is gone."""
+ instance_name = instance['name']
- while True:
try:
- state = self.get_info(instance['name'])['state']
- db.instance_set_state(context.get_admin_context(),
- instance['id'], state)
- if state == power_state.SHUTOFF:
- break
-
- # Let's not hammer on the DB
- time.sleep(1)
- except Exception as ex:
- msg = _("Error encountered when destroying instance '%(id)s': "
- "%(ex)s") % {"id": instance["id"], "ex": ex}
- LOG.debug(msg)
- db.instance_set_state(context.get_admin_context(),
- instance['id'],
- power_state.SHUTOFF)
- break
+ state = self.get_info(instance_name)['state']
+ except exception.NotFound:
+ msg = _("Instance %s destroyed successfully.") % instance_name
+ LOG.info(msg)
+ raise utils.LoopingCallDone
+
+ timer = utils.LoopingCall(_wait_for_destroy)
+ timer.start(interval=0.5, now=True)
self.firewall_driver.unfilter_instance(instance)
@@ -401,7 +386,7 @@ class LibvirtConnection(driver.ComputeDriver):
@exception.wrap_exception
def attach_volume(self, instance_name, device_path, mountpoint):
- virt_dom = self._conn.lookupByName(instance_name)
+ virt_dom = self._lookup_by_name(instance_name)
mount_device = mountpoint.rpartition("/")[2]
if device_path.startswith('/dev/'):
xml = """<disk type='block'>
@@ -419,7 +404,7 @@ class LibvirtConnection(driver.ComputeDriver):
name,
mount_device)
else:
- raise exception.Invalid(_("Invalid device path %s") % device_path)
+ raise exception.InvalidDevicePath(path=device_path)
virt_dom.attachDevice(xml)
@@ -445,11 +430,11 @@ class LibvirtConnection(driver.ComputeDriver):
@exception.wrap_exception
def detach_volume(self, instance_name, mountpoint):
- virt_dom = self._conn.lookupByName(instance_name)
+ virt_dom = self._lookup_by_name(instance_name)
mount_device = mountpoint.rpartition("/")[2]
xml = self._get_disk_xml(virt_dom.XMLDesc(0), mount_device)
if not xml:
- raise exception.NotFound(_("No disk at %s") % mount_device)
+ raise exception.DiskNotFound(location=mount_device)
virt_dom.detachDevice(xml)
@exception.wrap_exception
@@ -462,7 +447,7 @@ class LibvirtConnection(driver.ComputeDriver):
"""
image_service = utils.import_object(FLAGS.image_service)
- virt_dom = self._conn.lookupByName(instance['name'])
+ virt_dom = self._lookup_by_name(instance['name'])
elevated = context.get_admin_context()
base = image_service.show(elevated, instance['image_id'])
@@ -522,31 +507,43 @@ class LibvirtConnection(driver.ComputeDriver):
@exception.wrap_exception
def reboot(self, instance):
+ """Reboot a virtual machine, given an instance reference.
+
+ This method actually destroys and re-creates the domain to ensure the
+ reboot happens, as the guest OS cannot ignore this action.
+
+ """
+ virt_dom = self._conn.lookupByName(instance['name'])
+ # NOTE(itoumsn): Use XML delived from the running instance
+ # instead of using to_xml(instance). This is almost the ultimate
+ # stupid workaround.
+ xml = virt_dom.XMLDesc(0)
+ # NOTE(itoumsn): self.shutdown() and wait instead of self.destroy() is
+ # better because we cannot ensure flushing dirty buffers
+ # in the guest OS. But, in case of KVM, shutdown() does not work...
self.destroy(instance, False)
- xml = self.to_xml(instance)
self.firewall_driver.setup_basic_filtering(instance)
self.firewall_driver.prepare_instance_filter(instance)
self._create_new_domain(xml)
self.firewall_driver.apply_instance_filter(instance)
- timer = utils.LoopingCall(f=None)
-
def _wait_for_reboot():
+ """Called at an interval until the VM is running again."""
+ instance_name = instance['name']
+
try:
- state = self.get_info(instance['name'])['state']
- db.instance_set_state(context.get_admin_context(),
- instance['id'], state)
- if state == power_state.RUNNING:
- LOG.debug(_('instance %s: rebooted'), instance['name'])
- timer.stop()
- except Exception, exn:
- LOG.exception(_('_wait_for_reboot failed: %s'), exn)
- db.instance_set_state(context.get_admin_context(),
- instance['id'],
- power_state.SHUTDOWN)
- timer.stop()
+ state = self.get_info(instance_name)['state']
+ except exception.NotFound:
+ msg = _("During reboot, %s disappeared.") % instance_name
+ LOG.error(msg)
+ raise utils.LoopingCallDone
- timer.f = _wait_for_reboot
+ if state == power_state.RUNNING:
+ msg = _("Instance %s rebooted successfully.") % instance_name
+ LOG.info(msg)
+ raise utils.LoopingCallDone
+
+ timer = utils.LoopingCall(_wait_for_reboot)
return timer.start(interval=0.5, now=True)
@exception.wrap_exception
@@ -566,7 +563,15 @@ class LibvirtConnection(driver.ComputeDriver):
raise exception.ApiError("resume not supported for libvirt")
@exception.wrap_exception
- def rescue(self, instance, callback=None):
+ def rescue(self, instance):
+ """Loads a VM using rescue images.
+
+ A rescue is normally performed when something goes wrong with the
+ primary images and data needs to be corrected/recovered. Rescuing
+ should not edit or over-ride the original image, only allow for
+ data recovery.
+
+ """
self.destroy(instance, False)
xml = self.to_xml(instance, rescue=True)
@@ -576,29 +581,33 @@ class LibvirtConnection(driver.ComputeDriver):
self._create_image(instance, xml, '.rescue', rescue_images)
self._create_new_domain(xml)
- timer = utils.LoopingCall(f=None)
-
def _wait_for_rescue():
+ """Called at an interval until the VM is running again."""
+ instance_name = instance['name']
+
try:
- state = self.get_info(instance['name'])['state']
- db.instance_set_state(None, instance['id'], state)
- if state == power_state.RUNNING:
- LOG.debug(_('instance %s: rescued'), instance['name'])
- timer.stop()
- except Exception, exn:
- LOG.exception(_('_wait_for_rescue failed: %s'), exn)
- db.instance_set_state(None,
- instance['id'],
- power_state.SHUTDOWN)
- timer.stop()
+ state = self.get_info(instance_name)['state']
+ except exception.NotFound:
+ msg = _("During reboot, %s disappeared.") % instance_name
+ LOG.error(msg)
+ raise utils.LoopingCallDone
+
+ if state == power_state.RUNNING:
+ msg = _("Instance %s rescued successfully.") % instance_name
+ LOG.info(msg)
+ raise utils.LoopingCallDone
- timer.f = _wait_for_rescue
+ timer = utils.LoopingCall(_wait_for_rescue)
return timer.start(interval=0.5, now=True)
@exception.wrap_exception
- def unrescue(self, instance, callback=None):
- # NOTE(vish): Because reboot destroys and recreates an instance using
- # the normal xml file, we can just call reboot here
+ def unrescue(self, instance):
+ """Reboot the VM which is being rescued back into primary images.
+
+ Because reboot destroys and re-creates instances, unresue should
+ simply call reboot.
+
+ """
self.reboot(instance)
@exception.wrap_exception
@@ -610,13 +619,9 @@ class LibvirtConnection(driver.ComputeDriver):
@exception.wrap_exception
def spawn(self, instance, network_info=None):
xml = self.to_xml(instance, False, network_info)
- db.instance_set_state(context.get_admin_context(),
- instance['id'],
- power_state.NOSTATE,
- 'launching')
self.firewall_driver.setup_basic_filtering(instance, network_info)
self.firewall_driver.prepare_instance_filter(instance, network_info)
- self._create_image(instance, xml, network_info)
+ self._create_image(instance, xml, network_info=network_info)
domain = self._create_new_domain(xml)
LOG.debug(_("instance %s: is running"), instance['name'])
self.firewall_driver.apply_instance_filter(instance)
@@ -626,25 +631,23 @@ class LibvirtConnection(driver.ComputeDriver):
instance['name'])
domain.setAutostart(1)
- timer = utils.LoopingCall(f=None)
-
def _wait_for_boot():
+ """Called at an interval until the VM is running."""
+ instance_name = instance['name']
+
try:
- state = self.get_info(instance['name'])['state']
- db.instance_set_state(context.get_admin_context(),
- instance['id'], state)
- if state == power_state.RUNNING:
- LOG.debug(_('instance %s: booted'), instance['name'])
- timer.stop()
- except:
- LOG.exception(_('instance %s: failed to boot'),
- instance['name'])
- db.instance_set_state(context.get_admin_context(),
- instance['id'],
- power_state.SHUTDOWN)
- timer.stop()
+ state = self.get_info(instance_name)['state']
+ except exception.NotFound:
+ msg = _("During reboot, %s disappeared.") % instance_name
+ LOG.error(msg)
+ raise utils.LoopingCallDone
- timer.f = _wait_for_boot
+ if state == power_state.RUNNING:
+ msg = _("Instance %s spawned successfully.") % instance_name
+ LOG.info(msg)
+ raise utils.LoopingCallDone
+
+ timer = utils.LoopingCall(_wait_for_boot)
return timer.start(interval=0.5, now=True)
def _flush_xen_console(self, virsh_output):
@@ -710,7 +713,7 @@ class LibvirtConnection(driver.ComputeDriver):
raise Exception(_('Unable to find an open port'))
def get_pty_for_instance(instance_name):
- virt_dom = self._conn.lookupByName(instance_name)
+ virt_dom = self._lookup_by_name(instance_name)
xml = virt_dom.XMLDesc(0)
dom = minidom.parseString(xml)
@@ -732,10 +735,13 @@ class LibvirtConnection(driver.ComputeDriver):
subprocess.Popen(cmd, shell=True)
return {'token': token, 'host': host, 'port': port}
+ def get_host_ip_addr(self):
+ return FLAGS.my_ip
+
@exception.wrap_exception
def get_vnc_console(self, instance):
def get_vnc_port_for_instance(instance_name):
- virt_dom = self._conn.lookupByName(instance_name)
+ virt_dom = self._lookup_by_name(instance_name)
xml = virt_dom.XMLDesc(0)
# TODO: use etree instead of minidom
dom = minidom.parseString(xml)
@@ -957,26 +963,16 @@ class LibvirtConnection(driver.ComputeDriver):
mac_id = mapping['mac'].replace(':', '')
if FLAGS.allow_project_net_traffic:
+ template = "<parameter name=\"%s\"value=\"%s\" />\n"
+ net, mask = _get_net_and_mask(network['cidr'])
+ values = [("PROJNET", net), ("PROJMASK", mask)]
if FLAGS.use_ipv6:
- net, mask = _get_net_and_mask(network['cidr'])
net_v6, prefixlen_v6 = _get_net_and_prefixlen(
network['cidr_v6'])
- extra_params = ("<parameter name=\"PROJNET\" "
- "value=\"%s\" />\n"
- "<parameter name=\"PROJMASK\" "
- "value=\"%s\" />\n"
- "<parameter name=\"PROJNETV6\" "
- "value=\"%s\" />\n"
- "<parameter name=\"PROJMASKV6\" "
- "value=\"%s\" />\n") % \
- (net, mask, net_v6, prefixlen_v6)
- else:
- net, mask = _get_net_and_mask(network['cidr'])
- extra_params = ("<parameter name=\"PROJNET\" "
- "value=\"%s\" />\n"
- "<parameter name=\"PROJMASK\" "
- "value=\"%s\" />\n") % \
- (net, mask)
+ values.extend([("PROJNETV6", net_v6),
+ ("PROJMASKV6", prefixlen_v6)])
+
+ extra_params = "".join([template % value for value in values])
else:
extra_params = "\n"
@@ -994,10 +990,7 @@ class LibvirtConnection(driver.ComputeDriver):
return result
- def to_xml(self, instance, rescue=False, network_info=None):
- # TODO(termie): cache?
- LOG.debug(_('instance %s: starting toXML method'), instance['name'])
-
+ def _prepare_xml_info(self, instance, rescue=False, network_info=None):
# TODO(adiantum) remove network_info creation code
# when multinics will be completed
if not network_info:
@@ -1005,8 +998,7 @@ class LibvirtConnection(driver.ComputeDriver):
nics = []
for (network, mapping) in network_info:
- nics.append(self._get_nic_for_xml(network,
- mapping))
+ nics.append(self._get_nic_for_xml(network, mapping))
# FIXME(vish): stick this in db
inst_type_id = instance['instance_type_id']
inst_type = instance_types.get_instance_type(inst_type_id)
@@ -1038,29 +1030,43 @@ class LibvirtConnection(driver.ComputeDriver):
xml_info['ramdisk'] = xml_info['basepath'] + "/ramdisk"
xml_info['disk'] = xml_info['basepath'] + "/disk"
+ return xml_info
+ def to_xml(self, instance, rescue=False, network_info=None):
+ # TODO(termie): cache?
+ LOG.debug(_('instance %s: starting toXML method'), instance['name'])
+ xml_info = self._prepare_xml_info(instance, rescue, network_info)
xml = str(Template(self.libvirt_xml, searchList=[xml_info]))
- LOG.debug(_('instance %s: finished toXML method'),
- instance['name'])
+ LOG.debug(_('instance %s: finished toXML method'), instance['name'])
return xml
- def get_info(self, instance_name):
- # NOTE(justinsb): When libvirt isn't running / can't connect, we get:
- # libvir: Remote error : unable to connect to
- # '/var/run/libvirt/libvirt-sock', libvirtd may need to be started:
- # No such file or directory
+ def _lookup_by_name(self, instance_name):
+ """Retrieve libvirt domain object given an instance name.
+
+ All libvirt error handling should be handled in this method and
+ relevant nova exceptions should be raised in response.
+
+ """
try:
- virt_dom = self._conn.lookupByName(instance_name)
- except libvirt.libvirtError as e:
- errcode = e.get_error_code()
- if errcode == libvirt.VIR_ERR_NO_DOMAIN:
- raise exception.NotFound(_("Instance %s not found")
- % instance_name)
- LOG.warning(_("Error from libvirt during lookup. "
- "Code=%(errcode)s Error=%(e)s") %
- locals())
- raise
+ return self._conn.lookupByName(instance_name)
+ except libvirt.libvirtError as ex:
+ error_code = ex.get_error_code()
+ if error_code == libvirt.VIR_ERR_NO_DOMAIN:
+ raise exception.InstanceNotFound(instance_id=instance_name)
+
+ msg = _("Error from libvirt while looking up %(instance_name)s: "
+ "[Error Code %(error_code)s] %(ex)s") % locals()
+ raise exception.Error(msg)
+
+ def get_info(self, instance_name):
+ """Retrieve information from libvirt for a specific instance name.
+ If a libvirt error is encountered during lookup, we might raise a
+ NotFound exception or Error exception depending on how severe the
+ libvirt error is.
+
+ """
+ virt_dom = self._lookup_by_name(instance_name)
(state, max_mem, mem, num_cpu, cpu_time) = virt_dom.info()
return {'state': state,
'max_mem': max_mem,
@@ -1097,7 +1103,7 @@ class LibvirtConnection(driver.ComputeDriver):
Returns a list of all block devices for this domain.
"""
- domain = self._conn.lookupByName(instance_name)
+ domain = self._lookup_by_name(instance_name)
# TODO(devcamcar): Replace libxml2 with etree.
xml = domain.XMLDesc(0)
doc = None
@@ -1139,7 +1145,7 @@ class LibvirtConnection(driver.ComputeDriver):
Returns a list of all network interfaces for this instance.
"""
- domain = self._conn.lookupByName(instance_name)
+ domain = self._lookup_by_name(instance_name)
# TODO(devcamcar): Replace libxml2 with etree.
xml = domain.XMLDesc(0)
doc = None
@@ -1305,9 +1311,9 @@ class LibvirtConnection(driver.ComputeDriver):
xml = libxml2.parseDoc(xml)
nodes = xml.xpathEval('//host/cpu')
if len(nodes) != 1:
- raise exception.Invalid(_("Invalid xml. '<cpu>' must be 1,"
- "but %d\n") % len(nodes)
- + xml.serialize())
+ reason = _("'<cpu>' must be 1, but %d\n") % len(nodes)
+ reason += xml.serialize()
+ raise exception.InvalidCPUInfo(reason=reason)
cpu_info = dict()
@@ -1336,9 +1342,8 @@ class LibvirtConnection(driver.ComputeDriver):
tkeys = topology.keys()
if set(tkeys) != set(keys):
ks = ', '.join(keys)
- raise exception.Invalid(_("Invalid xml: topology"
- "(%(topology)s) must have "
- "%(ks)s") % locals())
+ reason = _("topology (%(topology)s) must have %(ks)s")
+ raise exception.InvalidCPUInfo(reason=reason % locals())
feature_nodes = xml.xpathEval('//host/cpu/feature')
features = list()
@@ -1354,7 +1359,7 @@ class LibvirtConnection(driver.ComputeDriver):
Note that this function takes an instance name, not an Instance, so
that it can be called by monitor.
"""
- domain = self._conn.lookupByName(instance_name)
+ domain = self._lookup_by_name(instance_name)
return domain.blockStats(disk)
def interface_stats(self, instance_name, interface):
@@ -1362,7 +1367,7 @@ class LibvirtConnection(driver.ComputeDriver):
Note that this function takes an instance name, not an Instance, so
that it can be called by monitor.
"""
- domain = self._conn.lookupByName(instance_name)
+ domain = self._lookup_by_name(instance_name)
return domain.interfaceStats(interface)
def get_console_pool_info(self, console_type):
@@ -1393,9 +1398,7 @@ class LibvirtConnection(driver.ComputeDriver):
try:
service_ref = db.service_get_all_compute_by_host(ctxt, host)[0]
except exception.NotFound:
- raise exception.Invalid(_("Cannot update compute manager "
- "specific info, because no service "
- "record was found."))
+ raise exception.ComputeServiceUnavailable(host=host)
# Updating host information
dic = {'vcpus': self.get_vcpu_total(),
@@ -1448,7 +1451,7 @@ class LibvirtConnection(driver.ComputeDriver):
raise
if ret <= 0:
- raise exception.Invalid(m % locals())
+ raise exception.InvalidCPUInfo(reason=m % locals())
return
@@ -1558,7 +1561,7 @@ class LibvirtConnection(driver.ComputeDriver):
FLAGS.live_migration_bandwidth)
except Exception:
- recover_method(ctxt, instance_ref)
+ recover_method(ctxt, instance_ref, dest=dest)
raise
# Waiting for completion of live_migration.
@@ -1579,6 +1582,14 @@ class LibvirtConnection(driver.ComputeDriver):
"""See comments of same method in firewall_driver."""
self.firewall_driver.unfilter_instance(instance_ref)
+ def update_host_status(self):
+ """See xenapi_conn.py implementation."""
+ pass
+
+ def get_host_stats(self, refresh=False):
+ """See xenapi_conn.py implementation."""
+ pass
+
class FirewallDriver(object):
def prepare_instance_filter(self, instance, network_info=None):
@@ -1734,11 +1745,16 @@ class NWFilterFirewall(FirewallDriver):
logging.info('ensuring static filters')
self._ensure_static_filters()
+ if instance['image_id'] == str(FLAGS.vpn_image_id):
+ base_filter = 'nova-vpn'
+ else:
+ base_filter = 'nova-base'
+
for (network, mapping) in network_info:
nic_id = mapping['mac'].replace(':', '')
instance_filter_name = self._instance_filter_name(instance, nic_id)
self._define_filter(self._filter_container(instance_filter_name,
- ['nova-base']))
+ [base_filter]))
def _ensure_static_filters(self):
if self.static_filters_configured:
@@ -1749,11 +1765,12 @@ class NWFilterFirewall(FirewallDriver):
'no-ip-spoofing',
'no-arp-spoofing',
'allow-dhcp-server']))
+ self._define_filter(self._filter_container('nova-vpn',
+ ['allow-dhcp-server']))
self._define_filter(self.nova_base_ipv4_filter)
self._define_filter(self.nova_base_ipv6_filter)
self._define_filter(self.nova_dhcp_filter)
self._define_filter(self.nova_ra_filter)
- self._define_filter(self.nova_vpn_filter)
if FLAGS.allow_project_net_traffic:
self._define_filter(self.nova_project_filter)
if FLAGS.use_ipv6:
@@ -1767,14 +1784,6 @@ class NWFilterFirewall(FirewallDriver):
''.join(["<filterref filter='%s'/>" % (f,) for f in filters]))
return xml
- nova_vpn_filter = '''<filter name='nova-vpn' chain='root'>
- <uuid>2086015e-cf03-11df-8c5d-080027c27973</uuid>
- <filterref filter='allow-dhcp-server'/>
- <filterref filter='nova-allow-dhcp-server'/>
- <filterref filter='nova-base-ipv4'/>
- <filterref filter='nova-base-ipv6'/>
- </filter>'''
-
def nova_base_ipv4_filter(self):
retval = "<filter name='nova-base-ipv4' chain='ipv4'>"
for protocol in ['tcp', 'udp', 'icmp']:
@@ -1837,10 +1846,6 @@ class NWFilterFirewall(FirewallDriver):
"""
if not network_info:
network_info = _get_network_info(instance)
- if instance['image_id'] == FLAGS.vpn_image_id:
- base_filter = 'nova-vpn'
- else:
- base_filter = 'nova-base'
ctxt = context.get_admin_context()
@@ -1852,41 +1857,59 @@ class NWFilterFirewall(FirewallDriver):
'nova-base-ipv6',
'nova-allow-dhcp-server']
+ if FLAGS.use_ipv6:
+ networks = [network for (network, _m) in network_info if
+ network['gateway_v6']]
+
+ if networks:
+ instance_secgroup_filter_children.\
+ append('nova-allow-ra-server')
+
for security_group in \
db.security_group_get_by_instance(ctxt, instance['id']):
self.refresh_security_group_rules(security_group['id'])
- instance_secgroup_filter_children += [('nova-secgroup-%s' %
- security_group['id'])]
+ instance_secgroup_filter_children.append('nova-secgroup-%s' %
+ security_group['id'])
self._define_filter(
self._filter_container(instance_secgroup_filter_name,
instance_secgroup_filter_children))
- for (network, mapping) in network_info:
- nic_id = mapping['mac'].replace(':', '')
- instance_filter_name = self._instance_filter_name(instance, nic_id)
- instance_filter_children = \
- [base_filter, instance_secgroup_filter_name]
+ network_filters = self.\
+ _create_network_filters(instance, network_info,
+ instance_secgroup_filter_name)
- if FLAGS.use_ipv6:
- gateway_v6 = network['gateway_v6']
+ for (name, children) in network_filters:
+ self._define_filters(name, children)
- if gateway_v6:
- instance_secgroup_filter_children += \
- ['nova-allow-ra-server']
+ def _create_network_filters(self, instance, network_info,
+ instance_secgroup_filter_name):
+ if instance['image_id'] == str(FLAGS.vpn_image_id):
+ base_filter = 'nova-vpn'
+ else:
+ base_filter = 'nova-base'
+
+ result = []
+ for (_n, mapping) in network_info:
+ nic_id = mapping['mac'].replace(':', '')
+ instance_filter_name = self._instance_filter_name(instance, nic_id)
+ instance_filter_children = [base_filter,
+ instance_secgroup_filter_name]
if FLAGS.allow_project_net_traffic:
- instance_filter_children += ['nova-project']
+ instance_filter_children.append('nova-project')
if FLAGS.use_ipv6:
- instance_filter_children += ['nova-project-v6']
+ instance_filter_children.append('nova-project-v6')
- self._define_filter(
- self._filter_container(instance_filter_name,
- instance_filter_children))
+ result.append((instance_filter_name, instance_filter_children))
- return
+ return result
+
+ def _define_filters(self, filter_name, filter_children):
+ self._define_filter(self._filter_container(filter_name,
+ filter_children))
def refresh_security_group_rules(self, security_group_id):
return self._define_filter(
@@ -1988,34 +2011,23 @@ class IptablesFirewallDriver(FirewallDriver):
self.add_filters_for_instance(instance, network_info)
self.iptables.apply()
- def add_filters_for_instance(self, instance, network_info=None):
- if not network_info:
- network_info = _get_network_info(instance)
- chain_name = self._instance_chain_name(instance)
+ def _create_filter(self, ips, chain_name):
+ return ['-d %s -j $%s' % (ip, chain_name) for ip in ips]
- self.iptables.ipv4['filter'].add_chain(chain_name)
-
- ips_v4 = [ip['ip'] for (_, mapping) in network_info
- for ip in mapping['ips']]
-
- for ipv4_address in ips_v4:
- self.iptables.ipv4['filter'].add_rule('local',
- '-d %s -j $%s' %
- (ipv4_address, chain_name))
+ def _filters_for_instance(self, chain_name, network_info):
+ ips_v4 = [ip['ip'] for (_n, mapping) in network_info
+ for ip in mapping['ips']]
+ ipv4_rules = self._create_filter(ips_v4, chain_name)
+ ipv6_rules = []
if FLAGS.use_ipv6:
- self.iptables.ipv6['filter'].add_chain(chain_name)
- ips_v6 = [ip['ip'] for (_, mapping) in network_info
- for ip in mapping['ip6s']]
+ ips_v6 = [ip['ip'] for (_n, mapping) in network_info
+ for ip in mapping['ip6s']]
+ ipv6_rules = self._create_filter(ips_v6, chain_name)
- for ipv6_address in ips_v6:
- self.iptables.ipv6['filter'].add_rule('local',
- '-d %s -j $%s' %
- (ipv6_address,
- chain_name))
-
- ipv4_rules, ipv6_rules = self.instance_rules(instance, network_info)
+ return ipv4_rules, ipv6_rules
+ def _add_filters(self, chain_name, ipv4_rules, ipv6_rules):
for rule in ipv4_rules:
self.iptables.ipv4['filter'].add_rule(chain_name, rule)
@@ -2023,6 +2035,17 @@ class IptablesFirewallDriver(FirewallDriver):
for rule in ipv6_rules:
self.iptables.ipv6['filter'].add_rule(chain_name, rule)
+ def add_filters_for_instance(self, instance, network_info=None):
+ chain_name = self._instance_chain_name(instance)
+ if FLAGS.use_ipv6:
+ self.iptables.ipv6['filter'].add_chain(chain_name)
+ self.iptables.ipv4['filter'].add_chain(chain_name)
+ ipv4_rules, ipv6_rules = self._filters_for_instance(chain_name,
+ network_info)
+ self._add_filters('local', ipv4_rules, ipv6_rules)
+ ipv4_rules, ipv6_rules = self.instance_rules(instance, network_info)
+ self._add_filters(chain_name, ipv4_rules, ipv6_rules)
+
def remove_filters_for_instance(self, instance):
chain_name = self._instance_chain_name(instance)
diff --git a/nova/virt/vmwareapi/fake.py b/nova/virt/vmwareapi/fake.py
index 4bb467fa9..7370684bd 100644
--- a/nova/virt/vmwareapi/fake.py
+++ b/nova/virt/vmwareapi/fake.py
@@ -387,12 +387,11 @@ def _add_file(file_path):
def _remove_file(file_path):
"""Removes a file reference from the db."""
if _db_content.get("files") is None:
- raise exception.NotFound(_("No files have been added yet"))
+ raise exception.NoFilesFound()
# Check if the remove is for a single file object or for a folder
if file_path.find(".vmdk") != -1:
if file_path not in _db_content.get("files"):
- raise exception.NotFound(_("File- '%s' is not there in the "
- "datastore") % file_path)
+ raise exception.FileNotFound(file_path=file_path)
_db_content.get("files").remove(file_path)
else:
# Removes the files in the folder and the folder too from the db
@@ -579,7 +578,7 @@ class FakeVim(object):
"""Searches the datastore for a file."""
ds_path = kwargs.get("datastorePath")
if _db_content.get("files", None) is None:
- raise exception.NotFound(_("No files have been added yet"))
+ raise exception.NoFilesFound()
for file in _db_content.get("files"):
if file.find(ds_path) != -1:
task_mdo = create_task(method, "success")
@@ -591,7 +590,7 @@ class FakeVim(object):
"""Creates a directory in the datastore."""
ds_path = kwargs.get("name")
if _db_content.get("files", None) is None:
- raise exception.NotFound(_("No files have been added yet"))
+ raise exception.NoFilesFound()
_db_content["files"].append(ds_path)
def _set_power_state(self, method, vm_ref, pwr_state="poweredOn"):
diff --git a/nova/virt/vmwareapi/vmops.py b/nova/virt/vmwareapi/vmops.py
index cf6c88bbd..c3e79a92f 100644
--- a/nova/virt/vmwareapi/vmops.py
+++ b/nova/virt/vmwareapi/vmops.py
@@ -100,8 +100,7 @@ class VMWareVMOps(object):
"""
vm_ref = self._get_vm_ref_from_the_name(instance.name)
if vm_ref:
- raise exception.Duplicate(_("Attempted to create a VM with a name"
- " %s, but that already exists on the host") % instance.name)
+ raise exception.InstanceExists(name=instance.name)
client_factory = self._session._get_vim().client.factory
service_content = self._session._get_vim().get_service_content()
@@ -116,8 +115,7 @@ class VMWareVMOps(object):
network_utils.get_network_with_the_name(self._session,
net_name)
if network_ref is None:
- raise exception.NotFound(_("Network with the name '%s' doesn't"
- " exist on the ESX host") % net_name)
+ raise exception.NetworkNotFoundForBridge(bridge=net_name)
_check_if_network_bridge_exists()
@@ -337,8 +335,7 @@ class VMWareVMOps(object):
"""
vm_ref = self._get_vm_ref_from_the_name(instance.name)
if vm_ref is None:
- raise exception.NotFound(_("instance - %s not present") %
- instance.name)
+ raise exception.InstanceNotFound(instance_id=instance.id)
client_factory = self._session._get_vim().client.factory
service_content = self._session._get_vim().get_service_content()
@@ -388,8 +385,7 @@ class VMWareVMOps(object):
"VirtualMachine",
"datastore")
if not ds_ref_ret:
- raise exception.NotFound(_("Failed to get the datastore "
- "reference(s) which the VM uses"))
+ raise exception.DatastoreNotFound()
ds_ref = ds_ref_ret.ManagedObjectReference[0]
ds_browser = vim_util.get_dynamic_property(
self._session._get_vim(),
@@ -480,8 +476,7 @@ class VMWareVMOps(object):
"""Reboot a VM instance."""
vm_ref = self._get_vm_ref_from_the_name(instance.name)
if vm_ref is None:
- raise exception.NotFound(_("instance - %s not present") %
- instance.name)
+ raise exception.InstanceNotFound(instance_id=instance.id)
lst_properties = ["summary.guest.toolsStatus", "runtime.powerState",
"summary.guest.toolsRunningStatus"]
props = self._session._call_method(vim_util, "get_object_properties",
@@ -501,8 +496,8 @@ class VMWareVMOps(object):
# Raise an exception if the VM is not powered On.
if pwr_state not in ["poweredOn"]:
- raise exception.Invalid(_("instance - %s not poweredOn. So can't "
- "be rebooted.") % instance.name)
+ reason = _("instance is not powered on")
+ raise exception.InstanceRebootFailure(reason=reason)
# If latest vmware tools are installed in the VM, and that the tools
# are running, then only do a guest reboot. Otherwise do a hard reset.
@@ -605,8 +600,7 @@ class VMWareVMOps(object):
"""Suspend the specified instance."""
vm_ref = self._get_vm_ref_from_the_name(instance.name)
if vm_ref is None:
- raise exception.NotFound(_("instance - %s not present") %
- instance.name)
+ raise exception.InstanceNotFound(instance_id=instance.id)
pwr_state = self._session._call_method(vim_util,
"get_dynamic_property", vm_ref,
@@ -620,8 +614,9 @@ class VMWareVMOps(object):
LOG.debug(_("Suspended the VM %s ") % instance.name)
# Raise Exception if VM is poweredOff
elif pwr_state == "poweredOff":
- raise exception.Invalid(_("instance - %s is poweredOff and hence "
- " can't be suspended.") % instance.name)
+ reason = _("instance is powered off and can not be suspended.")
+ raise exception.InstanceSuspendFailure(reason=reason)
+
LOG.debug(_("VM %s was already in suspended state. So returning "
"without doing anything") % instance.name)
@@ -629,8 +624,7 @@ class VMWareVMOps(object):
"""Resume the specified instance."""
vm_ref = self._get_vm_ref_from_the_name(instance.name)
if vm_ref is None:
- raise exception.NotFound(_("instance - %s not present") %
- instance.name)
+ raise exception.InstanceNotFound(instance_id=instance.id)
pwr_state = self._session._call_method(vim_util,
"get_dynamic_property", vm_ref,
@@ -643,15 +637,14 @@ class VMWareVMOps(object):
self._wait_with_callback(instance.id, suspend_task, callback)
LOG.debug(_("Resumed the VM %s ") % instance.name)
else:
- raise exception.Invalid(_("instance - %s not in Suspended state "
- "and hence can't be Resumed.") % instance.name)
+ reason = _("instance is not in a suspended state")
+ raise exception.InstanceResumeFailure(reason=reason)
def get_info(self, instance_name):
"""Return data about the VM instance."""
vm_ref = self._get_vm_ref_from_the_name(instance_name)
if vm_ref is None:
- raise exception.NotFound(_("instance - %s not present") %
- instance_name)
+ raise exception.InstanceNotFound(instance_id=instance_name)
lst_properties = ["summary.config.numCpu",
"summary.config.memorySizeMB",
@@ -687,8 +680,7 @@ class VMWareVMOps(object):
"""Return snapshot of console."""
vm_ref = self._get_vm_ref_from_the_name(instance.name)
if vm_ref is None:
- raise exception.NotFound(_("instance - %s not present") %
- instance.name)
+ raise exception.InstanceNotFound(instance_id=instance.id)
param_list = {"id": str(vm_ref)}
base_url = "%s://%s/screen?%s" % (self._session._scheme,
self._session._host_ip,
@@ -716,8 +708,7 @@ class VMWareVMOps(object):
"""
vm_ref = self._get_vm_ref_from_the_name(instance.name)
if vm_ref is None:
- raise exception.NotFound(_("instance - %s not present") %
- instance.name)
+ raise exception.InstanceNotFound(instance_id=instance.id)
network = db.network_get_by_instance(context.get_admin_context(),
instance['id'])
mac_addr = instance.mac_address
diff --git a/nova/virt/xenapi/fake.py b/nova/virt/xenapi/fake.py
index 4434dbf0b..e36ef3288 100644
--- a/nova/virt/xenapi/fake.py
+++ b/nova/virt/xenapi/fake.py
@@ -294,7 +294,7 @@ class Failure(Exception):
def __str__(self):
try:
return str(self.details)
- except Exception, exc:
+ except Exception:
return "XenAPI Fake Failure: %s" % str(self.details)
def _details_map(self):
diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py
index d2045a557..c8f342aa8 100644
--- a/nova/virt/xenapi/vm_utils.py
+++ b/nova/virt/xenapi/vm_utils.py
@@ -28,10 +28,7 @@ import urllib
import uuid
from xml.dom import minidom
-from eventlet import event
import glance.client
-from nova import context
-from nova import db
from nova import exception
from nova import flags
from nova import log as logging
@@ -306,7 +303,6 @@ class VMHelper(HelperBase):
% locals())
vm_vdi_ref, vm_vdi_rec = cls.get_vdi_for_vm_safely(session, vm_ref)
- vm_vdi_uuid = vm_vdi_rec["uuid"]
sr_ref = vm_vdi_rec["SR"]
original_parent_uuid = get_vhd_parent_uuid(session, vm_vdi_ref)
@@ -510,9 +506,7 @@ class VMHelper(HelperBase):
try:
return glance_disk_format2nova_type[disk_format]
except KeyError:
- raise exception.NotFound(
- _("Unrecognized disk_format '%(disk_format)s'")
- % locals())
+ raise exception.InvalidDiskFormat(disk_format=disk_format)
def determine_from_instance():
if instance.kernel_id:
@@ -647,8 +641,7 @@ class VMHelper(HelperBase):
if n == 0:
return None
elif n > 1:
- raise exception.Duplicate(_('duplicate name found: %s') %
- name_label)
+ raise exception.InstanceExists(name=name_label)
else:
return vm_refs[0]
@@ -755,14 +748,14 @@ class VMHelper(HelperBase):
session.call_xenapi('SR.scan', sr_ref)
-def get_rrd(host, uuid):
+def get_rrd(host, vm_uuid):
"""Return the VM RRD XML as a string"""
try:
xml = urllib.urlopen("http://%s:%s@%s/vm_rrd?uuid=%s" % (
FLAGS.xenapi_connection_username,
FLAGS.xenapi_connection_password,
host,
- uuid))
+ vm_uuid))
return xml.read()
except IOError:
return None
@@ -857,7 +850,7 @@ def safe_find_sr(session):
"""
sr_ref = find_sr(session)
if sr_ref is None:
- raise exception.NotFound(_('Cannot find SR to read/write VDI'))
+ raise exception.StorageRepositoryNotFound()
return sr_ref
@@ -1020,7 +1013,6 @@ def _stream_disk(dev, image_type, virtual_size, image_file):
def _write_partition(virtual_size, dev):
dest = '/dev/%s' % dev
- mbr_last = MBR_SIZE_SECTORS - 1
primary_first = MBR_SIZE_SECTORS
primary_last = MBR_SIZE_SECTORS + (virtual_size / SECTOR_SIZE) - 1
diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py
index 7c7aa8e98..fe9a74dd6 100644
--- a/nova/virt/xenapi/vmops.py
+++ b/nova/virt/xenapi/vmops.py
@@ -127,8 +127,7 @@ class VMOps(object):
instance_name = instance.name
vm_ref = VMHelper.lookup(self._session, instance_name)
if vm_ref is not None:
- raise exception.Duplicate(_('Attempted to create'
- ' non-unique name %s') % instance_name)
+ raise exception.InstanceExists(name=instance_name)
#ensure enough free memory is available
if not VMHelper.ensure_free_mem(self._session, instance):
@@ -211,8 +210,6 @@ class VMOps(object):
def _wait_for_boot():
try:
state = self.get_info(instance_name)['state']
- db.instance_set_state(context.get_admin_context(),
- instance['id'], state)
if state == power_state.RUNNING:
LOG.debug(_('Instance %s: booted'), instance_name)
timer.stop()
@@ -220,11 +217,7 @@ class VMOps(object):
return True
except Exception, exc:
LOG.warn(exc)
- LOG.exception(_('instance %s: failed to boot'),
- instance_name)
- db.instance_set_state(context.get_admin_context(),
- instance['id'],
- power_state.SHUTDOWN)
+ LOG.exception(_('Instance %s: failed to boot'), instance_name)
timer.stop()
return False
@@ -260,8 +253,7 @@ class VMOps(object):
instance_name = instance_or_vm.name
vm_ref = VMHelper.lookup(self._session, instance_name)
if vm_ref is None:
- raise exception.NotFound(
- _('Instance not present %s') % instance_name)
+ raise exception.InstanceNotFound(instance_id=instance_obj.id)
return vm_ref
def _acquire_bootlock(self, vm):
@@ -387,7 +379,6 @@ class VMOps(object):
def link_disks(self, instance, base_copy_uuid, cow_uuid):
"""Links the base copy VHD to the COW via the XAPI plugin."""
- vm_ref = VMHelper.lookup(self._session, instance.name)
new_base_copy_uuid = str(uuid.uuid4())
new_cow_uuid = str(uuid.uuid4())
params = {'instance_id': instance.id,
@@ -437,11 +428,12 @@ class VMOps(object):
"""
# Need to uniquely identify this request.
- transaction_id = str(uuid.uuid4())
+ key_init_transaction_id = str(uuid.uuid4())
# The simple Diffie-Hellman class is used to manage key exchange.
dh = SimpleDH()
- args = {'id': transaction_id, 'pub': str(dh.get_public())}
- resp = self._make_agent_call('key_init', instance, '', args)
+ key_init_args = {'id': key_init_transaction_id,
+ 'pub': str(dh.get_public())}
+ resp = self._make_agent_call('key_init', instance, '', key_init_args)
if resp is None:
# No response from the agent
return
@@ -455,8 +447,9 @@ class VMOps(object):
dh.compute_shared(agent_pub)
enc_pass = dh.encrypt(new_pass)
# Send the encrypted password
- args['enc_pass'] = enc_pass
- resp = self._make_agent_call('password', instance, '', args)
+ password_transaction_id = str(uuid.uuid4())
+ password_args = {'id': password_transaction_id, 'enc_pass': enc_pass}
+ resp = self._make_agent_call('password', instance, '', password_args)
if resp is None:
# No response from the agent
return
@@ -579,9 +572,8 @@ class VMOps(object):
if not (instance.kernel_id and instance.ramdisk_id):
# 2. We only have kernel xor ramdisk
- raise exception.NotFound(
- _("Instance %(instance_id)s has a kernel or ramdisk but not "
- "both" % locals()))
+ raise exception.InstanceUnacceptable(instance_id=instance_id,
+ reason=_("instance has a kernel or ramdisk but not both"))
# 3. We have both kernel and ramdisk
(kernel, ramdisk) = VMHelper.lookup_kernel_ramdisk(self._session,
@@ -722,8 +714,7 @@ class VMOps(object):
"%s-rescue" % instance.name)
if not rescue_vm_ref:
- raise exception.NotFound(_(
- "Instance is not in Rescue Mode: %s" % instance.name))
+ raise exception.InstanceNotInRescueMode(instance_id=instance.id)
original_vm_ref = VMHelper.lookup(self._session, instance.name)
instance._rescue = False
@@ -760,7 +751,6 @@ class VMOps(object):
instance)))
for vm in rescue_vms:
- rescue_name = vm["name"]
rescue_vm_ref = vm["vm_ref"]
self._destroy_rescue_instance(rescue_vm_ref)
@@ -798,7 +788,7 @@ class VMOps(object):
def _get_network_info(self, instance):
"""Creates network info list for instance."""
admin_context = context.get_admin_context()
- IPs = db.fixed_ip_get_all_by_instance(admin_context,
+ ips = db.fixed_ip_get_all_by_instance(admin_context,
instance['id'])
networks = db.network_get_all_by_instance(admin_context,
instance['id'])
@@ -808,7 +798,7 @@ class VMOps(object):
network_info = []
for network in networks:
- network_IPs = [ip for ip in IPs if ip.network_id == network.id]
+ network_ips = [ip for ip in ips if ip.network_id == network.id]
def ip_dict(ip):
return {
@@ -830,7 +820,7 @@ class VMOps(object):
'mac': instance.mac_address,
'rxtx_cap': inst_type['rxtx_cap'],
'dns': [network['dns']],
- 'ips': [ip_dict(ip) for ip in network_IPs]}
+ 'ips': [ip_dict(ip) for ip in network_ips]}
if network['cidr_v6']:
info['ip6s'] = [ip6_dict()]
if network['gateway_v6']:
@@ -923,7 +913,7 @@ class VMOps(object):
try:
ret = self._make_xenstore_call('read_record', vm, path,
{'ignore_missing_path': 'True'})
- except self.XenAPI.Failure, e:
+ except self.XenAPI.Failure:
return None
ret = json.loads(ret)
if ret == "None":
diff --git a/nova/virt/xenapi/volumeops.py b/nova/virt/xenapi/volumeops.py
index 757ecf5ad..afcb8cf47 100644
--- a/nova/virt/xenapi/volumeops.py
+++ b/nova/virt/xenapi/volumeops.py
@@ -45,8 +45,7 @@ class VolumeOps(object):
# Before we start, check that the VM exists
vm_ref = VMHelper.lookup(self._session, instance_name)
if vm_ref is None:
- raise exception.NotFound(_('Instance %s not found')
- % instance_name)
+ raise exception.InstanceNotFound(instance_id=instance_name)
# NOTE: No Resource Pool concept so far
LOG.debug(_("Attach_volume: %(instance_name)s, %(device_path)s,"
" %(mountpoint)s") % locals())
@@ -98,8 +97,7 @@ class VolumeOps(object):
# Before we start, check that the VM exists
vm_ref = VMHelper.lookup(self._session, instance_name)
if vm_ref is None:
- raise exception.NotFound(_('Instance %s not found')
- % instance_name)
+ raise exception.InstanceNotFound(instance_id=instance_name)
# Detach VBD from VM
LOG.debug(_("Detach_volume: %(instance_name)s, %(mountpoint)s")
% locals())
diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py
index 0cabccf08..8e9085277 100644
--- a/nova/virt/xenapi_conn.py
+++ b/nova/virt/xenapi_conn.py
@@ -57,6 +57,8 @@ reactor thread if the VM.get_by_name_label or VM.get_record calls block.
- suffix "_rec" for record objects
"""
+import json
+import random
import sys
import urlparse
import xmlrpclib
@@ -67,10 +69,12 @@ from eventlet import timeout
from nova import context
from nova import db
+from nova import exception
from nova import utils
from nova import flags
from nova import log as logging
from nova.virt import driver
+from nova.virt.xenapi import vm_utils
from nova.virt.xenapi.vmops import VMOps
from nova.virt.xenapi.volumeops import VolumeOps
@@ -168,6 +172,13 @@ class XenAPIConnection(driver.ComputeDriver):
session = XenAPISession(url, user, pw)
self._vmops = VMOps(session)
self._volumeops = VolumeOps(session)
+ self._host_state = None
+
+ @property
+ def HostState(self):
+ if not self._host_state:
+ self._host_state = HostState(self.session)
+ return self._host_state
def init_host(self, host):
#FIXME(armando): implement this
@@ -315,6 +326,16 @@ class XenAPIConnection(driver.ComputeDriver):
"""This method is supported only by libvirt."""
raise NotImplementedError('This method is supported only by libvirt.')
+ def update_host_status(self):
+ """Update the status info of the host, and return those values
+ to the calling program."""
+ return self.HostState.update_status()
+
+ def get_host_stats(self, refresh=False):
+ """Return the current state of the host. If 'refresh' is
+ True, run the update first."""
+ return self.HostState.get_host_stats(refresh=refresh)
+
class XenAPISession(object):
"""The session to invoke XenAPI SDK calls"""
@@ -436,6 +457,65 @@ class XenAPISession(object):
raise
+class HostState(object):
+ """Manages information about the XenServer host this compute
+ node is running on.
+ """
+ def __init__(self, session):
+ super(HostState, self).__init__()
+ self._session = session
+ self._stats = {}
+ self.update_status()
+
+ def get_host_stats(self, refresh=False):
+ """Return the current state of the host. If 'refresh' is
+ True, run the update first.
+ """
+ if refresh:
+ self.update_status()
+ return self._stats
+
+ def update_status(self):
+ """Since under Xenserver, a compute node runs on a given host,
+ we can get host status information using xenapi.
+ """
+ LOG.debug(_("Updating host stats"))
+ # Make it something unlikely to match any actual instance ID
+ task_id = random.randint(-80000, -70000)
+ task = self._session.async_call_plugin("xenhost", "host_data", {})
+ task_result = self._session.wait_for_task(task, task_id)
+ if not task_result:
+ task_result = json.dumps("")
+ try:
+ data = json.loads(task_result)
+ except ValueError as e:
+ # Invalid JSON object
+ LOG.error(_("Unable to get updated status: %s") % e)
+ return
+ # Get the SR usage
+ try:
+ sr_ref = vm_utils.safe_find_sr(self._session)
+ except exception.NotFound as e:
+ # No SR configured
+ LOG.error(_("Unable to get SR for this host: %s") % e)
+ return
+ sr_rec = self._session.get_xenapi().SR.get_record(sr_ref)
+ total = int(sr_rec["virtual_allocation"])
+ used = int(sr_rec["physical_utilisation"])
+ data["disk_total"] = total
+ data["disk_used"] = used
+ data["disk_available"] = total - used
+ host_memory = data.get('host_memory', None)
+ if host_memory:
+ data["host_memory_total"] = host_memory.get('total', 0)
+ data["host_memory_overhead"] = host_memory.get('overhead', 0)
+ data["host_memory_free"] = host_memory.get('free', 0)
+ data["host_memory_free_computed"] = \
+ host_memory.get('free-computed', 0)
+ del data['host_memory']
+ self._stats = data
+
+
def _parse_xmlrpc_value(val):
"""Parse the given value as if it were an XML-RPC value. This is
sometimes used as the format for the task.result field."""
diff --git a/nova/volume/api.py b/nova/volume/api.py
index f5285f31f..c1af30de0 100644
--- a/nova/volume/api.py
+++ b/nova/volume/api.py
@@ -113,6 +113,13 @@ class API(base.Base):
if volume['status'] == "available":
raise exception.ApiError(_("Volume is already detached"))
+ def remove_from_compute(self, context, volume_id, host):
+ """Remove volume from specified compute host."""
+ rpc.call(context,
+ self.db.queue_get_for(context, FLAGS.compute_topic, host),
+ {"method": "remove_volume",
+ "args": {'volume_id': volume_id}})
+
def create_snapshot(self, context, volume_id, name, description):
volume = self.get(context, volume_id)
if volume['status'] != "available":
diff --git a/nova/wsgi.py b/nova/wsgi.py
index de2e0749f..e60a8820d 100644
--- a/nova/wsgi.py
+++ b/nova/wsgi.py
@@ -17,9 +17,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-"""
-Utility methods for working with WSGI servers
-"""
+"""Utility methods for working with WSGI servers."""
import os
import sys
@@ -33,7 +31,6 @@ import routes.middleware
import webob
import webob.dec
import webob.exc
-
from paste import deploy
from nova import exception
@@ -66,7 +63,7 @@ class Server(object):
def start(self, application, port, host='0.0.0.0', backlog=128):
"""Run a WSGI server with the given application."""
arg0 = sys.argv[0]
- logging.audit(_("Starting %(arg0)s on %(host)s:%(port)s") % locals())
+ logging.audit(_('Starting %(arg0)s on %(host)s:%(port)s') % locals())
socket = eventlet.listen((host, port), backlog=backlog)
self.pool.spawn_n(self._run, application, socket)
@@ -87,30 +84,34 @@ class Server(object):
class Request(webob.Request):
def best_match_content_type(self):
- """
- Determine the most acceptable content-type based on the
- query extension then the Accept header
- """
+ """Determine the most acceptable content-type.
- parts = self.path.rsplit(".", 1)
+ Based on the query extension then the Accept header.
+
+ """
+ parts = self.path.rsplit('.', 1)
if len(parts) > 1:
format = parts[1]
- if format in ["json", "xml"]:
- return "application/{0}".format(parts[1])
+ if format in ['json', 'xml']:
+ return 'application/{0}'.format(parts[1])
- ctypes = ["application/json", "application/xml"]
+ ctypes = ['application/json', 'application/xml']
bm = self.accept.best_match(ctypes)
- return bm or "application/json"
+ return bm or 'application/json'
def get_content_type(self):
- try:
- ct = self.headers["Content-Type"]
- assert ct in ("application/xml", "application/json")
- return ct
- except Exception:
- raise webob.exc.HTTPBadRequest("Invalid content type")
+ allowed_types = ("application/xml", "application/json")
+ if not "Content-Type" in self.headers:
+ msg = _("Missing Content-Type")
+ LOG.debug(msg)
+ raise webob.exc.HTTPBadRequest(msg)
+ type = self.content_type
+ if type in allowed_types:
+ return type
+ LOG.debug(_("Wrong Content-Type: %s") % type)
+ raise webob.exc.HTTPBadRequest("Invalid content type")
class Application(object):
@@ -118,7 +119,7 @@ class Application(object):
@classmethod
def factory(cls, global_config, **local_config):
- """Used for paste app factories in paste.deploy config fles.
+ """Used for paste app factories in paste.deploy config files.
Any local configuration (that is, values under the [app:APPNAME]
section of the paste config) will be passed into the `__init__` method
@@ -173,8 +174,9 @@ class Application(object):
See the end of http://pythonpaste.org/webob/modules/dec.html
for more info.
+
"""
- raise NotImplementedError(_("You must implement __call__"))
+ raise NotImplementedError(_('You must implement __call__'))
class Middleware(Application):
@@ -184,11 +186,12 @@ class Middleware(Application):
initialized that will be called next. By default the middleware will
simply call its wrapped app, or you can override __call__ to customize its
behavior.
+
"""
@classmethod
def factory(cls, global_config, **local_config):
- """Used for paste app factories in paste.deploy config fles.
+ """Used for paste app factories in paste.deploy config files.
Any local configuration (that is, values under the [filter:APPNAME]
section of the paste config) will be passed into the `__init__` method
@@ -240,20 +243,24 @@ class Middleware(Application):
class Debug(Middleware):
- """Helper class that can be inserted into any WSGI application chain
- to get information about the request and response."""
+ """Helper class for debugging a WSGI application.
+
+ Can be inserted into any WSGI application chain to get information
+ about the request and response.
+
+ """
@webob.dec.wsgify(RequestClass=Request)
def __call__(self, req):
- print ("*" * 40) + " REQUEST ENVIRON"
+ print ('*' * 40) + ' REQUEST ENVIRON'
for key, value in req.environ.items():
- print key, "=", value
+ print key, '=', value
print
resp = req.get_response(self.application)
- print ("*" * 40) + " RESPONSE HEADERS"
+ print ('*' * 40) + ' RESPONSE HEADERS'
for (key, value) in resp.headers.iteritems():
- print key, "=", value
+ print key, '=', value
print
resp.app_iter = self.print_generator(resp.app_iter)
@@ -262,11 +269,8 @@ class Debug(Middleware):
@staticmethod
def print_generator(app_iter):
- """
- Iterator that prints the contents of a wrapper string iterator
- when iterated.
- """
- print ("*" * 40) + " BODY"
+ """Iterator that prints the contents of a wrapper string."""
+ print ('*' * 40) + ' BODY'
for part in app_iter:
sys.stdout.write(part)
sys.stdout.flush()
@@ -275,13 +279,10 @@ class Debug(Middleware):
class Router(object):
- """
- WSGI middleware that maps incoming requests to WSGI apps.
- """
+ """WSGI middleware that maps incoming requests to WSGI apps."""
def __init__(self, mapper):
- """
- Create a router for the given routes.Mapper.
+ """Create a router for the given routes.Mapper.
Each route in `mapper` must specify a 'controller', which is a
WSGI app to call. You'll probably want to specify an 'action' as
@@ -293,15 +294,16 @@ class Router(object):
sc = ServerController()
# Explicit mapping of one route to a controller+action
- mapper.connect(None, "/svrlist", controller=sc, action="list")
+ mapper.connect(None, '/svrlist', controller=sc, action='list')
# Actions are all implicitly defined
- mapper.resource("server", "servers", controller=sc)
+ mapper.resource('server', 'servers', controller=sc)
# Pointing to an arbitrary WSGI app. You can specify the
# {path_info:.*} parameter so the target app can be handed just that
# section of the URL.
- mapper.connect(None, "/v1.0/{path_info:.*}", controller=BlogApp())
+ mapper.connect(None, '/v1.0/{path_info:.*}', controller=BlogApp())
+
"""
self.map = mapper
self._router = routes.middleware.RoutesMiddleware(self._dispatch,
@@ -309,19 +311,22 @@ class Router(object):
@webob.dec.wsgify(RequestClass=Request)
def __call__(self, req):
- """
- Route the incoming request to a controller based on self.map.
+ """Route the incoming request to a controller based on self.map.
+
If no match, return a 404.
+
"""
return self._router
@staticmethod
@webob.dec.wsgify(RequestClass=Request)
def _dispatch(req):
- """
+ """Dispatch the request to the appropriate controller.
+
Called by self._router after matching the incoming request to a route
and putting the information into req.environ. Either returns 404
or the routed WSGI app's response.
+
"""
match = req.environ['wsgiorg.routing_args'][1]
if not match:
@@ -331,19 +336,19 @@ class Router(object):
class Controller(object):
- """
+ """WSGI app that dispatched to methods.
+
WSGI app that reads routing information supplied by RoutesMiddleware
and calls the requested action method upon itself. All action methods
must, in addition to their normal parameters, accept a 'req' argument
which is the incoming wsgi.Request. They raise a webob.exc exception,
or return a dict which will be serialized by requested content type.
+
"""
@webob.dec.wsgify(RequestClass=Request)
def __call__(self, req):
- """
- Call the method specified in req.environ by RoutesMiddleware.
- """
+ """Call the method specified in req.environ by RoutesMiddleware."""
arg_dict = req.environ['wsgiorg.routing_args'][1]
action = arg_dict['action']
method = getattr(self, action)
@@ -361,7 +366,7 @@ class Controller(object):
body = self._serialize(result, content_type, default_xmlns)
response = webob.Response()
- response.headers["Content-Type"] = content_type
+ response.headers['Content-Type'] = content_type
response.body = body
msg_dict = dict(url=req.url, status=response.status_int)
msg = _("%(url)s returned with HTTP %(status)d") % msg_dict
@@ -371,12 +376,13 @@ class Controller(object):
return result
def _serialize(self, data, content_type, default_xmlns):
- """
- Serialize the given dict to the provided content_type.
+ """Serialize the given dict to the provided content_type.
+
Uses self._serialization_metadata if it exists, which is a dict mapping
MIME types to information needed to serialize to that type.
+
"""
- _metadata = getattr(type(self), "_serialization_metadata", {})
+ _metadata = getattr(type(self), '_serialization_metadata', {})
serializer = Serializer(_metadata, default_xmlns)
try:
@@ -385,12 +391,13 @@ class Controller(object):
raise webob.exc.HTTPNotAcceptable()
def _deserialize(self, data, content_type):
- """
- Deserialize the request body to the specefied content type.
+ """Deserialize the request body to the specefied content type.
+
Uses self._serialization_metadata if it exists, which is a dict mapping
MIME types to information needed to serialize to that type.
+
"""
- _metadata = getattr(type(self), "_serialization_metadata", {})
+ _metadata = getattr(type(self), '_serialization_metadata', {})
serializer = Serializer(_metadata)
return serializer.deserialize(data, content_type)
@@ -400,55 +407,51 @@ class Controller(object):
class Serializer(object):
- """
- Serializes and deserializes dictionaries to certain MIME types.
- """
+ """Serializes and deserializes dictionaries to certain MIME types."""
def __init__(self, metadata=None, default_xmlns=None):
- """
- Create a serializer based on the given WSGI environment.
+ """Create a serializer based on the given WSGI environment.
+
'metadata' is an optional dict mapping MIME types to information
needed to serialize a dictionary to that type.
+
"""
self.metadata = metadata or {}
self.default_xmlns = default_xmlns
def _get_serialize_handler(self, content_type):
handlers = {
- "application/json": self._to_json,
- "application/xml": self._to_xml,
+ 'application/json': self._to_json,
+ 'application/xml': self._to_xml,
}
try:
return handlers[content_type]
except Exception:
- raise exception.InvalidContentType()
+ raise exception.InvalidContentType(content_type=content_type)
def serialize(self, data, content_type):
- """
- Serialize a dictionary into a string of the specified content type.
- """
+ """Serialize a dictionary into the specified content type."""
return self._get_serialize_handler(content_type)(data)
def deserialize(self, datastring, content_type):
- """
- Deserialize a string to a dictionary.
+ """Deserialize a string to a dictionary.
The string must be in the format of a supported MIME type.
+
"""
return self.get_deserialize_handler(content_type)(datastring)
def get_deserialize_handler(self, content_type):
handlers = {
- "application/json": self._from_json,
- "application/xml": self._from_xml,
+ 'application/json': self._from_json,
+ 'application/xml': self._from_xml,
}
try:
return handlers[content_type]
except Exception:
- raise exception.InvalidContentType(_("Invalid content type %s"
- % content_type))
+ raise exception.InvalidContentType(content_type=content_type)
def _from_json(self, datastring):
return utils.loads(datastring)
@@ -460,11 +463,11 @@ class Serializer(object):
return {node.nodeName: self._from_xml_node(node, plurals)}
def _from_xml_node(self, node, listnames):
- """
- Convert a minidom node to a simple Python type.
+ """Convert a minidom node to a simple Python type.
listnames is a collection of names of XML nodes whose subnodes should
be considered list items.
+
"""
if len(node.childNodes) == 1 and node.childNodes[0].nodeType == 3:
return node.childNodes[0].nodeValue
@@ -571,7 +574,6 @@ def paste_config_file(basename):
* /etc/nova, which may not be diffrerent from state_path on your distro
"""
-
configfiles = [basename,
os.path.join(FLAGS.state_path, 'etc', 'nova', basename),
os.path.join(FLAGS.state_path, 'etc', basename),
@@ -587,7 +589,7 @@ def load_paste_configuration(filename, appname):
filename = os.path.abspath(filename)
config = None
try:
- config = deploy.appconfig("config:%s" % filename, name=appname)
+ config = deploy.appconfig('config:%s' % filename, name=appname)
except LookupError:
pass
return config
@@ -598,7 +600,7 @@ def load_paste_app(filename, appname):
filename = os.path.abspath(filename)
app = None
try:
- app = deploy.loadapp("config:%s" % filename, name=appname)
+ app = deploy.loadapp('config:%s' % filename, name=appname)
except LookupError:
pass
return app