summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKen Pepple <ken.pepple@gmail.com>2011-04-07 17:59:10 -0700
committerKen Pepple <ken.pepple@gmail.com>2011-04-07 17:59:10 -0700
commite892deef8ba1f6c424dd8fa3fc6330d09245c89e (patch)
tree5c03f99f22db81bd5f46b4f94e2cfc57c26db6c5
parent86ffed4e988025023b570b9e6e87f89b6075c7b0 (diff)
parent4d8594cd7e36983cb55908ab8bfebe8aa3a40ff1 (diff)
merge trunk
-rwxr-xr-xbin/nova-manage40
-rw-r--r--nova/api/ec2/cloud.py52
-rw-r--r--nova/api/openstack/__init__.py6
-rw-r--r--nova/api/openstack/ips.py72
-rw-r--r--nova/api/openstack/servers.py18
-rw-r--r--nova/api/openstack/views/addresses.py10
-rw-r--r--nova/compute/manager.py8
-rw-r--r--nova/image/fake.py6
-rw-r--r--nova/image/glance.py31
-rw-r--r--nova/image/local.py16
-rw-r--r--nova/image/s3.py42
-rw-r--r--nova/image/service.py27
-rw-r--r--nova/network/linux_net.py11
-rw-r--r--nova/network/manager.py1
-rw-r--r--nova/tests/api/openstack/test_image_metadata.py2
-rw-r--r--nova/tests/api/openstack/test_servers.py130
-rw-r--r--nova/tests/image/test_glance.py8
-rw-r--r--nova/tests/test_compute.py3
-rw-r--r--nova/virt/libvirt_conn.py57
-rw-r--r--nova/wsgi.py18
20 files changed, 406 insertions, 152 deletions
diff --git a/bin/nova-manage b/bin/nova-manage
index 73da83767..015e1ae97 100755
--- a/bin/nova-manage
+++ b/bin/nova-manage
@@ -899,20 +899,17 @@ class ImageCommands(object):
def __init__(self, *args, **kwargs):
self.image_service = utils.import_object(FLAGS.image_service)
- def _register(self, image_type, disk_format, container_format,
+ def _register(self, container_format, disk_format,
path, owner, name=None, is_public='T',
architecture='x86_64', kernel_id=None, ramdisk_id=None):
- meta = {'is_public': True,
+ meta = {'is_public': (is_public == 'T'),
'name': name,
- 'disk_format': disk_format,
'container_format': container_format,
+ 'disk_format': disk_format,
'properties': {'image_state': 'available',
- 'owner_id': owner,
- 'type': image_type,
+ 'project_id': owner,
'architecture': architecture,
- 'image_location': 'local',
- 'is_public': (is_public == 'T')}}
- print image_type, meta
+ 'image_location': 'local'}}
if kernel_id:
meta['properties']['kernel_id'] = int(kernel_id)
if ramdisk_id:
@@ -937,16 +934,18 @@ class ImageCommands(object):
ramdisk_id = self.ramdisk_register(ramdisk, owner, None,
is_public, architecture)
self.image_register(image, owner, name, is_public,
- architecture, kernel_id, ramdisk_id)
+ architecture, 'ami', 'ami',
+ kernel_id, ramdisk_id)
def image_register(self, path, owner, name=None, is_public='T',
- architecture='x86_64', kernel_id=None, ramdisk_id=None,
- disk_format='ami', container_format='ami'):
+ architecture='x86_64', container_format='bare',
+ disk_format='raw', kernel_id=None, ramdisk_id=None):
"""Uploads an image into the image_service
arguments: path owner [name] [is_public='T'] [architecture='x86_64']
+ [container_format='bare'] [disk_format='raw']
[kernel_id=None] [ramdisk_id=None]
- [disk_format='ami'] [container_format='ami']"""
- return self._register('machine', disk_format, container_format, path,
+ """
+ return self._register(container_format, disk_format, path,
owner, name, is_public, architecture,
kernel_id, ramdisk_id)
@@ -955,7 +954,7 @@ class ImageCommands(object):
"""Uploads a kernel into the image_service
arguments: path owner [name] [is_public='T'] [architecture='x86_64']
"""
- return self._register('kernel', 'aki', 'aki', path, owner, name,
+ return self._register('aki', 'aki', path, owner, name,
is_public, architecture)
def ramdisk_register(self, path, owner, name=None, is_public='T',
@@ -963,7 +962,7 @@ class ImageCommands(object):
"""Uploads a ramdisk into the image_service
arguments: path owner [name] [is_public='T'] [architecture='x86_64']
"""
- return self._register('ramdisk', 'ari', 'ari', path, owner, name,
+ return self._register('ari', 'ari', path, owner, name,
is_public, architecture)
def _lookup(self, old_image_id):
@@ -980,16 +979,17 @@ class ImageCommands(object):
'ramdisk': 'ari'}
container_format = mapping[old['type']]
disk_format = container_format
+ if container_format == 'ami' and not old.get('kernelId'):
+ container_format = 'bare'
+ disk_format = 'raw'
new = {'disk_format': disk_format,
'container_format': container_format,
- 'is_public': True,
+ 'is_public': old['isPublic'],
'name': old['imageId'],
'properties': {'image_state': old['imageState'],
- 'owner_id': old['imageOwnerId'],
+ 'project_id': old['imageOwnerId'],
'architecture': old['architecture'],
- 'type': old['type'],
- 'image_location': old['imageLocation'],
- 'is_public': old['isPublic']}}
+ 'image_location': old['imageLocation']}}
if old.get('kernelId'):
new['properties']['kernel_id'] = self._lookup(old['kernelId'])
if old.get('ramdiskId'):
diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py
index c3124b89d..4ed8a9ecf 100644
--- a/nova/api/ec2/cloud.py
+++ b/nova/api/ec2/cloud.py
@@ -154,7 +154,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'], 'machine')
+ image_ec2_id = self._image_ec2_id(instance_ref['image_id'], 'ami')
data = {
'user-data': base64.b64decode(instance_ref['user_data']),
'meta-data': {
@@ -184,7 +184,7 @@ class CloudController(object):
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],
- image_type)
+ self._image_type(image_type))
data['meta-data']['%s-id' % image_type] = ec2_id
if False: # TODO(vish): store ancestor ids
@@ -875,13 +875,27 @@ class CloudController(object):
self.compute_api.update(context, instance_id=instance_id, **kwargs)
return True
- _type_prefix_map = {'machine': 'ami',
- 'kernel': 'aki',
- 'ramdisk': 'ari'}
+ @staticmethod
+ def _image_type(image_type):
+ """Converts to a three letter image type.
- def _image_ec2_id(self, image_id, image_type='machine'):
- prefix = self._type_prefix_map[image_type]
- template = prefix + '-%08x'
+ aki, kernel => aki
+ ari, ramdisk => ari
+ anything else => ami
+
+ """
+ if image_type == 'kernel':
+ return 'aki'
+ if image_type == 'ramdisk':
+ return 'ari'
+ if image_type not in ['aki', 'ari']:
+ return 'ami'
+ return image_type
+
+ @staticmethod
+ 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)
def _get_image(self, context, ec2_id):
@@ -894,27 +908,34 @@ class CloudController(object):
def _format_image(self, image):
"""Convert from format defined by BaseImageService to S3 format."""
i = {}
- image_type = image['properties'].get('type')
+ image_type = self._image_type(image.get('container_format'))
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, 'kernel')
+ 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, 'ramdisk')
+ i['ramdiskId'] = self._image_ec2_id(ramdisk_id, 'ari')
i['imageOwnerId'] = image['properties'].get('owner_id')
if name:
i['imageLocation'] = "%s (%s)" % (image['properties'].
get('image_location'), name)
else:
i['imageLocation'] = image['properties'].get('image_location')
- i['imageState'] = image['properties'].get('image_state')
+ # NOTE(vish): fallback status if image_state isn't set
+ state = image.get('status')
+ if state == 'active':
+ state = 'available'
+ i['imageState'] = image['properties'].get('image_state', state)
i['displayName'] = name
i['description'] = image.get('description')
- i['imageType'] = image_type
- i['isPublic'] = str(image['properties'].get('is_public', '')) == 'True'
+ display_mapping = {'aki': 'kernel',
+ 'ari': 'ramdisk',
+ 'ami': 'machine'}
+ i['imageType'] = display_mapping.get(image_type)
+ i['isPublic'] = image.get('is_public') == True
i['architecture'] = image['properties'].get('architecture')
return i
@@ -946,8 +967,9 @@ class CloudController(object):
image_location = kwargs['name']
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['properties']['type'])
+ image_type)
msg = _("Registered image %(image_location)s with"
" id %(image_id)s") % locals()
LOG.audit(msg, context=context)
diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py
index 7545eb0c9..5e76a06f7 100644
--- a/nova/api/openstack/__init__.py
+++ b/nova/api/openstack/__init__.py
@@ -34,6 +34,7 @@ from nova.api.openstack import consoles
from nova.api.openstack import flavors
from nova.api.openstack import images
from nova.api.openstack import image_metadata
+from nova.api.openstack import ips
from nova.api.openstack import limits
from nova.api.openstack import servers
from nova.api.openstack import server_metadata
@@ -144,6 +145,11 @@ class APIRouterV10(APIRouter):
parent_resource=dict(member_name='server',
collection_name='servers'))
+ mapper.resource("ip", "ips", controller=ips.Controller(),
+ collection=dict(public='GET', private='GET'),
+ parent_resource=dict(member_name='server',
+ collection_name='servers'))
+
class APIRouterV11(APIRouter):
"""Define routes specific to OpenStack API V1.1."""
diff --git a/nova/api/openstack/ips.py b/nova/api/openstack/ips.py
new file mode 100644
index 000000000..778e9ba1a
--- /dev/null
+++ b/nova/api/openstack/ips.py
@@ -0,0 +1,72 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2011 OpenStack LLC.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import time
+
+from webob import exc
+
+import nova
+import nova.api.openstack.views.addresses
+from nova.api.openstack import common
+from nova.api.openstack import faults
+
+
+class Controller(common.OpenstackController):
+ """The servers addresses API controller for the Openstack API."""
+
+ _serialization_metadata = {
+ 'application/xml': {
+ 'list_collections': {
+ 'public': {'item_name': 'ip', 'item_key': 'addr'},
+ 'private': {'item_name': 'ip', 'item_key': 'addr'},
+ },
+ },
+ }
+
+ def __init__(self):
+ self.compute_api = nova.compute.API()
+ self.builder = nova.api.openstack.views.addresses.ViewBuilderV10()
+
+ def index(self, req, server_id):
+ try:
+ instance = self.compute_api.get(req.environ['nova.context'], id)
+ except nova.exception.NotFound:
+ return faults.Fault(exc.HTTPNotFound())
+ return {'addresses': self.builder.build(instance)}
+
+ def public(self, req, server_id):
+ try:
+ instance = self.compute_api.get(req.environ['nova.context'], id)
+ except nova.exception.NotFound:
+ return faults.Fault(exc.HTTPNotFound())
+ return {'public': self.builder.build_public_parts(instance)}
+
+ def private(self, req, server_id):
+ try:
+ instance = self.compute_api.get(req.environ['nova.context'], id)
+ except nova.exception.NotFound:
+ return faults.Fault(exc.HTTPNotFound())
+ return {'private': self.builder.build_private_parts(instance)}
+
+ def show(self, req, server_id, id):
+ return faults.Fault(exc.HTTPNotImplemented())
+
+ def create(self, req, server_id):
+ return faults.Fault(exc.HTTPNotImplemented())
+
+ def delete(self, req, server_id, id):
+ return faults.Fault(exc.HTTPNotImplemented())
diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py
index f379839db..43e0c7963 100644
--- a/nova/api/openstack/servers.py
+++ b/nova/api/openstack/servers.py
@@ -55,6 +55,13 @@ class Controller(common.OpenstackController):
"imageRef"],
"link": ["rel", "type", "href"],
},
+ "dict_collections": {
+ "metadata": {"item_name": "meta", "item_key": "key"},
+ },
+ "list_collections": {
+ "public": {"item_name": "ip", "item_key": "addr"},
+ "private": {"item_name": "ip", "item_key": "addr"},
+ },
},
}
@@ -63,15 +70,6 @@ class Controller(common.OpenstackController):
self._image_service = utils.import_object(FLAGS.image_service)
super(Controller, self).__init__()
- def ips(self, req, id):
- try:
- instance = self.compute_api.get(req.environ['nova.context'], id)
- except exception.NotFound:
- return faults.Fault(exc.HTTPNotFound())
-
- builder = self._get_addresses_view_builder(req)
- return builder.build(instance)
-
def index(self, req):
""" Returns a list of server names and ids for a given user """
return self._items(req, is_detail=False)
@@ -567,7 +565,7 @@ class Controller(common.OpenstackController):
_("Cannot build from image %(image_id)s, status not active") %
locals())
- if image_meta['properties']['disk_format'] != 'ami':
+ if image_meta.get('container_format') != 'ami':
return None, None
try:
diff --git a/nova/api/openstack/views/addresses.py b/nova/api/openstack/views/addresses.py
index 90c77855b..2810cce39 100644
--- a/nova/api/openstack/views/addresses.py
+++ b/nova/api/openstack/views/addresses.py
@@ -28,10 +28,16 @@ class ViewBuilder(object):
class ViewBuilderV10(ViewBuilder):
def build(self, inst):
- private_ips = utils.get_from_path(inst, 'fixed_ip/address')
- public_ips = utils.get_from_path(inst, 'fixed_ip/floating_ips/address')
+ private_ips = self.build_private_parts(inst)
+ public_ips = self.build_public_parts(inst)
return dict(public=public_ips, private=private_ips)
+ def build_public_parts(self, inst):
+ return utils.get_from_path(inst, 'fixed_ip/floating_ips/address')
+
+ def build_private_parts(self, inst):
+ return utils.get_from_path(inst, 'fixed_ip/address')
+
class ViewBuilderV11(ViewBuilder):
def build(self, inst):
diff --git a/nova/compute/manager.py b/nova/compute/manager.py
index 08b772517..bbc0e5f62 100644
--- a/nova/compute/manager.py
+++ b/nova/compute/manager.py
@@ -1097,12 +1097,8 @@ class ComputeManager(manager.SchedulerDependentManager):
db_instance['id'],
vm_state)
- if vm_state == power_state.SHUTOFF:
- # TODO(soren): This is what the compute manager does when you
- # terminate an instance. At some point I figure we'll have a
- # "terminated" state and some sort of cleanup job that runs
- # occasionally, cleaning them out.
- self.db.instance_destroy(context, db_instance['id'])
+ # NOTE(justinsb): We no longer auto-remove SHUTOFF instances
+ # It's quite hard to get them back when we do.
# Are there VMs not in the DB?
for vm_not_found_in_db in vms_not_found_in_db:
diff --git a/nova/image/fake.py b/nova/image/fake.py
index 08302d6eb..d1c62757f 100644
--- a/nova/image/fake.py
+++ b/nova/image/fake.py
@@ -44,10 +44,10 @@ class FakeImageService(service.BaseImageService):
'created_at': timestamp,
'updated_at': timestamp,
'status': 'active',
- 'type': 'machine',
+ 'container_format': 'ami',
+ 'disk_format': 'raw',
'properties': {'kernel_id': FLAGS.null_kernel,
- 'ramdisk_id': FLAGS.null_kernel,
- 'disk_format': 'ami'}
+ 'ramdisk_id': FLAGS.null_kernel}
}
self.create(None, image)
super(FakeImageService, self).__init__()
diff --git a/nova/image/glance.py b/nova/image/glance.py
index fdf468594..bf49ca96c 100644
--- a/nova/image/glance.py
+++ b/nova/image/glance.py
@@ -151,6 +151,8 @@ class GlanceImageService(service.BaseImageService):
:raises NotFound 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:
@@ -165,6 +167,8 @@ class GlanceImageService(service.BaseImageService):
: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:
@@ -186,33 +190,6 @@ class GlanceImageService(service.BaseImageService):
image_meta = _convert_timestamps_to_datetimes(image_meta)
return image_meta
- @staticmethod
- def _is_image_available(context, image_meta):
- """
- 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
- # sorted out. Until then, filtering in Nova.
- if image_meta['is_public'] or context.is_admin:
- return True
-
- properties = image_meta['properties']
-
- if context.project_id and ('project_id' in properties):
- return str(properties['project_id']) == str(project_id)
-
- try:
- user_id = properties['user_id']
- except KeyError:
- return False
-
- return str(user_id) == str(context.user_id)
-
# utility functions
def _convert_timestamps_to_datetimes(image_meta):
diff --git a/nova/image/local.py b/nova/image/local.py
index 1fb6e1f13..d4fd62156 100644
--- a/nova/image/local.py
+++ b/nova/image/local.py
@@ -84,7 +84,10 @@ class LocalImageService(service.BaseImageService):
def show(self, context, image_id):
try:
with open(self._path_to(image_id)) as metadata_file:
- return json.load(metadata_file)
+ image_meta = json.load(metadata_file)
+ if not self._is_image_available(context, image_meta):
+ raise exception.NotFound
+ return image_meta
except (IOError, ValueError):
raise exception.NotFound
@@ -119,10 +122,15 @@ class LocalImageService(service.BaseImageService):
image_path = self._path_to(image_id, None)
if not os.path.exists(image_path):
os.mkdir(image_path)
- return self.update(context, image_id, metadata, data)
+ return self._store(context, image_id, metadata, data)
def update(self, context, image_id, metadata, data=None):
"""Replace the contents of the given image with the new data."""
+ # NOTE(vish): show is to check if image is available
+ self.show(context, image_id)
+ return self._store(context, image_id, metadata, data)
+
+ def _store(self, context, image_id, metadata, data=None):
metadata['id'] = image_id
try:
if data:
@@ -140,9 +148,11 @@ class LocalImageService(service.BaseImageService):
def delete(self, context, image_id):
"""Delete the given image.
- Raises OSError 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:
shutil.rmtree(self._path_to(image_id, None))
except (IOError, ValueError):
diff --git a/nova/image/s3.py b/nova/image/s3.py
index ddec5f3aa..554760d53 100644
--- a/nova/image/s3.py
+++ b/nova/image/s3.py
@@ -46,6 +46,7 @@ flags.DEFINE_string('image_decryption_dir', '/tmp',
class S3ImageService(service.BaseImageService):
+ """Wraps an existing image service to support s3 based register"""
def __init__(self, service=None, *args, **kwargs):
if service == None:
service = utils.import_object(FLAGS.image_service)
@@ -58,52 +59,23 @@ class S3ImageService(service.BaseImageService):
return image
def delete(self, context, image_id):
- # FIXME(vish): call to show is to check filter
- self.show(context, image_id)
self.service.delete(context, image_id)
def update(self, context, image_id, metadata, data=None):
- # FIXME(vish): call to show is to check filter
- self.show(context, image_id)
image = self.service.update(context, image_id, metadata, data)
return image
def index(self, context):
- images = self.service.index(context)
- # FIXME(vish): index doesn't filter so we do it manually
- return self._filter(context, images)
+ return self.service.index(context)
def detail(self, context):
- images = self.service.detail(context)
- # FIXME(vish): detail doesn't filter so we do it manually
- return self._filter(context, images)
-
- @classmethod
- def _is_visible(cls, context, image):
- return (context.is_admin
- or context.project_id == image['properties']['owner_id']
- or image['properties']['is_public'] == 'True')
-
- @classmethod
- def _filter(cls, context, images):
- filtered = []
- for image in images:
- if not cls._is_visible(context, image):
- continue
- filtered.append(image)
- return filtered
+ return self.service.detail(context)
def show(self, context, image_id):
- image = self.service.show(context, image_id)
- if not self._is_visible(context, image):
- raise exception.NotFound
- return image
+ return self.service.show(context, image_id)
def show_by_name(self, context, name):
- image = self.service.show_by_name(context, name)
- if not self._is_visible(context, image):
- raise exception.NotFound
- return image
+ return self.service.show(context, name)
@staticmethod
def _conn(context):
@@ -167,7 +139,7 @@ class S3ImageService(service.BaseImageService):
arch = 'x86_64'
properties = metadata['properties']
- properties['owner_id'] = context.project_id
+ properties['project_id'] = context.project_id
properties['architecture'] = arch
if kernel_id:
@@ -176,8 +148,6 @@ class S3ImageService(service.BaseImageService):
if ramdisk_id:
properties['ramdisk_id'] = ec2utils.ec2_id_to_id(ramdisk_id)
- properties['is_public'] = False
- properties['type'] = image_type
metadata.update({'disk_format': image_format,
'container_format': image_format,
'status': 'queued',
diff --git a/nova/image/service.py b/nova/image/service.py
index b9897ecae..fddc72409 100644
--- a/nova/image/service.py
+++ b/nova/image/service.py
@@ -136,6 +136,33 @@ class BaseImageService(object):
"""
raise NotImplementedError
+ @staticmethod
+ def _is_image_available(context, image_meta):
+ """
+ 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
+ # sorted out. Until then, filtering in Nova.
+ if image_meta['is_public'] or context.is_admin:
+ return True
+
+ properties = image_meta['properties']
+
+ if context.project_id and ('project_id' in properties):
+ return str(properties['project_id']) == str(context.project_id)
+
+ try:
+ user_id = properties['user_id']
+ except KeyError:
+ return False
+
+ return str(user_id) == str(context.user_id)
+
@classmethod
def _translate_to_base(cls, metadata):
"""Return a metadata dictionary that is BaseImageService compliant.
diff --git a/nova/network/linux_net.py b/nova/network/linux_net.py
index d11d21dad..ed6c943c7 100644
--- a/nova/network/linux_net.py
+++ b/nova/network/linux_net.py
@@ -391,6 +391,12 @@ def unbind_floating_ip(floating_ip):
'dev', FLAGS.public_interface)
+def ensure_metadata_ip():
+ """Sets up local metadata ip"""
+ _execute('sudo', 'ip', 'addr', 'add', '169.254.169.254/32',
+ 'scope', 'link', 'dev', 'lo', check_exit_code=False)
+
+
def ensure_vlan_forward(public_ip, port, private_ip):
"""Sets up forwarding rules for vlan"""
iptables_manager.ipv4['filter'].add_rule("FORWARD",
@@ -442,6 +448,7 @@ def ensure_vlan(vlan_num):
return interface
+@utils.synchronized('ensure_bridge', external=True)
def ensure_bridge(bridge, interface, net_attrs=None):
"""Create a bridge unless it already exists.
@@ -495,6 +502,8 @@ def ensure_bridge(bridge, interface, net_attrs=None):
fields = line.split()
if fields and fields[0] == "0.0.0.0" and fields[-1] == interface:
gateway = fields[1]
+ _execute('sudo', 'route', 'del', 'default', 'gw', gateway,
+ 'dev', interface)
out, err = _execute('sudo', 'ip', 'addr', 'show', 'dev', interface,
'scope', 'global')
for line in out.split("\n"):
@@ -504,7 +513,7 @@ def ensure_bridge(bridge, interface, net_attrs=None):
_execute(*_ip_bridge_cmd('del', params, fields[-1]))
_execute(*_ip_bridge_cmd('add', params, bridge))
if gateway:
- _execute('sudo', 'route', 'add', '0.0.0.0', 'gw', gateway)
+ _execute('sudo', 'route', 'add', 'default', 'gw', gateway)
out, err = _execute('sudo', 'brctl', 'addif', bridge, interface,
check_exit_code=False)
diff --git a/nova/network/manager.py b/nova/network/manager.py
index 86ee4fc00..0dd7f2360 100644
--- a/nova/network/manager.py
+++ b/nova/network/manager.py
@@ -126,6 +126,7 @@ class NetworkManager(manager.SchedulerDependentManager):
standalone service.
"""
self.driver.init_host()
+ self.driver.ensure_metadata_ip()
# Set up networking for the projects for which we're already
# the designated network host.
ctxt = context.get_admin_context()
diff --git a/nova/tests/api/openstack/test_image_metadata.py b/nova/tests/api/openstack/test_image_metadata.py
index 9be753f84..7c3287006 100644
--- a/nova/tests/api/openstack/test_image_metadata.py
+++ b/nova/tests/api/openstack/test_image_metadata.py
@@ -45,7 +45,6 @@ class ImageMetaDataTest(unittest.TestCase):
'is_public': True,
'deleted_at': None,
'properties': {
- 'type': 'ramdisk',
'key1': 'value1',
'key2': 'value2'
},
@@ -62,7 +61,6 @@ class ImageMetaDataTest(unittest.TestCase):
'is_public': True,
'deleted_at': None,
'properties': {
- 'type': 'ramdisk',
'key1': 'value1',
'key2': 'value2'
},
diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py
index 10d2d0fed..f10bc1d7b 100644
--- a/nova/tests/api/openstack/test_servers.py
+++ b/nova/tests/api/openstack/test_servers.py
@@ -199,6 +199,26 @@ class ServersTest(test.TestCase):
print res_dict['server']
self.assertEqual(res_dict['server']['links'], expected_links)
+ def test_get_server_by_id_with_addresses_xml(self):
+ private = "192.168.0.3"
+ public = ["1.2.3.4"]
+ new_return_server = return_server_with_addresses(private, public)
+ self.stubs.Set(nova.db.api, 'instance_get', new_return_server)
+ req = webob.Request.blank('/v1.0/servers/1')
+ req.headers['Accept'] = 'application/xml'
+ res = req.get_response(fakes.wsgi_app())
+ dom = minidom.parseString(res.body)
+ server = dom.childNodes[0]
+ self.assertEquals(server.nodeName, 'server')
+ self.assertEquals(server.getAttribute('id'), '1')
+ self.assertEquals(server.getAttribute('name'), 'server1')
+ (public,) = server.getElementsByTagName('public')
+ (ip,) = public.getElementsByTagName('ip')
+ self.assertEquals(ip.getAttribute('addr'), '1.2.3.4')
+ (private,) = server.getElementsByTagName('private')
+ (ip,) = private.getElementsByTagName('ip')
+ self.assertEquals(ip.getAttribute('addr'), '192.168.0.3')
+
def test_get_server_by_id_with_addresses(self):
private = "192.168.0.3"
public = ["1.2.3.4"]
@@ -215,6 +235,84 @@ class ServersTest(test.TestCase):
self.assertEqual(len(addresses["private"]), 1)
self.assertEqual(addresses["private"][0], private)
+ def test_get_server_addresses_V10(self):
+ private = '192.168.0.3'
+ public = ['1.2.3.4']
+ new_return_server = return_server_with_addresses(private, public)
+ self.stubs.Set(nova.db.api, 'instance_get', new_return_server)
+ req = webob.Request.blank('/v1.0/servers/1/ips')
+ res = req.get_response(fakes.wsgi_app())
+ res_dict = json.loads(res.body)
+ self.assertEqual(res_dict, {
+ 'addresses': {'public': public, 'private': [private]}})
+
+ def test_get_server_addresses_xml_V10(self):
+ private_expected = "192.168.0.3"
+ public_expected = ["1.2.3.4"]
+ new_return_server = return_server_with_addresses(private_expected,
+ public_expected)
+ self.stubs.Set(nova.db.api, 'instance_get', new_return_server)
+ req = webob.Request.blank('/v1.0/servers/1/ips')
+ req.headers['Accept'] = 'application/xml'
+ res = req.get_response(fakes.wsgi_app())
+ dom = minidom.parseString(res.body)
+ (addresses,) = dom.childNodes
+ self.assertEquals(addresses.nodeName, 'addresses')
+ (public,) = addresses.getElementsByTagName('public')
+ (ip,) = public.getElementsByTagName('ip')
+ self.assertEquals(ip.getAttribute('addr'), public_expected[0])
+ (private,) = addresses.getElementsByTagName('private')
+ (ip,) = private.getElementsByTagName('ip')
+ self.assertEquals(ip.getAttribute('addr'), private_expected)
+
+ def test_get_server_addresses_public_V10(self):
+ private = "192.168.0.3"
+ public = ["1.2.3.4"]
+ new_return_server = return_server_with_addresses(private, public)
+ self.stubs.Set(nova.db.api, 'instance_get', new_return_server)
+ req = webob.Request.blank('/v1.0/servers/1/ips/public')
+ res = req.get_response(fakes.wsgi_app())
+ res_dict = json.loads(res.body)
+ self.assertEqual(res_dict, {'public': public})
+
+ def test_get_server_addresses_private_V10(self):
+ private = "192.168.0.3"
+ public = ["1.2.3.4"]
+ new_return_server = return_server_with_addresses(private, public)
+ self.stubs.Set(nova.db.api, 'instance_get', new_return_server)
+ req = webob.Request.blank('/v1.0/servers/1/ips/private')
+ res = req.get_response(fakes.wsgi_app())
+ res_dict = json.loads(res.body)
+ self.assertEqual(res_dict, {'private': [private]})
+
+ def test_get_server_addresses_public_xml_V10(self):
+ private = "192.168.0.3"
+ public = ["1.2.3.4"]
+ new_return_server = return_server_with_addresses(private, public)
+ self.stubs.Set(nova.db.api, 'instance_get', new_return_server)
+ req = webob.Request.blank('/v1.0/servers/1/ips/public')
+ req.headers['Accept'] = 'application/xml'
+ res = req.get_response(fakes.wsgi_app())
+ dom = minidom.parseString(res.body)
+ (public_node,) = dom.childNodes
+ self.assertEquals(public_node.nodeName, 'public')
+ (ip,) = public_node.getElementsByTagName('ip')
+ self.assertEquals(ip.getAttribute('addr'), public[0])
+
+ def test_get_server_addresses_private_xml_V10(self):
+ private = "192.168.0.3"
+ public = ["1.2.3.4"]
+ new_return_server = return_server_with_addresses(private, public)
+ self.stubs.Set(nova.db.api, 'instance_get', new_return_server)
+ req = webob.Request.blank('/v1.0/servers/1/ips/private')
+ req.headers['Accept'] = 'application/xml'
+ res = req.get_response(fakes.wsgi_app())
+ dom = minidom.parseString(res.body)
+ (private_node,) = dom.childNodes
+ self.assertEquals(private_node.nodeName, 'private')
+ (ip,) = private_node.getElementsByTagName('ip')
+ self.assertEquals(ip.getAttribute('addr'), private)
+
def test_get_server_by_id_with_addresses_v11(self):
private = "192.168.0.3"
public = ["1.2.3.4"]
@@ -625,6 +723,22 @@ class ServersTest(test.TestCase):
res = req.get_response(fakes.wsgi_app())
self.assertEqual(res.status_int, 404)
+ def test_get_all_server_details_xml_v1_0(self):
+ req = webob.Request.blank('/v1.0/servers/detail')
+ req.headers['Accept'] = 'application/xml'
+ res = req.get_response(fakes.wsgi_app())
+ print res.body
+ dom = minidom.parseString(res.body)
+ for i, server in enumerate(dom.getElementsByTagName('server')):
+ self.assertEqual(server.getAttribute('id'), str(i))
+ self.assertEqual(server.getAttribute('hostId'), '')
+ self.assertEqual(server.getAttribute('name'), 'server%d' % i)
+ self.assertEqual(server.getAttribute('imageId'), '10')
+ self.assertEqual(server.getAttribute('status'), 'BUILD')
+ (meta,) = server.getElementsByTagName('meta')
+ self.assertEqual(meta.getAttribute('key'), 'seq')
+ self.assertEqual(meta.firstChild.data.strip(), str(i))
+
def test_get_all_server_details_v1_0(self):
req = webob.Request.blank('/v1.0/servers/detail')
res = req.get_response(fakes.wsgi_app())
@@ -1529,29 +1643,27 @@ class TestGetKernelRamdiskFromImage(test.TestCase):
def test_not_ami(self):
"""Anything other than ami should return no kernel and no ramdisk"""
- image_meta = {'id': 1, 'status': 'active',
- 'properties': {'disk_format': 'vhd'}}
+ image_meta = {'id': 1, 'status': 'active', 'container_format': 'vhd'}
kernel_id, ramdisk_id = self._get_k_r(image_meta)
self.assertEqual(kernel_id, None)
self.assertEqual(ramdisk_id, None)
def test_ami_no_kernel(self):
"""If an ami is missing a kernel it should raise NotFound"""
- image_meta = {'id': 1, 'status': 'active',
- 'properties': {'disk_format': 'ami', 'ramdisk_id': 1}}
+ image_meta = {'id': 1, 'status': 'active', 'container_format': 'ami',
+ 'properties': {'ramdisk_id': 1}}
self.assertRaises(exception.NotFound, self._get_k_r, image_meta)
def test_ami_no_ramdisk(self):
"""If an ami is missing a ramdisk it should raise NotFound"""
- image_meta = {'id': 1, 'status': 'active',
- 'properties': {'disk_format': 'ami', 'kernel_id': 1}}
+ image_meta = {'id': 1, 'status': 'active', 'container_format': 'ami',
+ 'properties': {'kernel_id': 1}}
self.assertRaises(exception.NotFound, self._get_k_r, image_meta)
def test_ami_kernel_ramdisk_present(self):
"""Return IDs if both kernel and ramdisk are present"""
- image_meta = {'id': 1, 'status': 'active',
- 'properties': {'disk_format': 'ami', 'kernel_id': 1,
- 'ramdisk_id': 2}}
+ image_meta = {'id': 1, 'status': 'active', 'container_format': 'ami',
+ 'properties': {'kernel_id': 1, 'ramdisk_id': 2}}
kernel_id, ramdisk_id = self._get_k_r(image_meta)
self.assertEqual(kernel_id, 1)
self.assertEqual(ramdisk_id, 2)
diff --git a/nova/tests/image/test_glance.py b/nova/tests/image/test_glance.py
index 9d0b14613..109905ded 100644
--- a/nova/tests/image/test_glance.py
+++ b/nova/tests/image/test_glance.py
@@ -209,17 +209,17 @@ class TestMutatorDateTimeTests(BaseGlanceTest):
self.assertDateTimesEmpty(image_meta)
def test_update_handles_datetimes(self):
+ self.client.images = {'image1': self._make_datetime_fixture()}
self.client.update_response = self._make_datetime_fixture()
- dummy_id = 'dummy_id'
dummy_meta = {}
- image_meta = self.service.update(self.context, 'dummy_id', dummy_meta)
+ image_meta = self.service.update(self.context, 'image1', dummy_meta)
self.assertDateTimesFilled(image_meta)
def test_update_handles_none_datetimes(self):
+ self.client.images = {'image1': self._make_datetime_fixture()}
self.client.update_response = self._make_none_datetime_fixture()
- dummy_id = 'dummy_id'
dummy_meta = {}
- image_meta = self.service.update(self.context, 'dummy_id', dummy_meta)
+ image_meta = self.service.update(self.context, 'image1', dummy_meta)
self.assertDateTimesEmpty(image_meta)
def _make_datetime_fixture(self):
diff --git a/nova/tests/test_compute.py b/nova/tests/test_compute.py
index 1917dff3e..393110791 100644
--- a/nova/tests/test_compute.py
+++ b/nova/tests/test_compute.py
@@ -666,4 +666,5 @@ class ComputeTestCase(test.TestCase):
instances = db.instance_get_all(context.get_admin_context())
LOG.info(_("After force-killing instances: %s"), instances)
- self.assertEqual(len(instances), 0)
+ self.assertEqual(len(instances), 1)
+ self.assertEqual(power_state.SHUTOFF, instances[0]['state'])
diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py
index d8d27a98e..b949e6c92 100644
--- a/nova/virt/libvirt_conn.py
+++ b/nova/virt/libvirt_conn.py
@@ -117,6 +117,8 @@ flags.DEFINE_integer('live_migration_bandwidth', 0,
'Define live migration behavior')
flags.DEFINE_string('qemu_img', 'qemu-img',
'binary to use for qemu-img commands')
+flags.DEFINE_bool('start_guests_on_host_boot', False,
+ 'Whether to restart guests when the host reboots')
def get_connection(read_only):
@@ -231,12 +233,8 @@ class LibvirtConnection(driver.ComputeDriver):
{'name': instance['name'], 'state': state})
db.instance_set_state(ctxt, instance['id'], state)
- if state == power_state.SHUTOFF:
- # TODO(soren): This is what the compute manager does when you
- # terminate # an instance. At some point I figure we'll have a
- # "terminated" state and some sort of cleanup job that runs
- # occasionally, cleaning them out.
- db.instance_destroy(ctxt, instance['id'])
+ # NOTE(justinsb): We no longer delete SHUTOFF instances,
+ # the user may want to power them back on
if state != power_state.RUNNING:
continue
@@ -424,7 +422,6 @@ class LibvirtConnection(driver.ComputeDriver):
'container_format': base['container_format'],
'is_public': False,
'properties': {'architecture': base['architecture'],
- 'type': base['type'],
'name': '%s.%s' % (base['name'], image_id),
'kernel_id': instance['kernel_id'],
'image_location': 'snapshot',
@@ -475,7 +472,7 @@ class LibvirtConnection(driver.ComputeDriver):
xml = self.to_xml(instance)
self.firewall_driver.setup_basic_filtering(instance)
self.firewall_driver.prepare_instance_filter(instance)
- self._conn.createXML(xml, 0)
+ self._create_new_domain(xml)
self.firewall_driver.apply_instance_filter(instance)
timer = utils.LoopingCall(f=None)
@@ -523,7 +520,7 @@ class LibvirtConnection(driver.ComputeDriver):
'kernel_id': FLAGS.rescue_kernel_id,
'ramdisk_id': FLAGS.rescue_ramdisk_id}
self._create_image(instance, xml, '.rescue', rescue_images)
- self._conn.createXML(xml, 0)
+ self._create_new_domain(xml)
timer = utils.LoopingCall(f=None)
@@ -566,10 +563,15 @@ class LibvirtConnection(driver.ComputeDriver):
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._conn.createXML(xml, 0)
+ domain = self._create_new_domain(xml)
LOG.debug(_("instance %s: is running"), instance['name'])
self.firewall_driver.apply_instance_filter(instance)
+ if FLAGS.start_guests_on_host_boot:
+ LOG.debug(_("instance %s: setting autostart ON") %
+ instance['name'])
+ domain.setAutostart(1)
+
timer = utils.LoopingCall(f=None)
def _wait_for_boot():
@@ -989,11 +991,22 @@ class LibvirtConnection(driver.ComputeDriver):
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
try:
virt_dom = self._conn.lookupByName(instance_name)
- except:
- raise exception.NotFound(_("Instance %s not found")
- % 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
+
(state, max_mem, mem, num_cpu, cpu_time) = virt_dom.info()
return {'state': state,
'max_mem': max_mem,
@@ -1001,6 +1014,24 @@ class LibvirtConnection(driver.ComputeDriver):
'num_cpu': num_cpu,
'cpu_time': cpu_time}
+ def _create_new_domain(self, xml, persistent=True, launch_flags=0):
+ # NOTE(justinsb): libvirt has two types of domain:
+ # * a transient domain disappears when the guest is shutdown
+ # or the host is rebooted.
+ # * a permanent domain is not automatically deleted
+ # NOTE(justinsb): Even for ephemeral instances, transient seems risky
+
+ if persistent:
+ # To create a persistent domain, first define it, then launch it.
+ domain = self._conn.defineXML(xml)
+
+ domain.createWithFlags(launch_flags)
+ else:
+ # createXML call creates a transient domain
+ domain = self._conn.createXML(xml, launch_flags)
+
+ return domain
+
def get_diagnostics(self, instance_name):
raise exception.ApiError(_("diagnostics are not supported "
"for libvirt"))
diff --git a/nova/wsgi.py b/nova/wsgi.py
index 94aafdf1c..72758e50e 100644
--- a/nova/wsgi.py
+++ b/nova/wsgi.py
@@ -502,6 +502,14 @@ class Serializer(object):
result.setAttribute('xmlns', xmlns)
if type(data) is list:
+ collections = metadata.get('list_collections', {})
+ if nodename in collections:
+ metadata = collections[nodename]
+ for item in data:
+ node = doc.createElement(metadata['item_name'])
+ node.setAttribute(metadata['item_key'], str(item))
+ result.appendChild(node)
+ return result
singular = metadata.get('plurals', {}).get(nodename, None)
if singular is None:
if nodename.endswith('s'):
@@ -512,6 +520,16 @@ class Serializer(object):
node = self._to_xml_node(doc, metadata, singular, item)
result.appendChild(node)
elif type(data) is dict:
+ collections = metadata.get('dict_collections', {})
+ if nodename in collections:
+ metadata = collections[nodename]
+ for k, v in data.items():
+ node = doc.createElement(metadata['item_name'])
+ node.setAttribute(metadata['item_key'], str(k))
+ text = doc.createTextNode(str(v))
+ node.appendChild(text)
+ result.appendChild(node)
+ return result
attrs = metadata.get('attributes', {}).get(nodename, {})
for k, v in data.items():
if k in attrs: