summaryrefslogtreecommitdiffstats
path: root/nova
diff options
context:
space:
mode:
authorBrian Lamar <brian.lamar@rackspace.com>2011-04-15 14:57:58 -0400
committerBrian Lamar <brian.lamar@rackspace.com>2011-04-15 14:57:58 -0400
commit394b8ffac5f7361a163b320afdc824fda7ef0406 (patch)
treee54d9b81f74cb647b0d093a648b6285f47eba1b5 /nova
parentcb51075ceeb17d43bd53617a3dc8a561e7608722 (diff)
parenta1db2db067574ae6188f4d21a14478c1c018fe66 (diff)
Merged trunk.
Diffstat (limited to 'nova')
-rw-r--r--nova/CA/openssl.cnf.tmpl6
-rw-r--r--nova/api/__init__.py2
-rw-r--r--nova/api/direct.py96
-rw-r--r--nova/api/ec2/cloud.py75
-rw-r--r--nova/api/openstack/__init__.py6
-rw-r--r--nova/api/openstack/accounts.py5
-rw-r--r--nova/api/openstack/backup_schedules.py4
-rw-r--r--nova/api/openstack/common.py11
-rw-r--r--nova/api/openstack/consoles.py4
-rw-r--r--nova/api/openstack/extensions.py10
-rw-r--r--nova/api/openstack/faults.py7
-rw-r--r--nova/api/openstack/flavors.py7
-rw-r--r--nova/api/openstack/image_metadata.py15
-rw-r--r--nova/api/openstack/images.py8
-rw-r--r--nova/api/openstack/ips.py72
-rw-r--r--nova/api/openstack/limits.py4
-rw-r--r--nova/api/openstack/server_metadata.py3
-rw-r--r--nova/api/openstack/servers.py29
-rw-r--r--nova/api/openstack/shared_ip_groups.py4
-rw-r--r--nova/api/openstack/users.py3
-rw-r--r--nova/api/openstack/views/addresses.py10
-rw-r--r--nova/api/openstack/views/images.py10
-rw-r--r--nova/api/openstack/views/servers.py6
-rw-r--r--nova/api/openstack/zones.py6
-rw-r--r--nova/compute/api.py139
-rw-r--r--nova/compute/instance_types.py93
-rw-r--r--nova/compute/manager.py25
-rw-r--r--nova/crypto.py4
-rw-r--r--nova/db/api.py5
-rw-r--r--nova/db/sqlalchemy/api.py22
-rw-r--r--nova/db/sqlalchemy/migrate_repo/versions/014_add_instance_type_id_to_instances.py84
-rw-r--r--nova/db/sqlalchemy/models.py8
-rw-r--r--nova/image/fake.py6
-rw-r--r--nova/image/glance.py34
-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_api.py8
-rw-r--r--nova/tests/api/openstack/test_faults.py9
-rw-r--r--nova/tests/api/openstack/test_image_metadata.py41
-rw-r--r--nova/tests/api/openstack/test_images.py19
-rw-r--r--nova/tests/api/openstack/test_limits.py16
-rw-r--r--nova/tests/api/openstack/test_servers.py158
-rw-r--r--nova/tests/db/fakes.py22
-rw-r--r--nova/tests/image/test_glance.py8
-rw-r--r--nova/tests/integrated/test_xml.py56
-rw-r--r--nova/tests/test_cloud.py45
-rw-r--r--nova/tests/test_compute.py21
-rw-r--r--nova/tests/test_console.py2
-rw-r--r--nova/tests/test_instance_types.py6
-rw-r--r--nova/tests/test_quota.py17
-rw-r--r--nova/tests/test_scheduler.py2
-rw-r--r--nova/tests/test_virt.py5
-rw-r--r--nova/tests/test_volume.py2
-rw-r--r--nova/tests/test_xenapi.py18
-rw-r--r--nova/version.py2
-rw-r--r--nova/virt/libvirt_conn.py223
-rw-r--r--nova/virt/vmwareapi_conn.py4
-rw-r--r--nova/virt/xenapi/vm_utils.py27
-rw-r--r--nova/virt/xenapi/vmops.py43
-rw-r--r--nova/virt/xenapi_conn.py9
-rw-r--r--nova/volume/driver.py12
-rw-r--r--nova/wsgi.py46
65 files changed, 1298 insertions, 443 deletions
diff --git a/nova/CA/openssl.cnf.tmpl b/nova/CA/openssl.cnf.tmpl
index dd81f1c2b..b80fadf40 100644
--- a/nova/CA/openssl.cnf.tmpl
+++ b/nova/CA/openssl.cnf.tmpl
@@ -41,9 +41,13 @@ nameopt = default_ca
certopt = default_ca
policy = policy_match
+# NOTE(dprince): stateOrProvinceName must be 'supplied' or 'optional' to
+# work around a stateOrProvince printable string UTF8 mismatch on
+# 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
-stateOrProvinceName = match
+stateOrProvinceName = supplied
organizationName = optional
organizationalUnitName = optional
commonName = supplied
diff --git a/nova/api/__init__.py b/nova/api/__init__.py
index 0fedbbfad..747015af5 100644
--- a/nova/api/__init__.py
+++ b/nova/api/__init__.py
@@ -15,5 +15,3 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
-
-"""No-op __init__ for directory full of api goodies."""
diff --git a/nova/api/direct.py b/nova/api/direct.py
index e5f33cee4..8ceae299c 100644
--- a/nova/api/direct.py
+++ b/nova/api/direct.py
@@ -44,14 +44,33 @@ from nova import utils
from nova import wsgi
+# Global storage for registering modules.
ROUTES = {}
def register_service(path, handle):
+ """Register a service handle at a given path.
+
+ Services registered in this way will be made available to any instances of
+ nova.api.direct.Router.
+
+ :param path: `routes` path, can be a basic string like "/path"
+ :param handle: an object whose methods will be made available via the api
+
+ """
ROUTES[path] = handle
class Router(wsgi.Router):
+ """A simple WSGI router configured via `register_service`.
+
+ This is a quick way to attach multiple services to a given endpoint.
+ It will automatically load the routes registered in the `ROUTES` global.
+
+ TODO(termie): provide a paste-deploy version of this.
+
+ """
+
def __init__(self, mapper=None):
if mapper is None:
mapper = routes.Mapper()
@@ -66,6 +85,24 @@ class Router(wsgi.Router):
class DelegatedAuthMiddleware(wsgi.Middleware):
+ """A simple and naive authentication middleware.
+
+ Designed mostly to provide basic support for alternative authentication
+ schemes, this middleware only desires the identity of the user and will
+ generate the appropriate nova.context.RequestContext for the rest of the
+ application. This allows any middleware above it in the stack to
+ authenticate however it would like while only needing to conform to a
+ minimal interface.
+
+ Expects two headers to determine identity:
+ - X-OpenStack-User
+ - X-OpenStack-Project
+
+ This middleware is tied to identity management and will need to be kept
+ in sync with any changes to the way identity is dealt with internally.
+
+ """
+
def process_request(self, request):
os_user = request.headers['X-OpenStack-User']
os_project = request.headers['X-OpenStack-Project']
@@ -74,6 +111,20 @@ class DelegatedAuthMiddleware(wsgi.Middleware):
class JsonParamsMiddleware(wsgi.Middleware):
+ """Middleware to allow method arguments to be passed as serialized JSON.
+
+ Accepting arguments as JSON is useful for accepting data that may be more
+ complex than simple primitives.
+
+ In this case we accept it as urlencoded data under the key 'json' as in
+ json=<urlencoded_json> but this could be extended to accept raw JSON
+ in the POST body.
+
+ Filters out the parameters `self`, `context` and anything beginning with
+ an underscore.
+
+ """
+
def process_request(self, request):
if 'json' not in request.params:
return
@@ -92,6 +143,13 @@ class JsonParamsMiddleware(wsgi.Middleware):
class PostParamsMiddleware(wsgi.Middleware):
+ """Middleware to allow method arguments to be passed as POST parameters.
+
+ Filters out the parameters `self`, `context` and anything beginning with
+ an underscore.
+
+ """
+
def process_request(self, request):
params_parsed = request.params
params = {}
@@ -106,12 +164,21 @@ class PostParamsMiddleware(wsgi.Middleware):
class Reflection(object):
- """Reflection methods to list available methods."""
+ """Reflection methods to list available methods.
+
+ This is an object that expects to be registered via register_service.
+ These methods allow the endpoint to be self-describing. They introspect
+ the exposed methods and provide call signatures and documentation for
+ them allowing quick experimentation.
+
+ """
+
def __init__(self):
self._methods = {}
self._controllers = {}
def _gather_methods(self):
+ """Introspect available methods and generate documentation for them."""
methods = {}
controllers = {}
for route, handler in ROUTES.iteritems():
@@ -185,6 +252,16 @@ class Reflection(object):
class ServiceWrapper(wsgi.Controller):
+ """Wrapper to dynamically povide a WSGI controller for arbitrary objects.
+
+ With lightweight introspection allows public methods on the object to
+ be accesed via simple WSGI routing and parameters and serializes the
+ return values.
+
+ Automatically used be nova.api.direct.Router to wrap registered instances.
+
+ """
+
def __init__(self, service_handle):
self.service_handle = service_handle
@@ -206,10 +283,14 @@ class ServiceWrapper(wsgi.Controller):
# NOTE(vish): make sure we have no unicode keys for py2.6.
params = dict([(str(k), v) for (k, v) in params.iteritems()])
result = method(context, **params)
+
if result is None or type(result) is str or type(result) is unicode:
return result
+
try:
- return self._serialize(result, req.best_match_content_type())
+ content_type = req.best_match_content_type()
+ default_xmlns = self.get_default_xmlns(req)
+ return self._serialize(result, content_type, default_xmlns)
except:
raise exception.Error("returned non-serializable type: %s"
% result)
@@ -256,7 +337,16 @@ class Limited(object):
class Proxy(object):
- """Pretend a Direct API endpoint is an object."""
+ """Pretend a Direct API endpoint is an object.
+
+ This is mostly useful in testing at the moment though it should be easily
+ extendable to provide a basic API library functionality.
+
+ In testing we use this to stub out internal objects to verify that results
+ from the API are serializable.
+
+ """
+
def __init__(self, app, prefix=None):
self.app = app
self.prefix = prefix
diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py
index 58effd134..10b1d0ac5 100644
--- a/nova/api/ec2/cloud.py
+++ b/nova/api/ec2/cloud.py
@@ -142,6 +142,11 @@ class CloudController(object):
instance_ref = self.compute_api.get_all(ctxt, fixed_ip=address)
if instance_ref is None:
return None
+
+ # This ensures that all attributes of the instance
+ # are populated.
+ instance_ref = db.instance_get(ctxt, instance_ref['id'])
+
mpi = self._get_mpi_data(ctxt, instance_ref['project_id'])
if instance_ref['key_name']:
keys = {'0': {'_name': instance_ref['key_name'],
@@ -154,7 +159,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 +189,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
@@ -730,7 +735,10 @@ class CloudController(object):
instance['project_id'],
instance['host'])
i['productCodesSet'] = self._convert_to_set([], 'product_codes')
- i['instanceType'] = instance['instance_type']
+ if instance['instance_type']:
+ i['instanceType'] = instance['instance_type'].get('name')
+ else:
+ i['instanceType'] = None
i['launchTime'] = instance['created_at']
i['amiLaunchIndex'] = instance['launch_index']
i['displayName'] = instance['display_name']
@@ -785,7 +793,7 @@ class CloudController(object):
def allocate_address(self, context, **kwargs):
LOG.audit(_("Allocate address"), context=context)
public_ip = self.network_api.allocate_floating_ip(context)
- return {'addressSet': [{'publicIp': public_ip}]}
+ return {'publicIp': public_ip}
def release_address(self, context, public_ip, **kwargs):
LOG.audit(_("Release address %s"), public_ip, context=context)
@@ -815,7 +823,7 @@ class CloudController(object):
ramdisk = self._get_image(context, kwargs['ramdisk_id'])
kwargs['ramdisk_id'] = ramdisk['id']
instances = self.compute_api.create(context,
- instance_type=instance_types.get_by_type(
+ instance_type=instance_types.get_instance_type_by_name(
kwargs.get('instance_type', None)),
image_id=self._get_image(context, kwargs['image_id'])['id'],
min_count=int(kwargs.get('min_count', max_count)),
@@ -872,13 +880,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):
@@ -886,32 +908,42 @@ class CloudController(object):
internal_id = ec2utils.ec2_id_to_id(ec2_id)
return self.image_service.show(context, internal_id)
except exception.NotFound:
- return self.image_service.show_by_name(context, ec2_id)
+ try:
+ return self.image_service.show_by_name(context, ec2_id)
+ except exception.NotFound:
+ raise exception.NotFound(_('Image %s not found') % ec2_id)
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
@@ -943,8 +975,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)
@@ -959,7 +992,7 @@ class CloudController(object):
except exception.NotFound:
raise exception.NotFound(_('Image %s not found') % image_id)
result = {'imageId': image_id, 'launchPermission': []}
- if image['properties']['is_public']:
+ if image['is_public']:
result['launchPermission'].append({'group': 'all'})
return result
@@ -984,7 +1017,7 @@ class CloudController(object):
internal_id = image['id']
del(image['id'])
- image['properties']['is_public'] = (operation_type == 'add')
+ image['is_public'] = (operation_type == 'add')
return self.image_service.update(context, internal_id, image)
def update_image(self, context, image_id, **kwargs):
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/accounts.py b/nova/api/openstack/accounts.py
index 86066fa20..6e3763e47 100644
--- a/nova/api/openstack/accounts.py
+++ b/nova/api/openstack/accounts.py
@@ -13,15 +13,14 @@
# License for the specific language governing permissions and limitations
# under the License.
-import common
import webob.exc
from nova import exception
from nova import flags
from nova import log as logging
-from nova import wsgi
from nova.auth import manager
+from nova.api.openstack import common
from nova.api.openstack import faults
FLAGS = flags.FLAGS
@@ -35,7 +34,7 @@ def _translate_keys(account):
manager=account.project_manager_id)
-class Controller(wsgi.Controller):
+class Controller(common.OpenstackController):
_serialization_metadata = {
'application/xml': {
diff --git a/nova/api/openstack/backup_schedules.py b/nova/api/openstack/backup_schedules.py
index f2d2d86e8..4bf744046 100644
--- a/nova/api/openstack/backup_schedules.py
+++ b/nova/api/openstack/backup_schedules.py
@@ -19,7 +19,7 @@ import time
from webob import exc
-from nova import wsgi
+from nova.api.openstack import common
from nova.api.openstack import faults
import nova.image.service
@@ -29,7 +29,7 @@ def _translate_keys(inst):
return dict(backupSchedule=inst)
-class Controller(wsgi.Controller):
+class Controller(common.OpenstackController):
""" The backup schedule API controller for the Openstack API """
_serialization_metadata = {
diff --git a/nova/api/openstack/common.py b/nova/api/openstack/common.py
index 75aeb0a5f..234f921ab 100644
--- a/nova/api/openstack/common.py
+++ b/nova/api/openstack/common.py
@@ -22,6 +22,7 @@ import webob
from nova import exception
from nova import flags
from nova import log as logging
+from nova import wsgi
LOG = logging.getLogger('common')
@@ -30,6 +31,10 @@ LOG = logging.getLogger('common')
FLAGS = flags.FLAGS
+XML_NS_V10 = 'http://docs.rackspacecloud.com/servers/api/v1.0'
+XML_NS_V11 = 'http://docs.openstack.org/compute/api/v1.1'
+
+
def limited(items, request, max_limit=FLAGS.osapi_max_limit):
"""
Return a slice of items according to requested offset and limit.
@@ -128,3 +133,9 @@ def get_id_from_href(href):
except:
LOG.debug(_("Error extracting id from href: %s") % href)
raise webob.exc.HTTPBadRequest(_('could not parse id from href'))
+
+
+class OpenstackController(wsgi.Controller):
+ def get_default_xmlns(self, req):
+ # Use V10 by default
+ return XML_NS_V10
diff --git a/nova/api/openstack/consoles.py b/nova/api/openstack/consoles.py
index 8c291c2eb..1a77f25d7 100644
--- a/nova/api/openstack/consoles.py
+++ b/nova/api/openstack/consoles.py
@@ -19,7 +19,7 @@ from webob import exc
from nova import console
from nova import exception
-from nova import wsgi
+from nova.api.openstack import common
from nova.api.openstack import faults
@@ -43,7 +43,7 @@ def _translate_detail_keys(cons):
return dict(console=info)
-class Controller(wsgi.Controller):
+class Controller(common.OpenstackController):
"""The Consoles Controller for the Openstack API"""
_serialization_metadata = {
diff --git a/nova/api/openstack/extensions.py b/nova/api/openstack/extensions.py
index fb1dccb28..7ea7afef6 100644
--- a/nova/api/openstack/extensions.py
+++ b/nova/api/openstack/extensions.py
@@ -28,6 +28,7 @@ from nova import exception
from nova import flags
from nova import log as logging
from nova import wsgi
+from nova.api.openstack import common
from nova.api.openstack import faults
@@ -115,7 +116,7 @@ class ExtensionDescriptor(object):
return response_exts
-class ActionExtensionController(wsgi.Controller):
+class ActionExtensionController(common.OpenstackController):
def __init__(self, application):
@@ -136,7 +137,7 @@ class ActionExtensionController(wsgi.Controller):
return res
-class ResponseExtensionController(wsgi.Controller):
+class ResponseExtensionController(common.OpenstackController):
def __init__(self, application):
self.application = application
@@ -155,7 +156,8 @@ class ResponseExtensionController(wsgi.Controller):
body = res.body
headers = res.headers
except AttributeError:
- body = self._serialize(res, content_type)
+ default_xmlns = None
+ body = self._serialize(res, content_type, default_xmlns)
headers = {"Content-Type": content_type}
res = webob.Response()
res.body = body
@@ -163,7 +165,7 @@ class ResponseExtensionController(wsgi.Controller):
return res
-class ExtensionController(wsgi.Controller):
+class ExtensionController(common.OpenstackController):
def __init__(self, extension_manager):
self.extension_manager = extension_manager
diff --git a/nova/api/openstack/faults.py b/nova/api/openstack/faults.py
index 940bd8771..87118ce19 100644
--- a/nova/api/openstack/faults.py
+++ b/nova/api/openstack/faults.py
@@ -20,10 +20,10 @@ import webob.dec
import webob.exc
from nova import wsgi
+from nova.api.openstack import common
class Fault(webob.exc.HTTPException):
-
"""An RS API fault response."""
_fault_names = {
@@ -47,7 +47,7 @@ class Fault(webob.exc.HTTPException):
"""Generate a WSGI response based on the exception passed to ctor."""
# Replace the body with fault details.
code = self.wrapped_exc.status_int
- fault_name = self._fault_names.get(code, "computeFault")
+ fault_name = self._fault_names.get(code, "cloudServersFault")
fault_data = {
fault_name: {
'code': code,
@@ -57,7 +57,8 @@ class Fault(webob.exc.HTTPException):
fault_data[fault_name]['retryAfter'] = retry
# 'code' is an attribute on the fault tag itself
metadata = {'application/xml': {'attributes': {fault_name: 'code'}}}
- serializer = wsgi.Serializer(metadata)
+ default_xmlns = common.XML_NS_V10
+ serializer = wsgi.Serializer(metadata, default_xmlns)
content_type = req.best_match_content_type()
self.wrapped_exc.body = serializer.serialize(fault_data, content_type)
self.wrapped_exc.content_type = content_type
diff --git a/nova/api/openstack/flavors.py b/nova/api/openstack/flavors.py
index 5b99b5a6f..40787bd17 100644
--- a/nova/api/openstack/flavors.py
+++ b/nova/api/openstack/flavors.py
@@ -19,11 +19,11 @@ import webob
from nova import db
from nova import exception
-from nova import wsgi
+from nova.api.openstack import common
from nova.api.openstack import views
-class Controller(wsgi.Controller):
+class Controller(common.OpenstackController):
"""Flavor controller for the OpenStack API."""
_serialization_metadata = {
@@ -76,3 +76,6 @@ class ControllerV11(Controller):
def _get_view_builder(self, req):
base_url = req.application_url
return views.flavors.ViewBuilderV11(base_url)
+
+ def get_default_xmlns(self, req):
+ return common.XML_NS_V11
diff --git a/nova/api/openstack/image_metadata.py b/nova/api/openstack/image_metadata.py
index c9d6ac532..1eccc0174 100644
--- a/nova/api/openstack/image_metadata.py
+++ b/nova/api/openstack/image_metadata.py
@@ -18,15 +18,17 @@
from webob import exc
from nova import flags
+from nova import quota
from nova import utils
from nova import wsgi
+from nova.api.openstack import common
from nova.api.openstack import faults
FLAGS = flags.FLAGS
-class Controller(wsgi.Controller):
+class Controller(common.OpenstackController):
"""The image metadata API controller for the Openstack API"""
def __init__(self):
@@ -39,6 +41,15 @@ class Controller(wsgi.Controller):
metadata = image.get('properties', {})
return metadata
+ def _check_quota_limit(self, context, metadata):
+ if metadata is None:
+ return
+ num_metadata = len(metadata)
+ quota_metadata = quota.allowed_metadata_items(context, num_metadata)
+ if quota_metadata < num_metadata:
+ expl = _("Image metadata limit exceeded")
+ raise exc.HTTPBadRequest(explanation=expl)
+
def index(self, req, image_id):
"""Returns the list of metadata for a given instance"""
context = req.environ['nova.context']
@@ -61,6 +72,7 @@ class Controller(wsgi.Controller):
if 'metadata' in body:
for key, value in body['metadata'].iteritems():
metadata[key] = value
+ self._check_quota_limit(context, metadata)
img['properties'] = metadata
self.image_service.update(context, image_id, img, None)
return dict(metadata=metadata)
@@ -77,6 +89,7 @@ class Controller(wsgi.Controller):
img = self.image_service.show(context, image_id)
metadata = self._get_metadata(context, image_id, img)
metadata[id] = body[id]
+ self._check_quota_limit(context, metadata)
img['properties'] = metadata
self.image_service.update(context, image_id, img, None)
diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py
index e77100d7b..77baf5947 100644
--- a/nova/api/openstack/images.py
+++ b/nova/api/openstack/images.py
@@ -13,8 +13,6 @@
# License for the specific language governing permissions and limitations
# under the License.
-import datetime
-
import webob.exc
from nova import compute
@@ -22,7 +20,6 @@ from nova import exception
from nova import flags
from nova import log
from nova import utils
-from nova import wsgi
from nova.api.openstack import common
from nova.api.openstack import faults
from nova.api.openstack.views import images as images_view
@@ -32,7 +29,7 @@ LOG = log.getLogger('nova.api.openstack.images')
FLAGS = flags.FLAGS
-class Controller(wsgi.Controller):
+class Controller(common.OpenstackController):
"""Base `wsgi.Controller` for retrieving/displaying images."""
_serialization_metadata = {
@@ -153,3 +150,6 @@ class ControllerV11(Controller):
"""Property to get the ViewBuilder class we need to use."""
base_url = request.application_url
return images_view.ViewBuilderV11(base_url)
+
+ def get_default_xmlns(self, req):
+ return common.XML_NS_V11
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/limits.py b/nova/api/openstack/limits.py
index efc7d193d..9877af191 100644
--- a/nova/api/openstack/limits.py
+++ b/nova/api/openstack/limits.py
@@ -31,8 +31,8 @@ from collections import defaultdict
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 Controller
from nova.wsgi import Middleware
@@ -43,7 +43,7 @@ PER_HOUR = 60 * 60
PER_DAY = 60 * 60 * 24
-class LimitsController(Controller):
+class LimitsController(common.OpenstackController):
"""
Controller for accessing limits in the OpenStack API.
"""
diff --git a/nova/api/openstack/server_metadata.py b/nova/api/openstack/server_metadata.py
index 45bbac99d..5c1390b9c 100644
--- a/nova/api/openstack/server_metadata.py
+++ b/nova/api/openstack/server_metadata.py
@@ -19,10 +19,11 @@ from webob import exc
from nova import compute
from nova import wsgi
+from nova.api.openstack import common
from nova.api.openstack import faults
-class Controller(wsgi.Controller):
+class Controller(common.OpenstackController):
""" The server metadata API controller for the Openstack API """
def __init__(self):
diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py
index 6704a68ae..43e0c7963 100644
--- a/nova/api/openstack/servers.py
+++ b/nova/api/openstack/servers.py
@@ -44,7 +44,7 @@ LOG = logging.getLogger('server')
FLAGS = flags.FLAGS
-class Controller(wsgi.Controller):
+class Controller(common.OpenstackController):
""" The Server API controller for the OpenStack API """
_serialization_metadata = {
@@ -55,6 +55,13 @@ class Controller(wsgi.Controller):
"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(wsgi.Controller):
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)
@@ -160,9 +158,11 @@ class Controller(wsgi.Controller):
name = name.strip()
try:
+ inst_type = \
+ instance_types.get_instance_type_by_flavor_id(flavor_id)
(inst,) = self.compute_api.create(
context,
- instance_types.get_by_flavor_id(flavor_id),
+ inst_type,
image_id,
kernel_id=kernel_id,
ramdisk_id=ramdisk_id,
@@ -175,7 +175,7 @@ class Controller(wsgi.Controller):
except quota.QuotaError as error:
self._handle_quota_error(error)
- inst['instance_type'] = flavor_id
+ inst['instance_type'] = inst_type
inst['image_id'] = requested_image_id
builder = self._get_view_builder(req)
@@ -565,7 +565,7 @@ class Controller(wsgi.Controller):
_("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:
@@ -648,6 +648,9 @@ class ControllerV11(Controller):
def _limit_items(self, items, req):
return common.limited_by_marker(items, req)
+ def get_default_xmlns(self, req):
+ return common.XML_NS_V11
+
class ServerCreateRequestXMLDeserializer(object):
"""
diff --git a/nova/api/openstack/shared_ip_groups.py b/nova/api/openstack/shared_ip_groups.py
index ee7991d7f..996db3648 100644
--- a/nova/api/openstack/shared_ip_groups.py
+++ b/nova/api/openstack/shared_ip_groups.py
@@ -17,7 +17,7 @@
from webob import exc
-from nova import wsgi
+from nova.api.openstack import common
from nova.api.openstack import faults
@@ -32,7 +32,7 @@ def _translate_detail_keys(inst):
return dict(sharedIpGroups=inst)
-class Controller(wsgi.Controller):
+class Controller(common.OpenstackController):
""" The Shared IP Groups Controller for the Openstack API """
_serialization_metadata = {
diff --git a/nova/api/openstack/users.py b/nova/api/openstack/users.py
index d3ab3d553..077ccfc79 100644
--- a/nova/api/openstack/users.py
+++ b/nova/api/openstack/users.py
@@ -18,7 +18,6 @@ from webob import exc
from nova import exception
from nova import flags
from nova import log as logging
-from nova import wsgi
from nova.api.openstack import common
from nova.api.openstack import faults
from nova.auth import manager
@@ -35,7 +34,7 @@ def _translate_keys(user):
admin=user.admin)
-class Controller(wsgi.Controller):
+class Controller(common.OpenstackController):
_serialization_metadata = {
'application/xml': {
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/api/openstack/views/images.py b/nova/api/openstack/views/images.py
index 16195b050..9dec8a355 100644
--- a/nova/api/openstack/views/images.py
+++ b/nova/api/openstack/views/images.py
@@ -34,11 +34,11 @@ class ViewBuilder(object):
def _format_status(self, image):
"""Update the status field to standardize format."""
status_mapping = {
- 'pending': 'queued',
- 'decrypting': 'preparing',
- 'untarring': 'saving',
- 'available': 'active',
- 'killed': 'failed',
+ 'pending': 'QUEUED',
+ 'decrypting': 'PREPARING',
+ 'untarring': 'SAVING',
+ 'available': 'ACTIVE',
+ 'killed': 'FAILED',
}
try:
diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py
index d24c025be..e52bfaea3 100644
--- a/nova/api/openstack/views/servers.py
+++ b/nova/api/openstack/views/servers.py
@@ -82,7 +82,7 @@ class ViewBuilder(object):
# Return the metadata as a dictionary
metadata = {}
for item in inst.get('metadata', []):
- metadata[item['key']] = item['value']
+ metadata[item['key']] = str(item['value'])
inst_dict['metadata'] = metadata
inst_dict['hostId'] = ''
@@ -115,7 +115,7 @@ class ViewBuilderV10(ViewBuilder):
def _build_flavor(self, response, inst):
if 'instance_type' in dict(inst):
- response['flavorId'] = inst['instance_type']
+ response['flavorId'] = inst['instance_type']['flavorid']
class ViewBuilderV11(ViewBuilder):
@@ -134,7 +134,7 @@ class ViewBuilderV11(ViewBuilder):
def _build_flavor(self, response, inst):
if "instance_type" in dict(inst):
- flavor_id = inst["instance_type"]
+ flavor_id = inst["instance_type"]['flavorid']
flavor_ref = self.flavor_builder.generate_href(flavor_id)
response["flavorRef"] = flavor_ref
diff --git a/nova/api/openstack/zones.py b/nova/api/openstack/zones.py
index 846cb48a1..227ffecdc 100644
--- a/nova/api/openstack/zones.py
+++ b/nova/api/openstack/zones.py
@@ -13,12 +13,10 @@
# License for the specific language governing permissions and limitations
# under the License.
-import common
-
from nova import db
from nova import flags
from nova import log as logging
-from nova import wsgi
+from nova.api.openstack import common
from nova.scheduler import api
@@ -43,7 +41,7 @@ def _scrub_zone(zone):
'deleted', 'deleted_at', 'updated_at'))
-class Controller(wsgi.Controller):
+class Controller(common.OpenstackController):
_serialization_metadata = {
'application/xml': {
diff --git a/nova/compute/api.py b/nova/compute/api.py
index 996955fe3..e6146231c 100644
--- a/nova/compute/api.py
+++ b/nova/compute/api.py
@@ -16,9 +16,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-"""
-Handles all requests relating to instances (guest vms).
-"""
+"""Handles all requests relating to instances (guest vms)."""
import datetime
import re
@@ -86,10 +84,10 @@ class API(base.Base):
{"method": "get_network_topic", "args": {'fake': 1}})
def _check_injected_file_quota(self, context, injected_files):
- """
- Enforce quota limits on injected files
+ """Enforce quota limits on injected files.
+
+ Raises a QuotaError if any limit is exceeded.
- Raises a QuotaError if any limit is exceeded
"""
if injected_files is None:
return
@@ -111,11 +109,17 @@ class API(base.Base):
key_name=None, key_data=None, security_group='default',
availability_zone=None, user_data=None, metadata=[],
injected_files=None):
- """Create the number of instances requested if quota and
- other arguments check out ok."""
+ """Create the number and type of instances requested.
- type_data = instance_types.get_instance_type(instance_type)
- num_instances = quota.allowed_instances(context, max_count, type_data)
+ Verifies that quota and other arguments are valid.
+
+ """
+
+ if not instance_type:
+ instance_type = instance_types.get_default_instance_type()
+
+ num_instances = quota.allowed_instances(context, max_count,
+ instance_type)
if num_instances < min_count:
pid = context.project_id
LOG.warn(_("Quota exceeeded for %(pid)s,"
@@ -201,10 +205,10 @@ class API(base.Base):
'user_id': context.user_id,
'project_id': context.project_id,
'launch_time': time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()),
- 'instance_type': instance_type,
- 'memory_mb': type_data['memory_mb'],
- 'vcpus': type_data['vcpus'],
- 'local_gb': type_data['local_gb'],
+ 'instance_type_id': instance_type['id'],
+ 'memory_mb': instance_type['memory_mb'],
+ 'vcpus': instance_type['vcpus'],
+ 'local_gb': instance_type['local_gb'],
'display_name': display_name,
'display_description': display_description,
'user_data': user_data or '',
@@ -259,8 +263,7 @@ class API(base.Base):
return [dict(x.iteritems()) for x in instances]
def has_finished_migration(self, context, instance_id):
- """Retrieves whether or not a finished migration exists for
- an instance"""
+ """Returns true if an instance has a finished migration."""
try:
db.migration_get_by_instance_and_status(context, instance_id,
'finished')
@@ -269,8 +272,10 @@ class API(base.Base):
return False
def ensure_default_security_group(self, context):
- """ Create security group for the security context if it
- does not already exist
+ """Ensure that a context has a security group.
+
+ Creates a security group for the security context if it does not
+ already exist.
:param context: the security context
@@ -286,7 +291,7 @@ class API(base.Base):
db.security_group_create(context, values)
def trigger_security_group_rules_refresh(self, context, security_group_id):
- """Called when a rule is added to or removed from a security_group"""
+ """Called when a rule is added to or removed from a security_group."""
security_group = self.db.security_group_get(context, security_group_id)
@@ -302,11 +307,12 @@ class API(base.Base):
"args": {"security_group_id": security_group.id}})
def trigger_security_group_members_refresh(self, context, group_id):
- """Called when a security group gains a new or loses a member
+ """Called when a security group gains a new or loses a member.
Sends an update request to each compute node for whom this is
- relevant."""
+ relevant.
+ """
# First, we get the security group rules that reference this group as
# the grantee..
security_group_rules = \
@@ -351,7 +357,7 @@ class API(base.Base):
as data fields of the instance to be
updated
- :retval None
+ :returns: None
"""
rv = self.db.instance_update(context, instance_id, kwargs)
@@ -359,6 +365,7 @@ class API(base.Base):
@scheduler_api.reroute_compute("delete")
def delete(self, context, instance_id):
+ """Terminate an instance."""
LOG.debug(_("Going to try to terminate %s"), instance_id)
try:
instance = self.get(context, instance_id)
@@ -367,11 +374,15 @@ class API(base.Base):
instance_id)
raise
- if (instance['state_description'] == 'terminating'):
+ if instance['state_description'] == 'terminating':
LOG.warning(_("Instance %s is already being terminated"),
instance_id)
return
+ if instance['state_description'] == 'migrating':
+ LOG.warning(_("Instance %s is being migrated"), instance_id)
+ return
+
self.update(context,
instance['id'],
state_description='terminating',
@@ -386,22 +397,28 @@ class API(base.Base):
self.db.instance_destroy(context, instance_id)
def get(self, context, instance_id):
- """Get a single instance with the given ID."""
+ """Get a single instance with the given instance_id."""
rv = self.db.instance_get(context, instance_id)
return dict(rv.iteritems())
@scheduler_api.reroute_compute("get")
def routing_get(self, context, instance_id):
- """Use this method instead of get() if this is the only
- operation you intend to to. It will route to novaclient.get
- if the instance is not found."""
+ """A version of get with special routing characteristics.
+
+ Use this method instead of get() if this is the only operation you
+ intend to to. It will route to novaclient.get if the instance is not
+ found.
+
+ """
return self.get(context, instance_id)
def get_all(self, context, project_id=None, reservation_id=None,
fixed_ip=None):
- """Get all instances, possibly filtered by one of the
- given parameters. If there is no filter and the context is
- an admin, it will retreive all instances in the system.
+ """Get all instances filtered by one of the given parameters.
+
+ If there is no filter and the context is an admin, it will retreive
+ all instances in the system.
+
"""
if reservation_id is not None:
return self.db.instance_get_all_by_reservation(
@@ -430,7 +447,8 @@ class API(base.Base):
:param params: Optional dictionary of arguments to be passed to the
compute worker
- :retval None
+ :returns: None
+
"""
if not params:
params = {}
@@ -449,7 +467,7 @@ class API(base.Base):
:param params: Optional dictionary of arguments to be passed to the
compute worker
- :retval: Result returned by compute worker
+ :returns: Result returned by compute worker
"""
if not params:
params = {}
@@ -462,13 +480,14 @@ class API(base.Base):
return rpc.call(context, queue, kwargs)
def _cast_scheduler_message(self, context, args):
- """Generic handler for RPC calls to the scheduler"""
+ """Generic handler for RPC calls to the scheduler."""
rpc.cast(context, FLAGS.scheduler_topic, args)
def snapshot(self, context, instance_id, name):
"""Snapshot the given instance.
- :retval: A dict containing image metadata
+ :returns: A dict containing image metadata
+
"""
properties = {'instance_id': str(instance_id),
'user_id': str(context.user_id)}
@@ -485,7 +504,7 @@ class API(base.Base):
self._cast_compute_message('reboot_instance', context, instance_id)
def revert_resize(self, context, instance_id):
- """Reverts a resize, deleting the 'new' instance in the process"""
+ """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')
@@ -500,8 +519,7 @@ class API(base.Base):
{'status': 'reverted'})
def confirm_resize(self, context, instance_id):
- """Confirms a migration/resize, deleting the 'old' instance in the
- process."""
+ """Confirms a migration/resize and deletes the 'old' instance."""
context = context.elevated()
migration_ref = self.db.migration_get_by_instance_and_status(context,
instance_id, 'finished')
@@ -521,8 +539,7 @@ class API(base.Base):
def resize(self, context, instance_id, flavor_id):
"""Resize a running instance."""
instance = self.db.instance_get(context, instance_id)
- current_instance_type = self.db.instance_type_get_by_name(
- context, instance['instance_type'])
+ current_instance_type = instance['instance_type']
new_instance_type = self.db.instance_type_get_by_flavor_id(
context, flavor_id)
@@ -562,10 +579,9 @@ class API(base.Base):
@scheduler_api.reroute_compute("diagnostics")
def get_diagnostics(self, context, instance_id):
"""Retrieve diagnostics for the given instance."""
- return self._call_compute_message(
- "get_diagnostics",
- context,
- instance_id)
+ return self._call_compute_message("get_diagnostics",
+ context,
+ instance_id)
def get_actions(self, context, instance_id):
"""Retrieve actions for the given instance."""
@@ -573,12 +589,12 @@ class API(base.Base):
@scheduler_api.reroute_compute("suspend")
def suspend(self, context, instance_id):
- """suspend the instance with instance_id"""
+ """Suspend the given instance."""
self._cast_compute_message('suspend_instance', context, instance_id)
@scheduler_api.reroute_compute("resume")
def resume(self, context, instance_id):
- """resume the instance with instance_id"""
+ """Resume the given instance."""
self._cast_compute_message('resume_instance', context, instance_id)
@scheduler_api.reroute_compute("rescue")
@@ -593,15 +609,15 @@ 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)
+ self._cast_compute_message(
+ 'set_admin_password', context, instance_id, password)
def inject_file(self, context, instance_id):
"""Write a file to the given instance."""
self._cast_compute_message('inject_file', context, instance_id)
def get_ajax_console(self, context, instance_id):
- """Get a url to an AJAX Console"""
+ """Get a url to an AJAX Console."""
output = self._call_compute_message('get_ajax_console',
context,
instance_id)
@@ -610,7 +626,7 @@ class API(base.Base):
'args': {'token': output['token'], 'host': output['host'],
'port': output['port']}})
return {'url': '%s/?token=%s' % (FLAGS.ajax_console_proxy_url,
- output['token'])}
+ output['token'])}
def get_vnc_console(self, context, instance_id):
"""Get a url to a VNC Console."""
@@ -632,39 +648,34 @@ class API(base.Base):
'portignore')}
def get_console_output(self, context, instance_id):
- """Get console output for an an instance"""
+ """Get console output for an an instance."""
return self._call_compute_message('get_console_output',
context,
instance_id)
def lock(self, context, instance_id):
- """lock the instance with instance_id"""
+ """Lock the given instance."""
self._cast_compute_message('lock_instance', context, instance_id)
def unlock(self, context, instance_id):
- """unlock the instance with instance_id"""
+ """Unlock the given instance."""
self._cast_compute_message('unlock_instance', context, instance_id)
def get_lock(self, context, instance_id):
- """return the boolean state of (instance with instance_id)'s lock"""
+ """Return the boolean state of given instance's lock."""
instance = self.get(context, instance_id)
return instance['locked']
def reset_network(self, context, instance_id):
- """
- Reset networking on the instance.
-
- """
+ """Reset networking on the instance."""
self._cast_compute_message('reset_network', context, instance_id)
def inject_network_info(self, context, instance_id):
- """
- Inject network info for the instance.
-
- """
+ """Inject network info for the instance."""
self._cast_compute_message('inject_network_info', context, instance_id)
def attach_volume(self, context, instance_id, volume_id, device):
+ """Attach an existing volume to an existing instance."""
if not re.match("^/dev/[a-z]d[a-z]+$", device):
raise exception.ApiError(_("Invalid device specified: %s. "
"Example device: /dev/vdb") % device)
@@ -679,6 +690,7 @@ class API(base.Base):
"mountpoint": device}})
def detach_volume(self, context, volume_id):
+ """Detach a volume from an instance."""
instance = self.db.volume_get_instance(context.elevated(), volume_id)
if not instance:
raise exception.ApiError(_("Volume isn't attached to anything!"))
@@ -692,6 +704,7 @@ class API(base.Base):
return instance
def associate_floating_ip(self, context, instance_id, address):
+ """Associate a floating ip with an instance."""
instance = self.get(context, instance_id)
self.network_api.associate_floating_ip(context,
floating_ip=address,
@@ -703,11 +716,11 @@ class API(base.Base):
return dict(rv.iteritems())
def delete_instance_metadata(self, context, instance_id, key):
- """Delete the given metadata item"""
+ """Delete the given metadata item from an instance."""
self.db.instance_metadata_delete(context, instance_id, key)
def update_or_create_instance_metadata(self, context, instance_id,
metadata):
- """Updates or creates instance metadata"""
+ """Updates or creates instance 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 fa02a5dfa..f893f8478 100644
--- a/nova/compute/instance_types.py
+++ b/nova/compute/instance_types.py
@@ -18,9 +18,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-"""
-The built-in instance properties.
-"""
+"""Built-in instance properties."""
from nova import context
from nova import db
@@ -34,9 +32,7 @@ LOG = logging.getLogger('nova.instance_types')
def create(name, memory, vcpus, local_gb, flavorid, swap=0,
rxtx_quota=0, rxtx_cap=0):
- """Creates instance types / flavors
- arguments: name memory vcpus local_gb flavorid swap rxtx_quota rxtx_cap
- """
+ """Creates instance types."""
for option in [memory, vcpus, local_gb, flavorid]:
try:
int(option)
@@ -59,83 +55,86 @@ def create(name, memory, vcpus, local_gb, flavorid, swap=0,
rxtx_quota=rxtx_quota,
rxtx_cap=rxtx_cap))
except exception.DBError, e:
- LOG.exception(_('DB error: %s' % e))
- raise exception.ApiError(_("Cannot create instance type: %s" % name))
+ LOG.exception(_('DB error: %s') % e)
+ raise exception.ApiError(_("Cannot create instance type: %s") % name)
def destroy(name):
- """Marks instance types / flavors as deleted
- arguments: name"""
+ """Marks instance types as deleted."""
if name == None:
raise exception.InvalidInputException(_("No instance type specified"))
else:
try:
db.instance_type_destroy(context.get_admin_context(), name)
except exception.NotFound:
- LOG.exception(_('Instance type %s not found for deletion' % name))
- raise exception.ApiError(_("Unknown instance type: %s" % name))
+ LOG.exception(_('Instance type %s not found for deletion') % name)
+ raise exception.ApiError(_("Unknown instance type: %s") % name)
def purge(name):
- """Removes instance types / flavors from database
- arguments: name"""
+ """Removes instance types from database."""
if name == None:
raise exception.InvalidInputException(_("No instance type specified"))
else:
try:
db.instance_type_purge(context.get_admin_context(), name)
except exception.NotFound:
- LOG.exception(_('Instance type %s not found for purge' % name))
- raise exception.ApiError(_("Unknown instance type: %s" % name))
+ LOG.exception(_('Instance type %s not found for purge') % name)
+ raise exception.ApiError(_("Unknown instance type: %s") % name)
def get_all_types(inactive=0):
- """Retrieves non-deleted instance_types.
- Pass true as argument if you want deleted instance types returned also."""
+ """Get all non-deleted instance_types.
+
+ Pass true as argument if you want deleted instance types returned also.
+
+ """
return db.instance_type_get_all(context.get_admin_context(), inactive)
-def get_all_flavors():
- """retrieves non-deleted flavors. alias for instance_types.get_all_types().
- Pass true as argument if you want deleted instance types returned also."""
- return get_all_types(context.get_admin_context())
+get_all_flavors = get_all_types
-def get_instance_type(name):
- """Retrieves single instance type by name"""
- if name is None:
- return FLAGS.default_instance_type
+def get_default_instance_type():
+ """Get the default instance type."""
+ name = FLAGS.default_instance_type
try:
- ctxt = context.get_admin_context()
- inst_type = db.instance_type_get_by_name(ctxt, name)
- return inst_type
+ return get_instance_type_by_name(name)
except exception.DBError:
- raise exception.ApiError(_("Unknown instance type: %s" % name))
+ raise exception.ApiError(_("Unknown instance type: %s") % name)
-def get_by_type(instance_type):
- """retrieve instance type name"""
- if instance_type is None:
- return FLAGS.default_instance_type
+def get_instance_type(id):
+ """Retrieves single instance type by id."""
+ if id is None:
+ return get_default_instance_type()
+ try:
+ ctxt = context.get_admin_context()
+ return db.instance_type_get_by_id(ctxt, id)
+ except exception.DBError:
+ raise exception.ApiError(_("Unknown instance type: %s") % name)
+
+def get_instance_type_by_name(name):
+ """Retrieves single instance type by name."""
+ if name is None:
+ return get_default_instance_type()
try:
ctxt = context.get_admin_context()
- inst_type = db.instance_type_get_by_name(ctxt, instance_type)
- return inst_type['name']
- except exception.DBError, e:
- LOG.exception(_('DB error: %s' % e))
- raise exception.ApiError(_("Unknown instance type: %s" %\
- instance_type))
+ return db.instance_type_get_by_name(ctxt, name)
+ except exception.DBError:
+ raise exception.ApiError(_("Unknown instance type: %s") % name)
-def get_by_flavor_id(flavor_id):
- """retrieve instance type's name by flavor_id"""
+# TODO(termie): flavor-specific code should probably be in the API that uses
+# flavors.
+def get_instance_type_by_flavor_id(flavor_id):
+ """Retrieve instance type by flavor_id."""
if flavor_id is None:
- return FLAGS.default_instance_type
+ return get_default_instance_type()
try:
ctxt = context.get_admin_context()
- flavor = db.instance_type_get_by_flavor_id(ctxt, flavor_id)
- return flavor['name']
+ return db.instance_type_get_by_flavor_id(ctxt, flavor_id)
except exception.DBError, e:
- LOG.exception(_('DB error: %s' % e))
- raise exception.ApiError(_("Unknown flavor: %s" % flavor_id))
+ LOG.exception(_('DB error: %s') % e)
+ raise exception.ApiError(_("Unknown flavor: %s") % flavor_id)
diff --git a/nova/compute/manager.py b/nova/compute/manager.py
index 08b772517..39d7af9c1 100644
--- a/nova/compute/manager.py
+++ b/nova/compute/manager.py
@@ -1090,6 +1090,14 @@ class ComputeManager(manager.SchedulerDependentManager):
vm_state = vm_instance.state
vms_not_found_in_db.remove(name)
+ if db_instance['state_description'] == 'migrating':
+ # A situation which db record exists, but no instance"
+ # sometimes occurs while live-migration at src compute,
+ # this case should be ignored.
+ LOG.debug(_("Ignoring %(name)s, as it's currently being "
+ "migrated.") % locals())
+ continue
+
if vm_state != db_state:
LOG.info(_("DB/VM state mismatch. Changing state from "
"'%(db_state)s' to '%(vm_state)s'") % locals())
@@ -1097,16 +1105,15 @@ 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:
name = vm_not_found_in_db
- # TODO(justinsb): What to do here? Adopt it? Shut it down?
- LOG.warning(_("Found VM not in DB: '%(name)s'. Ignoring")
- % locals())
+
+ # We only care about instances that compute *should* know about
+ if name.startswith("instance-"):
+ # TODO(justinsb): What to do here? Adopt it? Shut it down?
+ LOG.warning(_("Found VM not in DB: '%(name)s'. Ignoring")
+ % locals())
diff --git a/nova/crypto.py b/nova/crypto.py
index 2b122e560..605be2a32 100644
--- a/nova/crypto.py
+++ b/nova/crypto.py
@@ -232,7 +232,7 @@ def generate_vpn_files(project_id):
genvpn_sh_path = os.path.join(os.path.dirname(__file__),
'CA',
- 'geninter.sh')
+ 'genvpn.sh')
if os.path.exists(crt_fn):
return
_ensure_project_folder(project_id)
@@ -269,6 +269,8 @@ def _sign_csr(csr_text, ca_folder):
LOG.debug(_("Flags path: %s"), ca_folder)
start = os.getcwd()
# Change working dir to CA
+ if not os.path.exists(ca_folder):
+ os.makedirs(ca_folder)
os.chdir(ca_folder)
utils.execute('openssl', 'ca', '-batch', '-out', outbound, '-config',
'./openssl.cnf', '-infiles', inbound)
diff --git a/nova/db/api.py b/nova/db/api.py
index fd3c63b76..63901e94d 100644
--- a/nova/db/api.py
+++ b/nova/db/api.py
@@ -1124,6 +1124,11 @@ def instance_type_get_all(context, inactive=False):
return IMPL.instance_type_get_all(context, inactive)
+def instance_type_get_by_id(context, 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"""
return IMPL.instance_type_get_by_name(context, name)
diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py
index 6da8dac10..e675022e9 100644
--- a/nova/db/sqlalchemy/api.py
+++ b/nova/db/sqlalchemy/api.py
@@ -831,6 +831,7 @@ def instance_get(context, instance_id, session=None):
options(joinedload('volumes')).\
options(joinedload_all('fixed_ip.network')).\
options(joinedload('metadata')).\
+ options(joinedload('instance_type')).\
filter_by(id=instance_id).\
filter_by(deleted=can_read_deleted(context)).\
first()
@@ -840,6 +841,7 @@ def instance_get(context, instance_id, session=None):
options(joinedload_all('security_groups.rules')).\
options(joinedload('volumes')).\
options(joinedload('metadata')).\
+ options(joinedload('instance_type')).\
filter_by(project_id=context.project_id).\
filter_by(id=instance_id).\
filter_by(deleted=False).\
@@ -859,6 +861,7 @@ def instance_get_all(context):
options(joinedload_all('fixed_ip.floating_ips')).\
options(joinedload('security_groups')).\
options(joinedload_all('fixed_ip.network')).\
+ options(joinedload('instance_type')).\
filter_by(deleted=can_read_deleted(context)).\
all()
@@ -870,6 +873,7 @@ def instance_get_all_by_user(context, user_id):
options(joinedload_all('fixed_ip.floating_ips')).\
options(joinedload('security_groups')).\
options(joinedload_all('fixed_ip.network')).\
+ options(joinedload('instance_type')).\
filter_by(deleted=can_read_deleted(context)).\
filter_by(user_id=user_id).\
all()
@@ -882,6 +886,7 @@ def instance_get_all_by_host(context, host):
options(joinedload_all('fixed_ip.floating_ips')).\
options(joinedload('security_groups')).\
options(joinedload_all('fixed_ip.network')).\
+ options(joinedload('instance_type')).\
filter_by(host=host).\
filter_by(deleted=can_read_deleted(context)).\
all()
@@ -896,6 +901,7 @@ def instance_get_all_by_project(context, project_id):
options(joinedload_all('fixed_ip.floating_ips')).\
options(joinedload('security_groups')).\
options(joinedload_all('fixed_ip.network')).\
+ options(joinedload('instance_type')).\
filter_by(project_id=project_id).\
filter_by(deleted=can_read_deleted(context)).\
all()
@@ -910,6 +916,7 @@ def instance_get_all_by_reservation(context, reservation_id):
options(joinedload_all('fixed_ip.floating_ips')).\
options(joinedload('security_groups')).\
options(joinedload_all('fixed_ip.network')).\
+ options(joinedload('instance_type')).\
filter_by(reservation_id=reservation_id).\
filter_by(deleted=can_read_deleted(context)).\
all()
@@ -918,6 +925,7 @@ def instance_get_all_by_reservation(context, reservation_id):
options(joinedload_all('fixed_ip.floating_ips')).\
options(joinedload('security_groups')).\
options(joinedload_all('fixed_ip.network')).\
+ options(joinedload('instance_type')).\
filter_by(project_id=context.project_id).\
filter_by(reservation_id=reservation_id).\
filter_by(deleted=False).\
@@ -930,6 +938,7 @@ def instance_get_project_vpn(context, project_id):
return session.query(models.Instance).\
options(joinedload_all('fixed_ip.floating_ips')).\
options(joinedload('security_groups')).\
+ options(joinedload('instance_type')).\
filter_by(project_id=project_id).\
filter_by(image_id=FLAGS.vpn_image_id).\
filter_by(deleted=can_read_deleted(context)).\
@@ -2371,6 +2380,19 @@ def instance_type_get_all(context, inactive=False):
@require_context
+def instance_type_get_by_id(context, id):
+ """Returns a dict describing specific instance_type"""
+ session = get_session()
+ inst_type = session.query(models.InstanceTypes).\
+ filter_by(id=id).\
+ first()
+ if not inst_type:
+ raise exception.NotFound(_("No instance type with id %s") % id)
+ else:
+ return dict(inst_type)
+
+
+@require_context
def instance_type_get_by_name(context, name):
"""Returns a dict describing specific instance_type"""
session = get_session()
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
new file mode 100644
index 000000000..b12a0a801
--- /dev/null
+++ b/nova/db/sqlalchemy/migrate_repo/versions/014_add_instance_type_id_to_instances.py
@@ -0,0 +1,84 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2010 OpenStack LLC.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from sqlalchemy import *
+from sqlalchemy.sql import text
+from migrate import *
+
+#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,
+ _warn_on_bytestring=False),
+ nullable=True)
+
+c_instance_type_id = Column('instance_type_id',
+ String(length=255, convert_unicode=False,
+ assert_unicode=None, unicode_error=None,
+ _warn_on_bytestring=False),
+ nullable=True)
+
+instance_types = Table('instance_types', meta,
+ Column('id', Integer(), primary_key=True, nullable=False),
+ Column('name',
+ String(length=255, convert_unicode=False, assert_unicode=None,
+ unicode_error=None, _warn_on_bytestring=False),
+ unique=True))
+
+
+def upgrade(migrate_engine):
+ # Upgrade operations go here. Don't create your own engine;
+ # bind migrate_engine to your metadata
+ meta.bind = migrate_engine
+
+ instances = Table('instances', meta, autoload=True,
+ autoload_with=migrate_engine)
+
+ instances.create_column(c_instance_type_id)
+
+ recs = migrate_engine.execute(instance_types.select())
+ for row in recs:
+ type_id = row[0]
+ type_name = row[1]
+ migrate_engine.execute(instances.update()\
+ .where(instances.c.instance_type == type_name)\
+ .values(instance_type_id=type_id))
+
+ instances.c.instance_type.drop()
+
+
+def downgrade(migrate_engine):
+ meta.bind = migrate_engine
+
+ instances = Table('instances', meta, autoload=True,
+ autoload_with=migrate_engine)
+
+ instances.create_column(c_instance_type)
+
+ recs = migrate_engine.execute(instance_types.select())
+ for row in recs:
+ type_id = row[0]
+ type_name = row[1]
+ migrate_engine.execute(instances.update()\
+ .where(instances.c.instance_type_id == type_id)\
+ .values(instance_type=type_name))
+
+ instances.c.instance_type_id.drop()
diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py
index 3b95ac23e..f79d0f16c 100644
--- a/nova/db/sqlalchemy/models.py
+++ b/nova/db/sqlalchemy/models.py
@@ -209,7 +209,7 @@ class Instance(BASE, NovaBase):
hostname = Column(String(255))
host = Column(String(255)) # , ForeignKey('hosts.id'))
- instance_type = Column(String(255))
+ instance_type_id = Column(String(255))
user_data = Column(Text)
@@ -268,6 +268,12 @@ class InstanceTypes(BASE, NovaBase):
rxtx_quota = Column(Integer, nullable=False, default=0)
rxtx_cap = Column(Integer, nullable=False, default=0)
+ instances = relationship(Instance,
+ backref=backref('instance_type', uselist=False),
+ foreign_keys=id,
+ primaryjoin='and_(Instance.instance_type_id == '
+ 'InstanceTypes.id)')
+
class Volume(BASE, NovaBase):
"""Represents a block storage device that can be attached to a vm."""
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..1a80bb2af 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:
@@ -182,37 +186,11 @@ class GlanceImageService(service.BaseImageService):
"""Overriding the base translation to handle conversion to datetime
objects
"""
- image_meta = service.BaseImageService._translate_to_base(image_meta)
+ image_meta = service.BaseImageService._propertify_metadata(
+ image_meta, cls.SERVICE_IMAGE_ATTRS)
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..b1034d151 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_by_name(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..ec5579dee 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, check_exit_code=False)
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_api.py b/nova/tests/api/openstack/test_api.py
index 5112c486f..c63431a45 100644
--- a/nova/tests/api/openstack/test_api.py
+++ b/nova/tests/api/openstack/test_api.py
@@ -53,13 +53,13 @@ class APITest(test.TestCase):
#api.application = succeed
api = self._wsgi_app(succeed)
resp = Request.blank('/').get_response(api)
- self.assertFalse('computeFault' in resp.body, resp.body)
+ self.assertFalse('cloudServersFault' in resp.body, resp.body)
self.assertEqual(resp.status_int, 200, resp.body)
#api.application = raise_webob_exc
api = self._wsgi_app(raise_webob_exc)
resp = Request.blank('/').get_response(api)
- self.assertFalse('computeFault' in resp.body, resp.body)
+ self.assertFalse('cloudServersFault' in resp.body, resp.body)
self.assertEqual(resp.status_int, 404, resp.body)
#api.application = raise_api_fault
@@ -71,11 +71,11 @@ class APITest(test.TestCase):
#api.application = fail
api = self._wsgi_app(fail)
resp = Request.blank('/').get_response(api)
- self.assertTrue('{"computeFault' in resp.body, resp.body)
+ self.assertTrue('{"cloudServersFault' in resp.body, resp.body)
self.assertEqual(resp.status_int, 500, resp.body)
#api.application = fail
api = self._wsgi_app(fail)
resp = Request.blank('/.xml').get_response(api)
- self.assertTrue('<computeFault' in resp.body, resp.body)
+ self.assertTrue('<cloudServersFault' in resp.body, resp.body)
self.assertEqual(resp.status_int, 500, resp.body)
diff --git a/nova/tests/api/openstack/test_faults.py b/nova/tests/api/openstack/test_faults.py
index 9746e8168..4d86ffb26 100644
--- a/nova/tests/api/openstack/test_faults.py
+++ b/nova/tests/api/openstack/test_faults.py
@@ -22,6 +22,7 @@ import webob.dec
import webob.exc
from nova import test
+from nova.api.openstack import common
from nova.api.openstack import faults
@@ -47,10 +48,10 @@ class TestFaults(test.TestCase):
response = request.get_response(fault)
expected = self._prepare_xml("""
- <badRequest code="400">
+ <badRequest code="400" xmlns="%s">
<message>scram</message>
</badRequest>
- """)
+ """ % common.XML_NS_V10)
actual = self._prepare_xml(response.body)
self.assertEqual(response.content_type, "application/xml")
@@ -91,11 +92,11 @@ class TestFaults(test.TestCase):
response = request.get_response(fault)
expected = self._prepare_xml("""
- <overLimit code="413">
+ <overLimit code="413" xmlns="%s">
<message>sorry</message>
<retryAfter>4</retryAfter>
</overLimit>
- """)
+ """ % common.XML_NS_V10)
actual = self._prepare_xml(response.body)
self.assertEqual(expected, actual)
diff --git a/nova/tests/api/openstack/test_image_metadata.py b/nova/tests/api/openstack/test_image_metadata.py
index 9be753f84..543c59629 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,11 +61,23 @@ class ImageMetaDataTest(unittest.TestCase):
'is_public': True,
'deleted_at': None,
'properties': {
- 'type': 'ramdisk',
'key1': 'value1',
'key2': 'value2'
},
'size': 5882349},
+ {'status': 'active',
+ 'name': 'image3',
+ 'deleted': False,
+ 'container_format': None,
+ 'created_at': '2011-03-22T17:40:15',
+ 'disk_format': None,
+ 'updated_at': '2011-03-22T17:40:15',
+ 'id': '3',
+ 'location': 'file:///var/lib/glance/images/2',
+ 'is_public': True,
+ 'deleted_at': None,
+ 'properties': {},
+ 'size': 5882349},
]
def setUp(self):
@@ -77,6 +88,10 @@ class ImageMetaDataTest(unittest.TestCase):
fakes.FakeAuthManager.auth_data = {}
fakes.FakeAuthDatabase.data = {}
fakes.stub_out_auth(self.stubs)
+ # NOTE(dprince) max out properties/metadata in image 3 for testing
+ img3 = self.IMAGE_FIXTURES[2]
+ for num in range(FLAGS.quota_metadata_items):
+ img3['properties']['key%i' % num] = "blah"
fakes.stub_out_glance(self.stubs, self.IMAGE_FIXTURES)
def tearDown(self):
@@ -164,3 +179,25 @@ class ImageMetaDataTest(unittest.TestCase):
req.method = 'DELETE'
res = req.get_response(fakes.wsgi_app())
self.assertEqual(404, res.status_int)
+
+ def test_too_many_metadata_items_on_create(self):
+ 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/images/2/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_too_many_metadata_items_on_put(self):
+ req = webob.Request.blank('/v1.1/images/3/meta/blah')
+ req.environ['api.version'] = '1.1'
+ req.method = 'PUT'
+ req.body = '{"blah": "blah"}'
+ 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_images.py b/nova/tests/api/openstack/test_images.py
index 69cc3116d..ae86d0686 100644
--- a/nova/tests/api/openstack/test_images.py
+++ b/nova/tests/api/openstack/test_images.py
@@ -146,7 +146,7 @@ class LocalImageServiceTest(_BaseImageServiceTests):
for x in [1, 2, 3]:
tempfile.mkstemp(prefix='ami-', dir=self.tempdir)
# create some valid image directories names
- for x in ["1485baed", "1a60f0ee", "3123a73d"]:
+ for x in ["1485baed", "1a60f0ee", "3123a73d"]:
os.makedirs(os.path.join(self.tempdir, x))
found_image_ids = self.service._ids()
self.assertEqual(True, isinstance(found_image_ids, list))
@@ -335,7 +335,8 @@ class ImageControllerWithGlanceServiceTest(test.TestCase):
name="public image"
updated="%(expected_now)s"
created="%(expected_now)s"
- status="ACTIVE" />
+ status="ACTIVE"
+ xmlns="http://docs.rackspacecloud.com/servers/api/v1.0" />
""" % (locals()))
self.assertEqual(expected_image.toxml(), actual_image.toxml())
@@ -353,7 +354,8 @@ class ImageControllerWithGlanceServiceTest(test.TestCase):
name="None"
updated="%(expected_now)s"
created="%(expected_now)s"
- status="ACTIVE" />
+ status="ACTIVE"
+ xmlns="http://docs.rackspacecloud.com/servers/api/v1.0" />
""" % (locals()))
self.assertEqual(expected_image.toxml(), actual_image.toxml())
@@ -372,7 +374,8 @@ class ImageControllerWithGlanceServiceTest(test.TestCase):
name="public image"
updated="%(expected_now)s"
created="%(expected_now)s"
- status="ACTIVE">
+ status="ACTIVE"
+ xmlns="http://docs.openstack.org/compute/api/v1.1">
<links>
<link href="%(expected_href)s" rel="self"/>
<link href="%(expected_href)s" rel="bookmark"
@@ -408,7 +411,8 @@ class ImageControllerWithGlanceServiceTest(test.TestCase):
self.assertEqual(404, response.status_int)
expected = minidom.parseString("""
- <itemNotFound code="404">
+ <itemNotFound code="404"
+ xmlns="http://docs.rackspacecloud.com/servers/api/v1.0">
<message>
Image not found.
</message>
@@ -441,8 +445,11 @@ class ImageControllerWithGlanceServiceTest(test.TestCase):
response = request.get_response(fakes.wsgi_app())
self.assertEqual(404, response.status_int)
+ # NOTE(justinsb): I believe this should still use the v1.0 XSD,
+ # because the element hasn't changed definition
expected = minidom.parseString("""
- <itemNotFound code="404">
+ <itemNotFound code="404"
+ xmlns="http://docs.rackspacecloud.com/servers/api/v1.0">
<message>
Image not found.
</message>
diff --git a/nova/tests/api/openstack/test_limits.py b/nova/tests/api/openstack/test_limits.py
index 05cfacc60..df367005d 100644
--- a/nova/tests/api/openstack/test_limits.py
+++ b/nova/tests/api/openstack/test_limits.py
@@ -136,10 +136,17 @@ class LimitsControllerTest(BaseLimitTestSuite):
request = self._get_index_request("application/xml")
response = request.get_response(self.controller)
- expected = "<limits><rate/><absolute/></limits>"
- body = response.body.replace("\n", "").replace(" ", "")
+ expected = parseString("""
+ <limits
+ xmlns="http://docs.rackspacecloud.com/servers/api/v1.0">
+ <rate/>
+ <absolute/>
+ </limits>
+ """.replace(" ", ""))
- self.assertEqual(expected, body)
+ body = parseString(response.body.replace(" ", ""))
+
+ self.assertEqual(expected.toxml(), body.toxml())
def test_index_xml(self):
"""Test getting limit details in XML."""
@@ -148,7 +155,8 @@ class LimitsControllerTest(BaseLimitTestSuite):
response = request.get_response(self.controller)
expected = parseString("""
- <limits>
+ <limits
+ xmlns="http://docs.rackspacecloud.com/servers/api/v1.0">
<rate>
<limit URI="*" regex=".*" remaining="10" resetTime="0"
unit="MINUTE" value="10" verb="GET"/>
diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py
index 313676e72..34513734b 100644
--- a/nova/tests/api/openstack/test_servers.py
+++ b/nova/tests/api/openstack/test_servers.py
@@ -32,6 +32,7 @@ from nova import test
import nova.api.openstack
from nova.api.openstack import servers
import nova.compute.api
+from nova.compute import instance_types
import nova.db.api
from nova.db.sqlalchemy.models import Instance
from nova.db.sqlalchemy.models import InstanceMetadata
@@ -71,13 +72,19 @@ def instance_address(context, instance_id):
return None
-def stub_instance(id, user_id=1, private_address=None, public_addresses=None):
+def stub_instance(id, user_id=1, private_address=None, public_addresses=None,
+ host=None):
metadata = []
metadata.append(InstanceMetadata(key='seq', value=id))
+ inst_type = instance_types.get_instance_type_by_flavor_id(1)
+
if public_addresses == None:
public_addresses = list()
+ if host != None:
+ host = str(host)
+
instance = {
"id": id,
"admin_pass": "",
@@ -95,8 +102,8 @@ def stub_instance(id, user_id=1, private_address=None, public_addresses=None):
"vcpus": 0,
"local_gb": 0,
"hostname": "",
- "host": None,
- "instance_type": "1",
+ "host": host,
+ "instance_type": dict(inst_type),
"user_data": "",
"reservation_id": "",
"mac_address": "",
@@ -192,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"]
@@ -208,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"]
@@ -618,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())
@@ -628,9 +749,9 @@ class ServersTest(test.TestCase):
self.assertEqual(s['hostId'], '')
self.assertEqual(s['name'], 'server%d' % i)
self.assertEqual(s['imageId'], '10')
- self.assertEqual(s['flavorId'], '1')
+ self.assertEqual(s['flavorId'], 1)
self.assertEqual(s['status'], 'BUILD')
- self.assertEqual(s['metadata']['seq'], i)
+ self.assertEqual(s['metadata']['seq'], str(i))
def test_get_all_server_details_v1_1(self):
req = webob.Request.blank('/v1.1/servers/detail')
@@ -644,7 +765,7 @@ class ServersTest(test.TestCase):
self.assertEqual(s['imageRef'], 'http://localhost/v1.1/images/10')
self.assertEqual(s['flavorRef'], 'http://localhost/v1.1/flavors/1')
self.assertEqual(s['status'], 'BUILD')
- self.assertEqual(s['metadata']['seq'], i)
+ self.assertEqual(s['metadata']['seq'], str(i))
def test_get_all_server_details_with_host(self):
'''
@@ -654,12 +775,8 @@ class ServersTest(test.TestCase):
instances - 2 on one host and 3 on another.
'''
- def stub_instance(id, user_id=1):
- return Instance(id=id, state=0, image_id=10, user_id=user_id,
- display_name='server%s' % id, host='host%s' % (id % 2))
-
def return_servers_with_host(context, user_id=1):
- return [stub_instance(i) for i in xrange(5)]
+ return [stub_instance(i, 1, None, None, i % 2) for i in xrange(5)]
self.stubs.Set(nova.db.api, 'instance_get_all_by_user',
return_servers_with_host)
@@ -677,7 +794,8 @@ class ServersTest(test.TestCase):
self.assertEqual(s['id'], i)
self.assertEqual(s['hostId'], host_ids[i % 2])
self.assertEqual(s['name'], 'server%d' % i)
- self.assertEqual(s['imageId'], 10)
+ self.assertEqual(s['imageId'], '10')
+ self.assertEqual(s['flavorId'], 1)
def test_server_pause(self):
FLAGS.allow_admin_api = True
@@ -1525,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/db/fakes.py b/nova/tests/db/fakes.py
index 7ddfe377a..58d251b1e 100644
--- a/nova/tests/db/fakes.py
+++ b/nova/tests/db/fakes.py
@@ -28,29 +28,34 @@ def stub_out_db_instance_api(stubs, injected=True):
"""Stubs out the db API for creating Instances."""
INSTANCE_TYPES = {
- 'm1.tiny': dict(memory_mb=512,
+ 'm1.tiny': dict(id=2,
+ memory_mb=512,
vcpus=1,
local_gb=0,
flavorid=1,
rxtx_cap=1),
- 'm1.small': dict(memory_mb=2048,
+ 'm1.small': dict(id=5,
+ memory_mb=2048,
vcpus=1,
local_gb=20,
flavorid=2,
rxtx_cap=2),
'm1.medium':
- dict(memory_mb=4096,
+ dict(id=1,
+ memory_mb=4096,
vcpus=2,
local_gb=40,
flavorid=3,
rxtx_cap=3),
- 'm1.large': dict(memory_mb=8192,
+ 'm1.large': dict(id=3,
+ memory_mb=8192,
vcpus=4,
local_gb=80,
flavorid=4,
rxtx_cap=4),
'm1.xlarge':
- dict(memory_mb=16384,
+ dict(id=4,
+ memory_mb=16384,
vcpus=8,
local_gb=160,
flavorid=5,
@@ -107,6 +112,12 @@ def stub_out_db_instance_api(stubs, injected=True):
def fake_instance_type_get_by_name(context, name):
return INSTANCE_TYPES[name]
+ def fake_instance_type_get_by_id(context, id):
+ for name, inst_type in INSTANCE_TYPES.iteritems():
+ if str(inst_type['id']) == str(id):
+ return inst_type
+ return None
+
def fake_network_get_by_instance(context, instance_id):
# Even instance numbers are on vlan networks
if instance_id % 2 == 0:
@@ -136,6 +147,7 @@ def stub_out_db_instance_api(stubs, injected=True):
fake_network_get_all_by_instance)
stubs.Set(db, 'instance_type_get_all', fake_instance_type_get_all)
stubs.Set(db, 'instance_type_get_by_name', fake_instance_type_get_by_name)
+ stubs.Set(db, 'instance_type_get_by_id', fake_instance_type_get_by_id)
stubs.Set(db, 'instance_get_fixed_address',
fake_instance_get_fixed_address)
stubs.Set(db, 'instance_get_fixed_address_v6',
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/integrated/test_xml.py b/nova/tests/integrated/test_xml.py
new file mode 100644
index 000000000..8a9754777
--- /dev/null
+++ b/nova/tests/integrated/test_xml.py
@@ -0,0 +1,56 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2011 Justin Santa Barbara
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from nova import flags
+from nova.log import logging
+from nova.tests.integrated import integrated_helpers
+from nova.api.openstack import common
+
+
+LOG = logging.getLogger('nova.tests.integrated')
+
+
+FLAGS = flags.FLAGS
+FLAGS.verbose = True
+
+
+class XmlTests(integrated_helpers._IntegratedTestBase):
+ """"Some basic XML sanity checks."""
+
+ def test_namespace_limits(self):
+ """/limits should have v1.0 namespace (hasn't changed in 1.1)."""
+ headers = {}
+ headers['Accept'] = 'application/xml'
+
+ response = self.api.api_request('/limits', headers=headers)
+ data = response.read()
+ LOG.debug("data: %s" % data)
+
+ prefix = '<limits xmlns="%s"' % common.XML_NS_V10
+ self.assertTrue(data.startswith(prefix))
+
+ def test_namespace_servers(self):
+ """/servers should have v1.1 namespace (has changed in 1.1)."""
+ headers = {}
+ headers['Accept'] = 'application/xml'
+
+ response = self.api.api_request('/servers', headers=headers)
+ data = response.read()
+ LOG.debug("data: %s" % data)
+
+ prefix = '<servers xmlns="%s"' % common.XML_NS_V11
+ self.assertTrue(data.startswith(prefix))
diff --git a/nova/tests/test_cloud.py b/nova/tests/test_cloud.py
index 5cb969979..c45bdd12c 100644
--- a/nova/tests/test_cloud.py
+++ b/nova/tests/test_cloud.py
@@ -36,6 +36,7 @@ from nova import rpc
from nova import service
from nova import test
from nova import utils
+from nova import exception
from nova.auth import manager
from nova.compute import power_state
from nova.api.ec2 import cloud
@@ -247,6 +248,37 @@ class CloudTestCase(test.TestCase):
self.assertRaises(NotFound, describe_images,
self.context, ['ami-fake'])
+ def test_describe_image_attribute(self):
+ describe_image_attribute = self.cloud.describe_image_attribute
+
+ def fake_show(meh, context, id):
+ return {'id': 1, 'properties': {'kernel_id': 1, 'ramdisk_id': 1,
+ 'type': 'machine'}, 'is_public': True}
+
+ self.stubs.Set(local.LocalImageService, 'show', fake_show)
+ self.stubs.Set(local.LocalImageService, 'show_by_name', fake_show)
+ result = describe_image_attribute(self.context, 'ami-00000001',
+ 'launchPermission')
+ self.assertEqual([{'group': 'all'}], result['launchPermission'])
+
+ def test_modify_image_attribute(self):
+ modify_image_attribute = self.cloud.modify_image_attribute
+
+ def fake_show(meh, context, id):
+ return {'id': 1, 'properties': {'kernel_id': 1, 'ramdisk_id': 1,
+ 'type': 'machine'}, 'is_public': False}
+
+ def fake_update(meh, context, image_id, metadata, data=None):
+ return metadata
+
+ self.stubs.Set(local.LocalImageService, 'show', fake_show)
+ self.stubs.Set(local.LocalImageService, 'show_by_name', fake_show)
+ self.stubs.Set(local.LocalImageService, 'update', fake_update)
+ result = modify_image_attribute(self.context, 'ami-00000001',
+ 'launchPermission', 'add',
+ user_group=['all'])
+ self.assertEqual(True, result['is_public'])
+
def test_console_output(self):
instance_type = FLAGS.default_instance_type
max_count = 1
@@ -341,6 +373,19 @@ class CloudTestCase(test.TestCase):
LOG.debug(_("Terminating instance %s"), instance_id)
rv = self.compute.terminate_instance(instance_id)
+ def test_terminate_instances(self):
+ inst1 = db.instance_create(self.context, {'reservation_id': 'a',
+ 'image_id': 1,
+ 'host': 'host1'})
+ terminate_instances = self.cloud.terminate_instances
+ # valid instance_id
+ result = terminate_instances(self.context, ['i-00000001'])
+ self.assertTrue(result)
+ # non-existing instance_id
+ self.assertRaises(exception.InstanceNotFound, terminate_instances,
+ self.context, ['i-2'])
+ db.instance_destroy(self.context, inst1['id'])
+
def test_update_of_instance_display_fields(self):
inst = db.instance_create(self.context, {})
ec2_id = ec2utils.id_to_ec2_id(inst['id'])
diff --git a/nova/tests/test_compute.py b/nova/tests/test_compute.py
index 1b0f426d2..393110791 100644
--- a/nova/tests/test_compute.py
+++ b/nova/tests/test_compute.py
@@ -84,7 +84,8 @@ class ComputeTestCase(test.TestCase):
inst['launch_time'] = '10'
inst['user_id'] = self.user.id
inst['project_id'] = self.project.id
- inst['instance_type'] = 'm1.tiny'
+ type_id = instance_types.get_instance_type_by_name('m1.tiny')['id']
+ inst['instance_type_id'] = type_id
inst['mac_address'] = utils.generate_mac()
inst['ami_launch_index'] = 0
inst.update(params)
@@ -132,7 +133,7 @@ class ComputeTestCase(test.TestCase):
cases = [dict(), dict(display_name=None)]
for instance in cases:
ref = self.compute_api.create(self.context,
- FLAGS.default_instance_type, None, **instance)
+ instance_types.get_default_instance_type(), None, **instance)
try:
self.assertNotEqual(ref[0]['display_name'], None)
finally:
@@ -143,7 +144,7 @@ class ComputeTestCase(test.TestCase):
group = self._create_group()
ref = self.compute_api.create(
self.context,
- instance_type=FLAGS.default_instance_type,
+ instance_type=instance_types.get_default_instance_type(),
image_id=None,
security_group=['testgroup'])
try:
@@ -161,7 +162,7 @@ class ComputeTestCase(test.TestCase):
ref = self.compute_api.create(
self.context,
- instance_type=FLAGS.default_instance_type,
+ instance_type=instance_types.get_default_instance_type(),
image_id=None,
security_group=['testgroup'])
try:
@@ -177,7 +178,7 @@ class ComputeTestCase(test.TestCase):
ref = self.compute_api.create(
self.context,
- instance_type=FLAGS.default_instance_type,
+ instance_type=instance_types.get_default_instance_type(),
image_id=None,
security_group=['testgroup'])
@@ -359,8 +360,9 @@ class ComputeTestCase(test.TestCase):
instance_id = self._create_instance()
self.compute.run_instance(self.context, instance_id)
+ inst_type = instance_types.get_instance_type_by_name('m1.xlarge')
db.instance_update(self.context, instance_id,
- {'instance_type': 'm1.xlarge'})
+ {'instance_type_id': inst_type['id']})
self.assertRaises(exception.ApiError, self.compute_api.resize,
context, instance_id, 1)
@@ -380,8 +382,8 @@ class ComputeTestCase(test.TestCase):
self.compute.terminate_instance(context, instance_id)
def test_get_by_flavor_id(self):
- type = instance_types.get_by_flavor_id(1)
- self.assertEqual(type, 'm1.tiny')
+ type = instance_types.get_instance_type_by_flavor_id(1)
+ self.assertEqual(type['name'], 'm1.tiny')
def test_resize_same_source_fails(self):
"""Ensure instance fails to migrate when source and destination are
@@ -664,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/tests/test_console.py b/nova/tests/test_console.py
index d47c70d88..1a9a867ee 100644
--- a/nova/tests/test_console.py
+++ b/nova/tests/test_console.py
@@ -62,7 +62,7 @@ class ConsoleTestCase(test.TestCase):
inst['launch_time'] = '10'
inst['user_id'] = self.user.id
inst['project_id'] = self.project.id
- inst['instance_type'] = 'm1.tiny'
+ inst['instance_type_id'] = 1
inst['mac_address'] = utils.generate_mac()
inst['ami_launch_index'] = 0
return db.instance_create(self.context, inst)['id']
diff --git a/nova/tests/test_instance_types.py b/nova/tests/test_instance_types.py
index edc538879..5d6d5e1f4 100644
--- a/nova/tests/test_instance_types.py
+++ b/nova/tests/test_instance_types.py
@@ -40,7 +40,11 @@ class InstanceTypeTestCase(test.TestCase):
max_flavorid = session.query(models.InstanceTypes).\
order_by("flavorid desc").\
first()
+ max_id = session.query(models.InstanceTypes).\
+ order_by("id desc").\
+ first()
self.flavorid = max_flavorid["flavorid"] + 1
+ self.id = max_id["id"] + 1
self.name = str(int(time.time()))
def test_instance_type_create_then_delete(self):
@@ -53,7 +57,7 @@ class InstanceTypeTestCase(test.TestCase):
'instance type was not created')
instance_types.destroy(self.name)
self.assertEqual(1,
- instance_types.get_instance_type(self.name)["deleted"])
+ instance_types.get_instance_type(self.id)["deleted"])
self.assertEqual(starting_inst_list, instance_types.get_all_types())
instance_types.purge(self.name)
self.assertEqual(len(starting_inst_list),
diff --git a/nova/tests/test_quota.py b/nova/tests/test_quota.py
index c65bc459d..39a123158 100644
--- a/nova/tests/test_quota.py
+++ b/nova/tests/test_quota.py
@@ -67,7 +67,7 @@ class QuotaTestCase(test.TestCase):
inst['reservation_id'] = 'r-fakeres'
inst['user_id'] = self.user.id
inst['project_id'] = self.project.id
- inst['instance_type'] = 'm1.large'
+ inst['instance_type_id'] = '3' # m1.large
inst['vcpus'] = cores
inst['mac_address'] = utils.generate_mac()
return db.instance_create(self.context, inst)['id']
@@ -124,11 +124,12 @@ class QuotaTestCase(test.TestCase):
for i in range(FLAGS.quota_instances):
instance_id = self._create_instance()
instance_ids.append(instance_id)
+ inst_type = instance_types.get_instance_type_by_name('m1.small')
self.assertRaises(quota.QuotaError, compute.API().create,
self.context,
min_count=1,
max_count=1,
- instance_type='m1.small',
+ instance_type=inst_type,
image_id=1)
for instance_id in instance_ids:
db.instance_destroy(self.context, instance_id)
@@ -137,11 +138,12 @@ class QuotaTestCase(test.TestCase):
instance_ids = []
instance_id = self._create_instance(cores=4)
instance_ids.append(instance_id)
+ inst_type = instance_types.get_instance_type_by_name('m1.small')
self.assertRaises(quota.QuotaError, compute.API().create,
self.context,
min_count=1,
max_count=1,
- instance_type='m1.small',
+ instance_type=inst_type,
image_id=1)
for instance_id in instance_ids:
db.instance_destroy(self.context, instance_id)
@@ -192,11 +194,12 @@ class QuotaTestCase(test.TestCase):
metadata = {}
for i in range(FLAGS.quota_metadata_items + 1):
metadata['key%s' % i] = 'value%s' % i
+ inst_type = instance_types.get_instance_type_by_name('m1.small')
self.assertRaises(quota.QuotaError, compute.API().create,
self.context,
min_count=1,
max_count=1,
- instance_type='m1.small',
+ instance_type=inst_type,
image_id='fake',
metadata=metadata)
@@ -207,13 +210,15 @@ class QuotaTestCase(test.TestCase):
def _create_with_injected_files(self, files):
api = compute.API(image_service=self.StubImageService())
+ inst_type = instance_types.get_instance_type_by_name('m1.small')
api.create(self.context, min_count=1, max_count=1,
- instance_type='m1.small', image_id='fake',
+ instance_type=inst_type, image_id='fake',
injected_files=files)
def test_no_injected_files(self):
api = compute.API(image_service=self.StubImageService())
- api.create(self.context, instance_type='m1.small', image_id='fake')
+ inst_type = instance_types.get_instance_type_by_name('m1.small')
+ api.create(self.context, instance_type=inst_type, image_id='fake')
def test_max_injected_files(self):
files = []
diff --git a/nova/tests/test_scheduler.py b/nova/tests/test_scheduler.py
index 6df74dd61..ae56a1a16 100644
--- a/nova/tests/test_scheduler.py
+++ b/nova/tests/test_scheduler.py
@@ -263,7 +263,7 @@ class SimpleDriverTestCase(test.TestCase):
inst['reservation_id'] = 'r-fakeres'
inst['user_id'] = self.user.id
inst['project_id'] = self.project.id
- inst['instance_type'] = 'm1.tiny'
+ inst['instance_type_id'] = '1'
inst['mac_address'] = utils.generate_mac()
inst['vcpus'] = kwargs.get('vcpus', 1)
inst['ami_launch_index'] = 0
diff --git a/nova/tests/test_virt.py b/nova/tests/test_virt.py
index 958c8e3e2..aeaea91c7 100644
--- a/nova/tests/test_virt.py
+++ b/nova/tests/test_virt.py
@@ -140,7 +140,7 @@ class LibvirtConnTestCase(test.TestCase):
'vcpus': 2,
'project_id': 'fake',
'bridge': 'br101',
- 'instance_type': 'm1.small'}
+ 'instance_type_id': '5'} # m1.small
def lazy_load_library_exists(self):
"""check if libvirt is available."""
@@ -479,7 +479,7 @@ class LibvirtConnTestCase(test.TestCase):
fake_timer = FakeTime()
- self.create_fake_libvirt_mock(nwfilterLookupByName=fake_raise)
+ self.create_fake_libvirt_mock()
instance_ref = db.instance_create(self.context, self.test_instance)
# Start test
@@ -488,6 +488,7 @@ class LibvirtConnTestCase(test.TestCase):
conn = libvirt_conn.LibvirtConnection(False)
conn.firewall_driver.setattr('setup_basic_filtering', fake_none)
conn.firewall_driver.setattr('prepare_instance_filter', fake_none)
+ conn.firewall_driver.setattr('instance_filter_exists', fake_none)
conn.ensure_filtering_rules_for_instance(instance_ref,
time=fake_timer)
except exception.Error, e:
diff --git a/nova/tests/test_volume.py b/nova/tests/test_volume.py
index d71b75f3f..e9d8289aa 100644
--- a/nova/tests/test_volume.py
+++ b/nova/tests/test_volume.py
@@ -106,7 +106,7 @@ class VolumeTestCase(test.TestCase):
inst['launch_time'] = '10'
inst['user_id'] = 'fake'
inst['project_id'] = 'fake'
- inst['instance_type'] = 'm1.tiny'
+ inst['instance_type_id'] = '2' # m1.tiny
inst['mac_address'] = utils.generate_mac()
inst['ami_launch_index'] = 0
instance_id = db.instance_create(self.context, inst)['id']
diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py
index 17e3f55e9..375480a2e 100644
--- a/nova/tests/test_xenapi.py
+++ b/nova/tests/test_xenapi.py
@@ -80,7 +80,7 @@ class XenAPIVolumeTestCase(test.TestCase):
'image_id': 1,
'kernel_id': 2,
'ramdisk_id': 3,
- 'instance_type': 'm1.large',
+ 'instance_type_id': '3', # m1.large
'mac_address': 'aa:bb:cc:dd:ee:ff',
'os_type': 'linux'}
@@ -289,11 +289,11 @@ class XenAPIVMTestCase(test.TestCase):
'enabled':'1'}],
'ip6s': [{'ip': 'fe80::a8bb:ccff:fedd:eeff',
'netmask': '120',
- 'enabled': '1',
- 'gateway': 'fe80::a00:1'}],
+ 'enabled': '1'}],
'mac': 'aa:bb:cc:dd:ee:ff',
'dns': ['10.0.0.2'],
- 'gateway': '10.0.0.1'})
+ 'gateway': '10.0.0.1',
+ 'gateway6': 'fe80::a00:1'})
def check_vm_params_for_windows(self):
self.assertEquals(self.vm['platform']['nx'], 'true')
@@ -328,7 +328,7 @@ class XenAPIVMTestCase(test.TestCase):
self.assertEquals(self.vm['HVM_boot_policy'], '')
def _test_spawn(self, image_id, kernel_id, ramdisk_id,
- instance_type="m1.large", os_type="linux",
+ instance_type_id="3", os_type="linux",
instance_id=1, check_injection=False):
stubs.stubout_loopingcall_start(self.stubs)
values = {'id': instance_id,
@@ -337,7 +337,7 @@ class XenAPIVMTestCase(test.TestCase):
'image_id': image_id,
'kernel_id': kernel_id,
'ramdisk_id': ramdisk_id,
- 'instance_type': instance_type,
+ 'instance_type_id': instance_type_id,
'mac_address': 'aa:bb:cc:dd:ee:ff',
'os_type': os_type}
instance = db.instance_create(self.context, values)
@@ -349,7 +349,7 @@ class XenAPIVMTestCase(test.TestCase):
FLAGS.xenapi_image_service = 'glance'
self.assertRaises(Exception,
self._test_spawn,
- 1, 2, 3, "m1.xlarge")
+ 1, 2, 3, "4") # m1.xlarge
def test_spawn_raw_objectstore(self):
FLAGS.xenapi_image_service = 'objectstore'
@@ -523,7 +523,7 @@ class XenAPIVMTestCase(test.TestCase):
'image_id': 1,
'kernel_id': 2,
'ramdisk_id': 3,
- 'instance_type': 'm1.large',
+ 'instance_type_id': '3', # m1.large
'mac_address': 'aa:bb:cc:dd:ee:ff',
'os_type': 'linux'}
instance = db.instance_create(self.context, values)
@@ -580,7 +580,7 @@ class XenAPIMigrateInstance(test.TestCase):
'kernel_id': None,
'ramdisk_id': None,
'local_gb': 5,
- 'instance_type': 'm1.large',
+ 'instance_type_id': '3', # m1.large
'mac_address': 'aa:bb:cc:dd:ee:ff',
'os_type': 'linux'}
diff --git a/nova/version.py b/nova/version.py
index c3ecc2245..c43d12cf8 100644
--- a/nova/version.py
+++ b/nova/version.py
@@ -21,7 +21,7 @@ except ImportError:
'revision_id': 'LOCALREVISION',
'revno': 0}
-NOVA_VERSION = ['2011', '2']
+NOVA_VERSION = ['2011', '3']
YEAR, COUNT = NOVA_VERSION
FINAL = False # This becomes true at Release Candidate time
diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py
index 2be190256..ccfce39e4 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):
@@ -169,34 +171,34 @@ def _get_network_info(instance):
instance['id'])
network_info = []
- def ip_dict(ip):
- return {
- "ip": ip.address,
- "netmask": network["netmask"],
- "enabled": "1"}
-
- def ip6_dict(ip6):
- prefix = ip6.network.cidr_v6
- mac = instance.mac_address
- return {
- "ip": utils.to_global_ipv6(prefix, mac),
- "netmask": ip6.network.netmask_v6,
- "gateway": ip6.network.gateway_v6,
- "enabled": "1"}
-
for network in networks:
network_ips = [ip for ip in ip_addresses
- if ip.network_id == network.id]
+ if ip['network_id'] == network['id']]
+
+ def ip_dict(ip):
+ return {
+ 'ip': ip['address'],
+ 'netmask': network['netmask'],
+ 'enabled': '1'}
+
+ def ip6_dict():
+ prefix = network['cidr_v6']
+ mac = instance['mac_address']
+ return {
+ 'ip': utils.to_global_ipv6(prefix, mac),
+ 'netmask': network['netmask_v6'],
+ 'enabled': '1'}
mapping = {
'label': network['label'],
'gateway': network['gateway'],
- 'mac': instance.mac_address,
+ 'mac': instance['mac_address'],
'dns': [network['dns']],
'ips': [ip_dict(ip) for ip in network_ips]}
if FLAGS.use_ipv6:
- mapping['ip6s'] = [ip6_dict(ip) for ip in network_ips]
+ mapping['ip6s'] = [ip6_dict()]
+ mapping['gateway6'] = network['gateway_v6']
network_info.append((network, mapping))
return network_info
@@ -209,7 +211,6 @@ class LibvirtConnection(driver.ComputeDriver):
self.libvirt_uri = self.get_uri()
self.libvirt_xml = open(FLAGS.libvirt_xml_template).read()
- self.interfaces_xml = open(FLAGS.injected_network_template).read()
self.cpuinfo_xml = open(FLAGS.cpuinfo_xml_template).read()
self._wrapped_conn = None
self.read_only = read_only
@@ -231,12 +232,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
@@ -311,12 +308,57 @@ class LibvirtConnection(driver.ComputeDriver):
return infos
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'])
- virt_dom.destroy()
- except Exception as _err:
- pass
- # If the instance is already terminated, we're still happy
+ 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
+
+ # If the instance is already terminated, we're still happy
+ # Otherwise, destroy it
+ if virt_dom is not None:
+ try:
+ virt_dom.destroy()
+ except libvirt.libvirtError as e:
+ is_okay = False
+ errcode = e.get_error_code()
+ if errcode == libvirt.VIR_ERR_OPERATION_INVALID:
+ # If the instance if already shut off, we get this:
+ # Code=55 Error=Requested operation is not valid:
+ # domain is not running
+ (state, _max_mem, _mem, _cpus, _t) = virt_dom.info()
+ if state == power_state.SHUTOFF:
+ is_okay = True
+
+ if not is_okay:
+ LOG.warning(_("Error from libvirt during destroy of "
+ "%(instance_name)s. Code=%(errcode)s "
+ "Error=%(e)s") %
+ locals())
+ raise
+
+ try:
+ # NOTE(justinsb): We remove the domain definition. We probably
+ # would do better to keep it if cleanup=False (e.g. volumes?)
+ # (e.g. #2 - not losing machines on failure)
+ virt_dom.undefine()
+ except libvirt.libvirtError as e:
+ errcode = e.get_error_code()
+ LOG.warning(_("Error from libvirt during undefine of "
+ "%(instance_name)s. Code=%(errcode)s "
+ "Error=%(e)s") %
+ locals())
+ raise
# We'll save this for when we do shutdown,
# instead of destroy - but destroy returns immediately
@@ -327,12 +369,18 @@ class LibvirtConnection(driver.ComputeDriver):
state = self.get_info(instance['name'])['state']
db.instance_set_state(context.get_admin_context(),
instance['id'], state)
- if state == power_state.SHUTDOWN:
+ if state == power_state.SHUTOFF:
break
- except Exception:
+
+ # 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.SHUTDOWN)
+ power_state.SHUTOFF)
break
self.firewall_driver.unfilter_instance(instance)
@@ -424,7 +472,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 +522,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 +570,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 +613,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():
@@ -797,7 +849,10 @@ class LibvirtConnection(driver.ComputeDriver):
root_fname = '%08x' % int(disk_images['image_id'])
size = FLAGS.minimum_root_size
- if inst['instance_type'] == 'm1.tiny' or suffix == '.rescue':
+
+ inst_type_id = inst['instance_type_id']
+ inst_type = instance_types.get_instance_type(inst_type_id)
+ if inst_type['name'] == 'm1.tiny' or suffix == '.rescue':
size = None
root_fname += "_sm"
@@ -809,14 +864,13 @@ class LibvirtConnection(driver.ComputeDriver):
user=user,
project=project,
size=size)
- type_data = instance_types.get_instance_type(inst['instance_type'])
- if type_data['local_gb']:
+ if inst_type['local_gb']:
self._cache_image(fn=self._create_local,
target=basepath('disk.local'),
- fname="local_%s" % type_data['local_gb'],
+ fname="local_%s" % inst_type['local_gb'],
cow=FLAGS.use_cow_images,
- local_gb=type_data['local_gb'])
+ local_gb=inst_type['local_gb'])
# For now, we assume that if we're not using a kernel, we're using a
# partitioned disk image where the target partition is the first
@@ -950,8 +1004,8 @@ class LibvirtConnection(driver.ComputeDriver):
nics.append(self._get_nic_for_xml(network,
mapping))
# FIXME(vish): stick this in db
- instance_type_name = instance['instance_type']
- instance_type = instance_types.get_instance_type(instance_type_name)
+ inst_type_id = instance['instance_type_id']
+ inst_type = instance_types.get_instance_type(inst_type_id)
if FLAGS.use_cow_images:
driver_type = 'qcow2'
@@ -962,10 +1016,10 @@ class LibvirtConnection(driver.ComputeDriver):
'name': instance['name'],
'basepath': os.path.join(FLAGS.instances_path,
instance['name']),
- 'memory_kb': instance_type['memory_mb'] * 1024,
- 'vcpus': instance_type['vcpus'],
+ 'memory_kb': inst_type['memory_mb'] * 1024,
+ 'vcpus': inst_type['vcpus'],
'rescue': rescue,
- 'local': instance_type['local_gb'],
+ 'local': inst_type['local_gb'],
'driver_type': driver_type,
'nics': nics}
@@ -987,11 +1041,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,
@@ -999,6 +1064,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"))
@@ -1402,18 +1485,13 @@ class LibvirtConnection(driver.ComputeDriver):
# wait for completion
timeout_count = range(FLAGS.live_migration_retry_count)
while timeout_count:
- try:
- filter_name = 'nova-instance-%s' % instance_ref.name
- self._conn.nwfilterLookupByName(filter_name)
+ if self.firewall_driver.instance_filter_exists(instance_ref):
break
- except libvirt.libvirtError:
- timeout_count.pop()
- if len(timeout_count) == 0:
- ec2_id = instance_ref['hostname']
- iname = instance_ref.name
- msg = _('Timeout migrating for %(ec2_id)s(%(iname)s)')
- raise exception.Error(msg % locals())
- time.sleep(1)
+ timeout_count.pop()
+ if len(timeout_count) == 0:
+ msg = _('Timeout migrating for %s. nwfilter not found.')
+ raise exception.Error(msg % instance_ref.name)
+ time.sleep(1)
def live_migration(self, ctxt, instance_ref, dest,
post_method, recover_method):
@@ -1542,6 +1620,10 @@ class FirewallDriver(object):
"""
raise NotImplementedError()
+ def instance_filter_exists(self, instance):
+ """Check nova-instance-instance-xxx exists"""
+ raise NotImplementedError()
+
class NWFilterFirewall(FirewallDriver):
"""
@@ -1849,6 +1931,21 @@ class NWFilterFirewall(FirewallDriver):
return 'nova-instance-%s' % (instance['name'])
return 'nova-instance-%s-%s' % (instance['name'], nic_id)
+ def instance_filter_exists(self, instance):
+ """Check nova-instance-instance-xxx exists"""
+ network_info = _get_network_info(instance)
+ for (network, mapping) in network_info:
+ nic_id = mapping['mac'].replace(':', '')
+ instance_filter_name = self._instance_filter_name(instance, nic_id)
+ try:
+ self._conn.nwfilterLookupByName(instance_filter_name)
+ except libvirt.libvirtError:
+ name = instance.name
+ LOG.debug(_('The nwfilter(%(instance_filter_name)s) for'
+ '%(name)s is not found.') % locals())
+ return False
+ return True
+
class IptablesFirewallDriver(FirewallDriver):
def __init__(self, execute=None, **kwargs):
@@ -2038,6 +2135,10 @@ class IptablesFirewallDriver(FirewallDriver):
return ipv4_rules, ipv6_rules
+ def instance_filter_exists(self, instance):
+ """Check nova-instance-instance-xxx exists"""
+ return self.nwfilter.instance_filter_exists(instance)
+
def refresh_security_group_members(self, security_group):
pass
diff --git a/nova/virt/vmwareapi_conn.py b/nova/virt/vmwareapi_conn.py
index 20c1b2b45..1c6d2572d 100644
--- a/nova/virt/vmwareapi_conn.py
+++ b/nova/virt/vmwareapi_conn.py
@@ -42,6 +42,7 @@ from nova import exception
from nova import flags
from nova import log as logging
from nova import utils
+from nova.virt import driver
from nova.virt.vmwareapi import error_util
from nova.virt.vmwareapi import vim
from nova.virt.vmwareapi import vim_util
@@ -104,11 +105,12 @@ def get_connection(_):
api_retry_count)
-class VMWareESXConnection(object):
+class VMWareESXConnection(driver.ComputeDriver):
"""The ESX host connection object."""
def __init__(self, host_ip, host_username, host_password,
api_retry_count, scheme="https"):
+ super(VMWareESXConnection, self).__init__()
session = VMWareAPISession(host_ip, host_username, host_password,
api_retry_count, scheme=scheme)
self._vmops = VMWareVMOps(session)
diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py
index d07d60800..d2045a557 100644
--- a/nova/virt/xenapi/vm_utils.py
+++ b/nova/virt/xenapi/vm_utils.py
@@ -49,6 +49,8 @@ LOG = logging.getLogger("nova.virt.xenapi.vm_utils")
FLAGS = flags.FLAGS
flags.DEFINE_string('default_os_type', 'linux', 'Default OS type')
+flags.DEFINE_integer('block_device_creation_timeout', 10,
+ 'time to wait for a block device to be created')
XENAPI_POWER_STATE = {
'Halted': power_state.SHUTDOWN,
@@ -101,8 +103,8 @@ class VMHelper(HelperBase):
3. Using hardware virtualization
"""
- instance_type = instance_types.\
- get_instance_type(instance.instance_type)
+ inst_type_id = instance.instance_type_id
+ instance_type = instance_types.get_instance_type(inst_type_id)
mem = str(long(instance_type['memory_mb']) * 1024 * 1024)
vcpus = str(instance_type['vcpus'])
rec = {
@@ -169,8 +171,8 @@ class VMHelper(HelperBase):
@classmethod
def ensure_free_mem(cls, session, instance):
- instance_type = instance_types.get_instance_type(
- instance.instance_type)
+ inst_type_id = instance.instance_type_id
+ instance_type = instance_types.get_instance_type(inst_type_id)
mem = long(instance_type['memory_mb']) * 1024 * 1024
#get free memory from host
host = session.get_xenapi_host()
@@ -896,6 +898,16 @@ def remap_vbd_dev(dev):
return remapped_dev
+def _wait_for_device(dev):
+ """Wait for device node to appear"""
+ for i in xrange(0, FLAGS.block_device_creation_timeout):
+ if os.path.exists('/dev/%s' % dev):
+ return
+ time.sleep(1)
+
+ raise StorageError(_('Timeout waiting for device %s to be created') % dev)
+
+
def with_vdi_attached_here(session, vdi_ref, read_only, f):
this_vm_ref = get_this_vm_ref(session)
vbd_rec = {}
@@ -924,6 +936,11 @@ def with_vdi_attached_here(session, vdi_ref, read_only, f):
if dev != orig_dev:
LOG.debug(_('VBD %(vbd_ref)s plugged into wrong dev, '
'remapping to %(dev)s') % locals())
+ if dev != 'autodetect':
+ # NOTE(johannes): Unit tests will end up with a device called
+ # 'autodetect' which obviously won't exist. It's not ideal,
+ # but the alternatives were much messier
+ _wait_for_device(dev)
return f(dev)
finally:
LOG.debug(_('Destroying VBD for VDI %s ... '), vdi_ref)
@@ -1130,7 +1147,7 @@ def _prepare_injectables(inst, networks_info):
'dns': dns,
'address_v6': ip_v6 and ip_v6['ip'] or '',
'netmask_v6': ip_v6 and ip_v6['netmask'] or '',
- 'gateway_v6': ip_v6 and ip_v6['gateway'] or '',
+ 'gateway_v6': ip_v6 and info['gateway6'] or '',
'use_ipv6': FLAGS.use_ipv6}
interfaces_info.append(interface_info)
diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py
index c96c35a6e..7c7aa8e98 100644
--- a/nova/virt/xenapi/vmops.py
+++ b/nova/virt/xenapi/vmops.py
@@ -176,7 +176,7 @@ class VMOps(object):
vdi_ref, network_info)
self.create_vifs(vm_ref, network_info)
- self.inject_network_info(instance, vm_ref, network_info)
+ self.inject_network_info(instance, network_info, vm_ref)
return vm_ref
def _spawn(self, instance, vm_ref):
@@ -802,8 +802,10 @@ class VMOps(object):
instance['id'])
networks = db.network_get_all_by_instance(admin_context,
instance['id'])
- flavor = db.instance_type_get_by_name(admin_context,
- instance['instance_type'])
+
+ inst_type = db.instance_type_get_by_id(admin_context,
+ instance['instance_type_id'])
+
network_info = []
for network in networks:
network_IPs = [ip for ip in IPs if ip.network_id == network.id]
@@ -814,12 +816,11 @@ class VMOps(object):
"netmask": network["netmask"],
"enabled": "1"}
- def ip6_dict(ip6):
+ def ip6_dict():
return {
"ip": utils.to_global_ipv6(network['cidr_v6'],
instance['mac_address']),
"netmask": network['netmask_v6'],
- "gateway": network['gateway_v6'],
"enabled": "1"}
info = {
@@ -827,23 +828,41 @@ class VMOps(object):
'gateway': network['gateway'],
'broadcast': network['broadcast'],
'mac': instance.mac_address,
- 'rxtx_cap': flavor['rxtx_cap'],
+ 'rxtx_cap': inst_type['rxtx_cap'],
'dns': [network['dns']],
'ips': [ip_dict(ip) for ip in network_IPs]}
if network['cidr_v6']:
- info['ip6s'] = [ip6_dict(ip) for ip in network_IPs]
+ info['ip6s'] = [ip6_dict()]
+ if network['gateway_v6']:
+ info['gateway6'] = network['gateway_v6']
network_info.append((network, info))
return network_info
- def inject_network_info(self, instance, vm_ref, network_info):
+ #TODO{tr3buchet) remove this shim with nova-multi-nic
+ def inject_network_info(self, instance, network_info=None, vm_ref=None):
+ """
+ shim in place which makes inject_network_info work without being
+ passed network_info.
+ shim goes away after nova-multi-nic
+ """
+ if not network_info:
+ network_info = self._get_network_info(instance)
+ self._inject_network_info(instance, network_info, vm_ref)
+
+ def _inject_network_info(self, instance, network_info, vm_ref=None):
"""
Generate the network info and make calls to place it into the
xenstore and the xenstore param list.
+ vm_ref can be passed in because it will sometimes be different than
+ what VMHelper.lookup(session, instance.name) will find (ex: rescue)
"""
logging.debug(_("injecting network info to xs for vm: |%s|"), vm_ref)
- # this function raises if vm_ref is not a vm_opaque_ref
- self._session.get_xenapi().VM.get_record(vm_ref)
+ if vm_ref:
+ # this function raises if vm_ref is not a vm_opaque_ref
+ self._session.get_xenapi().VM.get_record(vm_ref)
+ else:
+ vm_ref = VMHelper.lookup(self._session, instance.name)
for (network, info) in network_info:
location = 'vm-data/networking/%s' % info['mac'].replace(':', '')
@@ -875,8 +894,10 @@ class VMOps(object):
VMHelper.create_vif(self._session, vm_ref, network_ref,
mac_address, device, rxtx_cap)
- def reset_network(self, instance, vm_ref):
+ def reset_network(self, instance, vm_ref=None):
"""Creates uuid arg to pass to make_agent_call and calls it."""
+ if not vm_ref:
+ vm_ref = VMHelper.lookup(self._session, instance.name)
args = {'id': str(uuid.uuid4())}
# TODO(tr3buchet): fix function call after refactor
#resp = self._make_agent_call('resetnetwork', instance, '', args)
diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py
index 99fd35c61..0cabccf08 100644
--- a/nova/virt/xenapi_conn.py
+++ b/nova/virt/xenapi_conn.py
@@ -63,6 +63,7 @@ import xmlrpclib
from eventlet import event
from eventlet import tpool
+from eventlet import timeout
from nova import context
from nova import db
@@ -140,6 +141,9 @@ flags.DEFINE_bool('xenapi_remap_vbd_dev', False,
flags.DEFINE_string('xenapi_remap_vbd_dev_prefix', 'sd',
'Specify prefix to remap VBD dev to '
'(ex. /dev/xvdb -> /dev/sdb)')
+flags.DEFINE_integer('xenapi_login_timeout',
+ 10,
+ 'Timeout in seconds for XenAPI login.')
def get_connection(_):
@@ -318,7 +322,10 @@ class XenAPISession(object):
def __init__(self, url, user, pw):
self.XenAPI = self.get_imported_xenapi()
self._session = self._create_session(url)
- self._session.login_with_password(user, pw)
+ exception = self.XenAPI.Failure(_("Unable to log in to XenAPI "
+ "(is the Dom0 disk full?)"))
+ with timeout.Timeout(FLAGS.xenapi_login_timeout, exception):
+ self._session.login_with_password(user, pw)
self.loop = None
def get_imported_xenapi(self):
diff --git a/nova/volume/driver.py b/nova/volume/driver.py
index 850893914..55307ad9b 100644
--- a/nova/volume/driver.py
+++ b/nova/volume/driver.py
@@ -112,6 +112,12 @@ class VolumeDriver(object):
# If the volume isn't present, then don't attempt to delete
return True
+ # zero out old volumes to prevent data leaking between users
+ # TODO(ja): reclaiming space should be done lazy and low priority
+ self._execute('sudo', 'dd', 'if=/dev/zero',
+ 'of=%s' % self.local_path(volume),
+ 'count=%d' % (volume['size'] * 1024),
+ 'bs=1M')
self._try_execute('sudo', 'lvremove', '-f', "%s/%s" %
(FLAGS.volume_group,
volume['name']))
@@ -557,7 +563,7 @@ class RBDDriver(VolumeDriver):
"""Returns the path of the rbd volume."""
# This is the same as the remote path
# since qemu accesses it directly.
- return self.discover_volume(volume)
+ return "rbd:%s/%s" % (FLAGS.rbd_pool, volume['name'])
def ensure_export(self, context, volume):
"""Synchronously recreates an export for a logical volume."""
@@ -571,10 +577,8 @@ class RBDDriver(VolumeDriver):
"""Removes an export for a logical volume"""
pass
- def discover_volume(self, volume):
+ def discover_volume(self, context, volume):
"""Discover volume on a remote host"""
- # NOTE(justinsb): This is messed up... discover_volume takes 3 args
- # but then that would break local_path
return "rbd:%s/%s" % (FLAGS.rbd_pool, volume['name'])
def undiscover_volume(self, volume):
diff --git a/nova/wsgi.py b/nova/wsgi.py
index d7a1594d9..de2e0749f 100644
--- a/nova/wsgi.py
+++ b/nova/wsgi.py
@@ -357,7 +357,8 @@ class Controller(object):
if type(result) is dict:
content_type = req.best_match_content_type()
- body = self._serialize(result, content_type)
+ default_xmlns = self.get_default_xmlns(req)
+ body = self._serialize(result, content_type, default_xmlns)
response = webob.Response()
response.headers["Content-Type"] = content_type
@@ -366,18 +367,18 @@ class Controller(object):
msg = _("%(url)s returned with HTTP %(status)d") % msg_dict
LOG.debug(msg)
return response
-
else:
return result
- def _serialize(self, data, content_type):
+ def _serialize(self, data, content_type, default_xmlns):
"""
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", {})
- serializer = Serializer(_metadata)
+
+ serializer = Serializer(_metadata, default_xmlns)
try:
return serializer.serialize(data, content_type)
except exception.InvalidContentType:
@@ -393,19 +394,24 @@ class Controller(object):
serializer = Serializer(_metadata)
return serializer.deserialize(data, content_type)
+ def get_default_xmlns(self, req):
+ """Provide the XML namespace to use if none is otherwise specified."""
+ return None
+
class Serializer(object):
"""
Serializes and deserializes dictionaries to certain MIME types.
"""
- def __init__(self, metadata=None):
+ def __init__(self, metadata=None, default_xmlns=None):
"""
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 = {
@@ -483,12 +489,32 @@ class Serializer(object):
root_key = data.keys()[0]
doc = minidom.Document()
node = self._to_xml_node(doc, metadata, root_key, data[root_key])
+
+ xmlns = node.getAttribute('xmlns')
+ if not xmlns and self.default_xmlns:
+ node.setAttribute('xmlns', self.default_xmlns)
+
return node.toprettyxml(indent=' ')
def _to_xml_node(self, doc, metadata, nodename, data):
"""Recursive method to convert data members to XML nodes."""
result = doc.createElement(nodename)
+
+ # Set the xml namespace if one is specified
+ # TODO(justinsb): We could also use prefixes on the keys
+ xmlns = metadata.get('xmlns', None)
+ if xmlns:
+ 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'):
@@ -499,6 +525,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: