summaryrefslogtreecommitdiffstats
path: root/nova
diff options
context:
space:
mode:
authorTrey Morris <trey.morris@rackspace.com>2011-06-03 13:34:54 -0500
committerTrey Morris <trey.morris@rackspace.com>2011-06-03 13:34:54 -0500
commit65e17925fc3dee9180fd0198475e4ffd7be2aa48 (patch)
tree049d3ee0934be84022ada50fe18928150699b3a3 /nova
parent3db24f73cd5772537b9508304f8db8a7bb64f5ca (diff)
parent54c341feff5043a11cfccc6e2eb02b1bf0669348 (diff)
downloadnova-65e17925fc3dee9180fd0198475e4ffd7be2aa48.tar.gz
nova-65e17925fc3dee9180fd0198475e4ffd7be2aa48.tar.xz
nova-65e17925fc3dee9180fd0198475e4ffd7be2aa48.zip
merged trunk again
Diffstat (limited to 'nova')
-rw-r--r--nova/api/ec2/admin.py3
-rw-r--r--nova/api/ec2/cloud.py20
-rw-r--r--nova/api/openstack/auth.py5
-rw-r--r--nova/api/openstack/common.py28
-rw-r--r--nova/api/openstack/contrib/__init__.py2
-rw-r--r--nova/api/openstack/image_metadata.py3
-rw-r--r--nova/api/openstack/images.py22
-rw-r--r--nova/api/openstack/limits.py2
-rw-r--r--nova/api/openstack/ratelimiting/__init__.py2
-rw-r--r--nova/api/openstack/servers.py57
-rw-r--r--nova/api/openstack/views/servers.py16
-rw-r--r--nova/auth/ldapdriver.py92
-rw-r--r--nova/auth/manager.py16
-rw-r--r--nova/compute/api.py27
-rw-r--r--nova/compute/manager.py16
-rw-r--r--nova/compute/monitor.py2
-rw-r--r--nova/context.py1
-rw-r--r--nova/db/sqlalchemy/api.py31
-rw-r--r--nova/db/sqlalchemy/migrate_repo/versions/016_make_quotas_key_and_value.py10
-rw-r--r--nova/db/sqlalchemy/migrate_repo/versions/021_rename_image_ids.py40
-rw-r--r--nova/db/sqlalchemy/models.py13
-rw-r--r--nova/exception.py22
-rw-r--r--nova/image/__init__.py93
-rw-r--r--nova/image/fake.py57
-rw-r--r--nova/image/s3.py5
-rw-r--r--nova/network/manager.py2
-rw-r--r--nova/notifier/api.py7
-rw-r--r--nova/scheduler/driver.py3
-rw-r--r--nova/scheduler/host_filter.py1
-rw-r--r--nova/scheduler/least_cost.py156
-rw-r--r--nova/scheduler/simple.py11
-rw-r--r--nova/scheduler/zone_aware_scheduler.py19
-rw-r--r--nova/scheduler/zone_manager.py15
-rw-r--r--nova/test.py22
-rw-r--r--nova/tests/api/openstack/fakes.py16
-rw-r--r--nova/tests/api/openstack/test_images.py1
-rw-r--r--nova/tests/api/openstack/test_servers.py49
-rw-r--r--nova/tests/glance/stubs.py11
-rw-r--r--nova/tests/integrated/integrated_helpers.py20
-rw-r--r--nova/tests/scheduler/__init__.py0
-rw-r--r--nova/tests/scheduler/test_host_filter.py206
-rw-r--r--nova/tests/scheduler/test_least_cost_scheduler.py144
-rw-r--r--nova/tests/scheduler/test_scheduler.py (renamed from nova/tests/test_scheduler.py)19
-rw-r--r--nova/tests/scheduler/test_zone_aware_scheduler.py (renamed from nova/tests/test_zone_aware_scheduler.py)31
-rw-r--r--nova/tests/test_auth.py1
-rw-r--r--nova/tests/test_cloud.py6
-rw-r--r--nova/tests/test_compute.py13
-rw-r--r--nova/tests/test_console.py2
-rw-r--r--nova/tests/test_libvirt.py2
-rw-r--r--nova/tests/test_middleware.py1
-rw-r--r--nova/tests/test_misc.py13
-rw-r--r--nova/tests/test_quota.py12
-rw-r--r--nova/tests/test_vmwareapi.py5
-rw-r--r--nova/tests/test_xenapi.py29
-rw-r--r--nova/tests/vmwareapi/db_fakes.py2
-rw-r--r--nova/utils.py2
-rw-r--r--nova/virt/hyperv.py2
-rw-r--r--nova/virt/images.py5
-rw-r--r--nova/virt/libvirt/connection.py21
-rw-r--r--nova/virt/libvirt/firewall.py4
-rw-r--r--nova/virt/vmwareapi/vmops.py12
-rw-r--r--nova/virt/vmwareapi/vmware_images.py16
-rw-r--r--nova/virt/xenapi/fake.py4
-rw-r--r--nova/virt/xenapi/vm_utils.py14
-rw-r--r--nova/virt/xenapi/vmops.py2
-rw-r--r--nova/volume/api.py5
-rw-r--r--nova/volume/manager.py4
67 files changed, 1152 insertions, 343 deletions
diff --git a/nova/api/ec2/admin.py b/nova/api/ec2/admin.py
index ea94d9c1f..aeebd86fb 100644
--- a/nova/api/ec2/admin.py
+++ b/nova/api/ec2/admin.py
@@ -21,7 +21,6 @@ Admin API controller, exposed through http via the api worker.
"""
import base64
-import datetime
from nova import db
from nova import exception
@@ -305,7 +304,7 @@ class AdminController(object):
* Volume Count
"""
services = db.service_get_all(context, False)
- now = datetime.datetime.utcnow()
+ now = utils.utcnow()
hosts = []
rv = []
for host in [service['host'] for service in services]:
diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py
index 1badb7bb4..117611d4b 100644
--- a/nova/api/ec2/cloud.py
+++ b/nova/api/ec2/cloud.py
@@ -23,7 +23,6 @@ datastore.
"""
import base64
-import datetime
import IPy
import os
import urllib
@@ -159,7 +158,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'])
+ image_ec2_id = self.image_ec2_id(instance_ref['image_ref'])
data = {
'user-data': base64.b64decode(instance_ref['user_data']),
'meta-data': {
@@ -235,7 +234,7 @@ class CloudController(object):
'zoneState': 'available'}]}
services = db.service_get_all(context, False)
- now = datetime.datetime.utcnow()
+ now = utils.utcnow()
hosts = []
for host in [service['host'] for service in services]:
if not host in hosts:
@@ -595,7 +594,7 @@ class CloudController(object):
instance_id = ec2utils.ec2_id_to_id(ec2_id)
output = self.compute_api.get_console_output(
context, instance_id=instance_id)
- now = datetime.datetime.utcnow()
+ now = utils.utcnow()
return {"InstanceId": ec2_id,
"Timestamp": now,
"output": base64.b64encode(output)}
@@ -774,13 +773,13 @@ class CloudController(object):
instances = self.compute_api.get_all(context, **kwargs)
for instance in instances:
if not context.is_admin:
- if instance['image_id'] == str(FLAGS.vpn_image_id):
+ if instance['image_ref'] == str(FLAGS.vpn_image_id):
continue
i = {}
instance_id = instance['id']
ec2_id = ec2utils.id_to_ec2_id(instance_id)
i['instanceId'] = ec2_id
- i['imageId'] = self.image_ec2_id(instance['image_id'])
+ i['imageId'] = self.image_ec2_id(instance['image_ref'])
i['instanceState'] = {
'code': instance['state'],
'name': instance['state_description']}
@@ -899,7 +898,7 @@ class CloudController(object):
instances = self.compute_api.create(context,
instance_type=instance_types.get_instance_type_by_name(
kwargs.get('instance_type', None)),
- image_id=self._get_image(context, kwargs['image_id'])['id'],
+ image_href=self._get_image(context, kwargs['image_id'])['id'],
min_count=int(kwargs.get('min_count', max_count)),
max_count=max_count,
kernel_id=kwargs.get('kernel_id'),
@@ -975,7 +974,12 @@ class CloudController(object):
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)
+ try:
+ return ec2utils.id_to_ec2_id(int(image_id), template=template)
+ except ValueError:
+ #TODO(wwolf): once we have ec2_id -> glance_id mapping
+ # in place, this wont be necessary
+ return "ami-00000000"
def _get_image(self, context, ec2_id):
try:
diff --git a/nova/api/openstack/auth.py b/nova/api/openstack/auth.py
index 6c6ee22a2..b49bf449b 100644
--- a/nova/api/openstack/auth.py
+++ b/nova/api/openstack/auth.py
@@ -13,9 +13,8 @@
# 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 datetime
+# under the License.
-import datetime
import hashlib
import time
@@ -127,7 +126,7 @@ class AuthMiddleware(wsgi.Middleware):
except exception.NotFound:
return None
if token:
- delta = datetime.datetime.utcnow() - token['created_at']
+ delta = utils.utcnow() - token['created_at']
if delta.days >= 2:
self.db.auth_token_destroy(ctxt, token['token_hash'])
else:
diff --git a/nova/api/openstack/common.py b/nova/api/openstack/common.py
index bb1a96812..571e46766 100644
--- a/nova/api/openstack/common.py
+++ b/nova/api/openstack/common.py
@@ -99,34 +99,6 @@ def limited_by_marker(items, request, max_limit=FLAGS.osapi_max_limit):
return items[start_index:range_end]
-def get_image_id_from_image_hash(image_service, context, image_hash):
- """Given an Image ID Hash, return an objectstore Image ID.
-
- image_service - reference to objectstore compatible image service.
- context - security context for image service requests.
- image_hash - hash of the image ID.
- """
-
- # FIX(sandy): This is terribly inefficient. It pulls all images
- # from objectstore in order to find the match. ObjectStore
- # should have a numeric counterpart to the string ID.
- try:
- items = image_service.detail(context)
- except NotImplementedError:
- items = image_service.index(context)
- for image in items:
- image_id = image['id']
- try:
- if abs(hash(image_id)) == int(image_hash):
- return image_id
- except ValueError:
- msg = _("Requested image_id has wrong format: %s,"
- "should have numerical format") % image_id
- LOG.error(msg)
- raise Exception(msg)
- raise exception.ImageNotFound(image_id=image_hash)
-
-
def get_id_from_href(href):
"""Return the id portion of a url as an int.
diff --git a/nova/api/openstack/contrib/__init__.py b/nova/api/openstack/contrib/__init__.py
index b42a1d89d..acb5eb280 100644
--- a/nova/api/openstack/contrib/__init__.py
+++ b/nova/api/openstack/contrib/__init__.py
@@ -13,7 +13,7 @@
# 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 datetime
+# under the License.
"""Contrib contains extensions that are shipped with nova.
diff --git a/nova/api/openstack/image_metadata.py b/nova/api/openstack/image_metadata.py
index 88e10168d..ebfe2bde9 100644
--- a/nova/api/openstack/image_metadata.py
+++ b/nova/api/openstack/image_metadata.py
@@ -18,6 +18,7 @@
from webob import exc
from nova import flags
+from nova import image
from nova import quota
from nova import utils
from nova.api.openstack import faults
@@ -31,7 +32,7 @@ class Controller(object):
"""The image metadata API controller for the Openstack API"""
def __init__(self):
- self.image_service = utils.import_object(FLAGS.image_service)
+ self.image_service = image.get_default_image_service()
def _get_metadata(self, context, image_id, image=None):
if not image:
diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py
index d6107a2d0..59d9e3082 100644
--- a/nova/api/openstack/images.py
+++ b/nova/api/openstack/images.py
@@ -18,6 +18,7 @@ import webob.exc
from nova import compute
from nova import exception
from nova import flags
+import nova.image
from nova import log
from nova import utils
from nova.api.openstack import common
@@ -40,11 +41,11 @@ class Controller(object):
:param compute_service: `nova.compute.api:API`
:param image_service: `nova.image.service:BaseImageService`
- """
- _default_service = utils.import_object(flags.FLAGS.image_service)
+ """
self._compute_service = compute_service or compute.API()
- self._image_service = image_service or _default_service
+ self._image_service = image_service or \
+ nova.image.get_default_image_service()
def index(self, req):
"""Return an index listing of images available to the request.
@@ -88,22 +89,16 @@ class Controller(object):
"""Return detailed information about a specific image.
:param req: `wsgi.Request` object
- :param id: Image identifier (integer)
+ :param id: Image identifier
"""
context = req.environ['nova.context']
try:
- image_id = int(id)
- except ValueError:
+ image = self._image_service.show(context, id)
+ except (exception.NotFound, exception.InvalidImageRef):
explanation = _("Image not found.")
raise faults.Fault(webob.exc.HTTPNotFound(explanation=explanation))
- try:
- image = self._image_service.show(context, image_id)
- except exception.NotFound:
- explanation = _("Image '%d' not found.") % (image_id)
- raise faults.Fault(webob.exc.HTTPNotFound(explanation=explanation))
-
return dict(image=self.get_builder(req).build(image, detail=True))
def delete(self, req, id):
@@ -112,9 +107,8 @@ class Controller(object):
:param req: `wsgi.Request` object
:param id: Image identifier (integer)
"""
- image_id = id
context = req.environ['nova.context']
- self._image_service.delete(context, image_id)
+ self._image_service.delete(context, id)
return webob.exc.HTTPNoContent()
def create(self, req, body):
diff --git a/nova/api/openstack/limits.py b/nova/api/openstack/limits.py
index 4d46b92df..dc2bc6bbc 100644
--- a/nova/api/openstack/limits.py
+++ b/nova/api/openstack/limits.py
@@ -11,7 +11,7 @@
# 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 datetime
+# under the License.
"""
Module dedicated functions/classes dealing with rate limiting requests.
diff --git a/nova/api/openstack/ratelimiting/__init__.py b/nova/api/openstack/ratelimiting/__init__.py
index 88ffc3246..9ede548c2 100644
--- a/nova/api/openstack/ratelimiting/__init__.py
+++ b/nova/api/openstack/ratelimiting/__init__.py
@@ -13,7 +13,7 @@
# 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 datetime
+# under the License.
"""Rate limiting of arbitrary actions."""
diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py
index 0f7dde389..82d8be4aa 100644
--- a/nova/api/openstack/servers.py
+++ b/nova/api/openstack/servers.py
@@ -22,6 +22,7 @@ from xml.dom import minidom
from nova import compute
from nova import exception
from nova import flags
+import nova.image
from nova import log as logging
from nova import quota
from nova import utils
@@ -51,13 +52,21 @@ class Controller(object):
def index(self, req):
""" Returns a list of server names and ids for a given user """
- return self._items(req, is_detail=False)
+ try:
+ servers = self._items(req, is_detail=False)
+ except exception.Invalid as err:
+ return exc.HTTPBadRequest(str(err))
+ return servers
def detail(self, req):
""" Returns a list of server details for a given user """
- return self._items(req, is_detail=True)
+ try:
+ servers = self._items(req, is_detail=True)
+ except exception.Invalid as err:
+ return exc.HTTPBadRequest(str(err))
+ return servers
- def _image_id_from_req_data(self, data):
+ def _image_ref_from_req_data(self, data):
raise NotImplementedError()
def _flavor_id_from_req_data(self, data):
@@ -121,18 +130,19 @@ class Controller(object):
key_name = key_pair['name']
key_data = key_pair['public_key']
- requested_image_id = self._image_id_from_req_data(body)
+ image_href = self._image_ref_from_req_data(body)
try:
- image_id = common.get_image_id_from_image_hash(self._image_service,
- context, requested_image_id)
+ image_service, image_id = nova.image.get_image_service(image_href)
+ kernel_id, ramdisk_id = self._get_kernel_ramdisk_from_image(
+ req, image_service, image_id)
+ images = set([str(x['id']) for x in image_service.index(context)])
+ assert str(image_id) in images
except:
- msg = _("Can not find requested image")
+ msg = _("Cannot find requested image %s") % image_href
return faults.Fault(exc.HTTPBadRequest(msg))
- kernel_id, ramdisk_id = self._get_kernel_ramdisk_from_image(
- req, image_id)
-
personality = body['server'].get('personality')
+
injected_files = []
if personality:
injected_files = self._get_injected_files(personality)
@@ -153,7 +163,7 @@ class Controller(object):
(inst,) = self.compute_api.create(
context,
inst_type,
- image_id,
+ image_href,
kernel_id=kernel_id,
ramdisk_id=ramdisk_id,
display_name=name,
@@ -165,9 +175,12 @@ class Controller(object):
admin_password=password)
except quota.QuotaError as error:
self._handle_quota_error(error)
+ except exception.ImageNotFound as error:
+ msg = _("Can not find requested image")
+ return faults.Fault(exc.HTTPBadRequest(msg))
inst['instance_type'] = inst_type
- inst['image_id'] = requested_image_id
+ inst['image_ref'] = image_href
builder = self._get_view_builder(req)
server = builder.build(inst, is_detail=True)
@@ -505,17 +518,15 @@ class Controller(object):
error=item.error))
return dict(actions=actions)
- def _get_kernel_ramdisk_from_image(self, req, image_id):
+ def _get_kernel_ramdisk_from_image(self, req, image_service, image_id):
"""Fetch an image from the ImageService, then if present, return the
associated kernel and ramdisk image IDs.
"""
context = req.environ['nova.context']
- image_meta = self._image_service.show(context, image_id)
+ image_meta = image_service.show(context, image_id)
# NOTE(sirp): extracted to a separate method to aid unit-testing, the
# new method doesn't need a request obj or an ImageService stub
- kernel_id, ramdisk_id = self._do_get_kernel_ramdisk_from_image(
- image_meta)
- return kernel_id, ramdisk_id
+ return self._do_get_kernel_ramdisk_from_image(image_meta)
@staticmethod
def _do_get_kernel_ramdisk_from_image(image_meta):
@@ -546,7 +557,7 @@ class Controller(object):
class ControllerV10(Controller):
- def _image_id_from_req_data(self, data):
+ def _image_ref_from_req_data(self, data):
return data['server']['imageId']
def _flavor_id_from_req_data(self, data):
@@ -604,9 +615,8 @@ class ControllerV10(Controller):
class ControllerV11(Controller):
- def _image_id_from_req_data(self, data):
- href = data['server']['imageRef']
- return common.get_id_from_href(href)
+ def _image_ref_from_req_data(self, data):
+ return data['server']['imageRef']
def _flavor_id_from_req_data(self, data):
href = data['server']['flavorRef']
@@ -686,13 +696,12 @@ class ControllerV11(Controller):
instance_id = int(instance_id)
try:
- image_ref = info["rebuild"]["imageRef"]
+ image_href = info["rebuild"]["imageRef"]
except (KeyError, TypeError):
msg = _("Could not parse imageRef from request.")
LOG.debug(msg)
return faults.Fault(exc.HTTPBadRequest(explanation=msg))
- image_id = common.get_id_from_href(image_ref)
personalities = info["rebuild"].get("personality", [])
metadata = info["rebuild"].get("metadata")
name = info["rebuild"].get("name")
@@ -702,7 +711,7 @@ class ControllerV11(Controller):
self._decode_personalities(personalities)
try:
- self.compute_api.rebuild(context, instance_id, image_id, name,
+ self.compute_api.rebuild(context, instance_id, image_href, name,
metadata, personalities)
except exception.BuildInProgress:
msg = _("Instance %d is currently being rebuilt.") % instance_id
diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py
index 0be468edc..b2352e3fd 100644
--- a/nova/api/openstack/views/servers.py
+++ b/nova/api/openstack/views/servers.py
@@ -18,6 +18,7 @@
import hashlib
import os
+from nova import exception
from nova.compute import power_state
import nova.compute
import nova.context
@@ -112,8 +113,11 @@ class ViewBuilderV10(ViewBuilder):
"""Model an Openstack API V1.0 server response."""
def _build_image(self, response, inst):
- if 'image_id' in dict(inst):
- response['imageId'] = inst['image_id']
+ if 'image_ref' in dict(inst):
+ image_ref = inst['image_ref']
+ if str(image_ref).startswith('http'):
+ raise exception.ListingImageRefsNotSupported()
+ response['imageId'] = int(image_ref)
def _build_flavor(self, response, inst):
if 'instance_type' in dict(inst):
@@ -130,9 +134,11 @@ class ViewBuilderV11(ViewBuilder):
self.base_url = base_url
def _build_image(self, response, inst):
- if "image_id" in dict(inst):
- image_id = inst.get("image_id")
- response["imageRef"] = self.image_builder.generate_href(image_id)
+ if 'image_ref' in dict(inst):
+ image_href = inst['image_ref']
+ if str(image_href).isdigit():
+ image_href = int(image_href)
+ response['imageRef'] = image_href
def _build_flavor(self, response, inst):
if "instance_type" in dict(inst):
diff --git a/nova/auth/ldapdriver.py b/nova/auth/ldapdriver.py
index 3f8432851..183f7a985 100644
--- a/nova/auth/ldapdriver.py
+++ b/nova/auth/ldapdriver.py
@@ -24,6 +24,7 @@ other backends by creating another class that exposes the same
public methods.
"""
+import functools
import sys
from nova import exception
@@ -68,6 +69,12 @@ flags.DEFINE_string('ldap_developer',
LOG = logging.getLogger("nova.ldapdriver")
+if FLAGS.memcached_servers:
+ import memcache
+else:
+ from nova import fakememcache as memcache
+
+
# TODO(vish): make an abstract base class with the same public methods
# to define a set interface for AuthDrivers. I'm delaying
# creating this now because I'm expecting an auth refactor
@@ -85,6 +92,7 @@ def _clean(attr):
def sanitize(fn):
"""Decorator to sanitize all args"""
+ @functools.wraps(fn)
def _wrapped(self, *args, **kwargs):
args = [_clean(x) for x in args]
kwargs = dict((k, _clean(v)) for (k, v) in kwargs)
@@ -103,29 +111,56 @@ class LdapDriver(object):
isadmin_attribute = 'isNovaAdmin'
project_attribute = 'owner'
project_objectclass = 'groupOfNames'
+ conn = None
+ mc = None
def __init__(self):
"""Imports the LDAP module"""
self.ldap = __import__('ldap')
- self.conn = None
if FLAGS.ldap_schema_version == 1:
LdapDriver.project_pattern = '(objectclass=novaProject)'
LdapDriver.isadmin_attribute = 'isAdmin'
LdapDriver.project_attribute = 'projectManager'
LdapDriver.project_objectclass = 'novaProject'
+ self.__cache = None
+ if LdapDriver.conn is None:
+ LdapDriver.conn = self.ldap.initialize(FLAGS.ldap_url)
+ LdapDriver.conn.simple_bind_s(FLAGS.ldap_user_dn,
+ FLAGS.ldap_password)
+ if LdapDriver.mc is None:
+ LdapDriver.mc = memcache.Client(FLAGS.memcached_servers, debug=0)
def __enter__(self):
- """Creates the connection to LDAP"""
- self.conn = self.ldap.initialize(FLAGS.ldap_url)
- self.conn.simple_bind_s(FLAGS.ldap_user_dn, FLAGS.ldap_password)
+ # TODO(yorik-sar): Should be per-request cache, not per-driver-request
+ self.__cache = {}
return self
def __exit__(self, exc_type, exc_value, traceback):
- """Destroys the connection to LDAP"""
- self.conn.unbind_s()
+ self.__cache = None
return False
+ def __local_cache(key_fmt):
+ """Wrap function to cache it's result in self.__cache.
+ Works only with functions with one fixed argument.
+ """
+ def do_wrap(fn):
+ @functools.wraps(fn)
+ def inner(self, arg, **kwargs):
+ cache_key = key_fmt % (arg,)
+ try:
+ res = self.__cache[cache_key]
+ LOG.debug('Local cache hit for %s by key %s' %
+ (fn.__name__, cache_key))
+ return res
+ except KeyError:
+ res = fn(self, arg, **kwargs)
+ self.__cache[cache_key] = res
+ return res
+ return inner
+ return do_wrap
+
@sanitize
+ @__local_cache('uid_user-%s')
def get_user(self, uid):
"""Retrieve user by id"""
attr = self.__get_ldap_user(uid)
@@ -134,15 +169,31 @@ class LdapDriver(object):
@sanitize
def get_user_from_access_key(self, access):
"""Retrieve user by access key"""
+ cache_key = 'uak_dn_%s' % (access,)
+ user_dn = self.mc.get(cache_key)
+ if user_dn:
+ user = self.__to_user(
+ self.__find_object(user_dn, scope=self.ldap.SCOPE_BASE))
+ if user:
+ if user['access'] == access:
+ return user
+ else:
+ self.mc.set(cache_key, None)
query = '(accessKey=%s)' % access
dn = FLAGS.ldap_user_subtree
- return self.__to_user(self.__find_object(dn, query))
+ user_obj = self.__find_object(dn, query)
+ user = self.__to_user(user_obj)
+ if user:
+ self.mc.set(cache_key, user_obj['dn'][0])
+ return user
@sanitize
+ @__local_cache('pid_project-%s')
def get_project(self, pid):
"""Retrieve project by id"""
- dn = self.__project_to_dn(pid)
- attr = self.__find_object(dn, LdapDriver.project_pattern)
+ dn = self.__project_to_dn(pid, search=False)
+ attr = self.__find_object(dn, LdapDriver.project_pattern,
+ scope=self.ldap.SCOPE_BASE)
return self.__to_project(attr)
@sanitize
@@ -395,6 +446,7 @@ class LdapDriver(object):
"""Check if project exists"""
return self.get_project(project_id) is not None
+ @__local_cache('uid_attrs-%s')
def __get_ldap_user(self, uid):
"""Retrieve LDAP user entry by id"""
dn = FLAGS.ldap_user_subtree
@@ -426,12 +478,20 @@ class LdapDriver(object):
if scope is None:
# One of the flags is 0!
scope = self.ldap.SCOPE_SUBTREE
+ if query is None:
+ query = "(objectClass=*)"
try:
res = self.conn.search_s(dn, scope, query)
except self.ldap.NO_SUCH_OBJECT:
return []
# Just return the attributes
- return [attributes for dn, attributes in res]
+ # FIXME(yorik-sar): Whole driver should be refactored to
+ # prevent this hack
+ res1 = []
+ for dn, attrs in res:
+ attrs['dn'] = [dn]
+ res1.append(attrs)
+ return res1
def __find_role_dns(self, tree):
"""Find dns of role objects in given tree"""
@@ -564,6 +624,7 @@ class LdapDriver(object):
'description': attr.get('description', [None])[0],
'member_ids': [self.__dn_to_uid(x) for x in member_dns]}
+ @__local_cache('uid_dn-%s')
def __uid_to_dn(self, uid, search=True):
"""Convert uid to dn"""
# By default return a generated DN
@@ -576,6 +637,7 @@ class LdapDriver(object):
userdn = user[0]
return userdn
+ @__local_cache('pid_dn-%s')
def __project_to_dn(self, pid, search=True):
"""Convert pid to dn"""
# By default return a generated DN
@@ -603,16 +665,18 @@ class LdapDriver(object):
else:
return None
+ @__local_cache('dn_uid-%s')
def __dn_to_uid(self, dn):
"""Convert user dn to uid"""
query = '(objectclass=novaUser)'
- user = self.__find_object(dn, query)
+ user = self.__find_object(dn, query, scope=self.ldap.SCOPE_BASE)
return user[FLAGS.ldap_user_id_attribute][0]
class FakeLdapDriver(LdapDriver):
"""Fake Ldap Auth driver"""
- def __init__(self): # pylint: disable=W0231
- __import__('nova.auth.fakeldap')
- self.ldap = sys.modules['nova.auth.fakeldap']
+ def __init__(self):
+ import nova.auth.fakeldap
+ sys.modules['ldap'] = nova.auth.fakeldap
+ super(FakeLdapDriver, self).__init__()
diff --git a/nova/auth/manager.py b/nova/auth/manager.py
index da640a533..b6131fb7f 100644
--- a/nova/auth/manager.py
+++ b/nova/auth/manager.py
@@ -73,6 +73,12 @@ flags.DEFINE_string('auth_driver', 'nova.auth.dbdriver.DbDriver',
LOG = logging.getLogger('nova.auth.manager')
+if FLAGS.memcached_servers:
+ import memcache
+else:
+ from nova import fakememcache as memcache
+
+
class AuthBase(object):
"""Base class for objects relating to auth
@@ -206,6 +212,7 @@ class AuthManager(object):
"""
_instance = None
+ mc = None
def __new__(cls, *args, **kwargs):
"""Returns the AuthManager singleton"""
@@ -222,13 +229,8 @@ class AuthManager(object):
self.network_manager = utils.import_object(FLAGS.network_manager)
if driver or not getattr(self, 'driver', None):
self.driver = utils.import_class(driver or FLAGS.auth_driver)
-
- if FLAGS.memcached_servers:
- import memcache
- else:
- from nova import fakememcache as memcache
- self.mc = memcache.Client(FLAGS.memcached_servers,
- debug=0)
+ if AuthManager.mc is None:
+ AuthManager.mc = memcache.Client(FLAGS.memcached_servers, debug=0)
def authenticate(self, access, signature, params, verb='GET',
server_string='127.0.0.1:8773', path='/',
diff --git a/nova/compute/api.py b/nova/compute/api.py
index 87d88b466..ae0979fee 100644
--- a/nova/compute/api.py
+++ b/nova/compute/api.py
@@ -18,7 +18,6 @@
"""Handles all requests relating to instances (guest vms)."""
-import datetime
import eventlet
import re
import time
@@ -26,6 +25,7 @@ import time
from nova import db
from nova import exception
from nova import flags
+import nova.image
from nova import log as logging
from nova import network
from nova import quota
@@ -58,9 +58,9 @@ class API(base.Base):
def __init__(self, image_service=None, network_api=None,
volume_api=None, hostname_factory=generate_default_hostname,
**kwargs):
- if not image_service:
- image_service = utils.import_object(FLAGS.image_service)
- self.image_service = image_service
+ self.image_service = image_service or \
+ nova.image.get_default_image_service()
+
if not network_api:
network_api = network.API()
self.network_api = network_api
@@ -112,7 +112,7 @@ class API(base.Base):
raise quota.QuotaError(msg, "MetadataLimitExceeded")
def create(self, context, instance_type,
- image_id, kernel_id=None, ramdisk_id=None,
+ image_href, kernel_id=None, ramdisk_id=None,
min_count=1, max_count=1,
display_name='', display_description='',
key_name=None, key_data=None, security_group='default',
@@ -143,7 +143,8 @@ class API(base.Base):
self._check_metadata_properties_quota(context, metadata)
self._check_injected_file_quota(context, injected_files)
- image = self.image_service.show(context, image_id)
+ (image_service, image_id) = nova.image.get_image_service(image_href)
+ image = image_service.show(context, image_id)
os_type = None
if 'properties' in image and 'os_type' in image['properties']:
@@ -163,9 +164,9 @@ class API(base.Base):
logging.debug("Using Kernel=%s, Ramdisk=%s" %
(kernel_id, ramdisk_id))
if kernel_id:
- self.image_service.show(context, kernel_id)
+ image_service.show(context, kernel_id)
if ramdisk_id:
- self.image_service.show(context, ramdisk_id)
+ image_service.show(context, ramdisk_id)
if security_group is None:
security_group = ['default']
@@ -186,7 +187,7 @@ class API(base.Base):
base_options = {
'reservation_id': utils.generate_uid('r'),
- 'image_id': image_id,
+ 'image_ref': image_href,
'kernel_id': kernel_id or '',
'ramdisk_id': ramdisk_id or '',
'state': 0,
@@ -387,7 +388,7 @@ class API(base.Base):
instance['id'],
state_description='terminating',
state=0,
- terminated_at=datetime.datetime.utcnow())
+ terminated_at=utils.utcnow())
host = instance['host']
if host:
@@ -511,8 +512,8 @@ class API(base.Base):
"""Reboot the given instance."""
self._cast_compute_message('reboot_instance', context, instance_id)
- def rebuild(self, context, instance_id, image_id, name=None, metadata=None,
- files_to_inject=None):
+ def rebuild(self, context, instance_id, image_href, name=None,
+ metadata=None, files_to_inject=None):
"""Rebuild the given instance with the provided metadata."""
instance = db.api.instance_get(context, instance_id)
@@ -532,7 +533,7 @@ class API(base.Base):
self.db.instance_update(context, instance_id, values)
rebuild_params = {
- "image_id": image_id,
+ "image_ref": image_href,
"injected_files": files_to_inject,
}
diff --git a/nova/compute/manager.py b/nova/compute/manager.py
index d5d71aa27..e3cfc8e0e 100644
--- a/nova/compute/manager.py
+++ b/nova/compute/manager.py
@@ -35,7 +35,6 @@ terminating it.
"""
-import datetime
import os
import socket
import sys
@@ -159,12 +158,12 @@ class ComputeManager(manager.SchedulerDependentManager):
def _update_launched_at(self, context, instance_id, launched_at=None):
"""Update the launched_at parameter of the given instance."""
- data = {'launched_at': launched_at or datetime.datetime.utcnow()}
+ data = {'launched_at': launched_at or utils.utcnow()}
self.db.instance_update(context, instance_id, data)
- def _update_image_id(self, context, instance_id, image_id):
+ def _update_image_ref(self, context, instance_id, image_ref):
"""Update the image_id for the given instance."""
- data = {'image_id': image_id}
+ data = {'image_ref': image_ref}
self.db.instance_update(context, instance_id, data)
def get_console_topic(self, context, **kwargs):
@@ -221,7 +220,7 @@ class ComputeManager(manager.SchedulerDependentManager):
power_state.NOSTATE,
'networking')
- is_vpn = instance['image_id'] == str(FLAGS.vpn_image_id)
+ is_vpn = instance['image_ref'] == str(FLAGS.vpn_image_id)
# NOTE(vish): This could be a cast because we don't do anything
# with the address currently, but I'm leaving it as
# a call to ensure that network setup completes. We
@@ -286,7 +285,7 @@ class ComputeManager(manager.SchedulerDependentManager):
:param context: `nova.RequestContext` object
:param instance_id: Instance identifier (integer)
- :param image_id: Image identifier (integer)
+ :param image_ref: Image identifier (href or integer)
"""
context = context.elevated()
@@ -296,11 +295,12 @@ class ComputeManager(manager.SchedulerDependentManager):
self._update_state(context, instance_id, power_state.BUILDING)
self.driver.destroy(instance_ref)
- instance_ref.image_id = kwargs.get('image_id')
+ image_ref = kwargs.get('image_ref')
+ instance_ref.image_ref = image_ref
instance_ref.injected_files = kwargs.get('injected_files', [])
self.driver.spawn(instance_ref)
- self._update_image_id(context, instance_id, image_id)
+ self._update_image_ref(context, instance_id, image_ref)
self._update_launched_at(context, instance_id)
self._update_state(context, instance_id)
diff --git a/nova/compute/monitor.py b/nova/compute/monitor.py
index 3bb54a382..613734bef 100644
--- a/nova/compute/monitor.py
+++ b/nova/compute/monitor.py
@@ -86,7 +86,7 @@ RRD_VALUES = {
]}
-utcnow = datetime.datetime.utcnow
+utcnow = utils.utcnow
LOG = logging.getLogger('nova.compute.monitor')
diff --git a/nova/context.py b/nova/context.py
index c113f7ea7..99085ed75 100644
--- a/nova/context.py
+++ b/nova/context.py
@@ -18,7 +18,6 @@
"""RequestContext: context for requests that persist through all of nova."""
-import datetime
import random
from nova import exception
diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py
index 07a31da71..dd7393bed 100644
--- a/nova/db/sqlalchemy/api.py
+++ b/nova/db/sqlalchemy/api.py
@@ -19,7 +19,6 @@
Implementation of SQLAlchemy backend.
"""
-import datetime
import warnings
from nova import db
@@ -678,7 +677,7 @@ def fixed_ip_disassociate_all_by_timeout(_context, host, time):
update({'instance_id': None,
'mac_address_id': None,
'leased': 0,
- 'updated_at': datetime.datetime.utcnow()},
+ 'updated_at': utils.utcnow()},
synchronize_session='fetch')
return result
@@ -990,17 +989,17 @@ def instance_destroy(context, instance_id):
session.query(models.Instance).\
filter_by(id=instance_id).\
update({'deleted': True,
- 'deleted_at': datetime.datetime.utcnow(),
+ 'deleted_at': utils.utcnow(),
'updated_at': literal_column('updated_at')})
session.query(models.SecurityGroupInstanceAssociation).\
filter_by(instance_id=instance_id).\
update({'deleted': True,
- 'deleted_at': datetime.datetime.utcnow(),
+ 'deleted_at': utils.utcnow(),
'updated_at': literal_column('updated_at')})
session.query(models.InstanceMetadata).\
filter_by(instance_id=instance_id).\
update({'deleted': True,
- 'deleted_at': datetime.datetime.utcnow(),
+ 'deleted_at': utils.utcnow(),
'updated_at': literal_column('updated_at')})
@@ -1135,7 +1134,7 @@ def instance_get_project_vpn(context, project_id):
options(joinedload('security_groups')).\
options(joinedload('instance_type')).\
filter_by(project_id=project_id).\
- filter_by(image_id=str(FLAGS.vpn_image_id)).\
+ filter_by(image_ref=str(FLAGS.vpn_image_id)).\
filter_by(deleted=can_read_deleted(context)).\
first()
@@ -1317,7 +1316,7 @@ def key_pair_destroy_all_by_user(context, user_id):
session.query(models.KeyPair).\
filter_by(user_id=user_id).\
update({'deleted': 1,
- 'deleted_at': datetime.datetime.utcnow(),
+ 'deleted_at': utils.utcnow(),
'updated_at': literal_column('updated_at')})
@@ -1863,7 +1862,7 @@ def volume_destroy(context, volume_id):
session.query(models.Volume).\
filter_by(id=volume_id).\
update({'deleted': 1,
- 'deleted_at': datetime.datetime.utcnow(),
+ 'deleted_at': utils.utcnow(),
'updated_at': literal_column('updated_at')})
session.query(models.ExportDevice).\
filter_by(volume_id=volume_id).\
@@ -2021,7 +2020,7 @@ def snapshot_destroy(context, snapshot_id):
session.query(models.Snapshot).\
filter_by(id=snapshot_id).\
update({'deleted': 1,
- 'deleted_at': datetime.datetime.utcnow(),
+ 'deleted_at': utils.utcnow(),
'updated_at': literal_column('updated_at')})
@@ -2176,17 +2175,17 @@ def security_group_destroy(context, security_group_id):
session.query(models.SecurityGroup).\
filter_by(id=security_group_id).\
update({'deleted': 1,
- 'deleted_at': datetime.datetime.utcnow(),
+ 'deleted_at': utils.utcnow(),
'updated_at': literal_column('updated_at')})
session.query(models.SecurityGroupInstanceAssociation).\
filter_by(security_group_id=security_group_id).\
update({'deleted': 1,
- 'deleted_at': datetime.datetime.utcnow(),
+ 'deleted_at': utils.utcnow(),
'updated_at': literal_column('updated_at')})
session.query(models.SecurityGroupIngressRule).\
filter_by(group_id=security_group_id).\
update({'deleted': 1,
- 'deleted_at': datetime.datetime.utcnow(),
+ 'deleted_at': utils.utcnow(),
'updated_at': literal_column('updated_at')})
@@ -2197,11 +2196,11 @@ def security_group_destroy_all(context, session=None):
with session.begin():
session.query(models.SecurityGroup).\
update({'deleted': 1,
- 'deleted_at': datetime.datetime.utcnow(),
+ 'deleted_at': utils.utcnow(),
'updated_at': literal_column('updated_at')})
session.query(models.SecurityGroupIngressRule).\
update({'deleted': 1,
- 'deleted_at': datetime.datetime.utcnow(),
+ 'deleted_at': utils.utcnow(),
'updated_at': literal_column('updated_at')})
@@ -2847,7 +2846,7 @@ def instance_metadata_delete(context, instance_id, key):
filter_by(key=key).\
filter_by(deleted=False).\
update({'deleted': True,
- 'deleted_at': datetime.datetime.utcnow(),
+ 'deleted_at': utils.utcnow(),
'updated_at': literal_column('updated_at')})
@@ -2858,7 +2857,7 @@ def instance_metadata_delete_all(context, instance_id):
filter_by(instance_id=instance_id).\
filter_by(deleted=False).\
update({'deleted': True,
- 'deleted_at': datetime.datetime.utcnow(),
+ 'deleted_at': utils.utcnow(),
'updated_at': literal_column('updated_at')})
diff --git a/nova/db/sqlalchemy/migrate_repo/versions/016_make_quotas_key_and_value.py b/nova/db/sqlalchemy/migrate_repo/versions/016_make_quotas_key_and_value.py
index 5d0593f2e..a4fe3e482 100644
--- a/nova/db/sqlalchemy/migrate_repo/versions/016_make_quotas_key_and_value.py
+++ b/nova/db/sqlalchemy/migrate_repo/versions/016_make_quotas_key_and_value.py
@@ -17,7 +17,7 @@
from sqlalchemy import Boolean, Column, DateTime, Integer
from sqlalchemy import MetaData, String, Table
-import datetime
+from nova import utils
meta = MetaData()
@@ -35,9 +35,9 @@ def old_style_quotas_table(name):
return Table(name, meta,
Column('id', Integer(), primary_key=True),
Column('created_at', DateTime(),
- default=datetime.datetime.utcnow),
+ default=utils.utcnow),
Column('updated_at', DateTime(),
- onupdate=datetime.datetime.utcnow),
+ onupdate=utils.utcnow),
Column('deleted_at', DateTime()),
Column('deleted', Boolean(), default=False),
Column('project_id',
@@ -57,9 +57,9 @@ def new_style_quotas_table(name):
return Table(name, meta,
Column('id', Integer(), primary_key=True),
Column('created_at', DateTime(),
- default=datetime.datetime.utcnow),
+ default=utils.utcnow),
Column('updated_at', DateTime(),
- onupdate=datetime.datetime.utcnow),
+ onupdate=utils.utcnow),
Column('deleted_at', DateTime()),
Column('deleted', Boolean(), default=False),
Column('project_id',
diff --git a/nova/db/sqlalchemy/migrate_repo/versions/021_rename_image_ids.py b/nova/db/sqlalchemy/migrate_repo/versions/021_rename_image_ids.py
new file mode 100644
index 000000000..73a5e8477
--- /dev/null
+++ b/nova/db/sqlalchemy/migrate_repo/versions/021_rename_image_ids.py
@@ -0,0 +1,40 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2011 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 Column, Integer, MetaData, String, Table
+
+
+meta = MetaData()
+
+
+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)
+
+ image_id_column = instances.c.image_id
+ image_id_column.alter(name='image_ref')
+
+
+def downgrade(migrate_engine):
+ meta.bind = migrate_engine
+ instances = Table('instances', meta, autoload=True,
+ autoload_with=migrate_engine)
+
+ image_ref_column = instances.c.image_ref
+ image_ref_column.alter(name='image_id')
diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py
index c0cc426ce..e0614a95f 100644
--- a/nova/db/sqlalchemy/models.py
+++ b/nova/db/sqlalchemy/models.py
@@ -19,8 +19,6 @@
SQLAlchemy models for nova data.
"""
-import datetime
-
from sqlalchemy.orm import relationship, backref, object_mapper
from sqlalchemy import Column, Integer, String, schema
from sqlalchemy import ForeignKey, DateTime, Boolean, Text
@@ -33,6 +31,7 @@ from nova.db.sqlalchemy.session import get_session
from nova import auth
from nova import exception
from nova import flags
+from nova import utils
FLAGS = flags.FLAGS
@@ -43,8 +42,8 @@ class NovaBase(object):
"""Base class for Nova Models."""
__table_args__ = {'mysql_engine': 'InnoDB'}
__table_initialized__ = False
- created_at = Column(DateTime, default=datetime.datetime.utcnow)
- updated_at = Column(DateTime, onupdate=datetime.datetime.utcnow)
+ created_at = Column(DateTime, default=utils.utcnow)
+ updated_at = Column(DateTime, onupdate=utils.utcnow)
deleted_at = Column(DateTime)
deleted = Column(Boolean, default=False)
@@ -64,7 +63,7 @@ class NovaBase(object):
def delete(self, session=None):
"""Delete this object."""
self.deleted = True
- self.deleted_at = datetime.datetime.utcnow()
+ self.deleted_at = utils.utcnow()
self.save(session=session)
def __setitem__(self, key, value):
@@ -191,11 +190,11 @@ class Instance(BASE, NovaBase):
def fixed_ip(self):
return self.fixed_ips[0] if self.fixed_ips else None
- image_id = Column(String(255))
+ image_ref = Column(String(255))
kernel_id = Column(String(255))
ramdisk_id = Column(String(255))
-# image_id = Column(Integer, ForeignKey('images.id'), nullable=True)
+# image_ref = Column(Integer, ForeignKey('images.id'), nullable=True)
# kernel_id = Column(Integer, ForeignKey('images.id'), nullable=True)
# ramdisk_id = Column(Integer, ForeignKey('images.id'), nullable=True)
# ramdisk = relationship(Ramdisk, backref=backref('instances', order_by=id))
diff --git a/nova/exception.py b/nova/exception.py
index f63200a63..1aab66c05 100644
--- a/nova/exception.py
+++ b/nova/exception.py
@@ -65,7 +65,7 @@ class BuildInProgress(Error):
class DBError(Error):
"""Wraps an implementation specific exception."""
- def __init__(self, inner_exception):
+ def __init__(self, inner_exception=None):
self.inner_exception = inner_exception
super(DBError, self).__init__(str(inner_exception))
@@ -126,7 +126,7 @@ class NotAuthorized(NovaException):
message = _("Not authorized.")
def __init__(self, *args, **kwargs):
- super(NotFound, self).__init__(**kwargs)
+ super(NotAuthorized, self).__init__(**kwargs)
class AdminRequired(NotAuthorized):
@@ -295,6 +295,15 @@ class DiskNotFound(NotFound):
message = _("No disk at %(location)s")
+class InvalidImageRef(Invalid):
+ message = _("Invalid image href %(image_href)s.")
+
+
+class ListingImageRefsNotSupported(Invalid):
+ message = _("Some images have been stored via hrefs."
+ + " This version of the api does not support displaying image hrefs.")
+
+
class ImageNotFound(NotFound):
message = _("Image %(image_id)s could not be found.")
@@ -497,6 +506,15 @@ class SchedulerHostFilterNotFound(NotFound):
message = _("Scheduler Host Filter %(filter_name)s could not be found.")
+class SchedulerCostFunctionNotFound(NotFound):
+ message = _("Scheduler cost function %(cost_fn_str)s could"
+ " not be found.")
+
+
+class SchedulerWeightFlagNotFound(NotFound):
+ message = _("Scheduler weight flag not found: %(flag_name)s")
+
+
class InstanceMetadataNotFound(NotFound):
message = _("Instance %(instance_id)s has no metadata with "
"key %(metadata_key)s.")
diff --git a/nova/image/__init__.py b/nova/image/__init__.py
index e69de29bb..93d83df24 100644
--- a/nova/image/__init__.py
+++ b/nova/image/__init__.py
@@ -0,0 +1,93 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2011 OpenStack LLC.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+
+from urlparse import urlparse
+
+import nova
+from nova import exception
+from nova import utils
+from nova import flags
+
+FLAGS = flags.FLAGS
+
+
+GlanceClient = utils.import_class('glance.client.Client')
+
+
+def _parse_image_ref(image_href):
+ """Parse an image href into composite parts.
+
+ :param image_href: href of an image
+ :returns: a tuple of the form (image_id, host, port)
+
+ """
+ o = urlparse(image_href)
+ port = o.port or 80
+ host = o.netloc.split(':', 1)[0]
+ image_id = int(o.path.split('/')[-1])
+ return (image_id, host, port)
+
+
+def get_default_image_service():
+ ImageService = utils.import_class(FLAGS.image_service)
+ return ImageService()
+
+
+def get_glance_client(image_href):
+ """Get the correct glance client and id for the given image_href.
+
+ The image_href param can be an href of the form
+ http://myglanceserver:9292/images/42, or just an int such as 42. If the
+ image_href is an int, then flags are used to create the default
+ glance client.
+
+ :param image_href: image ref/id for an image
+ :returns: a tuple of the form (glance_client, image_id)
+
+ """
+ image_href = image_href or 0
+ if str(image_href).isdigit():
+ glance_client = GlanceClient(FLAGS.glance_host, FLAGS.glance_port)
+ return (glance_client, int(image_href))
+
+ try:
+ (image_id, host, port) = _parse_image_ref(image_href)
+ except:
+ raise exception.InvalidImageRef(image_href=image_href)
+ glance_client = GlanceClient(host, port)
+ return (glance_client, image_id)
+
+
+def get_image_service(image_href):
+ """Get the proper image_service and id for the given image_href.
+
+ The image_href param can be an href of the form
+ http://myglanceserver:9292/images/42, or just an int such as 42. If the
+ image_href is an int, then the default image service is returned.
+
+ :param image_href: image ref/id for an image
+ :returns: a tuple of the form (image_service, image_id)
+
+ """
+ image_href = image_href or 0
+ if str(image_href).isdigit():
+ return (get_default_image_service(), int(image_href))
+
+ (glance_client, image_id) = get_glance_client(image_href)
+ image_service = nova.image.glance.GlanceImageService(glance_client)
+ return (image_service, image_id)
diff --git a/nova/image/fake.py b/nova/image/fake.py
index b220c935a..63966244b 100644
--- a/nova/image/fake.py
+++ b/nova/image/fake.py
@@ -41,7 +41,7 @@ class _FakeImageService(service.BaseImageService):
# NOTE(justinsb): The OpenStack API can't upload an image?
# So, make sure we've got one..
timestamp = datetime.datetime(2011, 01, 01, 01, 02, 03)
- image = {'id': '123456',
+ image1 = {'id': '123456',
'name': 'fakeimage123456',
'created_at': timestamp,
'updated_at': timestamp,
@@ -51,7 +51,52 @@ class _FakeImageService(service.BaseImageService):
'properties': {'kernel_id': FLAGS.null_kernel,
'ramdisk_id': FLAGS.null_kernel,
'architecture': 'x86_64'}}
- self.create(None, image)
+
+ image2 = {'id': 'fake',
+ 'name': 'fakeimage123456',
+ 'created_at': timestamp,
+ 'updated_at': timestamp,
+ 'status': 'active',
+ 'container_format': 'ami',
+ 'disk_format': 'raw',
+ 'properties': {'kernel_id': FLAGS.null_kernel,
+ 'ramdisk_id': FLAGS.null_kernel}}
+
+ image3 = {'id': '2',
+ 'name': 'fakeimage123456',
+ 'created_at': timestamp,
+ 'updated_at': timestamp,
+ 'status': 'active',
+ 'container_format': 'ami',
+ 'disk_format': 'raw',
+ 'properties': {'kernel_id': FLAGS.null_kernel,
+ 'ramdisk_id': FLAGS.null_kernel}}
+
+ image4 = {'id': '1',
+ 'name': 'fakeimage123456',
+ 'created_at': timestamp,
+ 'updated_at': timestamp,
+ 'status': 'active',
+ 'container_format': 'ami',
+ 'disk_format': 'raw',
+ 'properties': {'kernel_id': FLAGS.null_kernel,
+ 'ramdisk_id': FLAGS.null_kernel}}
+
+ image5 = {'id': '3',
+ 'name': 'fakeimage123456',
+ 'created_at': timestamp,
+ 'updated_at': timestamp,
+ 'status': 'active',
+ 'container_format': 'ami',
+ 'disk_format': 'raw',
+ 'properties': {'kernel_id': FLAGS.null_kernel,
+ 'ramdisk_id': FLAGS.null_kernel}}
+
+ self.create(None, image1)
+ self.create(None, image2)
+ self.create(None, image3)
+ self.create(None, image4)
+ self.create(None, image5)
super(_FakeImageService, self).__init__()
def index(self, context, filters=None):
@@ -68,8 +113,7 @@ class _FakeImageService(service.BaseImageService):
Returns a dict containing image data for the given opaque image id.
"""
- image_id = int(image_id)
- image = self.images.get(image_id)
+ image = self.images.get(str(image_id))
if image:
return copy.deepcopy(image)
LOG.warn('Unable to find image id %s. Have images: %s',
@@ -83,9 +127,10 @@ class _FakeImageService(service.BaseImageService):
"""
try:
- image_id = int(metadata['id'])
+ image_id = metadata['id']
except KeyError:
image_id = random.randint(0, 2 ** 31 - 1)
+ image_id = str(image_id)
if self.images.get(image_id):
raise exception.Duplicate()
@@ -100,7 +145,6 @@ class _FakeImageService(service.BaseImageService):
:raises: ImageNotFound if the image does not exist.
"""
- image_id = int(image_id)
if not self.images.get(image_id):
raise exception.ImageNotFound(image_id=image_id)
self.images[image_id] = copy.deepcopy(metadata)
@@ -111,7 +155,6 @@ class _FakeImageService(service.BaseImageService):
:raises: ImageNotFound if the image does not exist.
"""
- image_id = int(image_id)
removed = self.images.pop(image_id, None)
if not removed:
raise exception.ImageNotFound(image_id=image_id)
diff --git a/nova/image/s3.py b/nova/image/s3.py
index ec8852f09..9e95bd698 100644
--- a/nova/image/s3.py
+++ b/nova/image/s3.py
@@ -31,6 +31,7 @@ import eventlet
from nova import crypto
from nova import exception
from nova import flags
+from nova import image
from nova import log as logging
from nova import utils
from nova.auth import manager
@@ -48,9 +49,7 @@ class S3ImageService(service.BaseImageService):
"""Wraps an existing image service to support s3 based register."""
def __init__(self, service=None, *args, **kwargs):
- if service is None:
- service = utils.import_object(FLAGS.image_service)
- self.service = service
+ self.service = service or image.get_default_image_service()
self.service.__init__(*args, **kwargs)
def create(self, context, metadata, data=None):
diff --git a/nova/network/manager.py b/nova/network/manager.py
index 21f5c42a0..bd3363777 100644
--- a/nova/network/manager.py
+++ b/nova/network/manager.py
@@ -498,7 +498,7 @@ class NetworkManager(manager.SchedulerDependentManager):
if mac_address != mac:
raise exception.Error(_('IP %(address)s leased to bad'
' mac %(mac_address)s vs %(mac)s') % locals())
- now = datetime.datetime.utcnow()
+ now = utils.utcnow()
self.db.fixed_ip_update(context,
fixed_ip_ref['address'],
{'leased': True,
diff --git a/nova/notifier/api.py b/nova/notifier/api.py
index a3e7a039e..d49517c8b 100644
--- a/nova/notifier/api.py
+++ b/nova/notifier/api.py
@@ -11,9 +11,8 @@
# 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 datetime
+# under the License.
-import datetime
import uuid
from nova import flags
@@ -64,7 +63,7 @@ def notify(publisher_id, event_type, priority, payload):
{'message_id': str(uuid.uuid4()),
'publisher_id': 'compute.host1',
- 'timestamp': datetime.datetime.utcnow(),
+ 'timestamp': utils.utcnow(),
'priority': 'WARN',
'event_type': 'compute.create_instance',
'payload': {'instance_id': 12, ... }}
@@ -79,5 +78,5 @@ def notify(publisher_id, event_type, priority, payload):
event_type=event_type,
priority=priority,
payload=payload,
- timestamp=str(datetime.datetime.utcnow()))
+ timestamp=str(utils.utcnow()))
driver.notify(msg)
diff --git a/nova/scheduler/driver.py b/nova/scheduler/driver.py
index 2094e3565..0b257c5d8 100644
--- a/nova/scheduler/driver.py
+++ b/nova/scheduler/driver.py
@@ -28,6 +28,7 @@ from nova import exception
from nova import flags
from nova import log as logging
from nova import rpc
+from nova import utils
from nova.compute import power_state
FLAGS = flags.FLAGS
@@ -61,7 +62,7 @@ class Scheduler(object):
"""Check whether a service is up based on last heartbeat."""
last_heartbeat = service['updated_at'] or service['created_at']
# Timestamps in DB are UTC.
- elapsed = datetime.datetime.utcnow() - last_heartbeat
+ elapsed = utils.utcnow() - last_heartbeat
return elapsed < datetime.timedelta(seconds=FLAGS.service_down_time)
def hosts_up(self, context, topic):
diff --git a/nova/scheduler/host_filter.py b/nova/scheduler/host_filter.py
index 7d6ee0ee3..bd6b26608 100644
--- a/nova/scheduler/host_filter.py
+++ b/nova/scheduler/host_filter.py
@@ -41,6 +41,7 @@ import json
from nova import exception
from nova import flags
from nova import log as logging
+from nova.scheduler import zone_aware_scheduler
from nova import utils
from nova.scheduler import zone_aware_scheduler
diff --git a/nova/scheduler/least_cost.py b/nova/scheduler/least_cost.py
new file mode 100644
index 000000000..629fe2e42
--- /dev/null
+++ b/nova/scheduler/least_cost.py
@@ -0,0 +1,156 @@
+# Copyright (c) 2011 Openstack, LLC.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+"""
+Least Cost Scheduler is a mechanism for choosing which host machines to
+provision a set of resources to. The input of the least-cost-scheduler is a
+set of objective-functions, called the 'cost-functions', a weight for each
+cost-function, and a list of candidate hosts (gathered via FilterHosts).
+
+The cost-function and weights are tabulated, and the host with the least cost
+is then selected for provisioning.
+"""
+
+import collections
+
+from nova import flags
+from nova import log as logging
+from nova.scheduler import zone_aware_scheduler
+from nova import utils
+
+LOG = logging.getLogger('nova.scheduler.least_cost')
+
+FLAGS = flags.FLAGS
+flags.DEFINE_list('least_cost_scheduler_cost_functions',
+ ['nova.scheduler.least_cost.noop_cost_fn'],
+ 'Which cost functions the LeastCostScheduler should use.')
+
+
+# TODO(sirp): Once we have enough of these rules, we can break them out into a
+# cost_functions.py file (perhaps in a least_cost_scheduler directory)
+flags.DEFINE_integer('noop_cost_fn_weight', 1,
+ 'How much weight to give the noop cost function')
+
+
+def noop_cost_fn(host):
+ """Return a pre-weight cost of 1 for each host"""
+ return 1
+
+
+flags.DEFINE_integer('fill_first_cost_fn_weight', 1,
+ 'How much weight to give the fill-first cost function')
+
+
+def fill_first_cost_fn(host):
+ """Prefer hosts that have less ram available, filter_hosts will exclude
+ hosts that don't have enough ram"""
+ hostname, caps = host
+ free_mem = caps['compute']['host_memory_free']
+ return free_mem
+
+
+class LeastCostScheduler(zone_aware_scheduler.ZoneAwareScheduler):
+ def get_cost_fns(self):
+ """Returns a list of tuples containing weights and cost functions to
+ use for weighing hosts
+ """
+ cost_fns = []
+ for cost_fn_str in FLAGS.least_cost_scheduler_cost_functions:
+
+ try:
+ # NOTE(sirp): import_class is somewhat misnamed since it can
+ # any callable from a module
+ cost_fn = utils.import_class(cost_fn_str)
+ except exception.ClassNotFound:
+ raise exception.SchedulerCostFunctionNotFound(
+ cost_fn_str=cost_fn_str)
+
+ try:
+ weight = getattr(FLAGS, "%s_weight" % cost_fn.__name__)
+ except AttributeError:
+ raise exception.SchedulerWeightFlagNotFound(
+ flag_name=flag_name)
+
+ cost_fns.append((weight, cost_fn))
+
+ return cost_fns
+
+ def weigh_hosts(self, num, request_spec, hosts):
+ """Returns a list of dictionaries of form:
+ [ {weight: weight, hostname: hostname} ]"""
+
+ # FIXME(sirp): weigh_hosts should handle more than just instances
+ hostnames = [hostname for hostname, caps in hosts]
+
+ cost_fns = self.get_cost_fns()
+ costs = weighted_sum(domain=hosts, weighted_fns=cost_fns)
+
+ weighted = []
+ weight_log = []
+ for cost, hostname in zip(costs, hostnames):
+ weight_log.append("%s: %s" % (hostname, "%.2f" % cost))
+ weight_dict = dict(weight=cost, hostname=hostname)
+ weighted.append(weight_dict)
+
+ LOG.debug(_("Weighted Costs => %s") % weight_log)
+ return weighted
+
+
+def normalize_list(L):
+ """Normalize an array of numbers such that each element satisfies:
+ 0 <= e <= 1"""
+ if not L:
+ return L
+ max_ = max(L)
+ if max_ > 0:
+ return [(float(e) / max_) for e in L]
+ return L
+
+
+def weighted_sum(domain, weighted_fns, normalize=True):
+ """Use the weighted-sum method to compute a score for an array of objects.
+ Normalize the results of the objective-functions so that the weights are
+ meaningful regardless of objective-function's range.
+
+ domain - input to be scored
+ weighted_fns - list of weights and functions like:
+ [(weight, objective-functions)]
+
+ Returns an unsorted of scores. To pair with hosts do: zip(scores, hosts)
+ """
+ # Table of form:
+ # { domain1: [score1, score2, ..., scoreM]
+ # ...
+ # domainN: [score1, score2, ..., scoreM] }
+ score_table = collections.defaultdict(list)
+ for weight, fn in weighted_fns:
+ scores = [fn(elem) for elem in domain]
+
+ if normalize:
+ norm_scores = normalize_list(scores)
+ else:
+ norm_scores = scores
+
+ for idx, score in enumerate(norm_scores):
+ weighted_score = score * weight
+ score_table[idx].append(weighted_score)
+
+ # Sum rows in table to compute score for each element in domain
+ domain_scores = []
+ for idx in sorted(score_table):
+ elem_score = sum(score_table[idx])
+ elem = domain[idx]
+ domain_scores.append(elem_score)
+
+ return domain_scores
diff --git a/nova/scheduler/simple.py b/nova/scheduler/simple.py
index dd568d2c6..87cdef11d 100644
--- a/nova/scheduler/simple.py
+++ b/nova/scheduler/simple.py
@@ -21,10 +21,9 @@
Simple Scheduler
"""
-import datetime
-
from nova import db
from nova import flags
+from nova import utils
from nova.scheduler import driver
from nova.scheduler import chance
@@ -54,7 +53,7 @@ class SimpleScheduler(chance.ChanceScheduler):
# TODO(vish): this probably belongs in the manager, if we
# can generalize this somehow
- now = datetime.datetime.utcnow()
+ now = utils.utcnow()
db.instance_update(context, instance_id, {'host': host,
'scheduled_at': now})
return host
@@ -66,7 +65,7 @@ class SimpleScheduler(chance.ChanceScheduler):
if self.service_is_up(service):
# NOTE(vish): this probably belongs in the manager, if we
# can generalize this somehow
- now = datetime.datetime.utcnow()
+ now = utils.utcnow()
db.instance_update(context,
instance_id,
{'host': service['host'],
@@ -90,7 +89,7 @@ class SimpleScheduler(chance.ChanceScheduler):
# TODO(vish): this probably belongs in the manager, if we
# can generalize this somehow
- now = datetime.datetime.utcnow()
+ now = utils.utcnow()
db.volume_update(context, volume_id, {'host': host,
'scheduled_at': now})
return host
@@ -103,7 +102,7 @@ class SimpleScheduler(chance.ChanceScheduler):
if self.service_is_up(service):
# NOTE(vish): this probably belongs in the manager, if we
# can generalize this somehow
- now = datetime.datetime.utcnow()
+ now = utils.utcnow()
db.volume_update(context,
volume_id,
{'host': service['host'],
diff --git a/nova/scheduler/zone_aware_scheduler.py b/nova/scheduler/zone_aware_scheduler.py
index bc67c7794..df84cf7bd 100644
--- a/nova/scheduler/zone_aware_scheduler.py
+++ b/nova/scheduler/zone_aware_scheduler.py
@@ -39,7 +39,7 @@ class ZoneAwareScheduler(driver.Scheduler):
return api.call_zone_method(context, method, specs=specs)
def schedule_run_instance(self, context, instance_id, request_spec,
- *args, **kwargs):
+ *args, **kwargs):
"""This method is called from nova.compute.api to provision
an instance. However we need to look at the parameters being
passed in to see if this is a request to:
@@ -116,6 +116,9 @@ class ZoneAwareScheduler(driver.Scheduler):
# Filter local hosts based on requirements ...
host_list = self.filter_hosts(num_instances, request_spec)
+ # TODO(sirp): weigh_hosts should also be a function of 'topic' or
+ # resources, so that we can apply different objective functions to it
+
# then weigh the selected hosts.
# weighted = [{weight=weight, name=hostname}, ...]
weighted = self.weigh_hosts(num_instances, request_spec, host_list)
@@ -139,12 +142,16 @@ class ZoneAwareScheduler(driver.Scheduler):
def filter_hosts(self, num, request_spec):
"""Derived classes must override this method and return
- a list of hosts in [(hostname, capability_dict)] format.
+ a list of hosts in [(hostname, capability_dict)] format.
"""
- raise NotImplemented()
+ # NOTE(sirp): The default logic is the equivalent to AllHostsFilter
+ service_states = self.zone_manager.service_states
+ return [(host, services)
+ for host, services in service_states.iteritems()]
def weigh_hosts(self, num, request_spec, hosts):
- """Derived classes must override this method and return
- a lists of hosts in [{weight, hostname}] format.
+ """Derived classes may override this to provide more sophisticated
+ scheduling objectives
"""
- raise NotImplemented()
+ # NOTE(sirp): The default logic is the same as the NoopCostFunction
+ return [dict(weight=1, hostname=host) for host, caps in hosts]
diff --git a/nova/scheduler/zone_manager.py b/nova/scheduler/zone_manager.py
index 3ddf6f3c3..3f483adff 100644
--- a/nova/scheduler/zone_manager.py
+++ b/nova/scheduler/zone_manager.py
@@ -17,16 +17,17 @@
ZoneManager oversees all communications with child Zones.
"""
+import datetime
import novaclient
import thread
import traceback
-from datetime import datetime
from eventlet import greenpool
from nova import db
from nova import flags
from nova import log as logging
+from nova import utils
FLAGS = flags.FLAGS
flags.DEFINE_integer('zone_db_check_interval', 60,
@@ -42,7 +43,7 @@ class ZoneState(object):
self.name = None
self.capabilities = None
self.attempt = 0
- self.last_seen = datetime.min
+ self.last_seen = datetime.datetime.min
self.last_exception = None
self.last_exception_time = None
@@ -56,7 +57,7 @@ class ZoneState(object):
def update_metadata(self, zone_metadata):
"""Update zone metadata after successful communications with
child zone."""
- self.last_seen = datetime.now()
+ self.last_seen = utils.utcnow()
self.attempt = 0
self.name = zone_metadata.get("name", "n/a")
self.capabilities = ", ".join(["%s=%s" % (k, v)
@@ -72,7 +73,7 @@ class ZoneState(object):
"""Something went wrong. Check to see if zone should be
marked as offline."""
self.last_exception = exception
- self.last_exception_time = datetime.now()
+ self.last_exception_time = utils.utcnow()
api_url = self.api_url
logging.warning(_("'%(exception)s' error talking to "
"zone %(api_url)s") % locals())
@@ -104,7 +105,7 @@ def _poll_zone(zone):
class ZoneManager(object):
"""Keeps the zone states updated."""
def __init__(self):
- self.last_zone_db_check = datetime.min
+ self.last_zone_db_check = datetime.datetime.min
self.zone_states = {} # { <zone_id> : ZoneState }
self.service_states = {} # { <host> : { <service> : { cap k : v }}}
self.green_pool = greenpool.GreenPool()
@@ -158,10 +159,10 @@ class ZoneManager(object):
def ping(self, context=None):
"""Ping should be called periodically to update zone status."""
- diff = datetime.now() - self.last_zone_db_check
+ diff = utils.utcnow() - self.last_zone_db_check
if diff.seconds >= FLAGS.zone_db_check_interval:
logging.debug(_("Updating zone cache from db."))
- self.last_zone_db_check = datetime.now()
+ self.last_zone_db_check = utils.utcnow()
self._refresh_from_db(context)
self._poll_zones(context)
diff --git a/nova/test.py b/nova/test.py
index 80b2d0a74..4a0a18fe7 100644
--- a/nova/test.py
+++ b/nova/test.py
@@ -23,7 +23,6 @@ inline callbacks.
"""
-import datetime
import functools
import os
import shutil
@@ -37,6 +36,7 @@ from eventlet import greenthread
from nova import fakerabbit
from nova import flags
from nova import rpc
+from nova import utils
from nova import service
from nova import wsgi
from nova.virt import fake
@@ -69,7 +69,7 @@ class TestCase(unittest.TestCase):
# NOTE(vish): We need a better method for creating fixtures for tests
# now that we have some required db setup for the system
# to work properly.
- self.start = datetime.datetime.utcnow()
+ self.start = utils.utcnow()
shutil.copyfile(os.path.join(FLAGS.state_path, FLAGS.sqlite_clean_db),
os.path.join(FLAGS.state_path, FLAGS.sqlite_db))
@@ -184,7 +184,7 @@ class TestCase(unittest.TestCase):
wsgi.Server.start = _wrapped_start
# Useful assertions
- def assertDictMatch(self, d1, d2):
+ def assertDictMatch(self, d1, d2, approx_equal=False, tolerance=0.001):
"""Assert two dicts are equivalent.
This is a 'deep' match in the sense that it handles nested
@@ -215,15 +215,26 @@ class TestCase(unittest.TestCase):
for key in d1keys:
d1value = d1[key]
d2value = d2[key]
+ try:
+ error = abs(float(d1value) - float(d2value))
+ within_tolerance = error <= tolerance
+ except (ValueError, TypeError):
+ # If both values aren't convertable to float, just ignore
+ # ValueError if arg is a str, TypeError if it's something else
+ # (like None)
+ within_tolerance = False
+
if hasattr(d1value, 'keys') and hasattr(d2value, 'keys'):
self.assertDictMatch(d1value, d2value)
elif 'DONTCARE' in (d1value, d2value):
continue
+ elif approx_equal and within_tolerance:
+ continue
elif d1value != d2value:
raise_assertion("d1['%(key)s']=%(d1value)s != "
"d2['%(key)s']=%(d2value)s" % locals())
- def assertDictListMatch(self, L1, L2):
+ def assertDictListMatch(self, L1, L2, approx_equal=False, tolerance=0.001):
"""Assert a list of dicts are equivalent."""
def raise_assertion(msg):
L1str = str(L1)
@@ -239,4 +250,5 @@ class TestCase(unittest.TestCase):
'len(L2)=%(L2count)d' % locals())
for d1, d2 in zip(L1, L2):
- self.assertDictMatch(d1, d2)
+ self.assertDictMatch(d1, d2, approx_equal=approx_equal,
+ tolerance=tolerance)
diff --git a/nova/tests/api/openstack/fakes.py b/nova/tests/api/openstack/fakes.py
index 8e0156afa..62e44ba96 100644
--- a/nova/tests/api/openstack/fakes.py
+++ b/nova/tests/api/openstack/fakes.py
@@ -16,7 +16,6 @@
# under the License.
import copy
-import datetime
import json
import random
import string
@@ -38,6 +37,7 @@ from nova.api.openstack import auth
from nova.api.openstack import versions
from nova.api.openstack import limits
from nova.auth.manager import User, Project
+import nova.image.fake
from nova.image import glance
from nova.image import local
from nova.image import service
@@ -104,10 +104,12 @@ def stub_out_key_pair_funcs(stubs, have_key_pair=True):
def stub_out_image_service(stubs):
- def fake_image_show(meh, context, id):
- return dict(kernelId=1, ramdiskId=1)
-
- stubs.Set(local.LocalImageService, 'show', fake_image_show)
+ def fake_get_image_service(image_href):
+ image_id = int(str(image_href).split('/')[-1])
+ return (nova.image.fake.FakeImageService(), image_id)
+ stubs.Set(nova.image, 'get_image_service', fake_get_image_service)
+ stubs.Set(nova.image, 'get_default_image_service',
+ lambda: nova.image.fake.FakeImageService())
def stub_out_auth(stubs):
@@ -208,7 +210,7 @@ def stub_out_glance(stubs, initial_fixtures=None):
def _find_image(self, image_id):
for f in self.fixtures:
- if f['id'] == image_id:
+ if str(f['id']) == str(image_id):
return f
return None
@@ -253,7 +255,7 @@ class FakeAuthDatabase(object):
@staticmethod
def auth_token_create(context, token):
- fake_token = FakeToken(created_at=datetime.datetime.now(), **token)
+ fake_token = FakeToken(created_at=utils.utcnow(), **token)
FakeAuthDatabase.data[fake_token.token_hash] = fake_token
FakeAuthDatabase.data['id_%i' % fake_token.id] = fake_token
return fake_token
diff --git a/nova/tests/api/openstack/test_images.py b/nova/tests/api/openstack/test_images.py
index 9f1f28611..93b402081 100644
--- a/nova/tests/api/openstack/test_images.py
+++ b/nova/tests/api/openstack/test_images.py
@@ -22,7 +22,6 @@ and as a WSGI layer
import copy
import json
-import datetime
import os
import shutil
import tempfile
diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py
index 11dcaaade..28ad4a417 100644
--- a/nova/tests/api/openstack/test_servers.py
+++ b/nova/tests/api/openstack/test_servers.py
@@ -16,7 +16,6 @@
# under the License.
import base64
-import datetime
import json
import unittest
from xml.dom import minidom
@@ -29,6 +28,7 @@ from nova import db
from nova import exception
from nova import flags
from nova import test
+from nova import utils
import nova.api.openstack
from nova.api.openstack import servers
import nova.compute.api
@@ -37,6 +37,7 @@ from nova.compute import power_state
import nova.db.api
from nova.db.sqlalchemy.models import Instance
from nova.db.sqlalchemy.models import InstanceMetadata
+import nova.image.fake
import nova.rpc
from nova.tests.api.openstack import common
from nova.tests.api.openstack import fakes
@@ -97,7 +98,7 @@ def stub_instance(id, user_id=1, private_address=None, public_addresses=None,
"admin_pass": "",
"user_id": user_id,
"project_id": "",
- "image_id": "10",
+ "image_ref": "10",
"kernel_id": "",
"ramdisk_id": "",
"launch_index": 0,
@@ -114,9 +115,9 @@ def stub_instance(id, user_id=1, private_address=None, public_addresses=None,
"user_data": "",
"reservation_id": "",
"mac_address": "",
- "scheduled_at": datetime.datetime.now(),
- "launched_at": datetime.datetime.now(),
- "terminated_at": datetime.datetime.now(),
+ "scheduled_at": utils.utcnow(),
+ "launched_at": utils.utcnow(),
+ "terminated_at": utils.utcnow(),
"availability_zone": "",
"display_name": "server%s" % id,
"display_description": "",
@@ -484,8 +485,6 @@ class ServersTest(test.TestCase):
fake_method)
self.stubs.Set(nova.api.openstack.servers.Controller,
"_get_kernel_ramdisk_from_image", kernel_ramdisk_mapping)
- self.stubs.Set(nova.api.openstack.common,
- "get_image_id_from_image_hash", image_id_from_hash)
self.stubs.Set(nova.compute.api.API, "_find_host", find_host)
def _test_create_instance_helper(self):
@@ -588,12 +587,12 @@ class ServersTest(test.TestCase):
def test_create_instance_v1_1(self):
self._setup_for_create_instance()
- image_ref = 'http://localhost/v1.1/images/2'
+ image_href = 'http://localhost/v1.1/images/2'
flavor_ref = 'http://localhost/v1.1/flavors/3'
body = {
'server': {
'name': 'server_test',
- 'imageRef': image_ref,
+ 'imageRef': image_href,
'flavorRef': flavor_ref,
'metadata': {
'hello': 'world',
@@ -615,16 +614,16 @@ class ServersTest(test.TestCase):
self.assertEqual('server_test', server['name'])
self.assertEqual(1, server['id'])
self.assertEqual(flavor_ref, server['flavorRef'])
- self.assertEqual(image_ref, server['imageRef'])
+ self.assertEqual(image_href, server['imageRef'])
self.assertEqual(res.status_int, 200)
def test_create_instance_v1_1_bad_href(self):
self._setup_for_create_instance()
- image_ref = 'http://localhost/v1.1/images/asdf'
+ image_href = 'http://localhost/v1.1/images/asdf'
flavor_ref = 'http://localhost/v1.1/flavors/3'
body = dict(server=dict(
- name='server_test', imageRef=image_ref, flavorRef=flavor_ref,
+ name='server_test', imageRef=image_href, flavorRef=flavor_ref,
metadata={'hello': 'world', 'open': 'stack'},
personality={}))
req = webob.Request.blank('/v1.1/servers')
@@ -637,13 +636,12 @@ class ServersTest(test.TestCase):
def test_create_instance_v1_1_local_href(self):
self._setup_for_create_instance()
- image_ref = 'http://localhost/v1.1/images/2'
- image_ref_local = '2'
+ image_id = 2
flavor_ref = 'http://localhost/v1.1/flavors/3'
body = {
'server': {
'name': 'server_test',
- 'imageRef': image_ref_local,
+ 'imageRef': image_id,
'flavorRef': flavor_ref,
},
}
@@ -658,7 +656,7 @@ class ServersTest(test.TestCase):
server = json.loads(res.body)['server']
self.assertEqual(1, server['id'])
self.assertEqual(flavor_ref, server['flavorRef'])
- self.assertEqual(image_ref, server['imageRef'])
+ self.assertEqual(image_id, server['imageRef'])
self.assertEqual(res.status_int, 200)
def test_create_instance_with_admin_pass_v1_0(self):
@@ -685,12 +683,12 @@ class ServersTest(test.TestCase):
def test_create_instance_with_admin_pass_v1_1(self):
self._setup_for_create_instance()
- image_ref = 'http://localhost/v1.1/images/2'
+ image_href = 'http://localhost/v1.1/images/2'
flavor_ref = 'http://localhost/v1.1/flavors/3'
body = {
'server': {
'name': 'server_test',
- 'imageRef': image_ref,
+ 'imageRef': image_href,
'flavorRef': flavor_ref,
'adminPass': 'testpass',
},
@@ -707,12 +705,12 @@ class ServersTest(test.TestCase):
def test_create_instance_with_empty_admin_pass_v1_1(self):
self._setup_for_create_instance()
- image_ref = 'http://localhost/v1.1/images/2'
+ image_href = 'http://localhost/v1.1/images/2'
flavor_ref = 'http://localhost/v1.1/flavors/3'
body = {
'server': {
'name': 'server_test',
- 'imageRef': image_ref,
+ 'imageRef': image_href,
'flavorRef': flavor_ref,
'adminPass': '',
},
@@ -861,7 +859,7 @@ class ServersTest(test.TestCase):
self.assertEqual(s['id'], i)
self.assertEqual(s['hostId'], '')
self.assertEqual(s['name'], 'server%d' % i)
- self.assertEqual(s['imageId'], '10')
+ self.assertEqual(s['imageId'], 10)
self.assertEqual(s['flavorId'], 1)
self.assertEqual(s['status'], 'BUILD')
self.assertEqual(s['metadata']['seq'], str(i))
@@ -875,7 +873,7 @@ class ServersTest(test.TestCase):
self.assertEqual(s['id'], i)
self.assertEqual(s['hostId'], '')
self.assertEqual(s['name'], 'server%d' % i)
- self.assertEqual(s['imageRef'], 'http://localhost/v1.1/images/10')
+ self.assertEqual(s['imageRef'], 10)
self.assertEqual(s['flavorRef'], 'http://localhost/v1.1/flavors/1')
self.assertEqual(s['status'], 'BUILD')
self.assertEqual(s['metadata']['seq'], str(i))
@@ -907,7 +905,7 @@ 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):
@@ -1680,7 +1678,7 @@ b25zLiINCg0KLVJpY2hhcmQgQmFjaA==""",
request = self.deserializer.deserialize(serial_request, 'create')
self.assertEqual(request, expected)
- def test_request_xmlser_with_flavor_image_ref(self):
+ def test_request_xmlser_with_flavor_image_href(self):
serial_request = """
<server xmlns="http://docs.openstack.org/compute/api/v1.1"
name="new-server-test"
@@ -1702,6 +1700,7 @@ class TestServerInstanceCreation(test.TestCase):
fakes.FakeAuthManager.auth_data = {}
fakes.FakeAuthDatabase.data = {}
fakes.stub_out_auth(self.stubs)
+ fakes.stub_out_image_service(self.stubs)
fakes.stub_out_key_pair_funcs(self.stubs)
self.allow_admin = FLAGS.allow_admin_api
@@ -1736,8 +1735,6 @@ class TestServerInstanceCreation(test.TestCase):
self.stubs.Set(nova.compute, 'API', make_stub_method(compute_api))
self.stubs.Set(nova.api.openstack.servers.Controller,
'_get_kernel_ramdisk_from_image', make_stub_method((1, 1)))
- self.stubs.Set(nova.api.openstack.common,
- 'get_image_id_from_image_hash', make_stub_method(2))
return compute_api
def _create_personality_request_dict(self, personality_files):
diff --git a/nova/tests/glance/stubs.py b/nova/tests/glance/stubs.py
index 5872552ec..1e0b90d82 100644
--- a/nova/tests/glance/stubs.py
+++ b/nova/tests/glance/stubs.py
@@ -16,13 +16,14 @@
import StringIO
-import glance.client
+import nova.image
-def stubout_glance_client(stubs, cls):
- """Stubs out glance.client.Client"""
- stubs.Set(glance.client, 'Client',
- lambda *args, **kwargs: cls(*args, **kwargs))
+def stubout_glance_client(stubs):
+ def fake_get_glance_client(image_href):
+ image_id = int(str(image_href).split('/')[-1])
+ return (FakeGlance('foo'), image_id)
+ stubs.Set(nova.image, 'get_glance_client', fake_get_glance_client)
class FakeGlance(object):
diff --git a/nova/tests/integrated/integrated_helpers.py b/nova/tests/integrated/integrated_helpers.py
index 7f590441e..522c7cb0e 100644
--- a/nova/tests/integrated/integrated_helpers.py
+++ b/nova/tests/integrated/integrated_helpers.py
@@ -27,6 +27,7 @@ from nova import flags
from nova import service
from nova import test # For the flags
from nova.auth import manager
+import nova.image.glance
from nova.log import logging
from nova.tests.integrated.api import client
@@ -151,6 +152,11 @@ class _IntegratedTestBase(test.TestCase):
f = self._get_flags()
self.flags(**f)
+ def fake_get_image_service(image_href):
+ image_id = int(str(image_href).split('/')[-1])
+ return (nova.image.fake.FakeImageService(), image_id)
+ self.stubs.Set(nova.image, 'get_image_service', fake_get_image_service)
+
# set up services
self.start_service('compute')
self.start_service('volume')
@@ -199,19 +205,13 @@ class _IntegratedTestBase(test.TestCase):
LOG.debug("Image: %s" % image)
if 'imageRef' in image:
- image_ref = image['imageRef']
+ image_href = image['imageRef']
else:
- # NOTE(justinsb): The imageRef code hasn't yet landed
- LOG.warning("imageRef not yet in images output")
- image_ref = image['id']
-
- # TODO(justinsb): This is FUBAR
- image_ref = abs(hash(image_ref))
-
- image_ref = 'http://fake.server/%s' % image_ref
+ image_href = image['id']
+ image_href = 'http://fake.server/%s' % image_href
# We now have a valid imageId
- server['imageRef'] = image_ref
+ server['imageRef'] = image_href
# Set a valid flavorId
flavor = self.api.get_flavors()[0]
diff --git a/nova/tests/scheduler/__init__.py b/nova/tests/scheduler/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/nova/tests/scheduler/__init__.py
diff --git a/nova/tests/scheduler/test_host_filter.py b/nova/tests/scheduler/test_host_filter.py
new file mode 100644
index 000000000..07817cc5a
--- /dev/null
+++ b/nova/tests/scheduler/test_host_filter.py
@@ -0,0 +1,206 @@
+# Copyright 2011 OpenStack LLC.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+"""
+Tests For Scheduler Host Filters.
+"""
+
+import json
+
+from nova import exception
+from nova import flags
+from nova import test
+from nova.scheduler import host_filter
+
+FLAGS = flags.FLAGS
+
+
+class FakeZoneManager:
+ pass
+
+
+class HostFilterTestCase(test.TestCase):
+ """Test case for host filters."""
+
+ def _host_caps(self, multiplier):
+ # Returns host capabilities in the following way:
+ # host1 = memory:free 10 (100max)
+ # disk:available 100 (1000max)
+ # hostN = memory:free 10 + 10N
+ # disk:available 100 + 100N
+ # in other words: hostN has more resources than host0
+ # which means ... don't go above 10 hosts.
+ return {'host_name-description': 'XenServer %s' % multiplier,
+ 'host_hostname': 'xs-%s' % multiplier,
+ 'host_memory_total': 100,
+ 'host_memory_overhead': 10,
+ 'host_memory_free': 10 + multiplier * 10,
+ 'host_memory_free-computed': 10 + multiplier * 10,
+ 'host_other-config': {},
+ 'host_ip_address': '192.168.1.%d' % (100 + multiplier),
+ 'host_cpu_info': {},
+ 'disk_available': 100 + multiplier * 100,
+ 'disk_total': 1000,
+ 'disk_used': 0,
+ 'host_uuid': 'xxx-%d' % multiplier,
+ 'host_name-label': 'xs-%s' % multiplier}
+
+ def setUp(self):
+ self.old_flag = FLAGS.default_host_filter
+ FLAGS.default_host_filter = \
+ 'nova.scheduler.host_filter.AllHostsFilter'
+ self.instance_type = dict(name='tiny',
+ memory_mb=50,
+ vcpus=10,
+ local_gb=500,
+ flavorid=1,
+ swap=500,
+ rxtx_quota=30000,
+ rxtx_cap=200)
+
+ self.zone_manager = FakeZoneManager()
+ states = {}
+ for x in xrange(10):
+ states['host%02d' % (x + 1)] = {'compute': self._host_caps(x)}
+ self.zone_manager.service_states = states
+
+ def tearDown(self):
+ FLAGS.default_host_filter = self.old_flag
+
+ def test_choose_filter(self):
+ # Test default filter ...
+ hf = host_filter.choose_host_filter()
+ self.assertEquals(hf._full_name(),
+ 'nova.scheduler.host_filter.AllHostsFilter')
+ # Test valid filter ...
+ hf = host_filter.choose_host_filter(
+ 'nova.scheduler.host_filter.InstanceTypeFilter')
+ self.assertEquals(hf._full_name(),
+ 'nova.scheduler.host_filter.InstanceTypeFilter')
+ # Test invalid filter ...
+ try:
+ host_filter.choose_host_filter('does not exist')
+ self.fail("Should not find host filter.")
+ except exception.SchedulerHostFilterNotFound:
+ pass
+
+ def test_all_host_filter(self):
+ hf = host_filter.AllHostsFilter()
+ cooked = hf.instance_type_to_filter(self.instance_type)
+ hosts = hf.filter_hosts(self.zone_manager, cooked)
+ self.assertEquals(10, len(hosts))
+ for host, capabilities in hosts:
+ self.assertTrue(host.startswith('host'))
+
+ def test_instance_type_filter(self):
+ hf = host_filter.InstanceTypeFilter()
+ # filter all hosts that can support 50 ram and 500 disk
+ name, cooked = hf.instance_type_to_filter(self.instance_type)
+ self.assertEquals('nova.scheduler.host_filter.InstanceTypeFilter',
+ name)
+ hosts = hf.filter_hosts(self.zone_manager, cooked)
+ self.assertEquals(6, len(hosts))
+ just_hosts = [host for host, caps in hosts]
+ just_hosts.sort()
+ self.assertEquals('host05', just_hosts[0])
+ self.assertEquals('host10', just_hosts[5])
+
+ def test_json_filter(self):
+ hf = host_filter.JsonFilter()
+ # filter all hosts that can support 50 ram and 500 disk
+ name, cooked = hf.instance_type_to_filter(self.instance_type)
+ self.assertEquals('nova.scheduler.host_filter.JsonFilter', name)
+ hosts = hf.filter_hosts(self.zone_manager, cooked)
+ self.assertEquals(6, len(hosts))
+ just_hosts = [host for host, caps in hosts]
+ just_hosts.sort()
+ self.assertEquals('host05', just_hosts[0])
+ self.assertEquals('host10', just_hosts[5])
+
+ # Try some custom queries
+
+ raw = ['or',
+ ['and',
+ ['<', '$compute.host_memory_free', 30],
+ ['<', '$compute.disk_available', 300]
+ ],
+ ['and',
+ ['>', '$compute.host_memory_free', 70],
+ ['>', '$compute.disk_available', 700]
+ ]
+ ]
+ cooked = json.dumps(raw)
+ hosts = hf.filter_hosts(self.zone_manager, cooked)
+
+ self.assertEquals(5, len(hosts))
+ just_hosts = [host for host, caps in hosts]
+ just_hosts.sort()
+ for index, host in zip([1, 2, 8, 9, 10], just_hosts):
+ self.assertEquals('host%02d' % index, host)
+
+ raw = ['not',
+ ['=', '$compute.host_memory_free', 30],
+ ]
+ cooked = json.dumps(raw)
+ hosts = hf.filter_hosts(self.zone_manager, cooked)
+
+ self.assertEquals(9, len(hosts))
+ just_hosts = [host for host, caps in hosts]
+ just_hosts.sort()
+ for index, host in zip([1, 2, 4, 5, 6, 7, 8, 9, 10], just_hosts):
+ self.assertEquals('host%02d' % index, host)
+
+ raw = ['in', '$compute.host_memory_free', 20, 40, 60, 80, 100]
+ cooked = json.dumps(raw)
+ hosts = hf.filter_hosts(self.zone_manager, cooked)
+
+ self.assertEquals(5, len(hosts))
+ just_hosts = [host for host, caps in hosts]
+ just_hosts.sort()
+ for index, host in zip([2, 4, 6, 8, 10], just_hosts):
+ self.assertEquals('host%02d' % index, host)
+
+ # Try some bogus input ...
+ raw = ['unknown command', ]
+ cooked = json.dumps(raw)
+ try:
+ hf.filter_hosts(self.zone_manager, cooked)
+ self.fail("Should give KeyError")
+ except KeyError, e:
+ pass
+
+ self.assertTrue(hf.filter_hosts(self.zone_manager, json.dumps([])))
+ self.assertTrue(hf.filter_hosts(self.zone_manager, json.dumps({})))
+ self.assertTrue(hf.filter_hosts(self.zone_manager, json.dumps(
+ ['not', True, False, True, False]
+ )))
+
+ try:
+ hf.filter_hosts(self.zone_manager, json.dumps(
+ 'not', True, False, True, False
+ ))
+ self.fail("Should give KeyError")
+ except KeyError, e:
+ pass
+
+ self.assertFalse(hf.filter_hosts(self.zone_manager,
+ json.dumps(['=', '$foo', 100])))
+ self.assertFalse(hf.filter_hosts(self.zone_manager,
+ json.dumps(['=', '$.....', 100])))
+ self.assertFalse(hf.filter_hosts(self.zone_manager,
+ json.dumps(
+ ['>', ['and', ['or', ['not', ['<', ['>=', ['<=', ['in', ]]]]]]]])))
+
+ self.assertFalse(hf.filter_hosts(self.zone_manager,
+ json.dumps(['=', {}, ['>', '$missing....foo']])))
diff --git a/nova/tests/scheduler/test_least_cost_scheduler.py b/nova/tests/scheduler/test_least_cost_scheduler.py
new file mode 100644
index 000000000..506fa62fb
--- /dev/null
+++ b/nova/tests/scheduler/test_least_cost_scheduler.py
@@ -0,0 +1,144 @@
+# Copyright 2011 OpenStack LLC.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+"""
+Tests For Least Cost Scheduler
+"""
+
+from nova import flags
+from nova import test
+from nova.scheduler import least_cost
+from nova.tests.scheduler import test_zone_aware_scheduler
+
+MB = 1024 * 1024
+FLAGS = flags.FLAGS
+
+
+class FakeHost(object):
+ def __init__(self, host_id, free_ram, io):
+ self.id = host_id
+ self.free_ram = free_ram
+ self.io = io
+
+
+class WeightedSumTestCase(test.TestCase):
+ def test_empty_domain(self):
+ domain = []
+ weighted_fns = []
+ result = least_cost.weighted_sum(domain, weighted_fns)
+ expected = []
+ self.assertEqual(expected, result)
+
+ def test_basic_costing(self):
+ hosts = [
+ FakeHost(1, 512 * MB, 100),
+ FakeHost(2, 256 * MB, 400),
+ FakeHost(3, 512 * MB, 100)
+ ]
+
+ weighted_fns = [
+ (1, lambda h: h.free_ram), # Fill-first, free_ram is a *cost*
+ (2, lambda h: h.io), # Avoid high I/O
+ ]
+
+ costs = least_cost.weighted_sum(
+ domain=hosts, weighted_fns=weighted_fns)
+
+ # Each 256 MB unit of free-ram contributes 0.5 points by way of:
+ # cost = weight * (score/max_score) = 1 * (256/512) = 0.5
+ # Each 100 iops of IO adds 0.5 points by way of:
+ # cost = 2 * (100/400) = 2 * 0.25 = 0.5
+ expected = [1.5, 2.5, 1.5]
+ self.assertEqual(expected, costs)
+
+
+class LeastCostSchedulerTestCase(test.TestCase):
+ def setUp(self):
+ super(LeastCostSchedulerTestCase, self).setUp()
+
+ class FakeZoneManager:
+ pass
+
+ zone_manager = FakeZoneManager()
+
+ states = test_zone_aware_scheduler.fake_zone_manager_service_states(
+ num_hosts=10)
+ zone_manager.service_states = states
+
+ self.sched = least_cost.LeastCostScheduler()
+ self.sched.zone_manager = zone_manager
+
+ def tearDown(self):
+ super(LeastCostSchedulerTestCase, self).tearDown()
+
+ def assertWeights(self, expected, num, request_spec, hosts):
+ weighted = self.sched.weigh_hosts(num, request_spec, hosts)
+ self.assertDictListMatch(weighted, expected, approx_equal=True)
+
+ def test_no_hosts(self):
+ num = 1
+ request_spec = {}
+ hosts = []
+
+ expected = []
+ self.assertWeights(expected, num, request_spec, hosts)
+
+ def test_noop_cost_fn(self):
+ FLAGS.least_cost_scheduler_cost_functions = [
+ 'nova.scheduler.least_cost.noop_cost_fn'
+ ]
+ FLAGS.noop_cost_fn_weight = 1
+
+ num = 1
+ request_spec = {}
+ hosts = self.sched.filter_hosts(num, request_spec)
+
+ expected = [dict(weight=1, hostname=hostname)
+ for hostname, caps in hosts]
+ self.assertWeights(expected, num, request_spec, hosts)
+
+ def test_cost_fn_weights(self):
+ FLAGS.least_cost_scheduler_cost_functions = [
+ 'nova.scheduler.least_cost.noop_cost_fn'
+ ]
+ FLAGS.noop_cost_fn_weight = 2
+
+ num = 1
+ request_spec = {}
+ hosts = self.sched.filter_hosts(num, request_spec)
+
+ expected = [dict(weight=2, hostname=hostname)
+ for hostname, caps in hosts]
+ self.assertWeights(expected, num, request_spec, hosts)
+
+ def test_fill_first_cost_fn(self):
+ FLAGS.least_cost_scheduler_cost_functions = [
+ 'nova.scheduler.least_cost.fill_first_cost_fn'
+ ]
+ FLAGS.fill_first_cost_fn_weight = 1
+
+ num = 1
+ request_spec = {}
+ hosts = self.sched.filter_hosts(num, request_spec)
+
+ expected = []
+ for idx, (hostname, caps) in enumerate(hosts):
+ # Costs are normalized so over 10 hosts, each host with increasing
+ # free ram will cost 1/N more. Since the lowest cost host has some
+ # free ram, we add in the 1/N for the base_cost
+ weight = 0.1 + (0.1 * idx)
+ weight_dict = dict(weight=weight, hostname=hostname)
+ expected.append(weight_dict)
+
+ self.assertWeights(expected, num, request_spec, hosts)
diff --git a/nova/tests/test_scheduler.py b/nova/tests/scheduler/test_scheduler.py
index 54b3f80fb..50b6b52c6 100644
--- a/nova/tests/test_scheduler.py
+++ b/nova/tests/scheduler/test_scheduler.py
@@ -61,7 +61,8 @@ class SchedulerTestCase(test.TestCase):
"""Test case for scheduler"""
def setUp(self):
super(SchedulerTestCase, self).setUp()
- self.flags(scheduler_driver='nova.tests.test_scheduler.TestDriver')
+ driver = 'nova.tests.scheduler.test_scheduler.TestDriver'
+ self.flags(scheduler_driver=driver)
def _create_compute_service(self):
"""Create compute-manager(ComputeNode and Service record)."""
@@ -196,7 +197,7 @@ class ZoneSchedulerTestCase(test.TestCase):
service.topic = 'compute'
service.id = kwargs['id']
service.availability_zone = kwargs['zone']
- service.created_at = datetime.datetime.utcnow()
+ service.created_at = utils.utcnow()
return service
def test_with_two_zones(self):
@@ -290,7 +291,7 @@ class SimpleDriverTestCase(test.TestCase):
dic['host'] = kwargs.get('host', 'dummy')
s_ref = db.service_create(self.context, dic)
if 'created_at' in kwargs.keys() or 'updated_at' in kwargs.keys():
- t = datetime.datetime.utcnow() - datetime.timedelta(0)
+ t = utils.utcnow() - datetime.timedelta(0)
dic['created_at'] = kwargs.get('created_at', t)
dic['updated_at'] = kwargs.get('updated_at', t)
db.service_update(self.context, s_ref['id'], dic)
@@ -401,7 +402,7 @@ class SimpleDriverTestCase(test.TestCase):
FLAGS.compute_manager)
compute1.start()
s1 = db.service_get_by_args(self.context, 'host1', 'nova-compute')
- now = datetime.datetime.utcnow()
+ now = utils.utcnow()
delta = datetime.timedelta(seconds=FLAGS.service_down_time * 2)
past = now - delta
db.service_update(self.context, s1['id'], {'updated_at': past})
@@ -542,7 +543,7 @@ class SimpleDriverTestCase(test.TestCase):
def test_wont_sechedule_if_specified_host_is_down(self):
compute1 = self.start_service('compute', host='host1')
s1 = db.service_get_by_args(self.context, 'host1', 'nova-compute')
- now = datetime.datetime.utcnow()
+ now = utils.utcnow()
delta = datetime.timedelta(seconds=FLAGS.service_down_time * 2)
past = now - delta
db.service_update(self.context, s1['id'], {'updated_at': past})
@@ -692,7 +693,7 @@ class SimpleDriverTestCase(test.TestCase):
dic = {'instance_id': instance_id, 'size': 1}
v_ref = db.volume_create(self.context, {'instance_id': instance_id,
'size': 1})
- t1 = datetime.datetime.utcnow() - datetime.timedelta(1)
+ t1 = utils.utcnow() - datetime.timedelta(1)
dic = {'created_at': t1, 'updated_at': t1, 'binary': 'nova-volume',
'topic': 'volume', 'report_count': 0}
s_ref = db.service_create(self.context, dic)
@@ -709,7 +710,7 @@ class SimpleDriverTestCase(test.TestCase):
"""Confirms src-compute node is alive."""
instance_id = self._create_instance()
i_ref = db.instance_get(self.context, instance_id)
- t = datetime.datetime.utcnow() - datetime.timedelta(10)
+ t = utils.utcnow() - datetime.timedelta(10)
s_ref = self._create_compute_service(created_at=t, updated_at=t,
host=i_ref['host'])
@@ -737,7 +738,7 @@ class SimpleDriverTestCase(test.TestCase):
"""Confirms exception raises in case dest host does not exist."""
instance_id = self._create_instance()
i_ref = db.instance_get(self.context, instance_id)
- t = datetime.datetime.utcnow() - datetime.timedelta(10)
+ t = utils.utcnow() - datetime.timedelta(10)
s_ref = self._create_compute_service(created_at=t, updated_at=t,
host=i_ref['host'])
@@ -796,7 +797,7 @@ class SimpleDriverTestCase(test.TestCase):
# mocks for live_migration_common_check()
instance_id = self._create_instance()
i_ref = db.instance_get(self.context, instance_id)
- t1 = datetime.datetime.utcnow() - datetime.timedelta(10)
+ t1 = utils.utcnow() - datetime.timedelta(10)
s_ref = self._create_compute_service(created_at=t1, updated_at=t1,
host=dest)
diff --git a/nova/tests/test_zone_aware_scheduler.py b/nova/tests/scheduler/test_zone_aware_scheduler.py
index 72b74be20..561fdea94 100644
--- a/nova/tests/test_zone_aware_scheduler.py
+++ b/nova/tests/scheduler/test_zone_aware_scheduler.py
@@ -22,6 +22,37 @@ from nova.scheduler import zone_aware_scheduler
from nova.scheduler import zone_manager
+def _host_caps(multiplier):
+ # Returns host capabilities in the following way:
+ # host1 = memory:free 10 (100max)
+ # disk:available 100 (1000max)
+ # hostN = memory:free 10 + 10N
+ # disk:available 100 + 100N
+ # in other words: hostN has more resources than host0
+ # which means ... don't go above 10 hosts.
+ return {'host_name-description': 'XenServer %s' % multiplier,
+ 'host_hostname': 'xs-%s' % multiplier,
+ 'host_memory_total': 100,
+ 'host_memory_overhead': 10,
+ 'host_memory_free': 10 + multiplier * 10,
+ 'host_memory_free-computed': 10 + multiplier * 10,
+ 'host_other-config': {},
+ 'host_ip_address': '192.168.1.%d' % (100 + multiplier),
+ 'host_cpu_info': {},
+ 'disk_available': 100 + multiplier * 100,
+ 'disk_total': 1000,
+ 'disk_used': 0,
+ 'host_uuid': 'xxx-%d' % multiplier,
+ 'host_name-label': 'xs-%s' % multiplier}
+
+
+def fake_zone_manager_service_states(num_hosts):
+ states = {}
+ for x in xrange(num_hosts):
+ states['host%02d' % (x + 1)] = {'compute': _host_caps(x)}
+ return states
+
+
class FakeZoneAwareScheduler(zone_aware_scheduler.ZoneAwareScheduler):
def filter_hosts(self, num, specs):
# NOTE(sirp): this is returning [(hostname, services)]
diff --git a/nova/tests/test_auth.py b/nova/tests/test_auth.py
index f02dd94b7..7d00bddfe 100644
--- a/nova/tests/test_auth.py
+++ b/nova/tests/test_auth.py
@@ -86,6 +86,7 @@ class _AuthManagerBaseTestCase(test.TestCase):
super(_AuthManagerBaseTestCase, self).setUp()
self.flags(connection_type='fake')
self.manager = manager.AuthManager(new=True)
+ self.manager.mc.cache = {}
def test_create_and_find_user(self):
with user_generator(self.manager):
diff --git a/nova/tests/test_cloud.py b/nova/tests/test_cloud.py
index 55ea6be02..1bf1271c4 100644
--- a/nova/tests/test_cloud.py
+++ b/nova/tests/test_cloud.py
@@ -254,10 +254,10 @@ class CloudTestCase(test.TestCase):
def test_describe_instances(self):
"""Makes sure describe_instances works and filters results."""
inst1 = db.instance_create(self.context, {'reservation_id': 'a',
- 'image_id': 1,
+ 'image_ref': 1,
'host': 'host1'})
inst2 = db.instance_create(self.context, {'reservation_id': 'a',
- 'image_id': 1,
+ 'image_ref': 1,
'host': 'host2'})
comp1 = db.service_create(self.context, {'host': 'host1',
'availability_zone': 'zone1',
@@ -447,7 +447,7 @@ class CloudTestCase(test.TestCase):
def test_terminate_instances(self):
inst1 = db.instance_create(self.context, {'reservation_id': 'a',
- 'image_id': 1,
+ 'image_ref': 1,
'host': 'host1'})
terminate_instances = self.cloud.terminate_instances
# valid instance_id
diff --git a/nova/tests/test_compute.py b/nova/tests/test_compute.py
index 9170837b6..b4ac2dbc4 100644
--- a/nova/tests/test_compute.py
+++ b/nova/tests/test_compute.py
@@ -19,7 +19,6 @@
Tests For Compute
"""
-import datetime
import mox
import stubout
@@ -84,7 +83,7 @@ class ComputeTestCase(test.TestCase):
def _create_instance(self, params={}):
"""Create a test instance"""
inst = {}
- inst['image_id'] = 1
+ inst['image_ref'] = 1
inst['reservation_id'] = 'r-fakeres'
inst['launch_time'] = '10'
inst['user_id'] = self.user.id
@@ -150,7 +149,7 @@ class ComputeTestCase(test.TestCase):
ref = self.compute_api.create(
self.context,
instance_type=instance_types.get_default_instance_type(),
- image_id=None,
+ image_href=None,
security_group=['testgroup'])
try:
self.assertEqual(len(db.security_group_get_by_instance(
@@ -168,7 +167,7 @@ class ComputeTestCase(test.TestCase):
ref = self.compute_api.create(
self.context,
instance_type=instance_types.get_default_instance_type(),
- image_id=None,
+ image_href=None,
security_group=['testgroup'])
try:
db.instance_destroy(self.context, ref[0]['id'])
@@ -184,7 +183,7 @@ class ComputeTestCase(test.TestCase):
ref = self.compute_api.create(
self.context,
instance_type=instance_types.get_default_instance_type(),
- image_id=None,
+ image_href=None,
security_group=['testgroup'])
try:
@@ -217,12 +216,12 @@ class ComputeTestCase(test.TestCase):
instance_ref = db.instance_get(self.context, instance_id)
self.assertEqual(instance_ref['launched_at'], None)
self.assertEqual(instance_ref['deleted_at'], None)
- launch = datetime.datetime.utcnow()
+ launch = utils.utcnow()
self.compute.run_instance(self.context, instance_id)
instance_ref = db.instance_get(self.context, instance_id)
self.assert_(instance_ref['launched_at'] > launch)
self.assertEqual(instance_ref['deleted_at'], None)
- terminate = datetime.datetime.utcnow()
+ terminate = utils.utcnow()
self.compute.terminate_instance(self.context, instance_id)
self.context = self.context.elevated(True)
instance_ref = db.instance_get(self.context, instance_id)
diff --git a/nova/tests/test_console.py b/nova/tests/test_console.py
index 1a9a867ee..831e7670f 100644
--- a/nova/tests/test_console.py
+++ b/nova/tests/test_console.py
@@ -20,8 +20,6 @@
Tests For Console proxy.
"""
-import datetime
-
from nova import context
from nova import db
from nova import exception
diff --git a/nova/tests/test_libvirt.py b/nova/tests/test_libvirt.py
index d306a54f8..687964094 100644
--- a/nova/tests/test_libvirt.py
+++ b/nova/tests/test_libvirt.py
@@ -161,7 +161,7 @@ class LibvirtConnTestCase(test.TestCase):
'vcpus': 2,
'project_id': 'fake',
'bridge': 'br101',
- 'image_id': '123456',
+ 'image_ref': '123456',
'instance_type_id': '5'} # m1.small
def lazy_load_library_exists(self):
diff --git a/nova/tests/test_middleware.py b/nova/tests/test_middleware.py
index 6564a6955..40d117c45 100644
--- a/nova/tests/test_middleware.py
+++ b/nova/tests/test_middleware.py
@@ -16,7 +16,6 @@
# License for the specific language governing permissions and limitations
# under the License.
-import datetime
import webob
import webob.dec
import webob.exc
diff --git a/nova/tests/test_misc.py b/nova/tests/test_misc.py
index cf8f4c05e..c5875a843 100644
--- a/nova/tests/test_misc.py
+++ b/nova/tests/test_misc.py
@@ -21,11 +21,24 @@ import select
from eventlet import greenpool
from eventlet import greenthread
+from nova import exception
from nova import test
from nova import utils
from nova.utils import parse_mailmap, str_dict_replace
+class ExceptionTestCase(test.TestCase):
+ @staticmethod
+ def _raise_exc(exc):
+ raise exc()
+
+ def test_exceptions_raise(self):
+ for name in dir(exception):
+ exc = getattr(exception, name)
+ if isinstance(exc, type):
+ self.assertRaises(exc, self._raise_exc, exc)
+
+
class ProjectTestCase(test.TestCase):
def test_authors_up_to_date(self):
topdir = os.path.normpath(os.path.dirname(__file__) + '/../../')
diff --git a/nova/tests/test_quota.py b/nova/tests/test_quota.py
index 5ee1774ec..b8a023f56 100644
--- a/nova/tests/test_quota.py
+++ b/nova/tests/test_quota.py
@@ -222,7 +222,7 @@ class QuotaTestCase(test.TestCase):
min_count=1,
max_count=1,
instance_type=inst_type,
- image_id=1)
+ image_href=1)
for instance_id in instance_ids:
db.instance_destroy(self.context, instance_id)
@@ -236,7 +236,7 @@ class QuotaTestCase(test.TestCase):
min_count=1,
max_count=1,
instance_type=inst_type,
- image_id=1)
+ image_href=1)
for instance_id in instance_ids:
db.instance_destroy(self.context, instance_id)
@@ -294,7 +294,7 @@ class QuotaTestCase(test.TestCase):
min_count=1,
max_count=1,
instance_type=inst_type,
- image_id='fake',
+ image_href='fake',
metadata=metadata)
def test_default_allowed_injected_files(self):
@@ -340,16 +340,18 @@ class QuotaTestCase(test.TestCase):
self.assertEqual(limit, 23456)
def _create_with_injected_files(self, files):
+ FLAGS.image_service = 'nova.image.fake.FakeImageService'
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=inst_type, image_id='fake',
+ instance_type=inst_type, image_href='3',
injected_files=files)
def test_no_injected_files(self):
+ FLAGS.image_service = 'nova.image.fake.FakeImageService'
api = compute.API(image_service=self.StubImageService())
inst_type = instance_types.get_instance_type_by_name('m1.small')
- api.create(self.context, instance_type=inst_type, image_id='fake')
+ api.create(self.context, instance_type=inst_type, image_href='3')
def test_max_injected_files(self):
files = []
diff --git a/nova/tests/test_vmwareapi.py b/nova/tests/test_vmwareapi.py
index 22b66010a..e5ebd1600 100644
--- a/nova/tests/test_vmwareapi.py
+++ b/nova/tests/test_vmwareapi.py
@@ -55,8 +55,7 @@ class VMWareAPIVMTestCase(test.TestCase):
vmwareapi_fake.reset()
db_fakes.stub_out_db_instance_api(self.stubs)
stubs.set_stubs(self.stubs)
- glance_stubs.stubout_glance_client(self.stubs,
- glance_stubs.FakeGlance)
+ glance_stubs.stubout_glance_client(self.stubs)
self.conn = vmwareapi_conn.get_connection(False)
def _create_instance_in_the_db(self):
@@ -64,7 +63,7 @@ class VMWareAPIVMTestCase(test.TestCase):
'id': 1,
'project_id': self.project.id,
'user_id': self.user.id,
- 'image_id': "1",
+ 'image_ref': "1",
'kernel_id': "1",
'ramdisk_id': "1",
'instance_type': 'm1.large',
diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py
index 9d56c1644..3a175b106 100644
--- a/nova/tests/test_xenapi.py
+++ b/nova/tests/test_xenapi.py
@@ -79,7 +79,7 @@ class XenAPIVolumeTestCase(test.TestCase):
self.values = {'id': 1,
'project_id': 'fake',
'user_id': 'fake',
- 'image_id': 1,
+ 'image_ref': 1,
'kernel_id': 2,
'ramdisk_id': 3,
'instance_type_id': '3', # m1.large
@@ -193,8 +193,7 @@ class XenAPIVMTestCase(test.TestCase):
stubs.stubout_is_vdi_pv(self.stubs)
self.stubs.Set(VMOps, 'reset_network', reset_network)
stubs.stub_out_vm_methods(self.stubs)
- glance_stubs.stubout_glance_client(self.stubs,
- glance_stubs.FakeGlance)
+ glance_stubs.stubout_glance_client(self.stubs)
fake_utils.stub_out_utils_execute(self.stubs)
self.context = context.RequestContext('fake', 'fake', False)
self.conn = xenapi_conn.get_connection(False)
@@ -207,7 +206,7 @@ class XenAPIVMTestCase(test.TestCase):
'id': id,
'project_id': proj,
'user_id': user,
- 'image_id': 1,
+ 'image_ref': 1,
'kernel_id': 2,
'ramdisk_id': 3,
'instance_type_id': '3', # m1.large
@@ -351,14 +350,14 @@ class XenAPIVMTestCase(test.TestCase):
self.assertEquals(self.vm['HVM_boot_params'], {})
self.assertEquals(self.vm['HVM_boot_policy'], '')
- def _test_spawn(self, image_id, kernel_id, ramdisk_id,
+ def _test_spawn(self, image_ref, kernel_id, ramdisk_id,
instance_type_id="3", os_type="linux",
instance_id=1, check_injection=False):
stubs.stubout_loopingcall_start(self.stubs)
values = {'id': instance_id,
'project_id': self.project.id,
'user_id': self.user.id,
- 'image_id': image_id,
+ 'image_ref': image_ref,
'kernel_id': kernel_id,
'ramdisk_id': ramdisk_id,
'instance_type_id': instance_type_id,
@@ -567,7 +566,7 @@ class XenAPIVMTestCase(test.TestCase):
'id': 1,
'project_id': self.project.id,
'user_id': self.user.id,
- 'image_id': 1,
+ 'image_ref': 1,
'kernel_id': 2,
'ramdisk_id': 3,
'instance_type_id': '3', # m1.large
@@ -641,7 +640,7 @@ class XenAPIMigrateInstance(test.TestCase):
self.values = {'id': 1,
'project_id': self.project.id,
'user_id': self.user.id,
- 'image_id': 1,
+ 'image_ref': 1,
'kernel_id': None,
'ramdisk_id': None,
'local_gb': 5,
@@ -652,8 +651,7 @@ class XenAPIMigrateInstance(test.TestCase):
fake_utils.stub_out_utils_execute(self.stubs)
stubs.stub_out_migration_methods(self.stubs)
stubs.stubout_get_this_vm_uuid(self.stubs)
- glance_stubs.stubout_glance_client(self.stubs,
- glance_stubs.FakeGlance)
+ glance_stubs.stubout_glance_client(self.stubs)
def tearDown(self):
super(XenAPIMigrateInstance, self).tearDown()
@@ -679,8 +677,7 @@ class XenAPIDetermineDiskImageTestCase(test.TestCase):
"""Unit tests for code that detects the ImageType."""
def setUp(self):
super(XenAPIDetermineDiskImageTestCase, self).setUp()
- glance_stubs.stubout_glance_client(self.stubs,
- glance_stubs.FakeGlance)
+ glance_stubs.stubout_glance_client(self.stubs)
class FakeInstance(object):
pass
@@ -697,7 +694,7 @@ class XenAPIDetermineDiskImageTestCase(test.TestCase):
def test_instance_disk(self):
"""If a kernel is specified, the image type is DISK (aka machine)."""
FLAGS.xenapi_image_service = 'objectstore'
- self.fake_instance.image_id = glance_stubs.FakeGlance.IMAGE_MACHINE
+ self.fake_instance.image_ref = glance_stubs.FakeGlance.IMAGE_MACHINE
self.fake_instance.kernel_id = glance_stubs.FakeGlance.IMAGE_KERNEL
self.assert_disk_type(vm_utils.ImageType.DISK)
@@ -707,7 +704,7 @@ class XenAPIDetermineDiskImageTestCase(test.TestCase):
DISK_RAW is assumed.
"""
FLAGS.xenapi_image_service = 'objectstore'
- self.fake_instance.image_id = glance_stubs.FakeGlance.IMAGE_RAW
+ self.fake_instance.image_ref = glance_stubs.FakeGlance.IMAGE_RAW
self.fake_instance.kernel_id = None
self.assert_disk_type(vm_utils.ImageType.DISK_RAW)
@@ -717,7 +714,7 @@ class XenAPIDetermineDiskImageTestCase(test.TestCase):
this case will be 'raw'.
"""
FLAGS.xenapi_image_service = 'glance'
- self.fake_instance.image_id = glance_stubs.FakeGlance.IMAGE_RAW
+ self.fake_instance.image_ref = glance_stubs.FakeGlance.IMAGE_RAW
self.fake_instance.kernel_id = None
self.assert_disk_type(vm_utils.ImageType.DISK_RAW)
@@ -727,7 +724,7 @@ class XenAPIDetermineDiskImageTestCase(test.TestCase):
this case will be 'vhd'.
"""
FLAGS.xenapi_image_service = 'glance'
- self.fake_instance.image_id = glance_stubs.FakeGlance.IMAGE_VHD
+ self.fake_instance.image_ref = glance_stubs.FakeGlance.IMAGE_VHD
self.fake_instance.kernel_id = None
self.assert_disk_type(vm_utils.ImageType.DISK_VHD)
diff --git a/nova/tests/vmwareapi/db_fakes.py b/nova/tests/vmwareapi/db_fakes.py
index 0addd5573..764de42d8 100644
--- a/nova/tests/vmwareapi/db_fakes.py
+++ b/nova/tests/vmwareapi/db_fakes.py
@@ -61,7 +61,7 @@ def stub_out_db_instance_api(stubs):
'name': values['name'],
'id': values['id'],
'reservation_id': utils.generate_uid('r'),
- 'image_id': values['image_id'],
+ 'image_ref': values['image_ref'],
'kernel_id': values['kernel_id'],
'ramdisk_id': values['ramdisk_id'],
'state_description': 'scheduling',
diff --git a/nova/utils.py b/nova/utils.py
index 105d73c7c..b5aea54d0 100644
--- a/nova/utils.py
+++ b/nova/utils.py
@@ -299,7 +299,7 @@ def get_my_linklocal(interface):
def utcnow():
- """Overridable version of datetime.datetime.utcnow."""
+ """Overridable version of utils.utcnow."""
if utcnow.override_time:
return utcnow.override_time
return datetime.datetime.utcnow()
diff --git a/nova/virt/hyperv.py b/nova/virt/hyperv.py
index 85ebdd0b1..a2906ed50 100644
--- a/nova/virt/hyperv.py
+++ b/nova/virt/hyperv.py
@@ -151,7 +151,7 @@ class HyperVConnection(driver.ComputeDriver):
base_vhd_filename = os.path.join(FLAGS.instances_path,
instance.name)
vhdfile = "%s.vhd" % (base_vhd_filename)
- images.fetch(instance['image_id'], vhdfile, user, project)
+ images.fetch(instance['image_ref'], vhdfile, user, project)
try:
self._create_vm(instance)
diff --git a/nova/virt/images.py b/nova/virt/images.py
index 02c898fda..de7ac61df 100644
--- a/nova/virt/images.py
+++ b/nova/virt/images.py
@@ -23,6 +23,7 @@ Handling of VM disk images.
from nova import context
from nova import flags
+import nova.image
from nova import log as logging
from nova import utils
@@ -31,12 +32,12 @@ FLAGS = flags.FLAGS
LOG = logging.getLogger('nova.virt.images')
-def fetch(image_id, path, _user, _project):
+def fetch(image_href, path, _user, _project):
# TODO(vish): Improve context handling and add owner and auth data
# when it is added to glance. Right now there is no
# auth checking in glance, so we assume that access was
# checked before we got here.
- image_service = utils.import_object(FLAGS.image_service)
+ (image_service, image_id) = nova.image.get_image_service(image_href)
with open(path, "wb") as image_file:
elevated = context.get_admin_context()
metadata = image_service.get(elevated, image_id, image_file)
diff --git a/nova/virt/libvirt/connection.py b/nova/virt/libvirt/connection.py
index 64beebc85..c491418ae 100644
--- a/nova/virt/libvirt/connection.py
+++ b/nova/virt/libvirt/connection.py
@@ -36,6 +36,7 @@ Supports KVM, LXC, QEMU, UML, and XEN.
"""
+import hashlib
import multiprocessing
import os
import random
@@ -57,6 +58,7 @@ from nova import context
from nova import db
from nova import exception
from nova import flags
+import nova.image
from nova import log as logging
from nova import utils
from nova import vnc
@@ -378,7 +380,7 @@ class LibvirtConnection(driver.ComputeDriver):
virt_dom.detachDevice(xml)
@exception.wrap_exception
- def snapshot(self, instance, image_id):
+ def snapshot(self, instance, image_href):
"""Create snapshot from a running VM instance.
This command only works with qemu 0.14+, the qemu_img flag is
@@ -386,12 +388,15 @@ class LibvirtConnection(driver.ComputeDriver):
to support this command.
"""
- image_service = utils.import_object(FLAGS.image_service)
virt_dom = self._lookup_by_name(instance['name'])
elevated = context.get_admin_context()
- base = image_service.show(elevated, instance['image_id'])
- snapshot = image_service.show(elevated, image_id)
+ (image_service, image_id) = nova.image.get_image_service(
+ instance['image_ref'])
+ base = image_service.show(elevated, image_id)
+ (snapshot_image_service, snapshot_image_id) = \
+ nova.image.get_image_service(image_href)
+ snapshot = snapshot_image_service.show(elevated, snapshot_image_id)
metadata = {'disk_format': base['disk_format'],
'container_format': base['container_format'],
@@ -441,7 +446,7 @@ class LibvirtConnection(driver.ComputeDriver):
# Upload that image to the image service
with open(out_path) as image_file:
image_service.update(elevated,
- image_id,
+ image_href,
metadata,
image_file)
@@ -787,7 +792,7 @@ class LibvirtConnection(driver.ComputeDriver):
project = manager.AuthManager().get_project(inst['project_id'])
if not disk_images:
- disk_images = {'image_id': inst['image_id'],
+ disk_images = {'image_id': inst['image_ref'],
'kernel_id': inst['kernel_id'],
'ramdisk_id': inst['ramdisk_id']}
@@ -808,7 +813,7 @@ class LibvirtConnection(driver.ComputeDriver):
user=user,
project=project)
- root_fname = '%08x' % int(disk_images['image_id'])
+ root_fname = hashlib.sha1(disk_images['image_id']).hexdigest()
size = FLAGS.minimum_root_size
inst_type_id = inst['instance_type_id']
@@ -883,7 +888,7 @@ class LibvirtConnection(driver.ComputeDriver):
if key or net:
inst_name = inst['name']
- img_id = inst.image_id
+ img_id = inst.image_ref
if key:
LOG.info(_('instance %(inst_name)s: injecting key into'
' image %(img_id)s') % locals())
diff --git a/nova/virt/libvirt/firewall.py b/nova/virt/libvirt/firewall.py
index 7e00662cd..12727f2b1 100644
--- a/nova/virt/libvirt/firewall.py
+++ b/nova/virt/libvirt/firewall.py
@@ -195,7 +195,7 @@ class NWFilterFirewall(FirewallDriver):
logging.info('ensuring static filters')
self._ensure_static_filters()
- if instance['image_id'] == str(FLAGS.vpn_image_id):
+ if instance['image_ref'] == str(FLAGS.vpn_image_id):
base_filter = 'nova-vpn'
else:
base_filter = 'nova-base'
@@ -336,7 +336,7 @@ class NWFilterFirewall(FirewallDriver):
def _create_network_filters(self, instance, network_info,
instance_secgroup_filter_name):
- if instance['image_id'] == str(FLAGS.vpn_image_id):
+ if instance['image_ref'] == str(FLAGS.vpn_image_id):
base_filter = 'nova-vpn'
else:
base_filter = 'nova-base'
diff --git a/nova/virt/vmwareapi/vmops.py b/nova/virt/vmwareapi/vmops.py
index 4e54dbf69..94d9e6226 100644
--- a/nova/virt/vmwareapi/vmops.py
+++ b/nova/virt/vmwareapi/vmops.py
@@ -150,7 +150,7 @@ class VMWareVMOps(object):
"""
image_size, image_properties = \
vmware_images.get_vmdk_size_and_properties(
- instance.image_id, instance)
+ instance.image_ref, instance)
vmdk_file_size_in_kb = int(image_size) / 1024
os_type = image_properties.get("vmware_ostype", "otherGuest")
adapter_type = image_properties.get("vmware_adaptertype",
@@ -265,23 +265,23 @@ class VMWareVMOps(object):
def _fetch_image_on_esx_datastore():
"""Fetch image from Glance to ESX datastore."""
- LOG.debug(_("Downloading image file data %(image_id)s to the ESX "
+ LOG.debug(_("Downloading image file data %(image_ref)s to the ESX "
"data store %(data_store_name)s") %
- ({'image_id': instance.image_id,
+ ({'image_ref': instance.image_ref,
'data_store_name': data_store_name}))
# Upload the -flat.vmdk file whose meta-data file we just created
# above
vmware_images.fetch_image(
- instance.image_id,
+ instance.image_ref,
instance,
host=self._session._host_ip,
data_center_name=self._get_datacenter_name_and_ref()[1],
datastore_name=data_store_name,
cookies=cookies,
file_path=flat_uploaded_vmdk_name)
- LOG.debug(_("Downloaded image file data %(image_id)s to the ESX "
+ LOG.debug(_("Downloaded image file data %(image_ref)s to the ESX "
"data store %(data_store_name)s") %
- ({'image_id': instance.image_id,
+ ({'image_ref': instance.image_ref,
'data_store_name': data_store_name}))
_fetch_image_on_esx_datastore()
diff --git a/nova/virt/vmwareapi/vmware_images.py b/nova/virt/vmwareapi/vmware_images.py
index 50c6baedf..48edc5384 100644
--- a/nova/virt/vmwareapi/vmware_images.py
+++ b/nova/virt/vmwareapi/vmware_images.py
@@ -18,10 +18,9 @@
Utility functions for Image transfer.
"""
-from glance import client
-
from nova import exception
from nova import flags
+import nova.image
from nova import log as logging
from nova.virt.vmwareapi import io_util
from nova.virt.vmwareapi import read_write_util
@@ -117,8 +116,8 @@ def upload_image(image, instance, **kwargs):
def _get_glance_image(image, instance, **kwargs):
"""Download image from the glance image server."""
LOG.debug(_("Downloading image %s from glance image server") % image)
- glance_client = client.Client(FLAGS.glance_host, FLAGS.glance_port)
- metadata, read_iter = glance_client.get_image(image)
+ (glance_client, image_id) = nova.image.get_glance_client(image)
+ metadata, read_iter = glance_client.get_image(image_id)
read_file_handle = read_write_util.GlanceFileRead(read_iter)
file_size = int(metadata['size'])
write_file_handle = read_write_util.VMWareHTTPWriteFile(
@@ -153,7 +152,7 @@ def _put_glance_image(image, instance, **kwargs):
kwargs.get("cookies"),
kwargs.get("file_path"))
file_size = read_file_handle.get_size()
- glance_client = client.Client(FLAGS.glance_host, FLAGS.glance_port)
+ (glance_client, image_id) = nova.image.get_glance_client(image)
# The properties and other fields that we need to set for the image.
image_metadata = {"is_public": True,
"disk_format": "vmdk",
@@ -165,7 +164,7 @@ def _put_glance_image(image, instance, **kwargs):
"vmware_image_version":
kwargs.get("image_version")}}
start_transfer(read_file_handle, file_size, glance_client=glance_client,
- image_id=image, image_meta=image_metadata)
+ image_id=image_id, image_meta=image_metadata)
LOG.debug(_("Uploaded image %s to the Glance image server") % image)
@@ -188,9 +187,8 @@ def get_vmdk_size_and_properties(image, instance):
LOG.debug(_("Getting image size for the image %s") % image)
if FLAGS.image_service == "nova.image.glance.GlanceImageService":
- glance_client = client.Client(FLAGS.glance_host,
- FLAGS.glance_port)
- meta_data = glance_client.get_image_meta(image)
+ (glance_client, image_id) = nova.image.get_glance_client(image)
+ meta_data = glance_client.get_image_meta(image_id)
size, properties = meta_data["size"], meta_data["properties"]
elif FLAGS.image_service == "nova.image.s3.S3ImageService":
raise NotImplementedError
diff --git a/nova/virt/xenapi/fake.py b/nova/virt/xenapi/fake.py
index 76988b172..165888cb2 100644
--- a/nova/virt/xenapi/fake.py
+++ b/nova/virt/xenapi/fake.py
@@ -51,13 +51,13 @@ A fake XenAPI SDK.
"""
-import datetime
import uuid
from pprint import pformat
from nova import exception
from nova import log as logging
+from nova import utils
_CLASSES = ['host', 'network', 'session', 'SR', 'VBD',
@@ -540,7 +540,7 @@ class SessionBase(object):
except Failure, exc:
task['error_info'] = exc.details
task['status'] = 'failed'
- task['finished'] = datetime.datetime.now()
+ task['finished'] = utils.utcnow()
return task_ref
def _check_session(self, params):
diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py
index 4a2ae238b..98668e6ae 100644
--- a/nova/virt/xenapi/vm_utils.py
+++ b/nova/virt/xenapi/vm_utils.py
@@ -32,6 +32,7 @@ from xml.dom import minidom
import glance.client
from nova import exception
from nova import flags
+import nova.image
from nova import log as logging
from nova import utils
from nova.auth.manager import AuthManager
@@ -455,8 +456,8 @@ class VMHelper(HelperBase):
# DISK restores
sr_ref = safe_find_sr(session)
- client = glance.client.Client(FLAGS.glance_host, FLAGS.glance_port)
- meta, image_file = client.get_image(image)
+ glance_client, image_id = nova.image.get_glance_client(image)
+ meta, image_file = glance_client.get_image(image_id)
virtual_size = int(meta['size'])
vdi_size = virtual_size
LOG.debug(_("Size for image %(image)s:%(virtual_size)d") % locals())
@@ -515,10 +516,10 @@ class VMHelper(HelperBase):
ImageType.DISK_RAW: 'DISK_RAW',
ImageType.DISK_VHD: 'DISK_VHD'}
disk_format = pretty_format[image_type]
- image_id = instance.image_id
+ image_ref = instance.image_ref
instance_id = instance.id
LOG.debug(_("Detected %(disk_format)s format for image "
- "%(image_id)s, instance %(instance_id)s") % locals())
+ "%(image_ref)s, instance %(instance_id)s") % locals())
def determine_from_glance():
glance_disk_format2nova_type = {
@@ -527,8 +528,9 @@ class VMHelper(HelperBase):
'ari': ImageType.KERNEL_RAMDISK,
'raw': ImageType.DISK_RAW,
'vhd': ImageType.DISK_VHD}
- client = glance.client.Client(FLAGS.glance_host, FLAGS.glance_port)
- meta = client.get_image_meta(instance.image_id)
+ image_ref = instance.image_ref
+ glance_client, image_id = nova.image.get_glance_client(image_ref)
+ meta = glance_client.get_image_meta(image_id)
disk_format = meta['disk_format']
try:
return glance_disk_format2nova_type[disk_format]
diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py
index 2b3fb6a39..32dae97c2 100644
--- a/nova/virt/xenapi/vmops.py
+++ b/nova/virt/xenapi/vmops.py
@@ -111,7 +111,7 @@ class VMOps(object):
project = AuthManager().get_project(instance.project_id)
disk_image_type = VMHelper.determine_disk_image_type(instance)
vdis = VMHelper.fetch_image(self._session,
- instance.id, instance.image_id, user, project,
+ instance.id, instance.image_ref, user, project,
disk_image_type)
return vdis
diff --git a/nova/volume/api.py b/nova/volume/api.py
index 5804955f7..b07f2e94b 100644
--- a/nova/volume/api.py
+++ b/nova/volume/api.py
@@ -20,14 +20,13 @@
Handles all requests relating to volumes.
"""
-import datetime
-from nova import db
from nova import exception
from nova import flags
from nova import log as logging
from nova import quota
from nova import rpc
+from nova import utils
from nova.db import base
FLAGS = flags.FLAGS
@@ -78,7 +77,7 @@ class API(base.Base):
volume = self.get(context, volume_id)
if volume['status'] != "available":
raise exception.ApiError(_("Volume status must be available"))
- now = datetime.datetime.utcnow()
+ now = utils.utcnow()
self.db.volume_update(context, volume_id, {'status': 'deleting',
'terminated_at': now})
host = volume['host']
diff --git a/nova/volume/manager.py b/nova/volume/manager.py
index ff53f0701..798bd379a 100644
--- a/nova/volume/manager.py
+++ b/nova/volume/manager.py
@@ -42,8 +42,6 @@ intact.
"""
-import datetime
-
from nova import context
from nova import exception
@@ -127,7 +125,7 @@ class VolumeManager(manager.SchedulerDependentManager):
volume_ref['id'], {'status': 'error'})
raise
- now = datetime.datetime.utcnow()
+ now = utils.utcnow()
self.db.volume_update(context,
volume_ref['id'], {'status': 'available',
'launched_at': now})