summaryrefslogtreecommitdiffstats
path: root/nova
diff options
context:
space:
mode:
authorKen Pepple <ken.pepple@gmail.com>2011-04-21 10:29:11 -0700
committerKen Pepple <ken.pepple@gmail.com>2011-04-21 10:29:11 -0700
commitf205dcf659697adaae0d85a042ea2ea7ffe5c1c7 (patch)
treeb2da90f0cb240450bc30b9d6ebc3c703145b9bb6 /nova
parentdfcdafde2dc202aa3325cd1cea8d809f56fdde57 (diff)
parentc796920198305e101c75bcbf4e027ba9e81975d7 (diff)
downloadnova-f205dcf659697adaae0d85a042ea2ea7ffe5c1c7.tar.gz
nova-f205dcf659697adaae0d85a042ea2ea7ffe5c1c7.tar.xz
nova-f205dcf659697adaae0d85a042ea2ea7ffe5c1c7.zip
rebase trunk
Diffstat (limited to 'nova')
-rw-r--r--nova/CA/openssl.cnf.tmpl6
-rw-r--r--nova/api/__init__.py2
-rw-r--r--nova/api/direct.py96
-rw-r--r--nova/api/ec2/apirequest.py2
-rw-r--r--nova/api/ec2/cloud.py84
-rw-r--r--nova/api/openstack/__init__.py6
-rw-r--r--nova/api/openstack/accounts.py5
-rw-r--r--nova/api/openstack/auth.py20
-rw-r--r--nova/api/openstack/backup_schedules.py4
-rw-r--r--nova/api/openstack/common.py23
-rw-r--r--nova/api/openstack/consoles.py4
-rw-r--r--nova/api/openstack/contrib/volumes.py3
-rw-r--r--nova/api/openstack/extensions.py10
-rw-r--r--nova/api/openstack/faults.py7
-rw-r--r--nova/api/openstack/flavors.py7
-rw-r--r--nova/api/openstack/image_metadata.py15
-rw-r--r--nova/api/openstack/images.py8
-rw-r--r--nova/api/openstack/ips.py72
-rw-r--r--nova/api/openstack/limits.py4
-rw-r--r--nova/api/openstack/server_metadata.py32
-rw-r--r--nova/api/openstack/servers.py67
-rw-r--r--nova/api/openstack/shared_ip_groups.py4
-rw-r--r--nova/api/openstack/users.py3
-rw-r--r--nova/api/openstack/views/addresses.py10
-rw-r--r--nova/api/openstack/views/images.py10
-rw-r--r--nova/api/openstack/views/servers.py6
-rw-r--r--nova/api/openstack/zones.py6
-rw-r--r--nova/auth/dbdriver.py2
-rw-r--r--nova/auth/manager.py8
-rw-r--r--nova/compute/api.py192
-rw-r--r--nova/compute/instance_types.py97
-rw-r--r--nova/compute/manager.py263
-rw-r--r--nova/compute/monitor.py4
-rw-r--r--nova/console/api.py23
-rw-r--r--nova/console/fake.py22
-rw-r--r--nova/console/manager.py17
-rw-r--r--nova/console/vmrc.py48
-rw-r--r--nova/console/vmrc_manager.py79
-rw-r--r--nova/console/xvp.py48
-rw-r--r--nova/context.py10
-rw-r--r--nova/crypto.py65
-rw-r--r--nova/db/api.py64
-rw-r--r--nova/db/base.py8
-rw-r--r--nova/db/migration.py2
-rw-r--r--nova/db/sqlalchemy/api.py29
-rw-r--r--nova/db/sqlalchemy/migrate_repo/versions/014_add_instance_type_id_to_instances.py84
-rw-r--r--nova/db/sqlalchemy/models.py8
-rw-r--r--nova/exception.py25
-rw-r--r--nova/fakememcache.py4
-rw-r--r--nova/flags.py16
-rw-r--r--nova/image/fake.py16
-rw-r--r--nova/image/glance.py104
-rw-r--r--nova/image/local.py27
-rw-r--r--nova/image/s3.py86
-rw-r--r--nova/image/service.py94
-rw-r--r--nova/log.py51
-rw-r--r--nova/manager.py34
-rw-r--r--nova/network/linux_net.py11
-rw-r--r--nova/network/manager.py1
-rw-r--r--nova/network/xenapi_net.py2
-rw-r--r--nova/quota.py23
-rw-r--r--nova/rpc.py152
-rw-r--r--nova/service.py83
-rw-r--r--nova/test.py50
-rw-r--r--nova/tests/api/openstack/test_api.py8
-rw-r--r--nova/tests/api/openstack/test_faults.py9
-rw-r--r--nova/tests/api/openstack/test_image_metadata.py47
-rw-r--r--nova/tests/api/openstack/test_images.py19
-rw-r--r--nova/tests/api/openstack/test_limits.py16
-rw-r--r--nova/tests/api/openstack/test_server_metadata.py62
-rw-r--r--nova/tests/api/openstack/test_servers.py224
-rw-r--r--nova/tests/api/openstack/test_versions.py6
-rw-r--r--nova/tests/db/fakes.py22
-rw-r--r--nova/tests/image/test_glance.py8
-rw-r--r--nova/tests/integrated/test_servers.py88
-rw-r--r--nova/tests/integrated/test_xml.py56
-rw-r--r--nova/tests/test_cloud.py45
-rw-r--r--nova/tests/test_compute.py21
-rw-r--r--nova/tests/test_console.py2
-rw-r--r--nova/tests/test_instance_types.py6
-rw-r--r--nova/tests/test_quota.py17
-rw-r--r--nova/tests/test_scheduler.py6
-rw-r--r--nova/tests/test_virt.py5
-rw-r--r--nova/tests/test_volume.py2
-rw-r--r--nova/tests/test_xenapi.py18
-rw-r--r--nova/utils.py149
-rw-r--r--nova/version.py8
-rw-r--r--nova/virt/libvirt_conn.py261
-rw-r--r--nova/virt/vmwareapi/vim.py1
-rw-r--r--nova/virt/vmwareapi_conn.py4
-rw-r--r--nova/virt/xenapi/fake.py2
-rw-r--r--nova/virt/xenapi/vm_utils.py36
-rw-r--r--nova/virt/xenapi/vmops.py53
-rw-r--r--nova/virt/xenapi_conn.py9
-rw-r--r--nova/volume/driver.py12
-rw-r--r--nova/wsgi.py197
96 files changed, 2394 insertions, 1393 deletions
diff --git a/nova/CA/openssl.cnf.tmpl b/nova/CA/openssl.cnf.tmpl
index dd81f1c2b..b80fadf40 100644
--- a/nova/CA/openssl.cnf.tmpl
+++ b/nova/CA/openssl.cnf.tmpl
@@ -41,9 +41,13 @@ nameopt = default_ca
certopt = default_ca
policy = policy_match
+# NOTE(dprince): stateOrProvinceName must be 'supplied' or 'optional' to
+# work around a stateOrProvince printable string UTF8 mismatch on
+# RHEL 6 and Fedora 14 (using openssl-1.0.0-4.el6.x86_64 or
+# openssl-1.0.0d-1.fc14.x86_64)
[ policy_match ]
countryName = match
-stateOrProvinceName = match
+stateOrProvinceName = supplied
organizationName = optional
organizationalUnitName = optional
commonName = supplied
diff --git a/nova/api/__init__.py b/nova/api/__init__.py
index 0fedbbfad..747015af5 100644
--- a/nova/api/__init__.py
+++ b/nova/api/__init__.py
@@ -15,5 +15,3 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
-
-"""No-op __init__ for directory full of api goodies."""
diff --git a/nova/api/direct.py b/nova/api/direct.py
index e5f33cee4..8ceae299c 100644
--- a/nova/api/direct.py
+++ b/nova/api/direct.py
@@ -44,14 +44,33 @@ from nova import utils
from nova import wsgi
+# Global storage for registering modules.
ROUTES = {}
def register_service(path, handle):
+ """Register a service handle at a given path.
+
+ Services registered in this way will be made available to any instances of
+ nova.api.direct.Router.
+
+ :param path: `routes` path, can be a basic string like "/path"
+ :param handle: an object whose methods will be made available via the api
+
+ """
ROUTES[path] = handle
class Router(wsgi.Router):
+ """A simple WSGI router configured via `register_service`.
+
+ This is a quick way to attach multiple services to a given endpoint.
+ It will automatically load the routes registered in the `ROUTES` global.
+
+ TODO(termie): provide a paste-deploy version of this.
+
+ """
+
def __init__(self, mapper=None):
if mapper is None:
mapper = routes.Mapper()
@@ -66,6 +85,24 @@ class Router(wsgi.Router):
class DelegatedAuthMiddleware(wsgi.Middleware):
+ """A simple and naive authentication middleware.
+
+ Designed mostly to provide basic support for alternative authentication
+ schemes, this middleware only desires the identity of the user and will
+ generate the appropriate nova.context.RequestContext for the rest of the
+ application. This allows any middleware above it in the stack to
+ authenticate however it would like while only needing to conform to a
+ minimal interface.
+
+ Expects two headers to determine identity:
+ - X-OpenStack-User
+ - X-OpenStack-Project
+
+ This middleware is tied to identity management and will need to be kept
+ in sync with any changes to the way identity is dealt with internally.
+
+ """
+
def process_request(self, request):
os_user = request.headers['X-OpenStack-User']
os_project = request.headers['X-OpenStack-Project']
@@ -74,6 +111,20 @@ class DelegatedAuthMiddleware(wsgi.Middleware):
class JsonParamsMiddleware(wsgi.Middleware):
+ """Middleware to allow method arguments to be passed as serialized JSON.
+
+ Accepting arguments as JSON is useful for accepting data that may be more
+ complex than simple primitives.
+
+ In this case we accept it as urlencoded data under the key 'json' as in
+ json=<urlencoded_json> but this could be extended to accept raw JSON
+ in the POST body.
+
+ Filters out the parameters `self`, `context` and anything beginning with
+ an underscore.
+
+ """
+
def process_request(self, request):
if 'json' not in request.params:
return
@@ -92,6 +143,13 @@ class JsonParamsMiddleware(wsgi.Middleware):
class PostParamsMiddleware(wsgi.Middleware):
+ """Middleware to allow method arguments to be passed as POST parameters.
+
+ Filters out the parameters `self`, `context` and anything beginning with
+ an underscore.
+
+ """
+
def process_request(self, request):
params_parsed = request.params
params = {}
@@ -106,12 +164,21 @@ class PostParamsMiddleware(wsgi.Middleware):
class Reflection(object):
- """Reflection methods to list available methods."""
+ """Reflection methods to list available methods.
+
+ This is an object that expects to be registered via register_service.
+ These methods allow the endpoint to be self-describing. They introspect
+ the exposed methods and provide call signatures and documentation for
+ them allowing quick experimentation.
+
+ """
+
def __init__(self):
self._methods = {}
self._controllers = {}
def _gather_methods(self):
+ """Introspect available methods and generate documentation for them."""
methods = {}
controllers = {}
for route, handler in ROUTES.iteritems():
@@ -185,6 +252,16 @@ class Reflection(object):
class ServiceWrapper(wsgi.Controller):
+ """Wrapper to dynamically povide a WSGI controller for arbitrary objects.
+
+ With lightweight introspection allows public methods on the object to
+ be accesed via simple WSGI routing and parameters and serializes the
+ return values.
+
+ Automatically used be nova.api.direct.Router to wrap registered instances.
+
+ """
+
def __init__(self, service_handle):
self.service_handle = service_handle
@@ -206,10 +283,14 @@ class ServiceWrapper(wsgi.Controller):
# NOTE(vish): make sure we have no unicode keys for py2.6.
params = dict([(str(k), v) for (k, v) in params.iteritems()])
result = method(context, **params)
+
if result is None or type(result) is str or type(result) is unicode:
return result
+
try:
- return self._serialize(result, req.best_match_content_type())
+ content_type = req.best_match_content_type()
+ default_xmlns = self.get_default_xmlns(req)
+ return self._serialize(result, content_type, default_xmlns)
except:
raise exception.Error("returned non-serializable type: %s"
% result)
@@ -256,7 +337,16 @@ class Limited(object):
class Proxy(object):
- """Pretend a Direct API endpoint is an object."""
+ """Pretend a Direct API endpoint is an object.
+
+ This is mostly useful in testing at the moment though it should be easily
+ extendable to provide a basic API library functionality.
+
+ In testing we use this to stub out internal objects to verify that results
+ from the API are serializable.
+
+ """
+
def __init__(self, app, prefix=None):
self.app = app
self.prefix = prefix
diff --git a/nova/api/ec2/apirequest.py b/nova/api/ec2/apirequest.py
index d7ad08d2f..6672e60bb 100644
--- a/nova/api/ec2/apirequest.py
+++ b/nova/api/ec2/apirequest.py
@@ -196,7 +196,7 @@ class APIRequest(object):
elif isinstance(data, datetime.datetime):
data_el.appendChild(
xml.createTextNode(_database_to_isoformat(data)))
- elif data != None:
+ elif data is not None:
data_el.appendChild(xml.createTextNode(str(data)))
return data_el
diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py
index 99520b302..4785d812a 100644
--- a/nova/api/ec2/cloud.py
+++ b/nova/api/ec2/cloud.py
@@ -110,7 +110,8 @@ class CloudController(object):
'genrootca.sh')
start = os.getcwd()
- os.makedirs(FLAGS.ca_path)
+ if not os.path.exists(FLAGS.ca_path):
+ os.makedirs(FLAGS.ca_path)
os.chdir(FLAGS.ca_path)
# TODO(vish): Do this with M2Crypto instead
utils.runthis(_("Generating root CA: %s"), "sh", genrootca_sh_path)
@@ -141,6 +142,11 @@ class CloudController(object):
instance_ref = self.compute_api.get_all(ctxt, fixed_ip=address)
if instance_ref is None:
return None
+
+ # This ensures that all attributes of the instance
+ # are populated.
+ instance_ref = db.instance_get(ctxt, instance_ref['id'])
+
mpi = self._get_mpi_data(ctxt, instance_ref['project_id'])
if instance_ref['key_name']:
keys = {'0': {'_name': instance_ref['key_name'],
@@ -153,7 +159,7 @@ class CloudController(object):
floating_ip = db.instance_get_floating_address(ctxt,
instance_ref['id'])
ec2_id = ec2utils.id_to_ec2_id(instance_ref['id'])
- image_ec2_id = self._image_ec2_id(instance_ref['image_id'], 'machine')
+ image_ec2_id = self._image_ec2_id(instance_ref['image_id'], 'ami')
data = {
'user-data': base64.b64decode(instance_ref['user_data']),
'meta-data': {
@@ -181,9 +187,9 @@ class CloudController(object):
'mpi': mpi}}
for image_type in ['kernel', 'ramdisk']:
- if '%s_id' % image_type in instance_ref:
+ if instance_ref.get('%s_id' % image_type):
ec2_id = self._image_ec2_id(instance_ref['%s_id' % image_type],
- image_type)
+ self._image_type(image_type))
data['meta-data']['%s-id' % image_type] = ec2_id
if False: # TODO(vish): store ancestor ids
@@ -436,7 +442,7 @@ class CloudController(object):
group_name)
criteria = self._revoke_rule_args_to_dict(context, **kwargs)
- if criteria == None:
+ if criteria is None:
raise exception.ApiError(_("Not enough parameters to build a "
"valid rule."))
@@ -658,7 +664,7 @@ class CloudController(object):
'volumeId': ec2utils.id_to_ec2_id(volume_id, 'vol-%08x')}
def _convert_to_set(self, lst, label):
- if lst == None or lst == []:
+ if lst is None or lst == []:
return None
if not isinstance(lst, list):
lst = [lst]
@@ -729,7 +735,10 @@ class CloudController(object):
instance['project_id'],
instance['host'])
i['productCodesSet'] = self._convert_to_set([], 'product_codes')
- i['instanceType'] = instance['instance_type']
+ if instance['instance_type']:
+ i['instanceType'] = instance['instance_type'].get('name')
+ else:
+ i['instanceType'] = None
i['launchTime'] = instance['created_at']
i['amiLaunchIndex'] = instance['launch_index']
i['displayName'] = instance['display_name']
@@ -784,7 +793,7 @@ class CloudController(object):
def allocate_address(self, context, **kwargs):
LOG.audit(_("Allocate address"), context=context)
public_ip = self.network_api.allocate_floating_ip(context)
- return {'addressSet': [{'publicIp': public_ip}]}
+ return {'publicIp': public_ip}
def release_address(self, context, public_ip, **kwargs):
LOG.audit(_("Release address %s"), public_ip, context=context)
@@ -814,7 +823,7 @@ class CloudController(object):
ramdisk = self._get_image(context, kwargs['ramdisk_id'])
kwargs['ramdisk_id'] = ramdisk['id']
instances = self.compute_api.create(context,
- instance_type=instance_types.get_by_type(
+ instance_type=instance_types.get_instance_type_by_name(
kwargs.get('instance_type', None)),
image_id=self._get_image(context, kwargs['image_id'])['id'],
min_count=int(kwargs.get('min_count', max_count)),
@@ -871,13 +880,27 @@ class CloudController(object):
self.compute_api.update(context, instance_id=instance_id, **kwargs)
return True
- _type_prefix_map = {'machine': 'ami',
- 'kernel': 'aki',
- 'ramdisk': 'ari'}
+ @staticmethod
+ def _image_type(image_type):
+ """Converts to a three letter image type.
- def _image_ec2_id(self, image_id, image_type='machine'):
- prefix = self._type_prefix_map[image_type]
- template = prefix + '-%08x'
+ aki, kernel => aki
+ ari, ramdisk => ari
+ anything else => ami
+
+ """
+ if image_type == 'kernel':
+ return 'aki'
+ if image_type == 'ramdisk':
+ return 'ari'
+ if image_type not in ['aki', 'ari']:
+ return 'ami'
+ return image_type
+
+ @staticmethod
+ def _image_ec2_id(image_id, image_type='ami'):
+ """Returns image ec2_id using id and three letter type."""
+ template = image_type + '-%08x'
return ec2utils.id_to_ec2_id(int(image_id), template=template)
def _get_image(self, context, ec2_id):
@@ -885,32 +908,42 @@ class CloudController(object):
internal_id = ec2utils.ec2_id_to_id(ec2_id)
return self.image_service.show(context, internal_id)
except exception.NotFound:
- return self.image_service.show_by_name(context, ec2_id)
+ try:
+ return self.image_service.show_by_name(context, ec2_id)
+ except exception.NotFound:
+ raise exception.NotFound(_('Image %s not found') % ec2_id)
def _format_image(self, image):
"""Convert from format defined by BaseImageService to S3 format."""
i = {}
- image_type = image['properties'].get('type')
+ image_type = self._image_type(image.get('container_format'))
ec2_id = self._image_ec2_id(image.get('id'), image_type)
name = image.get('name')
i['imageId'] = ec2_id
kernel_id = image['properties'].get('kernel_id')
if kernel_id:
- i['kernelId'] = self._image_ec2_id(kernel_id, 'kernel')
+ i['kernelId'] = self._image_ec2_id(kernel_id, 'aki')
ramdisk_id = image['properties'].get('ramdisk_id')
if ramdisk_id:
- i['ramdiskId'] = self._image_ec2_id(ramdisk_id, 'ramdisk')
+ i['ramdiskId'] = self._image_ec2_id(ramdisk_id, 'ari')
i['imageOwnerId'] = image['properties'].get('owner_id')
if name:
i['imageLocation'] = "%s (%s)" % (image['properties'].
get('image_location'), name)
else:
i['imageLocation'] = image['properties'].get('image_location')
- i['imageState'] = image['properties'].get('image_state')
+ # NOTE(vish): fallback status if image_state isn't set
+ state = image.get('status')
+ if state == 'active':
+ state = 'available'
+ i['imageState'] = image['properties'].get('image_state', state)
i['displayName'] = name
i['description'] = image.get('description')
- i['imageType'] = image_type
- i['isPublic'] = str(image['properties'].get('is_public', '')) == 'True'
+ display_mapping = {'aki': 'kernel',
+ 'ari': 'ramdisk',
+ 'ami': 'machine'}
+ i['imageType'] = display_mapping.get(image_type)
+ i['isPublic'] = image.get('is_public') == True
i['architecture'] = image['properties'].get('architecture')
return i
@@ -942,8 +975,9 @@ class CloudController(object):
image_location = kwargs['name']
metadata = {'properties': {'image_location': image_location}}
image = self.image_service.create(context, metadata)
+ image_type = self._image_type(image.get('container_format'))
image_id = self._image_ec2_id(image['id'],
- image['properties']['type'])
+ image_type)
msg = _("Registered image %(image_location)s with"
" id %(image_id)s") % locals()
LOG.audit(msg, context=context)
@@ -958,7 +992,7 @@ class CloudController(object):
except exception.NotFound:
raise exception.NotFound(_('Image %s not found') % image_id)
result = {'imageId': image_id, 'launchPermission': []}
- if image['properties']['is_public']:
+ if image['is_public']:
result['launchPermission'].append({'group': 'all'})
return result
@@ -983,7 +1017,7 @@ class CloudController(object):
internal_id = image['id']
del(image['id'])
- image['properties']['is_public'] = (operation_type == 'add')
+ image['is_public'] = (operation_type == 'add')
return self.image_service.update(context, internal_id, image)
def update_image(self, context, image_id, **kwargs):
diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py
index 7545eb0c9..5e76a06f7 100644
--- a/nova/api/openstack/__init__.py
+++ b/nova/api/openstack/__init__.py
@@ -34,6 +34,7 @@ from nova.api.openstack import consoles
from nova.api.openstack import flavors
from nova.api.openstack import images
from nova.api.openstack import image_metadata
+from nova.api.openstack import ips
from nova.api.openstack import limits
from nova.api.openstack import servers
from nova.api.openstack import server_metadata
@@ -144,6 +145,11 @@ class APIRouterV10(APIRouter):
parent_resource=dict(member_name='server',
collection_name='servers'))
+ mapper.resource("ip", "ips", controller=ips.Controller(),
+ collection=dict(public='GET', private='GET'),
+ parent_resource=dict(member_name='server',
+ collection_name='servers'))
+
class APIRouterV11(APIRouter):
"""Define routes specific to OpenStack API V1.1."""
diff --git a/nova/api/openstack/accounts.py b/nova/api/openstack/accounts.py
index 86066fa20..6e3763e47 100644
--- a/nova/api/openstack/accounts.py
+++ b/nova/api/openstack/accounts.py
@@ -13,15 +13,14 @@
# License for the specific language governing permissions and limitations
# under the License.
-import common
import webob.exc
from nova import exception
from nova import flags
from nova import log as logging
-from nova import wsgi
from nova.auth import manager
+from nova.api.openstack import common
from nova.api.openstack import faults
FLAGS = flags.FLAGS
@@ -35,7 +34,7 @@ def _translate_keys(account):
manager=account.project_manager_id)
-class Controller(wsgi.Controller):
+class Controller(common.OpenstackController):
_serialization_metadata = {
'application/xml': {
diff --git a/nova/api/openstack/auth.py b/nova/api/openstack/auth.py
index f3a9bdeca..311e6bde9 100644
--- a/nova/api/openstack/auth.py
+++ b/nova/api/openstack/auth.py
@@ -55,6 +55,9 @@ class AuthMiddleware(wsgi.Middleware):
user = self.get_user_by_authentication(req)
accounts = self.auth.get_projects(user=user)
if not user:
+ token = req.headers["X-Auth-Token"]
+ msg = _("%(user)s could not be found with token '%(token)s'")
+ LOG.warn(msg % locals())
return faults.Fault(webob.exc.HTTPUnauthorized())
if accounts:
@@ -66,6 +69,8 @@ class AuthMiddleware(wsgi.Middleware):
if not self.auth.is_admin(user) and \
not self.auth.is_project_member(user, account):
+ msg = _("%(user)s must be an admin or a member of %(account)s")
+ LOG.warn(msg % locals())
return faults.Fault(webob.exc.HTTPUnauthorized())
req.environ['nova.context'] = context.RequestContext(user, account)
@@ -82,12 +87,16 @@ class AuthMiddleware(wsgi.Middleware):
# honor it
path_info = req.path_info
if len(path_info) > 1:
- return faults.Fault(webob.exc.HTTPUnauthorized())
+ msg = _("Authentication requests must be made against a version "
+ "root (e.g. /v1.0 or /v1.1).")
+ LOG.warn(msg)
+ return faults.Fault(webob.exc.HTTPUnauthorized(explanation=msg))
try:
username = req.headers['X-Auth-User']
key = req.headers['X-Auth-Key']
- except KeyError:
+ except KeyError as ex:
+ LOG.warn(_("Could not find %s in request.") % ex)
return faults.Fault(webob.exc.HTTPUnauthorized())
token, user = self._authorize_user(username, key, req)
@@ -100,6 +109,7 @@ class AuthMiddleware(wsgi.Middleware):
res.headers['X-CDN-Management-Url'] = token.cdn_management_url
res.content_type = 'text/plain'
res.status = '204'
+ LOG.debug(_("Successfully authenticated '%s'") % username)
return res
else:
return faults.Fault(webob.exc.HTTPUnauthorized())
@@ -139,6 +149,7 @@ class AuthMiddleware(wsgi.Middleware):
try:
user = self.auth.get_user_from_access_key(key)
except exception.NotFound:
+ LOG.warn(_("User not found with provided API key."))
user = None
if user and user.name == username:
@@ -153,4 +164,9 @@ class AuthMiddleware(wsgi.Middleware):
token_dict['user_id'] = user.id
token = self.db.auth_token_create(ctxt, token_dict)
return token, user
+ elif user and user.name != username:
+ msg = _("Provided API key is valid, but not for user "
+ "'%(username)s'") % locals()
+ LOG.warn(msg)
+
return None, None
diff --git a/nova/api/openstack/backup_schedules.py b/nova/api/openstack/backup_schedules.py
index f2d2d86e8..4bf744046 100644
--- a/nova/api/openstack/backup_schedules.py
+++ b/nova/api/openstack/backup_schedules.py
@@ -19,7 +19,7 @@ import time
from webob import exc
-from nova import wsgi
+from nova.api.openstack import common
from nova.api.openstack import faults
import nova.image.service
@@ -29,7 +29,7 @@ def _translate_keys(inst):
return dict(backupSchedule=inst)
-class Controller(wsgi.Controller):
+class Controller(common.OpenstackController):
""" The backup schedule API controller for the Openstack API """
_serialization_metadata = {
diff --git a/nova/api/openstack/common.py b/nova/api/openstack/common.py
index 75aeb0a5f..0b6dc944a 100644
--- a/nova/api/openstack/common.py
+++ b/nova/api/openstack/common.py
@@ -22,14 +22,19 @@ import webob
from nova import exception
from nova import flags
from nova import log as logging
+from nova import wsgi
-LOG = logging.getLogger('common')
+LOG = logging.getLogger('nova.api.openstack.common')
FLAGS = flags.FLAGS
+XML_NS_V10 = 'http://docs.rackspacecloud.com/servers/api/v1.0'
+XML_NS_V11 = 'http://docs.openstack.org/compute/api/v1.1'
+
+
def limited(items, request, max_limit=FLAGS.osapi_max_limit):
"""
Return a slice of items according to requested offset and limit.
@@ -111,8 +116,14 @@ def get_image_id_from_image_hash(image_service, context, image_hash):
items = image_service.index(context)
for image in items:
image_id = image['id']
- if abs(hash(image_id)) == int(image_hash):
- return 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.NotFound(image_hash)
@@ -128,3 +139,9 @@ def get_id_from_href(href):
except:
LOG.debug(_("Error extracting id from href: %s") % href)
raise webob.exc.HTTPBadRequest(_('could not parse id from href'))
+
+
+class OpenstackController(wsgi.Controller):
+ def get_default_xmlns(self, req):
+ # Use V10 by default
+ return XML_NS_V10
diff --git a/nova/api/openstack/consoles.py b/nova/api/openstack/consoles.py
index 8c291c2eb..1a77f25d7 100644
--- a/nova/api/openstack/consoles.py
+++ b/nova/api/openstack/consoles.py
@@ -19,7 +19,7 @@ from webob import exc
from nova import console
from nova import exception
-from nova import wsgi
+from nova.api.openstack import common
from nova.api.openstack import faults
@@ -43,7 +43,7 @@ def _translate_detail_keys(cons):
return dict(console=info)
-class Controller(wsgi.Controller):
+class Controller(common.OpenstackController):
"""The Consoles Controller for the Openstack API"""
_serialization_metadata = {
diff --git a/nova/api/openstack/contrib/volumes.py b/nova/api/openstack/contrib/volumes.py
index 6efacce52..18de2ec71 100644
--- a/nova/api/openstack/contrib/volumes.py
+++ b/nova/api/openstack/contrib/volumes.py
@@ -322,8 +322,7 @@ class Volumes(extensions.ExtensionDescriptor):
# Does this matter?
res = extensions.ResourceExtension('volumes',
VolumeController(),
- collection_actions={'detail': 'GET'}
- )
+ collection_actions={'detail': 'GET'})
resources.append(res)
res = extensions.ResourceExtension('volume_attachments',
diff --git a/nova/api/openstack/extensions.py b/nova/api/openstack/extensions.py
index fb1dccb28..7ea7afef6 100644
--- a/nova/api/openstack/extensions.py
+++ b/nova/api/openstack/extensions.py
@@ -28,6 +28,7 @@ from nova import exception
from nova import flags
from nova import log as logging
from nova import wsgi
+from nova.api.openstack import common
from nova.api.openstack import faults
@@ -115,7 +116,7 @@ class ExtensionDescriptor(object):
return response_exts
-class ActionExtensionController(wsgi.Controller):
+class ActionExtensionController(common.OpenstackController):
def __init__(self, application):
@@ -136,7 +137,7 @@ class ActionExtensionController(wsgi.Controller):
return res
-class ResponseExtensionController(wsgi.Controller):
+class ResponseExtensionController(common.OpenstackController):
def __init__(self, application):
self.application = application
@@ -155,7 +156,8 @@ class ResponseExtensionController(wsgi.Controller):
body = res.body
headers = res.headers
except AttributeError:
- body = self._serialize(res, content_type)
+ default_xmlns = None
+ body = self._serialize(res, content_type, default_xmlns)
headers = {"Content-Type": content_type}
res = webob.Response()
res.body = body
@@ -163,7 +165,7 @@ class ResponseExtensionController(wsgi.Controller):
return res
-class ExtensionController(wsgi.Controller):
+class ExtensionController(common.OpenstackController):
def __init__(self, extension_manager):
self.extension_manager = extension_manager
diff --git a/nova/api/openstack/faults.py b/nova/api/openstack/faults.py
index 940bd8771..87118ce19 100644
--- a/nova/api/openstack/faults.py
+++ b/nova/api/openstack/faults.py
@@ -20,10 +20,10 @@ import webob.dec
import webob.exc
from nova import wsgi
+from nova.api.openstack import common
class Fault(webob.exc.HTTPException):
-
"""An RS API fault response."""
_fault_names = {
@@ -47,7 +47,7 @@ class Fault(webob.exc.HTTPException):
"""Generate a WSGI response based on the exception passed to ctor."""
# Replace the body with fault details.
code = self.wrapped_exc.status_int
- fault_name = self._fault_names.get(code, "computeFault")
+ fault_name = self._fault_names.get(code, "cloudServersFault")
fault_data = {
fault_name: {
'code': code,
@@ -57,7 +57,8 @@ class Fault(webob.exc.HTTPException):
fault_data[fault_name]['retryAfter'] = retry
# 'code' is an attribute on the fault tag itself
metadata = {'application/xml': {'attributes': {fault_name: 'code'}}}
- serializer = wsgi.Serializer(metadata)
+ default_xmlns = common.XML_NS_V10
+ serializer = wsgi.Serializer(metadata, default_xmlns)
content_type = req.best_match_content_type()
self.wrapped_exc.body = serializer.serialize(fault_data, content_type)
self.wrapped_exc.content_type = content_type
diff --git a/nova/api/openstack/flavors.py b/nova/api/openstack/flavors.py
index 5b99b5a6f..40787bd17 100644
--- a/nova/api/openstack/flavors.py
+++ b/nova/api/openstack/flavors.py
@@ -19,11 +19,11 @@ import webob
from nova import db
from nova import exception
-from nova import wsgi
+from nova.api.openstack import common
from nova.api.openstack import views
-class Controller(wsgi.Controller):
+class Controller(common.OpenstackController):
"""Flavor controller for the OpenStack API."""
_serialization_metadata = {
@@ -76,3 +76,6 @@ class ControllerV11(Controller):
def _get_view_builder(self, req):
base_url = req.application_url
return views.flavors.ViewBuilderV11(base_url)
+
+ def get_default_xmlns(self, req):
+ return common.XML_NS_V11
diff --git a/nova/api/openstack/image_metadata.py b/nova/api/openstack/image_metadata.py
index c9d6ac532..1eccc0174 100644
--- a/nova/api/openstack/image_metadata.py
+++ b/nova/api/openstack/image_metadata.py
@@ -18,15 +18,17 @@
from webob import exc
from nova import flags
+from nova import quota
from nova import utils
from nova import wsgi
+from nova.api.openstack import common
from nova.api.openstack import faults
FLAGS = flags.FLAGS
-class Controller(wsgi.Controller):
+class Controller(common.OpenstackController):
"""The image metadata API controller for the Openstack API"""
def __init__(self):
@@ -39,6 +41,15 @@ class Controller(wsgi.Controller):
metadata = image.get('properties', {})
return metadata
+ def _check_quota_limit(self, context, metadata):
+ if metadata is None:
+ return
+ num_metadata = len(metadata)
+ quota_metadata = quota.allowed_metadata_items(context, num_metadata)
+ if quota_metadata < num_metadata:
+ expl = _("Image metadata limit exceeded")
+ raise exc.HTTPBadRequest(explanation=expl)
+
def index(self, req, image_id):
"""Returns the list of metadata for a given instance"""
context = req.environ['nova.context']
@@ -61,6 +72,7 @@ class Controller(wsgi.Controller):
if 'metadata' in body:
for key, value in body['metadata'].iteritems():
metadata[key] = value
+ self._check_quota_limit(context, metadata)
img['properties'] = metadata
self.image_service.update(context, image_id, img, None)
return dict(metadata=metadata)
@@ -77,6 +89,7 @@ class Controller(wsgi.Controller):
img = self.image_service.show(context, image_id)
metadata = self._get_metadata(context, image_id, img)
metadata[id] = body[id]
+ self._check_quota_limit(context, metadata)
img['properties'] = metadata
self.image_service.update(context, image_id, img, None)
diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py
index e3f93f635..34d4c27fc 100644
--- a/nova/api/openstack/images.py
+++ b/nova/api/openstack/images.py
@@ -13,8 +13,6 @@
# License for the specific language governing permissions and limitations
# under the License.
-import datetime
-
import webob.exc
from nova import compute
@@ -22,7 +20,6 @@ from nova import exception
from nova import flags
from nova import log
from nova import utils
-from nova import wsgi
from nova.api.openstack import common
from nova.api.openstack import faults
from nova.api.openstack.views import images as images_view
@@ -32,7 +29,7 @@ LOG = log.getLogger('nova.api.openstack.images')
FLAGS = flags.FLAGS
-class Controller(wsgi.Controller):
+class Controller(common.OpenstackController):
"""Base `wsgi.Controller` for retrieving/displaying images."""
_serialization_metadata = {
@@ -153,3 +150,6 @@ class ControllerV11(Controller):
"""Property to get the ViewBuilder class we need to use."""
base_url = request.application_url
return images_view.ViewBuilderV11(base_url)
+
+ def get_default_xmlns(self, req):
+ return common.XML_NS_V11
diff --git a/nova/api/openstack/ips.py b/nova/api/openstack/ips.py
new file mode 100644
index 000000000..778e9ba1a
--- /dev/null
+++ b/nova/api/openstack/ips.py
@@ -0,0 +1,72 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2011 OpenStack LLC.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import time
+
+from webob import exc
+
+import nova
+import nova.api.openstack.views.addresses
+from nova.api.openstack import common
+from nova.api.openstack import faults
+
+
+class Controller(common.OpenstackController):
+ """The servers addresses API controller for the Openstack API."""
+
+ _serialization_metadata = {
+ 'application/xml': {
+ 'list_collections': {
+ 'public': {'item_name': 'ip', 'item_key': 'addr'},
+ 'private': {'item_name': 'ip', 'item_key': 'addr'},
+ },
+ },
+ }
+
+ def __init__(self):
+ self.compute_api = nova.compute.API()
+ self.builder = nova.api.openstack.views.addresses.ViewBuilderV10()
+
+ def index(self, req, server_id):
+ try:
+ instance = self.compute_api.get(req.environ['nova.context'], id)
+ except nova.exception.NotFound:
+ return faults.Fault(exc.HTTPNotFound())
+ return {'addresses': self.builder.build(instance)}
+
+ def public(self, req, server_id):
+ try:
+ instance = self.compute_api.get(req.environ['nova.context'], id)
+ except nova.exception.NotFound:
+ return faults.Fault(exc.HTTPNotFound())
+ return {'public': self.builder.build_public_parts(instance)}
+
+ def private(self, req, server_id):
+ try:
+ instance = self.compute_api.get(req.environ['nova.context'], id)
+ except nova.exception.NotFound:
+ return faults.Fault(exc.HTTPNotFound())
+ return {'private': self.builder.build_private_parts(instance)}
+
+ def show(self, req, server_id, id):
+ return faults.Fault(exc.HTTPNotImplemented())
+
+ def create(self, req, server_id):
+ return faults.Fault(exc.HTTPNotImplemented())
+
+ def delete(self, req, server_id, id):
+ return faults.Fault(exc.HTTPNotImplemented())
diff --git a/nova/api/openstack/limits.py b/nova/api/openstack/limits.py
index efc7d193d..9877af191 100644
--- a/nova/api/openstack/limits.py
+++ b/nova/api/openstack/limits.py
@@ -31,8 +31,8 @@ from collections import defaultdict
from webob.dec import wsgify
from nova import wsgi
+from nova.api.openstack import common
from nova.api.openstack import faults
-from nova.wsgi import Controller
from nova.wsgi import Middleware
@@ -43,7 +43,7 @@ PER_HOUR = 60 * 60
PER_DAY = 60 * 60 * 24
-class LimitsController(Controller):
+class LimitsController(common.OpenstackController):
"""
Controller for accessing limits in the OpenStack API.
"""
diff --git a/nova/api/openstack/server_metadata.py b/nova/api/openstack/server_metadata.py
index 45bbac99d..fd64ee4fb 100644
--- a/nova/api/openstack/server_metadata.py
+++ b/nova/api/openstack/server_metadata.py
@@ -18,11 +18,13 @@
from webob import exc
from nova import compute
+from nova import quota
from nova import wsgi
+from nova.api.openstack import common
from nova.api.openstack import faults
-class Controller(wsgi.Controller):
+class Controller(common.OpenstackController):
""" The server metadata API controller for the Openstack API """
def __init__(self):
@@ -43,10 +45,14 @@ class Controller(wsgi.Controller):
def create(self, req, server_id):
context = req.environ['nova.context']
- body = self._deserialize(req.body, req.get_content_type())
- self.compute_api.update_or_create_instance_metadata(context,
- server_id,
- body['metadata'])
+ data = self._deserialize(req.body, req.get_content_type())
+ metadata = data.get('metadata')
+ try:
+ self.compute_api.update_or_create_instance_metadata(context,
+ server_id,
+ metadata)
+ except quota.QuotaError as error:
+ self._handle_quota_error(error)
return req.body
def update(self, req, server_id, id):
@@ -58,9 +64,13 @@ class Controller(wsgi.Controller):
if len(body) > 1:
expl = _('Request body contains too many items')
raise exc.HTTPBadRequest(explanation=expl)
- self.compute_api.update_or_create_instance_metadata(context,
- server_id,
- body)
+ try:
+ self.compute_api.update_or_create_instance_metadata(context,
+ server_id,
+ body)
+ except quota.QuotaError as error:
+ self._handle_quota_error(error)
+
return req.body
def show(self, req, server_id, id):
@@ -76,3 +86,9 @@ class Controller(wsgi.Controller):
""" Deletes an existing metadata """
context = req.environ['nova.context']
self.compute_api.delete_instance_metadata(context, server_id, id)
+
+ def _handle_quota_error(self, error):
+ """Reraise quota errors as api-specific http exceptions."""
+ if error.code == "MetadataLimitExceeded":
+ raise exc.HTTPBadRequest(explanation=error.message)
+ raise error
diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py
index 6704a68ae..22a9c632c 100644
--- a/nova/api/openstack/servers.py
+++ b/nova/api/openstack/servers.py
@@ -44,7 +44,7 @@ LOG = logging.getLogger('server')
FLAGS = flags.FLAGS
-class Controller(wsgi.Controller):
+class Controller(common.OpenstackController):
""" The Server API controller for the OpenStack API """
_serialization_metadata = {
@@ -55,6 +55,13 @@ class Controller(wsgi.Controller):
"imageRef"],
"link": ["rel", "type", "href"],
},
+ "dict_collections": {
+ "metadata": {"item_name": "meta", "item_key": "key"},
+ },
+ "list_collections": {
+ "public": {"item_name": "ip", "item_key": "addr"},
+ "private": {"item_name": "ip", "item_key": "addr"},
+ },
},
}
@@ -63,15 +70,6 @@ class Controller(wsgi.Controller):
self._image_service = utils.import_object(FLAGS.image_service)
super(Controller, self).__init__()
- def ips(self, req, id):
- try:
- instance = self.compute_api.get(req.environ['nova.context'], id)
- except exception.NotFound:
- return faults.Fault(exc.HTTPNotFound())
-
- builder = self._get_addresses_view_builder(req)
- return builder.build(instance)
-
def index(self, req):
""" Returns a list of server names and ids for a given user """
return self._items(req, is_detail=False)
@@ -120,6 +118,8 @@ class Controller(wsgi.Controller):
context = req.environ['nova.context']
+ password = self._get_server_admin_password(env['server'])
+
key_name = None
key_data = None
key_pairs = auth_manager.AuthManager.get_key_pairs(context)
@@ -129,21 +129,16 @@ class Controller(wsgi.Controller):
key_data = key_pair['public_key']
requested_image_id = self._image_id_from_req_data(env)
- image_id = common.get_image_id_from_image_hash(self._image_service,
- context, requested_image_id)
+ try:
+ image_id = common.get_image_id_from_image_hash(self._image_service,
+ context, requested_image_id)
+ except:
+ msg = _("Can not find requested image")
+ return faults.Fault(exc.HTTPBadRequest(msg))
+
kernel_id, ramdisk_id = self._get_kernel_ramdisk_from_image(
req, image_id)
- # Metadata is a list, not a Dictionary, because we allow duplicate keys
- # (even though JSON can't encode this)
- # In future, we may not allow duplicate keys.
- # However, the CloudServers API is not definitive on this front,
- # and we want to be compatible.
- metadata = []
- if env['server'].get('metadata'):
- for k, v in env['server']['metadata'].items():
- metadata.append({'key': k, 'value': v})
-
personality = env['server'].get('personality')
injected_files = []
if personality:
@@ -160,9 +155,11 @@ class Controller(wsgi.Controller):
name = name.strip()
try:
+ inst_type = \
+ instance_types.get_instance_type_by_flavor_id(flavor_id)
(inst,) = self.compute_api.create(
context,
- instance_types.get_by_flavor_id(flavor_id),
+ inst_type,
image_id,
kernel_id=kernel_id,
ramdisk_id=ramdisk_id,
@@ -170,17 +167,16 @@ class Controller(wsgi.Controller):
display_description=name,
key_name=key_name,
key_data=key_data,
- metadata=metadata,
+ metadata=env['server'].get('metadata', {}),
injected_files=injected_files)
except quota.QuotaError as error:
self._handle_quota_error(error)
- inst['instance_type'] = flavor_id
+ inst['instance_type'] = inst_type
inst['image_id'] = requested_image_id
builder = self._get_view_builder(req)
server = builder.build(inst, is_detail=True)
- password = utils.generate_password(16)
server['server']['adminPass'] = password
self.compute_api.set_admin_password(context, server['server']['id'],
password)
@@ -242,6 +238,10 @@ class Controller(wsgi.Controller):
# if the original error is okay, just reraise it
raise error
+ def _get_server_admin_password(self, server):
+ """ Determine the admin password for a server on creation """
+ return utils.generate_password(16)
+
@scheduler_api.redirect_handler
def update(self, req, id):
""" Updates the server name or password """
@@ -565,7 +565,7 @@ class Controller(wsgi.Controller):
_("Cannot build from image %(image_id)s, status not active") %
locals())
- if image_meta['properties']['disk_format'] != 'ami':
+ if image_meta.get('container_format') != 'ami':
return None, None
try:
@@ -648,6 +648,19 @@ class ControllerV11(Controller):
def _limit_items(self, items, req):
return common.limited_by_marker(items, req)
+ def _get_server_admin_password(self, server):
+ """ Determine the admin password for a server on creation """
+ password = server.get('adminPass')
+ if password is None:
+ return utils.generate_password(16)
+ if not isinstance(password, basestring) or password == '':
+ msg = _("Invalid adminPass")
+ raise exc.HTTPBadRequest(msg)
+ return password
+
+ def get_default_xmlns(self, req):
+ return common.XML_NS_V11
+
class ServerCreateRequestXMLDeserializer(object):
"""
diff --git a/nova/api/openstack/shared_ip_groups.py b/nova/api/openstack/shared_ip_groups.py
index ee7991d7f..996db3648 100644
--- a/nova/api/openstack/shared_ip_groups.py
+++ b/nova/api/openstack/shared_ip_groups.py
@@ -17,7 +17,7 @@
from webob import exc
-from nova import wsgi
+from nova.api.openstack import common
from nova.api.openstack import faults
@@ -32,7 +32,7 @@ def _translate_detail_keys(inst):
return dict(sharedIpGroups=inst)
-class Controller(wsgi.Controller):
+class Controller(common.OpenstackController):
""" The Shared IP Groups Controller for the Openstack API """
_serialization_metadata = {
diff --git a/nova/api/openstack/users.py b/nova/api/openstack/users.py
index d3ab3d553..077ccfc79 100644
--- a/nova/api/openstack/users.py
+++ b/nova/api/openstack/users.py
@@ -18,7 +18,6 @@ from webob import exc
from nova import exception
from nova import flags
from nova import log as logging
-from nova import wsgi
from nova.api.openstack import common
from nova.api.openstack import faults
from nova.auth import manager
@@ -35,7 +34,7 @@ def _translate_keys(user):
admin=user.admin)
-class Controller(wsgi.Controller):
+class Controller(common.OpenstackController):
_serialization_metadata = {
'application/xml': {
diff --git a/nova/api/openstack/views/addresses.py b/nova/api/openstack/views/addresses.py
index 90c77855b..2810cce39 100644
--- a/nova/api/openstack/views/addresses.py
+++ b/nova/api/openstack/views/addresses.py
@@ -28,10 +28,16 @@ class ViewBuilder(object):
class ViewBuilderV10(ViewBuilder):
def build(self, inst):
- private_ips = utils.get_from_path(inst, 'fixed_ip/address')
- public_ips = utils.get_from_path(inst, 'fixed_ip/floating_ips/address')
+ private_ips = self.build_private_parts(inst)
+ public_ips = self.build_public_parts(inst)
return dict(public=public_ips, private=private_ips)
+ def build_public_parts(self, inst):
+ return utils.get_from_path(inst, 'fixed_ip/floating_ips/address')
+
+ def build_private_parts(self, inst):
+ return utils.get_from_path(inst, 'fixed_ip/address')
+
class ViewBuilderV11(ViewBuilder):
def build(self, inst):
diff --git a/nova/api/openstack/views/images.py b/nova/api/openstack/views/images.py
index 16195b050..9dec8a355 100644
--- a/nova/api/openstack/views/images.py
+++ b/nova/api/openstack/views/images.py
@@ -34,11 +34,11 @@ class ViewBuilder(object):
def _format_status(self, image):
"""Update the status field to standardize format."""
status_mapping = {
- 'pending': 'queued',
- 'decrypting': 'preparing',
- 'untarring': 'saving',
- 'available': 'active',
- 'killed': 'failed',
+ 'pending': 'QUEUED',
+ 'decrypting': 'PREPARING',
+ 'untarring': 'SAVING',
+ 'available': 'ACTIVE',
+ 'killed': 'FAILED',
}
try:
diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py
index d24c025be..e52bfaea3 100644
--- a/nova/api/openstack/views/servers.py
+++ b/nova/api/openstack/views/servers.py
@@ -82,7 +82,7 @@ class ViewBuilder(object):
# Return the metadata as a dictionary
metadata = {}
for item in inst.get('metadata', []):
- metadata[item['key']] = item['value']
+ metadata[item['key']] = str(item['value'])
inst_dict['metadata'] = metadata
inst_dict['hostId'] = ''
@@ -115,7 +115,7 @@ class ViewBuilderV10(ViewBuilder):
def _build_flavor(self, response, inst):
if 'instance_type' in dict(inst):
- response['flavorId'] = inst['instance_type']
+ response['flavorId'] = inst['instance_type']['flavorid']
class ViewBuilderV11(ViewBuilder):
@@ -134,7 +134,7 @@ class ViewBuilderV11(ViewBuilder):
def _build_flavor(self, response, inst):
if "instance_type" in dict(inst):
- flavor_id = inst["instance_type"]
+ flavor_id = inst["instance_type"]['flavorid']
flavor_ref = self.flavor_builder.generate_href(flavor_id)
response["flavorRef"] = flavor_ref
diff --git a/nova/api/openstack/zones.py b/nova/api/openstack/zones.py
index 846cb48a1..227ffecdc 100644
--- a/nova/api/openstack/zones.py
+++ b/nova/api/openstack/zones.py
@@ -13,12 +13,10 @@
# License for the specific language governing permissions and limitations
# under the License.
-import common
-
from nova import db
from nova import flags
from nova import log as logging
-from nova import wsgi
+from nova.api.openstack import common
from nova.scheduler import api
@@ -43,7 +41,7 @@ def _scrub_zone(zone):
'deleted', 'deleted_at', 'updated_at'))
-class Controller(wsgi.Controller):
+class Controller(common.OpenstackController):
_serialization_metadata = {
'application/xml': {
diff --git a/nova/auth/dbdriver.py b/nova/auth/dbdriver.py
index d1e3f2ed5..b2c580d83 100644
--- a/nova/auth/dbdriver.py
+++ b/nova/auth/dbdriver.py
@@ -115,7 +115,7 @@ class DbDriver(object):
# on to create the project. This way we won't have to destroy
# the project again because a user turns out to be invalid.
members = set([manager])
- if member_uids != None:
+ if member_uids is not None:
for member_uid in member_uids:
member = db.user_get(context.get_admin_context(), member_uid)
if not member:
diff --git a/nova/auth/manager.py b/nova/auth/manager.py
index 486845399..8479c95a4 100644
--- a/nova/auth/manager.py
+++ b/nova/auth/manager.py
@@ -268,7 +268,7 @@ class AuthManager(object):
LOG.debug(_('Looking up user: %r'), access_key)
user = self.get_user_from_access_key(access_key)
LOG.debug('user: %r', user)
- if user == None:
+ if user is None:
LOG.audit(_("Failed authorization for access key %s"), access_key)
raise exception.NotFound(_('No user found for access key %s')
% access_key)
@@ -280,7 +280,7 @@ class AuthManager(object):
project_id = user.name
project = self.get_project(project_id)
- if project == None:
+ if project is None:
pjid = project_id
uname = user.name
LOG.audit(_("failed authorization: no project named %(pjid)s"
@@ -646,9 +646,9 @@ class AuthManager(object):
@rtype: User
@return: The new user.
"""
- if access == None:
+ if access is None:
access = str(uuid.uuid4())
- if secret == None:
+ if secret is None:
secret = str(uuid.uuid4())
with self.driver() as drv:
user_dict = drv.create_user(name, access, secret, admin)
diff --git a/nova/compute/api.py b/nova/compute/api.py
index 996955fe3..264961fe3 100644
--- a/nova/compute/api.py
+++ b/nova/compute/api.py
@@ -16,9 +16,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-"""
-Handles all requests relating to instances (guest vms).
-"""
+"""Handles all requests relating to instances (guest vms)."""
import datetime
import re
@@ -86,10 +84,10 @@ class API(base.Base):
{"method": "get_network_topic", "args": {'fake': 1}})
def _check_injected_file_quota(self, context, injected_files):
- """
- Enforce quota limits on injected files
+ """Enforce quota limits on injected files.
+
+ Raises a QuotaError if any limit is exceeded.
- Raises a QuotaError if any limit is exceeded
"""
if injected_files is None:
return
@@ -104,50 +102,54 @@ class API(base.Base):
if len(content) > content_limit:
raise quota.QuotaError(code="OnsetFileContentLimitExceeded")
- def create(self, context, instance_type,
- image_id, kernel_id=None, ramdisk_id=None,
- min_count=1, max_count=1,
- display_name='', display_description='',
- key_name=None, key_data=None, security_group='default',
- availability_zone=None, user_data=None, metadata=[],
- injected_files=None):
- """Create the number of instances requested if quota and
- other arguments check out ok."""
-
- type_data = instance_types.get_instance_type(instance_type)
- num_instances = quota.allowed_instances(context, max_count, type_data)
- if num_instances < min_count:
- pid = context.project_id
- LOG.warn(_("Quota exceeeded for %(pid)s,"
- " tried to run %(min_count)s instances") % locals())
- raise quota.QuotaError(_("Instance quota exceeded. You can only "
- "run %s more instances of this type.") %
- num_instances, "InstanceLimitExceeded")
-
+ def _check_metadata_properties_quota(self, context, metadata={}):
+ """Enforce quota limits on metadata properties."""
num_metadata = len(metadata)
quota_metadata = quota.allowed_metadata_items(context, num_metadata)
if quota_metadata < num_metadata:
pid = context.project_id
- msg = (_("Quota exceeeded for %(pid)s,"
- " tried to set %(num_metadata)s metadata properties")
- % locals())
+ msg = _("Quota exceeeded for %(pid)s, tried to set "
+ "%(num_metadata)s metadata properties") % locals()
LOG.warn(msg)
raise quota.QuotaError(msg, "MetadataLimitExceeded")
# Because metadata is stored in the DB, we hard-code the size limits
# In future, we may support more variable length strings, so we act
# as if this is quota-controlled for forwards compatibility
- for metadata_item in metadata:
- k = metadata_item['key']
- v = metadata_item['value']
+ for k, v in metadata.iteritems():
if len(k) > 255 or len(v) > 255:
pid = context.project_id
- msg = (_("Quota exceeeded for %(pid)s,"
- " metadata property key or value too long")
- % locals())
+ msg = _("Quota exceeeded for %(pid)s, metadata property "
+ "key or value too long") % locals()
LOG.warn(msg)
raise quota.QuotaError(msg, "MetadataLimitExceeded")
+ def create(self, context, instance_type,
+ image_id, kernel_id=None, ramdisk_id=None,
+ min_count=1, max_count=1,
+ display_name='', display_description='',
+ key_name=None, key_data=None, security_group='default',
+ availability_zone=None, user_data=None, metadata={},
+ injected_files=None):
+ """Create the number and type of instances requested.
+
+ Verifies that quota and other arguments are valid.
+
+ """
+ if not instance_type:
+ instance_type = instance_types.get_default_instance_type()
+
+ num_instances = quota.allowed_instances(context, max_count,
+ instance_type)
+ if num_instances < min_count:
+ pid = context.project_id
+ LOG.warn(_("Quota exceeeded for %(pid)s,"
+ " tried to run %(min_count)s instances") % locals())
+ raise quota.QuotaError(_("Instance quota exceeded. You can only "
+ "run %s more instances of this type.") %
+ num_instances, "InstanceLimitExceeded")
+
+ self._check_metadata_properties_quota(context, metadata)
self._check_injected_file_quota(context, injected_files)
image = self.image_service.show(context, image_id)
@@ -201,10 +203,10 @@ class API(base.Base):
'user_id': context.user_id,
'project_id': context.project_id,
'launch_time': time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()),
- 'instance_type': instance_type,
- 'memory_mb': type_data['memory_mb'],
- 'vcpus': type_data['vcpus'],
- 'local_gb': type_data['local_gb'],
+ 'instance_type_id': instance_type['id'],
+ 'memory_mb': instance_type['memory_mb'],
+ 'vcpus': instance_type['vcpus'],
+ 'local_gb': instance_type['local_gb'],
'display_name': display_name,
'display_description': display_description,
'user_data': user_data or '',
@@ -235,7 +237,7 @@ class API(base.Base):
# Set sane defaults if not specified
updates = dict(hostname=self.hostname_factory(instance_id))
if (not hasattr(instance, 'display_name') or
- instance.display_name == None):
+ instance.display_name is None):
updates['display_name'] = "Server %s" % instance_id
instance = self.update(context, instance_id, **updates)
@@ -259,8 +261,7 @@ class API(base.Base):
return [dict(x.iteritems()) for x in instances]
def has_finished_migration(self, context, instance_id):
- """Retrieves whether or not a finished migration exists for
- an instance"""
+ """Returns true if an instance has a finished migration."""
try:
db.migration_get_by_instance_and_status(context, instance_id,
'finished')
@@ -269,8 +270,10 @@ class API(base.Base):
return False
def ensure_default_security_group(self, context):
- """ Create security group for the security context if it
- does not already exist
+ """Ensure that a context has a security group.
+
+ Creates a security group for the security context if it does not
+ already exist.
:param context: the security context
@@ -286,7 +289,7 @@ class API(base.Base):
db.security_group_create(context, values)
def trigger_security_group_rules_refresh(self, context, security_group_id):
- """Called when a rule is added to or removed from a security_group"""
+ """Called when a rule is added to or removed from a security_group."""
security_group = self.db.security_group_get(context, security_group_id)
@@ -302,11 +305,12 @@ class API(base.Base):
"args": {"security_group_id": security_group.id}})
def trigger_security_group_members_refresh(self, context, group_id):
- """Called when a security group gains a new or loses a member
+ """Called when a security group gains a new or loses a member.
Sends an update request to each compute node for whom this is
- relevant."""
+ relevant.
+ """
# First, we get the security group rules that reference this group as
# the grantee..
security_group_rules = \
@@ -351,7 +355,7 @@ class API(base.Base):
as data fields of the instance to be
updated
- :retval None
+ :returns: None
"""
rv = self.db.instance_update(context, instance_id, kwargs)
@@ -359,6 +363,7 @@ class API(base.Base):
@scheduler_api.reroute_compute("delete")
def delete(self, context, instance_id):
+ """Terminate an instance."""
LOG.debug(_("Going to try to terminate %s"), instance_id)
try:
instance = self.get(context, instance_id)
@@ -367,11 +372,15 @@ class API(base.Base):
instance_id)
raise
- if (instance['state_description'] == 'terminating'):
+ if instance['state_description'] == 'terminating':
LOG.warning(_("Instance %s is already being terminated"),
instance_id)
return
+ if instance['state_description'] == 'migrating':
+ LOG.warning(_("Instance %s is being migrated"), instance_id)
+ return
+
self.update(context,
instance['id'],
state_description='terminating',
@@ -386,22 +395,28 @@ class API(base.Base):
self.db.instance_destroy(context, instance_id)
def get(self, context, instance_id):
- """Get a single instance with the given ID."""
+ """Get a single instance with the given instance_id."""
rv = self.db.instance_get(context, instance_id)
return dict(rv.iteritems())
@scheduler_api.reroute_compute("get")
def routing_get(self, context, instance_id):
- """Use this method instead of get() if this is the only
- operation you intend to to. It will route to novaclient.get
- if the instance is not found."""
+ """A version of get with special routing characteristics.
+
+ Use this method instead of get() if this is the only operation you
+ intend to to. It will route to novaclient.get if the instance is not
+ found.
+
+ """
return self.get(context, instance_id)
def get_all(self, context, project_id=None, reservation_id=None,
fixed_ip=None):
- """Get all instances, possibly filtered by one of the
- given parameters. If there is no filter and the context is
- an admin, it will retreive all instances in the system.
+ """Get all instances filtered by one of the given parameters.
+
+ If there is no filter and the context is an admin, it will retreive
+ all instances in the system.
+
"""
if reservation_id is not None:
return self.db.instance_get_all_by_reservation(
@@ -430,7 +445,8 @@ class API(base.Base):
:param params: Optional dictionary of arguments to be passed to the
compute worker
- :retval None
+ :returns: None
+
"""
if not params:
params = {}
@@ -449,7 +465,7 @@ class API(base.Base):
:param params: Optional dictionary of arguments to be passed to the
compute worker
- :retval: Result returned by compute worker
+ :returns: Result returned by compute worker
"""
if not params:
params = {}
@@ -462,13 +478,14 @@ class API(base.Base):
return rpc.call(context, queue, kwargs)
def _cast_scheduler_message(self, context, args):
- """Generic handler for RPC calls to the scheduler"""
+ """Generic handler for RPC calls to the scheduler."""
rpc.cast(context, FLAGS.scheduler_topic, args)
def snapshot(self, context, instance_id, name):
"""Snapshot the given instance.
- :retval: A dict containing image metadata
+ :returns: A dict containing image metadata
+
"""
properties = {'instance_id': str(instance_id),
'user_id': str(context.user_id)}
@@ -485,7 +502,7 @@ class API(base.Base):
self._cast_compute_message('reboot_instance', context, instance_id)
def revert_resize(self, context, instance_id):
- """Reverts a resize, deleting the 'new' instance in the process"""
+ """Reverts a resize, deleting the 'new' instance in the process."""
context = context.elevated()
migration_ref = self.db.migration_get_by_instance_and_status(context,
instance_id, 'finished')
@@ -500,8 +517,7 @@ class API(base.Base):
{'status': 'reverted'})
def confirm_resize(self, context, instance_id):
- """Confirms a migration/resize, deleting the 'old' instance in the
- process."""
+ """Confirms a migration/resize and deletes the 'old' instance."""
context = context.elevated()
migration_ref = self.db.migration_get_by_instance_and_status(context,
instance_id, 'finished')
@@ -521,8 +537,7 @@ class API(base.Base):
def resize(self, context, instance_id, flavor_id):
"""Resize a running instance."""
instance = self.db.instance_get(context, instance_id)
- current_instance_type = self.db.instance_type_get_by_name(
- context, instance['instance_type'])
+ current_instance_type = instance['instance_type']
new_instance_type = self.db.instance_type_get_by_flavor_id(
context, flavor_id)
@@ -562,10 +577,9 @@ class API(base.Base):
@scheduler_api.reroute_compute("diagnostics")
def get_diagnostics(self, context, instance_id):
"""Retrieve diagnostics for the given instance."""
- return self._call_compute_message(
- "get_diagnostics",
- context,
- instance_id)
+ return self._call_compute_message("get_diagnostics",
+ context,
+ instance_id)
def get_actions(self, context, instance_id):
"""Retrieve actions for the given instance."""
@@ -573,12 +587,12 @@ class API(base.Base):
@scheduler_api.reroute_compute("suspend")
def suspend(self, context, instance_id):
- """suspend the instance with instance_id"""
+ """Suspend the given instance."""
self._cast_compute_message('suspend_instance', context, instance_id)
@scheduler_api.reroute_compute("resume")
def resume(self, context, instance_id):
- """resume the instance with instance_id"""
+ """Resume the given instance."""
self._cast_compute_message('resume_instance', context, instance_id)
@scheduler_api.reroute_compute("rescue")
@@ -593,15 +607,15 @@ class API(base.Base):
def set_admin_password(self, context, instance_id, password=None):
"""Set the root/admin password for the given instance."""
- self._cast_compute_message('set_admin_password', context, instance_id,
- password)
+ self._cast_compute_message(
+ 'set_admin_password', context, instance_id, password)
def inject_file(self, context, instance_id):
"""Write a file to the given instance."""
self._cast_compute_message('inject_file', context, instance_id)
def get_ajax_console(self, context, instance_id):
- """Get a url to an AJAX Console"""
+ """Get a url to an AJAX Console."""
output = self._call_compute_message('get_ajax_console',
context,
instance_id)
@@ -610,7 +624,7 @@ class API(base.Base):
'args': {'token': output['token'], 'host': output['host'],
'port': output['port']}})
return {'url': '%s/?token=%s' % (FLAGS.ajax_console_proxy_url,
- output['token'])}
+ output['token'])}
def get_vnc_console(self, context, instance_id):
"""Get a url to a VNC Console."""
@@ -632,39 +646,34 @@ class API(base.Base):
'portignore')}
def get_console_output(self, context, instance_id):
- """Get console output for an an instance"""
+ """Get console output for an an instance."""
return self._call_compute_message('get_console_output',
context,
instance_id)
def lock(self, context, instance_id):
- """lock the instance with instance_id"""
+ """Lock the given instance."""
self._cast_compute_message('lock_instance', context, instance_id)
def unlock(self, context, instance_id):
- """unlock the instance with instance_id"""
+ """Unlock the given instance."""
self._cast_compute_message('unlock_instance', context, instance_id)
def get_lock(self, context, instance_id):
- """return the boolean state of (instance with instance_id)'s lock"""
+ """Return the boolean state of given instance's lock."""
instance = self.get(context, instance_id)
return instance['locked']
def reset_network(self, context, instance_id):
- """
- Reset networking on the instance.
-
- """
+ """Reset networking on the instance."""
self._cast_compute_message('reset_network', context, instance_id)
def inject_network_info(self, context, instance_id):
- """
- Inject network info for the instance.
-
- """
+ """Inject network info for the instance."""
self._cast_compute_message('inject_network_info', context, instance_id)
def attach_volume(self, context, instance_id, volume_id, device):
+ """Attach an existing volume to an existing instance."""
if not re.match("^/dev/[a-z]d[a-z]+$", device):
raise exception.ApiError(_("Invalid device specified: %s. "
"Example device: /dev/vdb") % device)
@@ -679,6 +688,7 @@ class API(base.Base):
"mountpoint": device}})
def detach_volume(self, context, volume_id):
+ """Detach a volume from an instance."""
instance = self.db.volume_get_instance(context.elevated(), volume_id)
if not instance:
raise exception.ApiError(_("Volume isn't attached to anything!"))
@@ -692,6 +702,7 @@ class API(base.Base):
return instance
def associate_floating_ip(self, context, instance_id, address):
+ """Associate a floating ip with an instance."""
instance = self.get(context, instance_id)
self.network_api.associate_floating_ip(context,
floating_ip=address,
@@ -703,11 +714,14 @@ class API(base.Base):
return dict(rv.iteritems())
def delete_instance_metadata(self, context, instance_id, key):
- """Delete the given metadata item"""
+ """Delete the given metadata item from an instance."""
self.db.instance_metadata_delete(context, instance_id, key)
def update_or_create_instance_metadata(self, context, instance_id,
metadata):
- """Updates or creates instance metadata"""
+ """Updates or creates instance metadata."""
+ combined_metadata = self.get_instance_metadata(context, instance_id)
+ combined_metadata.update(metadata)
+ self._check_metadata_properties_quota(context, combined_metadata)
self.db.instance_metadata_update_or_create(context, instance_id,
metadata)
diff --git a/nova/compute/instance_types.py b/nova/compute/instance_types.py
index fa02a5dfa..98b4425c8 100644
--- a/nova/compute/instance_types.py
+++ b/nova/compute/instance_types.py
@@ -18,9 +18,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-"""
-The built-in instance properties.
-"""
+"""Built-in instance properties."""
from nova import context
from nova import db
@@ -34,9 +32,7 @@ LOG = logging.getLogger('nova.instance_types')
def create(name, memory, vcpus, local_gb, flavorid, swap=0,
rxtx_quota=0, rxtx_cap=0):
- """Creates instance types / flavors
- arguments: name memory vcpus local_gb flavorid swap rxtx_quota rxtx_cap
- """
+ """Creates instance types."""
for option in [memory, vcpus, local_gb, flavorid]:
try:
int(option)
@@ -59,83 +55,86 @@ def create(name, memory, vcpus, local_gb, flavorid, swap=0,
rxtx_quota=rxtx_quota,
rxtx_cap=rxtx_cap))
except exception.DBError, e:
- LOG.exception(_('DB error: %s' % e))
- raise exception.ApiError(_("Cannot create instance type: %s" % name))
+ LOG.exception(_('DB error: %s') % e)
+ raise exception.ApiError(_("Cannot create instance type: %s") % name)
def destroy(name):
- """Marks instance types / flavors as deleted
- arguments: name"""
- if name == None:
+ """Marks instance types as deleted."""
+ if name is None:
raise exception.InvalidInputException(_("No instance type specified"))
else:
try:
db.instance_type_destroy(context.get_admin_context(), name)
except exception.NotFound:
- LOG.exception(_('Instance type %s not found for deletion' % name))
- raise exception.ApiError(_("Unknown instance type: %s" % name))
+ LOG.exception(_('Instance type %s not found for deletion') % name)
+ raise exception.ApiError(_("Unknown instance type: %s") % name)
def purge(name):
- """Removes instance types / flavors from database
- arguments: name"""
- if name == None:
+ """Removes instance types from database."""
+ if name is None:
raise exception.InvalidInputException(_("No instance type specified"))
else:
try:
db.instance_type_purge(context.get_admin_context(), name)
except exception.NotFound:
- LOG.exception(_('Instance type %s not found for purge' % name))
- raise exception.ApiError(_("Unknown instance type: %s" % name))
+ LOG.exception(_('Instance type %s not found for purge') % name)
+ raise exception.ApiError(_("Unknown instance type: %s") % name)
def get_all_types(inactive=0):
- """Retrieves non-deleted instance_types.
- Pass true as argument if you want deleted instance types returned also."""
+ """Get all non-deleted instance_types.
+
+ Pass true as argument if you want deleted instance types returned also.
+
+ """
return db.instance_type_get_all(context.get_admin_context(), inactive)
-def get_all_flavors():
- """retrieves non-deleted flavors. alias for instance_types.get_all_types().
- Pass true as argument if you want deleted instance types returned also."""
- return get_all_types(context.get_admin_context())
+get_all_flavors = get_all_types
-def get_instance_type(name):
- """Retrieves single instance type by name"""
- if name is None:
- return FLAGS.default_instance_type
+def get_default_instance_type():
+ """Get the default instance type."""
+ name = FLAGS.default_instance_type
try:
- ctxt = context.get_admin_context()
- inst_type = db.instance_type_get_by_name(ctxt, name)
- return inst_type
+ return get_instance_type_by_name(name)
except exception.DBError:
- raise exception.ApiError(_("Unknown instance type: %s" % name))
+ raise exception.ApiError(_("Unknown instance type: %s") % name)
-def get_by_type(instance_type):
- """retrieve instance type name"""
- if instance_type is None:
- return FLAGS.default_instance_type
+def get_instance_type(id):
+ """Retrieves single instance type by id."""
+ if id is None:
+ return get_default_instance_type()
+ try:
+ ctxt = context.get_admin_context()
+ return db.instance_type_get_by_id(ctxt, id)
+ except exception.DBError:
+ raise exception.ApiError(_("Unknown instance type: %s") % name)
+
+def get_instance_type_by_name(name):
+ """Retrieves single instance type by name."""
+ if name is None:
+ return get_default_instance_type()
try:
ctxt = context.get_admin_context()
- inst_type = db.instance_type_get_by_name(ctxt, instance_type)
- return inst_type['name']
- except exception.DBError, e:
- LOG.exception(_('DB error: %s' % e))
- raise exception.ApiError(_("Unknown instance type: %s" %\
- instance_type))
+ return db.instance_type_get_by_name(ctxt, name)
+ except exception.DBError:
+ raise exception.ApiError(_("Unknown instance type: %s") % name)
-def get_by_flavor_id(flavor_id):
- """retrieve instance type's name by flavor_id"""
+# TODO(termie): flavor-specific code should probably be in the API that uses
+# flavors.
+def get_instance_type_by_flavor_id(flavor_id):
+ """Retrieve instance type by flavor_id."""
if flavor_id is None:
- return FLAGS.default_instance_type
+ return get_default_instance_type()
try:
ctxt = context.get_admin_context()
- flavor = db.instance_type_get_by_flavor_id(ctxt, flavor_id)
- return flavor['name']
+ return db.instance_type_get_by_flavor_id(ctxt, flavor_id)
except exception.DBError, e:
- LOG.exception(_('DB error: %s' % e))
- raise exception.ApiError(_("Unknown flavor: %s" % flavor_id))
+ LOG.exception(_('DB error: %s') % e)
+ raise exception.ApiError(_("Unknown flavor: %s") % flavor_id)
diff --git a/nova/compute/manager.py b/nova/compute/manager.py
index 08b772517..fac00e45e 100644
--- a/nova/compute/manager.py
+++ b/nova/compute/manager.py
@@ -17,8 +17,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-"""
-Handles all processes relating to instances (guest vms).
+"""Handles all processes relating to instances (guest vms).
The :py:class:`ComputeManager` class is a :py:class:`nova.manager.Manager` that
handles RPC calls relating to creating instances. It is responsible for
@@ -33,6 +32,7 @@ terminating it.
by :func:`nova.utils.import_object`
:volume_manager: Name of class that handles persistent storage, loaded by
:func:`nova.utils.import_object`
+
"""
import datetime
@@ -55,6 +55,7 @@ from nova import utils
from nova.compute import power_state
from nova.virt import driver
+
FLAGS = flags.FLAGS
flags.DEFINE_string('instances_path', '$state_path/instances',
'where instances are stored on disk')
@@ -74,19 +75,14 @@ flags.DEFINE_integer("rescue_timeout", 0,
"Automatically unrescue an instance after N seconds."
" Set to 0 to disable.")
+
LOG = logging.getLogger('nova.compute.manager')
def checks_instance_lock(function):
- """
- decorator used for preventing action against locked instances
- unless, of course, you happen to be admin
-
- """
-
+ """Decorator to prevent action against locked instances for non-admins."""
@functools.wraps(function)
def decorated_function(self, context, instance_id, *args, **kwargs):
-
LOG.info(_("check_instance_lock: decorating: |%s|"), function,
context=context)
LOG.info(_("check_instance_lock: arguments: |%(self)s| |%(context)s|"
@@ -112,7 +108,6 @@ def checks_instance_lock(function):
class ComputeManager(manager.SchedulerDependentManager):
-
"""Manages the running instances from creation to destruction."""
def __init__(self, compute_driver=None, *args, **kwargs):
@@ -136,9 +131,7 @@ class ComputeManager(manager.SchedulerDependentManager):
*args, **kwargs)
def init_host(self):
- """Do any initialization that needs to be run if this is a
- standalone service.
- """
+ """Initialization for a standalone compute service."""
self.driver.init_host(host=self.host)
def _update_state(self, context, instance_id):
@@ -153,16 +146,18 @@ class ComputeManager(manager.SchedulerDependentManager):
self.db.instance_set_state(context, instance_id, state)
def get_console_topic(self, context, **kwargs):
- """Retrieves the console host for a project on this host
- Currently this is just set in the flags for each compute
- host."""
+ """Retrieves the console host for a project on this host.
+
+ Currently this is just set in the flags for each compute host.
+
+ """
#TODO(mdragon): perhaps make this variable by console_type?
return self.db.queue_get_for(context,
FLAGS.console_topic,
FLAGS.console_host)
def get_network_topic(self, context, **kwargs):
- """Retrieves the network host for a project on this host"""
+ """Retrieves the network host for a project on this host."""
# TODO(vish): This method should be memoized. This will make
# the call to get_network_host cheaper, so that
# it can pas messages instead of checking the db
@@ -179,15 +174,23 @@ class ComputeManager(manager.SchedulerDependentManager):
return self.driver.get_console_pool_info(console_type)
@exception.wrap_exception
- def refresh_security_group_rules(self, context,
- security_group_id, **kwargs):
- """This call passes straight through to the virtualization driver."""
+ def refresh_security_group_rules(self, context, security_group_id,
+ **kwargs):
+ """Tell the virtualization driver to refresh security group rules.
+
+ Passes straight through to the virtualization driver.
+
+ """
return self.driver.refresh_security_group_rules(security_group_id)
@exception.wrap_exception
def refresh_security_group_members(self, context,
security_group_id, **kwargs):
- """This call passes straight through to the virtualization driver."""
+ """Tell the virtualization driver to refresh security group members.
+
+ Passes straight through to the virtualization driver.
+
+ """
return self.driver.refresh_security_group_members(security_group_id)
@exception.wrap_exception
@@ -249,7 +252,7 @@ class ComputeManager(manager.SchedulerDependentManager):
@exception.wrap_exception
@checks_instance_lock
def terminate_instance(self, context, instance_id):
- """Terminate an instance on this machine."""
+ """Terminate an instance on this host."""
context = context.elevated()
instance_ref = self.db.instance_get(context, instance_id)
LOG.audit(_("Terminating instance %s"), instance_id, context=context)
@@ -297,7 +300,7 @@ class ComputeManager(manager.SchedulerDependentManager):
@exception.wrap_exception
@checks_instance_lock
def reboot_instance(self, context, instance_id):
- """Reboot an instance on this server."""
+ """Reboot an instance on this host."""
context = context.elevated()
self._update_state(context, instance_id)
instance_ref = self.db.instance_get(context, instance_id)
@@ -321,7 +324,7 @@ class ComputeManager(manager.SchedulerDependentManager):
@exception.wrap_exception
def snapshot_instance(self, context, instance_id, image_id):
- """Snapshot an instance on this server."""
+ """Snapshot an instance on this host."""
context = context.elevated()
instance_ref = self.db.instance_get(context, instance_id)
@@ -344,7 +347,7 @@ class ComputeManager(manager.SchedulerDependentManager):
@exception.wrap_exception
@checks_instance_lock
def set_admin_password(self, context, instance_id, new_pass=None):
- """Set the root/admin password for an instance on this server."""
+ """Set the root/admin password for an instance on this host."""
context = context.elevated()
instance_ref = self.db.instance_get(context, instance_id)
instance_id = instance_ref['id']
@@ -365,7 +368,7 @@ class ComputeManager(manager.SchedulerDependentManager):
@exception.wrap_exception
@checks_instance_lock
def inject_file(self, context, instance_id, path, file_contents):
- """Write a file to the specified path on an instance on this server"""
+ """Write a file to the specified path in an instance on this host."""
context = context.elevated()
instance_ref = self.db.instance_get(context, instance_id)
instance_id = instance_ref['id']
@@ -383,44 +386,34 @@ class ComputeManager(manager.SchedulerDependentManager):
@exception.wrap_exception
@checks_instance_lock
def rescue_instance(self, context, instance_id):
- """Rescue an instance on this server."""
+ """Rescue an instance on this host."""
context = context.elevated()
instance_ref = self.db.instance_get(context, instance_id)
LOG.audit(_('instance %s: rescuing'), instance_id, context=context)
- self.db.instance_set_state(
- context,
- instance_id,
- power_state.NOSTATE,
- 'rescuing')
+ self.db.instance_set_state(context,
+ instance_id,
+ power_state.NOSTATE,
+ 'rescuing')
self.network_manager.setup_compute_network(context, instance_id)
- self.driver.rescue(
- instance_ref,
- lambda result: self._update_state_callback(
- self,
- context,
- instance_id,
- result))
+ _update_state = lambda result: self._update_state_callback(
+ self, context, instance_id, result)
+ self.driver.rescue(instance_ref, _update_state)
self._update_state(context, instance_id)
@exception.wrap_exception
@checks_instance_lock
def unrescue_instance(self, context, instance_id):
- """Rescue an instance on this server."""
+ """Rescue an instance on this host."""
context = context.elevated()
instance_ref = self.db.instance_get(context, instance_id)
LOG.audit(_('instance %s: unrescuing'), instance_id, context=context)
- self.db.instance_set_state(
- context,
- instance_id,
- power_state.NOSTATE,
- 'unrescuing')
- self.driver.unrescue(
- instance_ref,
- lambda result: self._update_state_callback(
- self,
- context,
- instance_id,
- result))
+ self.db.instance_set_state(context,
+ instance_id,
+ power_state.NOSTATE,
+ 'unrescuing')
+ _update_state = lambda result: self._update_state_callback(
+ self, context, instance_id, result)
+ self.driver.unrescue(instance_ref, _update_state)
self._update_state(context, instance_id)
@staticmethod
@@ -431,18 +424,20 @@ class ComputeManager(manager.SchedulerDependentManager):
@exception.wrap_exception
@checks_instance_lock
def confirm_resize(self, context, instance_id, migration_id):
- """Destroys the source instance"""
+ """Destroys the source instance."""
context = context.elevated()
instance_ref = self.db.instance_get(context, instance_id)
- migration_ref = self.db.migration_get(context, migration_id)
self.driver.destroy(instance_ref)
@exception.wrap_exception
@checks_instance_lock
def revert_resize(self, context, instance_id, migration_id):
- """Destroys the new instance on the destination machine,
- reverts the model changes, and powers on the old
- instance on the source machine"""
+ """Destroys the new instance on the destination machine.
+
+ Reverts the model changes, and powers on the old instance on the
+ source machine.
+
+ """
instance_ref = self.db.instance_get(context, instance_id)
migration_ref = self.db.migration_get(context, migration_id)
@@ -459,9 +454,12 @@ class ComputeManager(manager.SchedulerDependentManager):
@exception.wrap_exception
@checks_instance_lock
def finish_revert_resize(self, context, instance_id, migration_id):
- """Finishes the second half of reverting a resize, powering back on
- the source instance and reverting the resized attributes in the
- database"""
+ """Finishes the second half of reverting a resize.
+
+ Power back on the source instance and revert the resized attributes
+ in the database.
+
+ """
instance_ref = self.db.instance_get(context, instance_id)
migration_ref = self.db.migration_get(context, migration_id)
instance_type = self.db.instance_type_get_by_flavor_id(context,
@@ -481,8 +479,11 @@ class ComputeManager(manager.SchedulerDependentManager):
@exception.wrap_exception
@checks_instance_lock
def prep_resize(self, context, instance_id, flavor_id):
- """Initiates the process of moving a running instance to another
- host, possibly changing the RAM and disk size in the process"""
+ """Initiates the process of moving a running instance to another host.
+
+ Possibly changes the RAM and disk size in the process.
+
+ """
context = context.elevated()
instance_ref = self.db.instance_get(context, instance_id)
if instance_ref['host'] == FLAGS.host:
@@ -514,34 +515,38 @@ class ComputeManager(manager.SchedulerDependentManager):
@exception.wrap_exception
@checks_instance_lock
def resize_instance(self, context, instance_id, migration_id):
- """Starts the migration of a running instance to another host"""
+ """Starts the migration of a running instance to another host."""
migration_ref = self.db.migration_get(context, migration_id)
instance_ref = self.db.instance_get(context, instance_id)
- self.db.migration_update(context, migration_id,
- {'status': 'migrating', })
-
- disk_info = self.driver.migrate_disk_and_power_off(instance_ref,
- migration_ref['dest_host'])
- self.db.migration_update(context, migration_id,
- {'status': 'post-migrating', })
-
- service = self.db.service_get_by_host_and_topic(context,
- migration_ref['dest_compute'], FLAGS.compute_topic)
- topic = self.db.queue_get_for(context, FLAGS.compute_topic,
- migration_ref['dest_compute'])
- rpc.cast(context, topic,
- {'method': 'finish_resize',
- 'args': {
- 'migration_id': migration_id,
- 'instance_id': instance_id,
- 'disk_info': disk_info, },
- })
+ self.db.migration_update(context,
+ migration_id,
+ {'status': 'migrating'})
+
+ disk_info = self.driver.migrate_disk_and_power_off(
+ instance_ref, migration_ref['dest_host'])
+ self.db.migration_update(context,
+ migration_id,
+ {'status': 'post-migrating'})
+
+ service = self.db.service_get_by_host_and_topic(
+ context, migration_ref['dest_compute'], FLAGS.compute_topic)
+ topic = self.db.queue_get_for(context,
+ FLAGS.compute_topic,
+ migration_ref['dest_compute'])
+ rpc.cast(context, topic, {'method': 'finish_resize',
+ 'args': {'migration_id': migration_id,
+ 'instance_id': instance_id,
+ 'disk_info': disk_info}})
@exception.wrap_exception
@checks_instance_lock
def finish_resize(self, context, instance_id, migration_id, disk_info):
- """Completes the migration process by setting up the newly transferred
- disk and turning on the instance on its new host machine"""
+ """Completes the migration process.
+
+ Sets up the newly transferred disk and turns on the instance at its
+ new host machine.
+
+ """
migration_ref = self.db.migration_get(context, migration_id)
instance_ref = self.db.instance_get(context,
migration_ref['instance_id'])
@@ -566,7 +571,7 @@ class ComputeManager(manager.SchedulerDependentManager):
@exception.wrap_exception
@checks_instance_lock
def pause_instance(self, context, instance_id):
- """Pause an instance on this server."""
+ """Pause an instance on this host."""
context = context.elevated()
instance_ref = self.db.instance_get(context, instance_id)
LOG.audit(_('instance %s: pausing'), instance_id, context=context)
@@ -583,7 +588,7 @@ class ComputeManager(manager.SchedulerDependentManager):
@exception.wrap_exception
@checks_instance_lock
def unpause_instance(self, context, instance_id):
- """Unpause a paused instance on this server."""
+ """Unpause a paused instance on this host."""
context = context.elevated()
instance_ref = self.db.instance_get(context, instance_id)
LOG.audit(_('instance %s: unpausing'), instance_id, context=context)
@@ -599,7 +604,7 @@ class ComputeManager(manager.SchedulerDependentManager):
@exception.wrap_exception
def get_diagnostics(self, context, instance_id):
- """Retrieve diagnostics for an instance on this server."""
+ """Retrieve diagnostics for an instance on this host."""
instance_ref = self.db.instance_get(context, instance_id)
if instance_ref["state"] == power_state.RUNNING:
@@ -610,10 +615,7 @@ class ComputeManager(manager.SchedulerDependentManager):
@exception.wrap_exception
@checks_instance_lock
def suspend_instance(self, context, instance_id):
- """
- suspend the instance with instance_id
-
- """
+ """Suspend the given instance."""
context = context.elevated()
instance_ref = self.db.instance_get(context, instance_id)
LOG.audit(_('instance %s: suspending'), instance_id, context=context)
@@ -629,10 +631,7 @@ class ComputeManager(manager.SchedulerDependentManager):
@exception.wrap_exception
@checks_instance_lock
def resume_instance(self, context, instance_id):
- """
- resume the suspended instance with instance_id
-
- """
+ """Resume the given suspended instance."""
context = context.elevated()
instance_ref = self.db.instance_get(context, instance_id)
LOG.audit(_('instance %s: resuming'), instance_id, context=context)
@@ -647,34 +646,23 @@ class ComputeManager(manager.SchedulerDependentManager):
@exception.wrap_exception
def lock_instance(self, context, instance_id):
- """
- lock the instance with instance_id
-
- """
+ """Lock the given instance."""
context = context.elevated()
- instance_ref = self.db.instance_get(context, instance_id)
LOG.debug(_('instance %s: locking'), instance_id, context=context)
self.db.instance_update(context, instance_id, {'locked': True})
@exception.wrap_exception
def unlock_instance(self, context, instance_id):
- """
- unlock the instance with instance_id
-
- """
+ """Unlock the given instance."""
context = context.elevated()
- instance_ref = self.db.instance_get(context, instance_id)
LOG.debug(_('instance %s: unlocking'), instance_id, context=context)
self.db.instance_update(context, instance_id, {'locked': False})
@exception.wrap_exception
def get_lock(self, context, instance_id):
- """
- return the boolean state of (instance with instance_id)'s lock
-
- """
+ """Return the boolean state of the given instance's lock."""
context = context.elevated()
LOG.debug(_('instance %s: getting locked state'), instance_id,
context=context)
@@ -683,10 +671,7 @@ class ComputeManager(manager.SchedulerDependentManager):
@checks_instance_lock
def reset_network(self, context, instance_id):
- """
- Reset networking on the instance.
-
- """
+ """Reset networking on the given instance."""
context = context.elevated()
instance_ref = self.db.instance_get(context, instance_id)
LOG.debug(_('instance %s: reset network'), instance_id,
@@ -695,10 +680,7 @@ class ComputeManager(manager.SchedulerDependentManager):
@checks_instance_lock
def inject_network_info(self, context, instance_id):
- """
- Inject network info for the instance.
-
- """
+ """Inject network info for the given instance."""
context = context.elevated()
instance_ref = self.db.instance_get(context, instance_id)
LOG.debug(_('instance %s: inject network info'), instance_id,
@@ -707,7 +689,7 @@ class ComputeManager(manager.SchedulerDependentManager):
@exception.wrap_exception
def get_console_output(self, context, instance_id):
- """Send the console output for an instance."""
+ """Send the console output for the given instance."""
context = context.elevated()
instance_ref = self.db.instance_get(context, instance_id)
LOG.audit(_("Get console output for instance %s"), instance_id,
@@ -716,20 +698,18 @@ class ComputeManager(manager.SchedulerDependentManager):
@exception.wrap_exception
def get_ajax_console(self, context, instance_id):
- """Return connection information for an ajax console"""
+ """Return connection information for an ajax console."""
context = context.elevated()
LOG.debug(_("instance %s: getting ajax console"), instance_id)
instance_ref = self.db.instance_get(context, instance_id)
-
return self.driver.get_ajax_console(instance_ref)
@exception.wrap_exception
def get_vnc_console(self, context, instance_id):
- """Return connection information for an vnc console."""
+ """Return connection information for a vnc console."""
context = context.elevated()
LOG.debug(_("instance %s: getting vnc console"), instance_id)
instance_ref = self.db.instance_get(context, instance_id)
-
return self.driver.get_vnc_console(instance_ref)
@checks_instance_lock
@@ -783,7 +763,7 @@ class ComputeManager(manager.SchedulerDependentManager):
@exception.wrap_exception
def compare_cpu(self, context, cpu_info):
- """Checks the host cpu is compatible to a cpu given by xml.
+ """Checks that the host cpu is compatible with a cpu given by xml.
:param context: security context
:param cpu_info: json string obtained from virConnect.getCapabilities
@@ -804,7 +784,6 @@ class ComputeManager(manager.SchedulerDependentManager):
:returns: tmpfile name(basename)
"""
-
dirpath = FLAGS.instances_path
fd, tmp_file = tempfile.mkstemp(dir=dirpath)
LOG.debug(_("Creating tmpfile %s to notify to other "
@@ -821,7 +800,6 @@ class ComputeManager(manager.SchedulerDependentManager):
:param filename: confirm existence of FLAGS.instances_path/thisfile
"""
-
tmp_file = os.path.join(FLAGS.instances_path, filename)
if not os.path.exists(tmp_file):
raise exception.NotFound(_('%s not found') % tmp_file)
@@ -834,7 +812,6 @@ class ComputeManager(manager.SchedulerDependentManager):
:param filename: remove existence of FLAGS.instances_path/thisfile
"""
-
tmp_file = os.path.join(FLAGS.instances_path, filename)
os.remove(tmp_file)
@@ -846,7 +823,6 @@ class ComputeManager(manager.SchedulerDependentManager):
:returns: See driver.update_available_resource()
"""
-
return self.driver.update_available_resource(context, self.host)
def pre_live_migration(self, context, instance_id, time=None):
@@ -856,7 +832,6 @@ class ComputeManager(manager.SchedulerDependentManager):
:param instance_id: nova.db.sqlalchemy.models.Instance.Id
"""
-
if not time:
time = greenthread
@@ -915,7 +890,6 @@ class ComputeManager(manager.SchedulerDependentManager):
:param dest: destination host
"""
-
# Get instance for error handling.
instance_ref = self.db.instance_get(context, instance_id)
i_name = instance_ref.name
@@ -1011,12 +985,10 @@ class ComputeManager(manager.SchedulerDependentManager):
:param ctxt: security context
:param instance_id: nova.db.sqlalchemy.models.Instance.Id
- :param host:
- DB column value is updated by this hostname.
- if none, the host instance currently running is selected.
+ :param host: DB column value is updated by this hostname.
+ If none, the host instance currently running is selected.
"""
-
if not host:
host = instance_ref['host']
@@ -1090,6 +1062,14 @@ class ComputeManager(manager.SchedulerDependentManager):
vm_state = vm_instance.state
vms_not_found_in_db.remove(name)
+ if db_instance['state_description'] == 'migrating':
+ # A situation which db record exists, but no instance"
+ # sometimes occurs while live-migration at src compute,
+ # this case should be ignored.
+ LOG.debug(_("Ignoring %(name)s, as it's currently being "
+ "migrated.") % locals())
+ continue
+
if vm_state != db_state:
LOG.info(_("DB/VM state mismatch. Changing state from "
"'%(db_state)s' to '%(vm_state)s'") % locals())
@@ -1097,16 +1077,15 @@ class ComputeManager(manager.SchedulerDependentManager):
db_instance['id'],
vm_state)
- if vm_state == power_state.SHUTOFF:
- # TODO(soren): This is what the compute manager does when you
- # terminate an instance. At some point I figure we'll have a
- # "terminated" state and some sort of cleanup job that runs
- # occasionally, cleaning them out.
- self.db.instance_destroy(context, db_instance['id'])
+ # NOTE(justinsb): We no longer auto-remove SHUTOFF instances
+ # It's quite hard to get them back when we do.
# Are there VMs not in the DB?
for vm_not_found_in_db in vms_not_found_in_db:
name = vm_not_found_in_db
- # TODO(justinsb): What to do here? Adopt it? Shut it down?
- LOG.warning(_("Found VM not in DB: '%(name)s'. Ignoring")
- % locals())
+
+ # We only care about instances that compute *should* know about
+ if name.startswith("instance-"):
+ # TODO(justinsb): What to do here? Adopt it? Shut it down?
+ LOG.warning(_("Found VM not in DB: '%(name)s'. Ignoring")
+ % locals())
diff --git a/nova/compute/monitor.py b/nova/compute/monitor.py
index 04e08a235..3bb54a382 100644
--- a/nova/compute/monitor.py
+++ b/nova/compute/monitor.py
@@ -260,7 +260,7 @@ class Instance(object):
try:
data = self.fetch_cpu_stats()
- if data != None:
+ if data is not None:
LOG.debug('CPU: %s', data)
update_rrd(self, 'cpu', data)
@@ -313,7 +313,7 @@ class Instance(object):
LOG.debug('CPU: %d', self.cputime)
# Skip calculation on first pass. Need delta to get a meaningful value.
- if cputime_last_updated == None:
+ if cputime_last_updated is None:
return None
# Calculate the number of seconds between samples.
diff --git a/nova/console/api.py b/nova/console/api.py
index 3850d2c44..137ddcaac 100644
--- a/nova/console/api.py
+++ b/nova/console/api.py
@@ -15,23 +15,19 @@
# License for the specific language governing permissions and limitations
# under the License.
-"""
-Handles ConsoleProxy API requests
-"""
+"""Handles ConsoleProxy API requests."""
from nova import exception
-from nova.db import base
-
-
from nova import flags
from nova import rpc
+from nova.db import base
FLAGS = flags.FLAGS
class API(base.Base):
- """API for spining up or down console proxy connections"""
+ """API for spinning up or down console proxy connections."""
def __init__(self, **kwargs):
super(API, self).__init__(**kwargs)
@@ -51,8 +47,8 @@ class API(base.Base):
self.db.queue_get_for(context,
FLAGS.console_topic,
pool['host']),
- {"method": "remove_console",
- "args": {"console_id": console['id']}})
+ {'method': 'remove_console',
+ 'args': {'console_id': console['id']}})
def create_console(self, context, instance_id):
instance = self.db.instance_get(context, instance_id)
@@ -63,13 +59,12 @@ class API(base.Base):
# here.
rpc.cast(context,
self._get_console_topic(context, instance['host']),
- {"method": "add_console",
- "args": {"instance_id": instance_id}})
+ {'method': 'add_console',
+ 'args': {'instance_id': instance_id}})
def _get_console_topic(self, context, instance_host):
topic = self.db.queue_get_for(context,
FLAGS.compute_topic,
instance_host)
- return rpc.call(context,
- topic,
- {"method": "get_console_topic", "args": {'fake': 1}})
+ return rpc.call(context, topic, {'method': 'get_console_topic',
+ 'args': {'fake': 1}})
diff --git a/nova/console/fake.py b/nova/console/fake.py
index 7a90d5221..e2eb886f8 100644
--- a/nova/console/fake.py
+++ b/nova/console/fake.py
@@ -15,9 +15,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-"""
-Fake ConsoleProxy driver for tests.
-"""
+"""Fake ConsoleProxy driver for tests."""
from nova import exception
@@ -27,32 +25,32 @@ class FakeConsoleProxy(object):
@property
def console_type(self):
- return "fake"
+ return 'fake'
def setup_console(self, context, console):
- """Sets up actual proxies"""
+ """Sets up actual proxies."""
pass
def teardown_console(self, context, console):
- """Tears down actual proxies"""
+ """Tears down actual proxies."""
pass
def init_host(self):
- """Start up any config'ed consoles on start"""
+ """Start up any config'ed consoles on start."""
pass
def generate_password(self, length=8):
- """Returns random console password"""
- return "fakepass"
+ """Returns random console password."""
+ return 'fakepass'
def get_port(self, context):
- """get available port for consoles that need one"""
+ """Get available port for consoles that need one."""
return 5999
def fix_pool_password(self, password):
- """Trim password to length, and any other massaging"""
+ """Trim password to length, and any other massaging."""
return password
def fix_console_password(self, password):
- """Trim password to length, and any other massaging"""
+ """Trim password to length, and any other massaging."""
return password
diff --git a/nova/console/manager.py b/nova/console/manager.py
index bfa571ea9..e0db21666 100644
--- a/nova/console/manager.py
+++ b/nova/console/manager.py
@@ -15,9 +15,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-"""
-Console Proxy Service
-"""
+"""Console Proxy Service."""
import functools
import socket
@@ -29,6 +27,7 @@ from nova import manager
from nova import rpc
from nova import utils
+
FLAGS = flags.FLAGS
flags.DEFINE_string('console_driver',
'nova.console.xvp.XVPConsoleProxy',
@@ -41,9 +40,11 @@ flags.DEFINE_string('console_public_hostname',
class ConsoleProxyManager(manager.Manager):
+ """Sets up and tears down any console proxy connections.
+
+ Needed for accessing instance consoles securely.
- """ Sets up and tears down any proxy connections needed for accessing
- instance consoles securely"""
+ """
def __init__(self, console_driver=None, *args, **kwargs):
if not console_driver:
@@ -67,7 +68,7 @@ class ConsoleProxyManager(manager.Manager):
pool['id'],
instance_id)
except exception.NotFound:
- logging.debug(_("Adding console"))
+ logging.debug(_('Adding console'))
if not password:
password = utils.generate_password(8)
if not port:
@@ -115,8 +116,8 @@ class ConsoleProxyManager(manager.Manager):
self.db.queue_get_for(context,
FLAGS.compute_topic,
instance_host),
- {"method": "get_console_pool_info",
- "args": {"console_type": console_type}})
+ {'method': 'get_console_pool_info',
+ 'args': {'console_type': console_type}})
pool_info['password'] = self.driver.fix_pool_password(
pool_info['password'])
pool_info['host'] = self.host
diff --git a/nova/console/vmrc.py b/nova/console/vmrc.py
index 521da289f..127d31121 100644
--- a/nova/console/vmrc.py
+++ b/nova/console/vmrc.py
@@ -15,9 +15,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-"""
-VMRC console drivers.
-"""
+"""VMRC console drivers."""
import base64
import json
@@ -27,6 +25,8 @@ from nova import flags
from nova import log as logging
from nova.virt.vmwareapi import vim_util
+
+FLAGS = flags.FLAGS
flags.DEFINE_integer('console_vmrc_port',
443,
"port for VMware VMRC connections")
@@ -34,8 +34,6 @@ flags.DEFINE_integer('console_vmrc_error_retries',
10,
"number of retries for retrieving VMRC information")
-FLAGS = flags.FLAGS
-
class VMRCConsole(object):
"""VMRC console driver with ESX credentials."""
@@ -69,34 +67,34 @@ class VMRCConsole(object):
return password
def generate_password(self, vim_session, pool, instance_name):
- """
- Returns VMRC Connection credentials.
+ """Returns VMRC Connection credentials.
Return string is of the form '<VM PATH>:<ESX Username>@<ESX Password>'.
+
"""
username, password = pool['username'], pool['password']
- vms = vim_session._call_method(vim_util, "get_objects",
- "VirtualMachine", ["name", "config.files.vmPathName"])
+ vms = vim_session._call_method(vim_util, 'get_objects',
+ 'VirtualMachine', ['name', 'config.files.vmPathName'])
vm_ds_path_name = None
vm_ref = None
for vm in vms:
vm_name = None
ds_path_name = None
for prop in vm.propSet:
- if prop.name == "name":
+ if prop.name == 'name':
vm_name = prop.val
- elif prop.name == "config.files.vmPathName":
+ elif prop.name == 'config.files.vmPathName':
ds_path_name = prop.val
if vm_name == instance_name:
vm_ref = vm.obj
vm_ds_path_name = ds_path_name
break
if vm_ref is None:
- raise exception.NotFound(_("instance - %s not present") %
+ raise exception.NotFound(_('instance - %s not present') %
instance_name)
- json_data = json.dumps({"vm_id": vm_ds_path_name,
- "username": username,
- "password": password})
+ json_data = json.dumps({'vm_id': vm_ds_path_name,
+ 'username': username,
+ 'password': password})
return base64.b64encode(json_data)
def is_otp(self):
@@ -115,28 +113,28 @@ class VMRCSessionConsole(VMRCConsole):
return 'vmrc+session'
def generate_password(self, vim_session, pool, instance_name):
- """
- Returns a VMRC Session.
+ """Returns a VMRC Session.
Return string is of the form '<VM MOID>:<VMRC Ticket>'.
+
"""
- vms = vim_session._call_method(vim_util, "get_objects",
- "VirtualMachine", ["name"])
- vm_ref = None
+ vms = vim_session._call_method(vim_util, 'get_objects',
+ 'VirtualMachine', ['name'])
+ vm_ref = NoneV
for vm in vms:
if vm.propSet[0].val == instance_name:
vm_ref = vm.obj
if vm_ref is None:
- raise exception.NotFound(_("instance - %s not present") %
+ raise exception.NotFound(_('instance - %s not present') %
instance_name)
virtual_machine_ticket = \
vim_session._call_method(
vim_session._get_vim(),
- "AcquireCloneTicket",
+ 'AcquireCloneTicket',
vim_session._get_vim().get_service_content().sessionManager)
- json_data = json.dumps({"vm_id": str(vm_ref.value),
- "username": virtual_machine_ticket,
- "password": virtual_machine_ticket})
+ json_data = json.dumps({'vm_id': str(vm_ref.value),
+ 'username': virtual_machine_ticket,
+ 'password': virtual_machine_ticket})
return base64.b64encode(json_data)
def is_otp(self):
diff --git a/nova/console/vmrc_manager.py b/nova/console/vmrc_manager.py
index 09beac7a0..acecc1075 100644
--- a/nova/console/vmrc_manager.py
+++ b/nova/console/vmrc_manager.py
@@ -15,9 +15,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-"""
-VMRC Console Manager.
-"""
+"""VMRC Console Manager."""
from nova import exception
from nova import flags
@@ -25,24 +23,21 @@ from nova import log as logging
from nova import manager
from nova import rpc
from nova import utils
-from nova.virt.vmwareapi_conn import VMWareAPISession
+from nova.virt import vmwareapi_conn
+
LOG = logging.getLogger("nova.console.vmrc_manager")
+
FLAGS = flags.FLAGS
-flags.DEFINE_string('console_public_hostname',
- '',
+flags.DEFINE_string('console_public_hostname', '',
'Publicly visible name for this console host')
-flags.DEFINE_string('console_driver',
- 'nova.console.vmrc.VMRCConsole',
+flags.DEFINE_string('console_driver', 'nova.console.vmrc.VMRCConsole',
'Driver to use for the console')
class ConsoleVMRCManager(manager.Manager):
-
- """
- Manager to handle VMRC connections needed for accessing instance consoles.
- """
+ """Manager to handle VMRC connections for accessing instance consoles."""
def __init__(self, console_driver=None, *args, **kwargs):
self.driver = utils.import_object(FLAGS.console_driver)
@@ -56,16 +51,17 @@ class ConsoleVMRCManager(manager.Manager):
"""Get VIM session for the pool specified."""
vim_session = None
if pool['id'] not in self.sessions.keys():
- vim_session = VMWareAPISession(pool['address'],
- pool['username'],
- pool['password'],
- FLAGS.console_vmrc_error_retries)
+ vim_session = vmwareapi_conn.VMWareAPISession(
+ pool['address'],
+ pool['username'],
+ pool['password'],
+ FLAGS.console_vmrc_error_retries)
self.sessions[pool['id']] = vim_session
return self.sessions[pool['id']]
def _generate_console(self, context, pool, name, instance_id, instance):
"""Sets up console for the instance."""
- LOG.debug(_("Adding console"))
+ LOG.debug(_('Adding console'))
password = self.driver.generate_password(
self._get_vim_session(pool),
@@ -84,9 +80,10 @@ class ConsoleVMRCManager(manager.Manager):
@exception.wrap_exception
def add_console(self, context, instance_id, password=None,
port=None, **kwargs):
- """
- Adds a console for the instance. If it is one time password, then we
- generate new console credentials.
+ """Adds a console for the instance.
+
+ If it is one time password, then we generate new console credentials.
+
"""
instance = self.db.instance_get(context, instance_id)
host = instance['host']
@@ -97,19 +94,17 @@ class ConsoleVMRCManager(manager.Manager):
pool['id'],
instance_id)
if self.driver.is_otp():
- console = self._generate_console(
- context,
- pool,
- name,
- instance_id,
- instance)
+ console = self._generate_console(context,
+ pool,
+ name,
+ instance_id,
+ instance)
except exception.NotFound:
- console = self._generate_console(
- context,
- pool,
- name,
- instance_id,
- instance)
+ console = self._generate_console(context,
+ pool,
+ name,
+ instance_id,
+ instance)
return console['id']
@exception.wrap_exception
@@ -118,13 +113,11 @@ class ConsoleVMRCManager(manager.Manager):
try:
console = self.db.console_get(context, console_id)
except exception.NotFound:
- LOG.debug(_("Tried to remove non-existent console "
- "%(console_id)s.") %
- {'console_id': console_id})
+ LOG.debug(_('Tried to remove non-existent console '
+ '%(console_id)s.') % {'console_id': console_id})
return
- LOG.debug(_("Removing console "
- "%(console_id)s.") %
- {'console_id': console_id})
+ LOG.debug(_('Removing console '
+ '%(console_id)s.') % {'console_id': console_id})
self.db.console_delete(context, console_id)
self.driver.teardown_console(context, console)
@@ -139,11 +132,11 @@ class ConsoleVMRCManager(manager.Manager):
console_type)
except exception.NotFound:
pool_info = rpc.call(context,
- self.db.queue_get_for(context,
- FLAGS.compute_topic,
- instance_host),
- {"method": "get_console_pool_info",
- "args": {"console_type": console_type}})
+ self.db.queue_get_for(context,
+ FLAGS.compute_topic,
+ instance_host),
+ {'method': 'get_console_pool_info',
+ 'args': {'console_type': console_type}})
pool_info['password'] = self.driver.fix_pool_password(
pool_info['password'])
pool_info['host'] = self.host
diff --git a/nova/console/xvp.py b/nova/console/xvp.py
index 0cedfbb13..3cd287183 100644
--- a/nova/console/xvp.py
+++ b/nova/console/xvp.py
@@ -15,16 +15,14 @@
# License for the specific language governing permissions and limitations
# under the License.
-"""
-XVP (Xenserver VNC Proxy) driver.
-"""
+"""XVP (Xenserver VNC Proxy) driver."""
import fcntl
import os
import signal
import subprocess
-from Cheetah.Template import Template
+from Cheetah import Template
from nova import context
from nova import db
@@ -33,6 +31,8 @@ from nova import flags
from nova import log as logging
from nova import utils
+
+FLAGS = flags.FLAGS
flags.DEFINE_string('console_xvp_conf_template',
utils.abspath('console/xvp.conf.template'),
'XVP conf template')
@@ -47,12 +47,11 @@ flags.DEFINE_string('console_xvp_log',
'XVP log file')
flags.DEFINE_integer('console_xvp_multiplex_port',
5900,
- "port for XVP to multiplex VNC connections on")
-FLAGS = flags.FLAGS
+ 'port for XVP to multiplex VNC connections on')
class XVPConsoleProxy(object):
- """Sets up XVP config, and manages xvp daemon"""
+ """Sets up XVP config, and manages XVP daemon."""
def __init__(self):
self.xvpconf_template = open(FLAGS.console_xvp_conf_template).read()
@@ -61,50 +60,51 @@ class XVPConsoleProxy(object):
@property
def console_type(self):
- return "vnc+xvp"
+ return 'vnc+xvp'
def get_port(self, context):
- """get available port for consoles that need one"""
+ """Get available port for consoles that need one."""
#TODO(mdragon): implement port selection for non multiplex ports,
# we are not using that, but someone else may want
# it.
return FLAGS.console_xvp_multiplex_port
def setup_console(self, context, console):
- """Sets up actual proxies"""
+ """Sets up actual proxies."""
self._rebuild_xvp_conf(context.elevated())
def teardown_console(self, context, console):
- """Tears down actual proxies"""
+ """Tears down actual proxies."""
self._rebuild_xvp_conf(context.elevated())
def init_host(self):
- """Start up any config'ed consoles on start"""
+ """Start up any config'ed consoles on start."""
ctxt = context.get_admin_context()
self._rebuild_xvp_conf(ctxt)
def fix_pool_password(self, password):
- """Trim password to length, and encode"""
+ """Trim password to length, and encode."""
return self._xvp_encrypt(password, is_pool_password=True)
def fix_console_password(self, password):
- """Trim password to length, and encode"""
+ """Trim password to length, and encode."""
return self._xvp_encrypt(password)
def _rebuild_xvp_conf(self, context):
- logging.debug(_("Rebuilding xvp conf"))
+ logging.debug(_('Rebuilding xvp conf'))
pools = [pool for pool in
db.console_pool_get_all_by_host_type(context, self.host,
self.console_type)
if pool['consoles']]
if not pools:
- logging.debug("No console pools!")
+ logging.debug('No console pools!')
self._xvp_stop()
return
conf_data = {'multiplex_port': FLAGS.console_xvp_multiplex_port,
'pools': pools,
'pass_encode': self.fix_console_password}
- config = str(Template(self.xvpconf_template, searchList=[conf_data]))
+ config = str(Template.Template(self.xvpconf_template,
+ searchList=[conf_data]))
self._write_conf(config)
self._xvp_restart()
@@ -114,7 +114,7 @@ class XVPConsoleProxy(object):
cfile.write(config)
def _xvp_stop(self):
- logging.debug(_("Stopping xvp"))
+ logging.debug(_('Stopping xvp'))
pid = self._xvp_pid()
if not pid:
return
@@ -127,19 +127,19 @@ class XVPConsoleProxy(object):
def _xvp_start(self):
if self._xvp_check_running():
return
- logging.debug(_("Starting xvp"))
+ logging.debug(_('Starting xvp'))
try:
utils.execute('xvp',
'-p', FLAGS.console_xvp_pid,
'-c', FLAGS.console_xvp_conf,
'-l', FLAGS.console_xvp_log)
except exception.ProcessExecutionError, err:
- logging.error(_("Error starting xvp: %s") % err)
+ logging.error(_('Error starting xvp: %s') % err)
def _xvp_restart(self):
- logging.debug(_("Restarting xvp"))
+ logging.debug(_('Restarting xvp'))
if not self._xvp_check_running():
- logging.debug(_("xvp not running..."))
+ logging.debug(_('xvp not running...'))
self._xvp_start()
else:
pid = self._xvp_pid()
@@ -178,7 +178,9 @@ class XVPConsoleProxy(object):
Note that xvp's obfuscation should not be considered 'real' encryption.
It simply DES encrypts the passwords with static keys plainly viewable
- in the xvp source code."""
+ in the xvp source code.
+
+ """
maxlen = 8
flag = '-e'
if is_pool_password:
diff --git a/nova/context.py b/nova/context.py
index 0256bf448..c113f7ea7 100644
--- a/nova/context.py
+++ b/nova/context.py
@@ -16,9 +16,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-"""
-RequestContext: context for requests that persist through all of nova.
-"""
+"""RequestContext: context for requests that persist through all of nova."""
import datetime
import random
@@ -28,6 +26,12 @@ from nova import utils
class RequestContext(object):
+ """Security context and request information.
+
+ Represents the user taking a given action within the system.
+
+ """
+
def __init__(self, user, project, is_admin=None, read_deleted=False,
remote_address=None, timestamp=None, request_id=None):
if hasattr(user, 'id'):
diff --git a/nova/crypto.py b/nova/crypto.py
index 2b122e560..14b9cbef6 100644
--- a/nova/crypto.py
+++ b/nova/crypto.py
@@ -15,10 +15,11 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
-"""
-Wrappers around standard crypto data elements.
+
+"""Wrappers around standard crypto data elements.
Includes root and intermediate CAs, SSH key_pairs and x509 certificates.
+
"""
import base64
@@ -43,6 +44,8 @@ from nova import log as logging
LOG = logging.getLogger("nova.crypto")
+
+
FLAGS = flags.FLAGS
flags.DEFINE_string('ca_file', 'cacert.pem', _('Filename of root CA'))
flags.DEFINE_string('key_file',
@@ -90,13 +93,13 @@ def key_path(project_id=None):
def fetch_ca(project_id=None, chain=True):
if not FLAGS.use_project_ca:
project_id = None
- buffer = ""
+ buffer = ''
if project_id:
- with open(ca_path(project_id), "r") as cafile:
+ with open(ca_path(project_id), 'r') as cafile:
buffer += cafile.read()
if not chain:
return buffer
- with open(ca_path(None), "r") as cafile:
+ with open(ca_path(None), 'r') as cafile:
buffer += cafile.read()
return buffer
@@ -143,7 +146,7 @@ def ssl_pub_to_ssh_pub(ssl_public_key, name='root', suffix='nova'):
def revoke_cert(project_id, file_name):
- """Revoke a cert by file name"""
+ """Revoke a cert by file name."""
start = os.getcwd()
os.chdir(ca_folder(project_id))
# NOTE(vish): potential race condition here
@@ -155,14 +158,14 @@ def revoke_cert(project_id, file_name):
def revoke_certs_by_user(user_id):
- """Revoke all user certs"""
+ """Revoke all user certs."""
admin = context.get_admin_context()
for cert in db.certificate_get_all_by_user(admin, user_id):
revoke_cert(cert['project_id'], cert['file_name'])
def revoke_certs_by_project(project_id):
- """Revoke all project certs"""
+ """Revoke all project certs."""
# NOTE(vish): This is somewhat useless because we can just shut down
# the vpn.
admin = context.get_admin_context()
@@ -171,29 +174,29 @@ def revoke_certs_by_project(project_id):
def revoke_certs_by_user_and_project(user_id, project_id):
- """Revoke certs for user in project"""
+ """Revoke certs for user in project."""
admin = context.get_admin_context()
for cert in db.certificate_get_all_by_user(admin, user_id, project_id):
revoke_cert(cert['project_id'], cert['file_name'])
def _project_cert_subject(project_id):
- """Helper to generate user cert subject"""
+ """Helper to generate user cert subject."""
return FLAGS.project_cert_subject % (project_id, utils.isotime())
def _vpn_cert_subject(project_id):
- """Helper to generate user cert subject"""
+ """Helper to generate user cert subject."""
return FLAGS.vpn_cert_subject % (project_id, utils.isotime())
def _user_cert_subject(user_id, project_id):
- """Helper to generate user cert subject"""
+ """Helper to generate user cert subject."""
return FLAGS.user_cert_subject % (project_id, user_id, utils.isotime())
def generate_x509_cert(user_id, project_id, bits=1024):
- """Generate and sign a cert for user in project"""
+ """Generate and sign a cert for user in project."""
subject = _user_cert_subject(user_id, project_id)
tmpdir = tempfile.mkdtemp()
keyfile = os.path.abspath(os.path.join(tmpdir, 'temp.key'))
@@ -205,7 +208,7 @@ def generate_x509_cert(user_id, project_id, bits=1024):
csr = open(csrfile).read()
shutil.rmtree(tmpdir)
(serial, signed_csr) = sign_csr(csr, project_id)
- fname = os.path.join(ca_folder(project_id), "newcerts/%s.pem" % serial)
+ fname = os.path.join(ca_folder(project_id), 'newcerts/%s.pem' % serial)
cert = {'user_id': user_id,
'project_id': project_id,
'file_name': fname}
@@ -227,12 +230,12 @@ def _ensure_project_folder(project_id):
def generate_vpn_files(project_id):
project_folder = ca_folder(project_id)
- csr_fn = os.path.join(project_folder, "server.csr")
- crt_fn = os.path.join(project_folder, "server.crt")
+ csr_fn = os.path.join(project_folder, 'server.csr')
+ crt_fn = os.path.join(project_folder, 'server.crt')
genvpn_sh_path = os.path.join(os.path.dirname(__file__),
'CA',
- 'geninter.sh')
+ 'genvpn.sh')
if os.path.exists(crt_fn):
return
_ensure_project_folder(project_id)
@@ -241,10 +244,10 @@ def generate_vpn_files(project_id):
# TODO(vish): the shell scripts could all be done in python
utils.execute('sh', genvpn_sh_path,
project_id, _vpn_cert_subject(project_id))
- with open(csr_fn, "r") as csrfile:
+ with open(csr_fn, 'r') as csrfile:
csr_text = csrfile.read()
(serial, signed_csr) = sign_csr(csr_text, project_id)
- with open(crt_fn, "w") as crtfile:
+ with open(crt_fn, 'w') as crtfile:
crtfile.write(signed_csr)
os.chdir(start)
@@ -261,26 +264,28 @@ def sign_csr(csr_text, project_id=None):
def _sign_csr(csr_text, ca_folder):
tmpfolder = tempfile.mkdtemp()
- inbound = os.path.join(tmpfolder, "inbound.csr")
- outbound = os.path.join(tmpfolder, "outbound.csr")
- csrfile = open(inbound, "w")
+ inbound = os.path.join(tmpfolder, 'inbound.csr')
+ outbound = os.path.join(tmpfolder, 'outbound.csr')
+ csrfile = open(inbound, 'w')
csrfile.write(csr_text)
csrfile.close()
- LOG.debug(_("Flags path: %s"), ca_folder)
+ LOG.debug(_('Flags path: %s'), ca_folder)
start = os.getcwd()
# Change working dir to CA
+ if not os.path.exists(ca_folder):
+ os.makedirs(ca_folder)
os.chdir(ca_folder)
utils.execute('openssl', 'ca', '-batch', '-out', outbound, '-config',
'./openssl.cnf', '-infiles', inbound)
out, _err = utils.execute('openssl', 'x509', '-in', outbound,
'-serial', '-noout')
- serial = string.strip(out.rpartition("=")[2])
+ serial = string.strip(out.rpartition('=')[2])
os.chdir(start)
- with open(outbound, "r") as crtfile:
+ with open(outbound, 'r') as crtfile:
return (serial, crtfile.read())
-def mkreq(bits, subject="foo", ca=0):
+def mkreq(bits, subject='foo', ca=0):
pk = M2Crypto.EVP.PKey()
req = M2Crypto.X509.Request()
rsa = M2Crypto.RSA.gen_key(bits, 65537, callback=lambda: None)
@@ -312,7 +317,7 @@ def mkcacert(subject='nova', years=1):
cert.set_not_before(now)
cert.set_not_after(nowPlusYear)
issuer = M2Crypto.X509.X509_Name()
- issuer.C = "US"
+ issuer.C = 'US'
issuer.CN = subject
cert.set_issuer(issuer)
cert.set_pubkey(pkey)
@@ -350,13 +355,15 @@ def mkcacert(subject='nova', years=1):
# http://code.google.com/p/boto
def compute_md5(fp):
- """
+ """Compute an md5 hash.
+
:type fp: file
:param fp: File pointer to the file to MD5 hash. The file pointer will be
reset to the beginning of the file before the method returns.
:rtype: tuple
- :return: the hex digest version of the MD5 hash
+ :returns: the hex digest version of the MD5 hash
+
"""
m = hashlib.md5()
fp.seek(0)
diff --git a/nova/db/api.py b/nova/db/api.py
index fd3c63b76..1b33d8932 100644
--- a/nova/db/api.py
+++ b/nova/db/api.py
@@ -15,8 +15,8 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
-"""
-Defines interface for DB access.
+
+"""Defines interface for DB access.
The underlying driver is loaded as a :class:`LazyPluggable`.
@@ -30,6 +30,7 @@ The underlying driver is loaded as a :class:`LazyPluggable`.
:enable_new_services: when adding a new service to the database, is it in the
pool of available hardware (Default: True)
+
"""
from nova import exception
@@ -86,7 +87,7 @@ def service_get(context, service_id):
def service_get_by_host_and_topic(context, host, topic):
- """Get a service by host it's on and topic it listens to"""
+ """Get a service by host it's on and topic it listens to."""
return IMPL.service_get_by_host_and_topic(context, host, topic)
@@ -113,7 +114,7 @@ def service_get_all_compute_by_host(context, host):
def service_get_all_compute_sorted(context):
"""Get all compute services sorted by instance count.
- Returns a list of (Service, instance_count) tuples.
+ :returns: a list of (Service, instance_count) tuples.
"""
return IMPL.service_get_all_compute_sorted(context)
@@ -122,7 +123,7 @@ def service_get_all_compute_sorted(context):
def service_get_all_network_sorted(context):
"""Get all network services sorted by network count.
- Returns a list of (Service, network_count) tuples.
+ :returns: a list of (Service, network_count) tuples.
"""
return IMPL.service_get_all_network_sorted(context)
@@ -131,7 +132,7 @@ def service_get_all_network_sorted(context):
def service_get_all_volume_sorted(context):
"""Get all volume services sorted by volume count.
- Returns a list of (Service, volume_count) tuples.
+ :returns: a list of (Service, volume_count) tuples.
"""
return IMPL.service_get_all_volume_sorted(context)
@@ -241,7 +242,7 @@ def floating_ip_count_by_project(context, project_id):
def floating_ip_deallocate(context, address):
- """Deallocate an floating ip by address"""
+ """Deallocate an floating ip by address."""
return IMPL.floating_ip_deallocate(context, address)
@@ -253,7 +254,7 @@ def floating_ip_destroy(context, address):
def floating_ip_disassociate(context, address):
"""Disassociate an floating ip from a fixed ip by address.
- Returns the address of the existing fixed ip.
+ :returns: the address of the existing fixed ip.
"""
return IMPL.floating_ip_disassociate(context, address)
@@ -294,22 +295,22 @@ def floating_ip_update(context, address, values):
####################
def migration_update(context, id, values):
- """Update a migration instance"""
+ """Update a migration instance."""
return IMPL.migration_update(context, id, values)
def migration_create(context, values):
- """Create a migration record"""
+ """Create a migration record."""
return IMPL.migration_create(context, values)
def migration_get(context, migration_id):
- """Finds a migration by the id"""
+ """Finds a migration by the id."""
return IMPL.migration_get(context, migration_id)
def migration_get_by_instance_and_status(context, instance_id, status):
- """Finds a migration by the instance id its migrating"""
+ """Finds a migration by the instance id its migrating."""
return IMPL.migration_get_by_instance_and_status(context, instance_id,
status)
@@ -579,7 +580,9 @@ def network_create_safe(context, values):
def network_delete_safe(context, network_id):
"""Delete network with key network_id.
+
This method assumes that the network is not associated with any project
+
"""
return IMPL.network_delete_safe(context, network_id)
@@ -674,7 +677,6 @@ def project_get_network(context, project_id, associate=True):
network if one is not found, otherwise it returns None.
"""
-
return IMPL.project_get_network(context, project_id, associate)
@@ -722,7 +724,9 @@ def iscsi_target_create_safe(context, values):
The device is not returned. If the create violates the unique
constraints because the iscsi_target and host already exist,
- no exception is raised."""
+ no exception is raised.
+
+ """
return IMPL.iscsi_target_create_safe(context, values)
@@ -1050,10 +1054,7 @@ def project_delete(context, project_id):
def host_get_networks(context, host):
- """Return all networks for which the given host is the designated
- network host.
-
- """
+ """All networks for which the given host is the network host."""
return IMPL.host_get_networks(context, host)
@@ -1115,33 +1116,40 @@ def console_get(context, console_id, instance_id=None):
def instance_type_create(context, values):
- """Create a new instance type"""
+ """Create a new instance type."""
return IMPL.instance_type_create(context, values)
def instance_type_get_all(context, inactive=False):
- """Get all instance types"""
+ """Get all instance types."""
return IMPL.instance_type_get_all(context, inactive)
+def instance_type_get_by_id(context, id):
+ """Get instance type by id."""
+ return IMPL.instance_type_get_by_id(context, id)
+
+
def instance_type_get_by_name(context, name):
- """Get instance type by name"""
+ """Get instance type by name."""
return IMPL.instance_type_get_by_name(context, name)
def instance_type_get_by_flavor_id(context, id):
- """Get instance type by name"""
+ """Get instance type by name."""
return IMPL.instance_type_get_by_flavor_id(context, id)
def instance_type_destroy(context, name):
- """Delete a instance type"""
+ """Delete a instance type."""
return IMPL.instance_type_destroy(context, name)
def instance_type_purge(context, name):
- """Purges (removes) an instance type from DB
- Use instance_type_destroy for most cases
+ """Purges (removes) an instance type from DB.
+
+ Use instance_type_destroy for most cases
+
"""
return IMPL.instance_type_purge(context, name)
@@ -1178,15 +1186,15 @@ def zone_get_all(context):
def instance_metadata_get(context, instance_id):
- """Get all metadata for an instance"""
+ """Get all metadata for an instance."""
return IMPL.instance_metadata_get(context, instance_id)
def instance_metadata_delete(context, instance_id, key):
- """Delete the given metadata item"""
+ """Delete the given metadata item."""
IMPL.instance_metadata_delete(context, instance_id, key)
def instance_metadata_update_or_create(context, instance_id, metadata):
- """Create or update instance metadata"""
+ """Create or update instance metadata."""
IMPL.instance_metadata_update_or_create(context, instance_id, metadata)
diff --git a/nova/db/base.py b/nova/db/base.py
index a0f2180c6..a0d055d5b 100644
--- a/nova/db/base.py
+++ b/nova/db/base.py
@@ -16,20 +16,20 @@
# License for the specific language governing permissions and limitations
# under the License.
-"""
-Base class for classes that need modular database access.
-"""
+"""Base class for classes that need modular database access."""
from nova import utils
from nova import flags
+
FLAGS = flags.FLAGS
flags.DEFINE_string('db_driver', 'nova.db.api',
'driver to use for database access')
class Base(object):
- """DB driver is injected in the init method"""
+ """DB driver is injected in the init method."""
+
def __init__(self, db_driver=None):
if not db_driver:
db_driver = FLAGS.db_driver
diff --git a/nova/db/migration.py b/nova/db/migration.py
index e54b90cd8..ccd06cffe 100644
--- a/nova/db/migration.py
+++ b/nova/db/migration.py
@@ -15,11 +15,13 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
+
"""Database setup and migration commands."""
from nova import flags
from nova import utils
+
FLAGS = flags.FLAGS
flags.DECLARE('db_backend', 'nova.db.api')
diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py
index 6da8dac10..cd6052506 100644
--- a/nova/db/sqlalchemy/api.py
+++ b/nova/db/sqlalchemy/api.py
@@ -770,9 +770,10 @@ def instance_create(context, values):
metadata = values.get('metadata')
metadata_refs = []
if metadata:
- for metadata_item in metadata:
+ for k, v in metadata.iteritems():
metadata_ref = models.InstanceMetadata()
- metadata_ref.update(metadata_item)
+ metadata_ref['key'] = k
+ metadata_ref['value'] = v
metadata_refs.append(metadata_ref)
values['metadata'] = metadata_refs
@@ -831,6 +832,7 @@ def instance_get(context, instance_id, session=None):
options(joinedload('volumes')).\
options(joinedload_all('fixed_ip.network')).\
options(joinedload('metadata')).\
+ options(joinedload('instance_type')).\
filter_by(id=instance_id).\
filter_by(deleted=can_read_deleted(context)).\
first()
@@ -840,6 +842,7 @@ def instance_get(context, instance_id, session=None):
options(joinedload_all('security_groups.rules')).\
options(joinedload('volumes')).\
options(joinedload('metadata')).\
+ options(joinedload('instance_type')).\
filter_by(project_id=context.project_id).\
filter_by(id=instance_id).\
filter_by(deleted=False).\
@@ -859,6 +862,7 @@ def instance_get_all(context):
options(joinedload_all('fixed_ip.floating_ips')).\
options(joinedload('security_groups')).\
options(joinedload_all('fixed_ip.network')).\
+ options(joinedload('instance_type')).\
filter_by(deleted=can_read_deleted(context)).\
all()
@@ -870,6 +874,7 @@ def instance_get_all_by_user(context, user_id):
options(joinedload_all('fixed_ip.floating_ips')).\
options(joinedload('security_groups')).\
options(joinedload_all('fixed_ip.network')).\
+ options(joinedload('instance_type')).\
filter_by(deleted=can_read_deleted(context)).\
filter_by(user_id=user_id).\
all()
@@ -882,6 +887,7 @@ def instance_get_all_by_host(context, host):
options(joinedload_all('fixed_ip.floating_ips')).\
options(joinedload('security_groups')).\
options(joinedload_all('fixed_ip.network')).\
+ options(joinedload('instance_type')).\
filter_by(host=host).\
filter_by(deleted=can_read_deleted(context)).\
all()
@@ -896,6 +902,7 @@ def instance_get_all_by_project(context, project_id):
options(joinedload_all('fixed_ip.floating_ips')).\
options(joinedload('security_groups')).\
options(joinedload_all('fixed_ip.network')).\
+ options(joinedload('instance_type')).\
filter_by(project_id=project_id).\
filter_by(deleted=can_read_deleted(context)).\
all()
@@ -910,6 +917,7 @@ def instance_get_all_by_reservation(context, reservation_id):
options(joinedload_all('fixed_ip.floating_ips')).\
options(joinedload('security_groups')).\
options(joinedload_all('fixed_ip.network')).\
+ options(joinedload('instance_type')).\
filter_by(reservation_id=reservation_id).\
filter_by(deleted=can_read_deleted(context)).\
all()
@@ -918,6 +926,7 @@ def instance_get_all_by_reservation(context, reservation_id):
options(joinedload_all('fixed_ip.floating_ips')).\
options(joinedload('security_groups')).\
options(joinedload_all('fixed_ip.network')).\
+ options(joinedload('instance_type')).\
filter_by(project_id=context.project_id).\
filter_by(reservation_id=reservation_id).\
filter_by(deleted=False).\
@@ -930,6 +939,7 @@ def instance_get_project_vpn(context, project_id):
return session.query(models.Instance).\
options(joinedload_all('fixed_ip.floating_ips')).\
options(joinedload('security_groups')).\
+ options(joinedload('instance_type')).\
filter_by(project_id=project_id).\
filter_by(image_id=FLAGS.vpn_image_id).\
filter_by(deleted=can_read_deleted(context)).\
@@ -1826,7 +1836,7 @@ def security_group_get_by_instance(context, instance_id):
def security_group_exists(context, project_id, group_name):
try:
group = security_group_get_by_name(context, project_id, group_name)
- return group != None
+ return group is not None
except exception.NotFound:
return False
@@ -2371,6 +2381,19 @@ def instance_type_get_all(context, inactive=False):
@require_context
+def instance_type_get_by_id(context, id):
+ """Returns a dict describing specific instance_type"""
+ session = get_session()
+ inst_type = session.query(models.InstanceTypes).\
+ filter_by(id=id).\
+ first()
+ if not inst_type:
+ raise exception.NotFound(_("No instance type with id %s") % id)
+ else:
+ return dict(inst_type)
+
+
+@require_context
def instance_type_get_by_name(context, name):
"""Returns a dict describing specific instance_type"""
session = get_session()
diff --git a/nova/db/sqlalchemy/migrate_repo/versions/014_add_instance_type_id_to_instances.py b/nova/db/sqlalchemy/migrate_repo/versions/014_add_instance_type_id_to_instances.py
new file mode 100644
index 000000000..b12a0a801
--- /dev/null
+++ b/nova/db/sqlalchemy/migrate_repo/versions/014_add_instance_type_id_to_instances.py
@@ -0,0 +1,84 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2010 OpenStack LLC.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from sqlalchemy import *
+from sqlalchemy.sql import text
+from migrate import *
+
+#from nova import log as logging
+
+
+meta = MetaData()
+
+
+c_instance_type = Column('instance_type',
+ String(length=255, convert_unicode=False,
+ assert_unicode=None, unicode_error=None,
+ _warn_on_bytestring=False),
+ nullable=True)
+
+c_instance_type_id = Column('instance_type_id',
+ String(length=255, convert_unicode=False,
+ assert_unicode=None, unicode_error=None,
+ _warn_on_bytestring=False),
+ nullable=True)
+
+instance_types = Table('instance_types', meta,
+ Column('id', Integer(), primary_key=True, nullable=False),
+ Column('name',
+ String(length=255, convert_unicode=False, assert_unicode=None,
+ unicode_error=None, _warn_on_bytestring=False),
+ unique=True))
+
+
+def upgrade(migrate_engine):
+ # Upgrade operations go here. Don't create your own engine;
+ # bind migrate_engine to your metadata
+ meta.bind = migrate_engine
+
+ instances = Table('instances', meta, autoload=True,
+ autoload_with=migrate_engine)
+
+ instances.create_column(c_instance_type_id)
+
+ recs = migrate_engine.execute(instance_types.select())
+ for row in recs:
+ type_id = row[0]
+ type_name = row[1]
+ migrate_engine.execute(instances.update()\
+ .where(instances.c.instance_type == type_name)\
+ .values(instance_type_id=type_id))
+
+ instances.c.instance_type.drop()
+
+
+def downgrade(migrate_engine):
+ meta.bind = migrate_engine
+
+ instances = Table('instances', meta, autoload=True,
+ autoload_with=migrate_engine)
+
+ instances.create_column(c_instance_type)
+
+ recs = migrate_engine.execute(instance_types.select())
+ for row in recs:
+ type_id = row[0]
+ type_name = row[1]
+ migrate_engine.execute(instances.update()\
+ .where(instances.c.instance_type_id == type_id)\
+ .values(instance_type=type_name))
+
+ instances.c.instance_type_id.drop()
diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py
index 3b95ac23e..f79d0f16c 100644
--- a/nova/db/sqlalchemy/models.py
+++ b/nova/db/sqlalchemy/models.py
@@ -209,7 +209,7 @@ class Instance(BASE, NovaBase):
hostname = Column(String(255))
host = Column(String(255)) # , ForeignKey('hosts.id'))
- instance_type = Column(String(255))
+ instance_type_id = Column(String(255))
user_data = Column(Text)
@@ -268,6 +268,12 @@ class InstanceTypes(BASE, NovaBase):
rxtx_quota = Column(Integer, nullable=False, default=0)
rxtx_cap = Column(Integer, nullable=False, default=0)
+ instances = relationship(Instance,
+ backref=backref('instance_type', uselist=False),
+ foreign_keys=id,
+ primaryjoin='and_(Instance.instance_type_id == '
+ 'InstanceTypes.id)')
+
class Volume(BASE, NovaBase):
"""Represents a block storage device that can be attached to a vm."""
diff --git a/nova/exception.py b/nova/exception.py
index 4e2bbdbaf..3123b2f1f 100644
--- a/nova/exception.py
+++ b/nova/exception.py
@@ -16,31 +16,34 @@
# License for the specific language governing permissions and limitations
# under the License.
-"""
-Nova base exception handling, including decorator for re-raising
-Nova-type exceptions. SHOULD include dedicated exception logging.
+"""Nova base exception handling.
+
+Includes decorator for re-raising Nova-type exceptions.
+
+SHOULD include dedicated exception logging.
+
"""
from nova import log as logging
+
+
LOG = logging.getLogger('nova.exception')
class ProcessExecutionError(IOError):
-
def __init__(self, stdout=None, stderr=None, exit_code=None, cmd=None,
description=None):
if description is None:
- description = _("Unexpected error while running command.")
+ description = _('Unexpected error while running command.')
if exit_code is None:
exit_code = '-'
- message = _("%(description)s\nCommand: %(cmd)s\n"
- "Exit code: %(exit_code)s\nStdout: %(stdout)r\n"
- "Stderr: %(stderr)r") % locals()
+ message = _('%(description)s\nCommand: %(cmd)s\n'
+ 'Exit code: %(exit_code)s\nStdout: %(stdout)r\n'
+ 'Stderr: %(stderr)r') % locals()
IOError.__init__(self, message)
class Error(Exception):
-
def __init__(self, message=None):
super(Error, self).__init__(message)
@@ -97,7 +100,7 @@ class TimeoutException(Error):
class DBError(Error):
- """Wraps an implementation specific exception"""
+ """Wraps an implementation specific exception."""
def __init__(self, inner_exception):
self.inner_exception = inner_exception
super(DBError, self).__init__(str(inner_exception))
@@ -108,7 +111,7 @@ def wrap_db_error(f):
try:
return f(*args, **kwargs)
except Exception, e:
- LOG.exception(_('DB exception wrapped'))
+ LOG.exception(_('DB exception wrapped.'))
raise DBError(e)
return _wrap
_wrap.func_name = f.func_name
diff --git a/nova/fakememcache.py b/nova/fakememcache.py
index 67f46dbdc..e4f238aa9 100644
--- a/nova/fakememcache.py
+++ b/nova/fakememcache.py
@@ -18,14 +18,14 @@
"""Super simple fake memcache client."""
-import utils
+from nova import utils
class Client(object):
"""Replicates a tiny subset of memcached client interface."""
def __init__(self, *args, **kwargs):
- """Ignores the passed in args"""
+ """Ignores the passed in args."""
self.cache = {}
def get(self, key):
diff --git a/nova/flags.py b/nova/flags.py
index f011ab383..d1b93f0a8 100644
--- a/nova/flags.py
+++ b/nova/flags.py
@@ -16,9 +16,13 @@
# License for the specific language governing permissions and limitations
# under the License.
-"""
+"""Command-line flag library.
+
+Wraps gflags.
+
Package-level global flags are defined here, the rest are defined
where they're used.
+
"""
import getopt
@@ -145,10 +149,12 @@ class FlagValues(gflags.FlagValues):
class StrWrapper(object):
- """Wrapper around FlagValues objects
+ """Wrapper around FlagValues objects.
Wraps FlagValues objects for string.Template so that we're
- sure to return strings."""
+ sure to return strings.
+
+ """
def __init__(self, context_objs):
self.context_objs = context_objs
@@ -169,6 +175,7 @@ def _GetCallingModule():
We generally use this function to get the name of the module calling a
DEFINE_foo... function.
+
"""
# Walk down the stack to find the first globals dict that's not ours.
for depth in range(1, sys.getrecursionlimit()):
@@ -192,6 +199,7 @@ def __GetModuleName(globals_dict):
Returns:
A string (the name of the module) or None (if the module could not
be identified.
+
"""
for name, module in sys.modules.iteritems():
if getattr(module, '__dict__', None) is globals_dict:
@@ -326,7 +334,7 @@ DEFINE_integer('auth_token_ttl', 3600, 'Seconds for auth tokens to linger')
DEFINE_string('state_path', os.path.join(os.path.dirname(__file__), '../'),
"Top-level directory for maintaining nova's state")
DEFINE_string('lock_path', os.path.join(os.path.dirname(__file__), '../'),
- "Directory for lock files")
+ 'Directory for lock files')
DEFINE_string('logdir', None, 'output to a per-service log file in named '
'directory')
diff --git a/nova/image/fake.py b/nova/image/fake.py
index 08302d6eb..3bc2a8287 100644
--- a/nova/image/fake.py
+++ b/nova/image/fake.py
@@ -14,6 +14,7 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
+
"""Implementation of an fake image service"""
import copy
@@ -44,11 +45,10 @@ class FakeImageService(service.BaseImageService):
'created_at': timestamp,
'updated_at': timestamp,
'status': 'active',
- 'type': 'machine',
+ 'container_format': 'ami',
+ 'disk_format': 'raw',
'properties': {'kernel_id': FLAGS.null_kernel,
- 'ramdisk_id': FLAGS.null_kernel,
- 'disk_format': 'ami'}
- }
+ 'ramdisk_id': FLAGS.null_kernel}}
self.create(None, image)
super(FakeImageService, self).__init__()
@@ -70,14 +70,14 @@ class FakeImageService(service.BaseImageService):
image = self.images.get(image_id)
if image:
return copy.deepcopy(image)
- LOG.warn("Unable to find image id %s. Have images: %s",
+ LOG.warn('Unable to find image id %s. Have images: %s',
image_id, self.images)
raise exception.NotFound
def create(self, context, data):
"""Store the image data and return the new image id.
- :raises Duplicate if the image already exist.
+ :raises: Duplicate if the image already exist.
"""
image_id = int(data['id'])
@@ -89,7 +89,7 @@ class FakeImageService(service.BaseImageService):
def update(self, context, image_id, data):
"""Replace the contents of the given image with the new data.
- :raises NotFound if the image does not exist.
+ :raises: NotFound if the image does not exist.
"""
image_id = int(image_id)
@@ -100,7 +100,7 @@ class FakeImageService(service.BaseImageService):
def delete(self, context, image_id):
"""Delete the given image.
- :raises NotFound if the image does not exist.
+ :raises: NotFound if the image does not exist.
"""
image_id = int(image_id)
diff --git a/nova/image/glance.py b/nova/image/glance.py
index fdf468594..81661b3b0 100644
--- a/nova/image/glance.py
+++ b/nova/image/glance.py
@@ -14,6 +14,7 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
+
"""Implementation of an image service that uses Glance as the backend"""
from __future__ import absolute_import
@@ -31,16 +32,18 @@ from nova.image import service
LOG = logging.getLogger('nova.image.glance')
+
FLAGS = flags.FLAGS
+
GlanceClient = utils.import_class('glance.client.Client')
class GlanceImageService(service.BaseImageService):
"""Provides storage and retrieval of disk image objects within Glance."""
- GLANCE_ONLY_ATTRS = ["size", "location", "disk_format",
- "container_format"]
+ GLANCE_ONLY_ATTRS = ['size', 'location', 'disk_format',
+ 'container_format']
# NOTE(sirp): Overriding to use _translate_to_service provided by
# BaseImageService
@@ -56,9 +59,7 @@ class GlanceImageService(service.BaseImageService):
self.client = client
def index(self, context):
- """
- Calls out to Glance for a list of images available
- """
+ """Calls out to Glance for a list of images available."""
# NOTE(sirp): We need to use `get_images_detailed` and not
# `get_images` here because we need `is_public` and `properties`
# included so we can filter by user
@@ -71,9 +72,7 @@ class GlanceImageService(service.BaseImageService):
return filtered
def detail(self, context):
- """
- Calls out to Glance for a list of detailed image information
- """
+ """Calls out to Glance for a list of detailed image information."""
filtered = []
image_metas = self.client.get_images_detailed()
for image_meta in image_metas:
@@ -83,9 +82,7 @@ class GlanceImageService(service.BaseImageService):
return filtered
def show(self, context, image_id):
- """
- Returns a dict containing image data for the given opaque image id.
- """
+ """Returns a dict with image data for the given opaque image id."""
try:
image_meta = self.client.get_image_meta(image_id)
except glance_exception.NotFound:
@@ -98,9 +95,7 @@ class GlanceImageService(service.BaseImageService):
return base_image_meta
def show_by_name(self, context, name):
- """
- Returns a dict containing image data for the given name.
- """
+ """Returns a dict containing image data for the given name."""
# TODO(vish): replace this with more efficient call when glance
# supports it.
image_metas = self.detail(context)
@@ -110,9 +105,7 @@ class GlanceImageService(service.BaseImageService):
raise exception.NotFound
def get(self, context, image_id, data):
- """
- Calls out to Glance for metadata and data and writes data.
- """
+ """Calls out to Glance for metadata and data and writes data."""
try:
image_meta, image_chunks = self.client.get_image(image_id)
except glance_exception.NotFound:
@@ -125,16 +118,16 @@ class GlanceImageService(service.BaseImageService):
return base_image_meta
def create(self, context, image_meta, data=None):
- """
- Store the image data and return the new image id.
+ """Store the image data and return the new image id.
+
+ :raises: AlreadyExists if the image already exist.
- :raises AlreadyExists if the image already exist.
"""
# Translate Base -> Service
- LOG.debug(_("Creating image in Glance. Metadata passed in %s"),
+ LOG.debug(_('Creating image in Glance. Metadata passed in %s'),
image_meta)
sent_service_image_meta = self._translate_to_service(image_meta)
- LOG.debug(_("Metadata after formatting for Glance %s"),
+ LOG.debug(_('Metadata after formatting for Glance %s'),
sent_service_image_meta)
recv_service_image_meta = self.client.add_image(
@@ -142,15 +135,18 @@ class GlanceImageService(service.BaseImageService):
# Translate Service -> Base
base_image_meta = self._translate_to_base(recv_service_image_meta)
- LOG.debug(_("Metadata returned from Glance formatted for Base %s"),
+ LOG.debug(_('Metadata returned from Glance formatted for Base %s'),
base_image_meta)
return base_image_meta
def update(self, context, image_id, image_meta, data=None):
"""Replace the contents of the given image with the new data.
- :raises NotFound if the image does not exist.
+ :raises: NotFound if the image does not exist.
+
"""
+ # NOTE(vish): show is to check if image is available
+ self.show(context, image_id)
try:
image_meta = self.client.update_image(image_id, image_meta, data)
except glance_exception.NotFound:
@@ -160,11 +156,13 @@ class GlanceImageService(service.BaseImageService):
return base_image_meta
def delete(self, context, image_id):
- """
- Delete the given image.
+ """Delete the given image.
+
+ :raises: NotFound if the image does not exist.
- :raises NotFound if the image does not exist.
"""
+ # NOTE(vish): show is to check if image is available
+ self.show(context, image_id)
try:
result = self.client.delete_image(image_id)
except glance_exception.NotFound:
@@ -172,53 +170,21 @@ class GlanceImageService(service.BaseImageService):
return result
def delete_all(self):
- """
- Clears out all images
- """
+ """Clears out all images."""
pass
@classmethod
def _translate_to_base(cls, image_meta):
- """Overriding the base translation to handle conversion to datetime
- objects
- """
- image_meta = service.BaseImageService._translate_to_base(image_meta)
+ """Override translation to handle conversion to datetime objects."""
+ image_meta = service.BaseImageService._propertify_metadata(
+ image_meta, cls.SERVICE_IMAGE_ATTRS)
image_meta = _convert_timestamps_to_datetimes(image_meta)
return image_meta
- @staticmethod
- def _is_image_available(context, image_meta):
- """
- Images are always available if they are public or if the user is an
- admin.
-
- Otherwise, we filter by project_id (if present) and then fall-back to
- images owned by user.
- """
- # FIXME(sirp): We should be filtering by user_id on the Glance side
- # for security; however, we can't do that until we get authn/authz
- # sorted out. Until then, filtering in Nova.
- if image_meta['is_public'] or context.is_admin:
- return True
-
- properties = image_meta['properties']
-
- if context.project_id and ('project_id' in properties):
- return str(properties['project_id']) == str(project_id)
-
- try:
- user_id = properties['user_id']
- except KeyError:
- return False
-
- return str(user_id) == str(context.user_id)
-
# utility functions
def _convert_timestamps_to_datetimes(image_meta):
- """
- Returns image with known timestamp fields converted to datetime objects
- """
+ """Returns image with timestamp fields converted to datetime objects."""
for attr in ['created_at', 'updated_at', 'deleted_at']:
if image_meta.get(attr):
image_meta[attr] = _parse_glance_iso8601_timestamp(
@@ -227,10 +193,8 @@ def _convert_timestamps_to_datetimes(image_meta):
def _parse_glance_iso8601_timestamp(timestamp):
- """
- Parse a subset of iso8601 timestamps into datetime objects
- """
- iso_formats = ["%Y-%m-%dT%H:%M:%S.%f", "%Y-%m-%dT%H:%M:%S"]
+ """Parse a subset of iso8601 timestamps into datetime objects."""
+ iso_formats = ['%Y-%m-%dT%H:%M:%S.%f', '%Y-%m-%dT%H:%M:%S']
for iso_format in iso_formats:
try:
@@ -238,5 +202,5 @@ def _parse_glance_iso8601_timestamp(timestamp):
except ValueError:
pass
- raise ValueError(_("%(timestamp)s does not follow any of the "
- "signatures: %(ISO_FORMATS)s") % locals())
+ raise ValueError(_('%(timestamp)s does not follow any of the '
+ 'signatures: %(ISO_FORMATS)s') % locals())
diff --git a/nova/image/local.py b/nova/image/local.py
index 1fb6e1f13..50f00bee1 100644
--- a/nova/image/local.py
+++ b/nova/image/local.py
@@ -23,14 +23,15 @@ import shutil
from nova import exception
from nova import flags
from nova import log as logging
-from nova.image import service
from nova import utils
+from nova.image import service
FLAGS = flags.FLAGS
flags.DEFINE_string('images_path', '$state_path/images',
'path to decrypted images')
+
LOG = logging.getLogger('nova.image.local')
@@ -56,9 +57,8 @@ class LocalImageService(service.BaseImageService):
try:
unhexed_image_id = int(image_dir, 16)
except ValueError:
- LOG.error(
- _("%s is not in correct directory naming format"\
- % image_dir))
+ LOG.error(_('%s is not in correct directory naming format')
+ % image_dir)
else:
images.append(unhexed_image_id)
return images
@@ -84,7 +84,10 @@ class LocalImageService(service.BaseImageService):
def show(self, context, image_id):
try:
with open(self._path_to(image_id)) as metadata_file:
- return json.load(metadata_file)
+ image_meta = json.load(metadata_file)
+ if not self._is_image_available(context, image_meta):
+ raise exception.NotFound
+ return image_meta
except (IOError, ValueError):
raise exception.NotFound
@@ -98,7 +101,7 @@ class LocalImageService(service.BaseImageService):
if name == cantidate.get('name'):
image = cantidate
break
- if image == None:
+ if image is None:
raise exception.NotFound
return image
@@ -119,10 +122,15 @@ class LocalImageService(service.BaseImageService):
image_path = self._path_to(image_id, None)
if not os.path.exists(image_path):
os.mkdir(image_path)
- return self.update(context, image_id, metadata, data)
+ return self._store(context, image_id, metadata, data)
def update(self, context, image_id, metadata, data=None):
"""Replace the contents of the given image with the new data."""
+ # NOTE(vish): show is to check if image is available
+ self.show(context, image_id)
+ return self._store(context, image_id, metadata, data)
+
+ def _store(self, context, image_id, metadata, data=None):
metadata['id'] = image_id
try:
if data:
@@ -140,9 +148,12 @@ class LocalImageService(service.BaseImageService):
def delete(self, context, image_id):
"""Delete the given image.
- Raises OSError if the image does not exist.
+
+ :raises: NotFound if the image does not exist.
"""
+ # NOTE(vish): show is to check if image is available
+ self.show(context, image_id)
try:
shutil.rmtree(self._path_to(image_id, None))
except (IOError, ValueError):
diff --git a/nova/image/s3.py b/nova/image/s3.py
index ddec5f3aa..c38c58d95 100644
--- a/nova/image/s3.py
+++ b/nova/image/s3.py
@@ -16,13 +16,9 @@
# License for the specific language governing permissions and limitations
# under the License.
-"""
-Proxy AMI-related calls from the cloud controller, to the running
-objectstore service.
-"""
+"""Proxy AMI-related calls from cloud controller to objectstore service."""
import binascii
-import eventlet
import os
import shutil
import tarfile
@@ -30,6 +26,7 @@ import tempfile
from xml.etree import ElementTree
import boto.s3.connection
+import eventlet
from nova import crypto
from nova import exception
@@ -46,64 +43,41 @@ flags.DEFINE_string('image_decryption_dir', '/tmp',
class S3ImageService(service.BaseImageService):
+ """Wraps an existing image service to support s3 based register."""
+
def __init__(self, service=None, *args, **kwargs):
- if service == None:
+ if service is None:
service = utils.import_object(FLAGS.image_service)
self.service = service
self.service.__init__(*args, **kwargs)
def create(self, context, metadata, data=None):
- """metadata['properties'] should contain image_location"""
+ """Create an image.
+
+ metadata['properties'] should contain image_location.
+
+ """
image = self._s3_create(context, metadata)
return image
def delete(self, context, image_id):
- # FIXME(vish): call to show is to check filter
- self.show(context, image_id)
self.service.delete(context, image_id)
def update(self, context, image_id, metadata, data=None):
- # FIXME(vish): call to show is to check filter
- self.show(context, image_id)
image = self.service.update(context, image_id, metadata, data)
return image
def index(self, context):
- images = self.service.index(context)
- # FIXME(vish): index doesn't filter so we do it manually
- return self._filter(context, images)
+ return self.service.index(context)
def detail(self, context):
- images = self.service.detail(context)
- # FIXME(vish): detail doesn't filter so we do it manually
- return self._filter(context, images)
-
- @classmethod
- def _is_visible(cls, context, image):
- return (context.is_admin
- or context.project_id == image['properties']['owner_id']
- or image['properties']['is_public'] == 'True')
-
- @classmethod
- def _filter(cls, context, images):
- filtered = []
- for image in images:
- if not cls._is_visible(context, image):
- continue
- filtered.append(image)
- return filtered
+ return self.service.detail(context)
def show(self, context, image_id):
- image = self.service.show(context, image_id)
- if not self._is_visible(context, image):
- raise exception.NotFound
- return image
+ return self.service.show(context, image_id)
def show_by_name(self, context, name):
- image = self.service.show_by_name(context, name)
- if not self._is_visible(context, image):
- raise exception.NotFound
- return image
+ return self.service.show_by_name(context, name)
@staticmethod
def _conn(context):
@@ -128,12 +102,12 @@ class S3ImageService(service.BaseImageService):
return local_filename
def _s3_create(self, context, metadata):
- """Gets a manifext from s3 and makes an image"""
+ """Gets a manifext from s3 and makes an image."""
image_path = tempfile.mkdtemp(dir=FLAGS.image_decryption_dir)
image_location = metadata['properties']['image_location']
- bucket_name = image_location.split("/")[0]
+ bucket_name = image_location.split('/')[0]
manifest_path = image_location[len(bucket_name) + 1:]
bucket = self._conn(context).get_bucket(bucket_name)
key = bucket.get_key(manifest_path)
@@ -144,7 +118,7 @@ class S3ImageService(service.BaseImageService):
image_type = 'machine'
try:
- kernel_id = manifest.find("machine_configuration/kernel_id").text
+ kernel_id = manifest.find('machine_configuration/kernel_id').text
if kernel_id == 'true':
image_format = 'aki'
image_type = 'kernel'
@@ -153,7 +127,7 @@ class S3ImageService(service.BaseImageService):
kernel_id = None
try:
- ramdisk_id = manifest.find("machine_configuration/ramdisk_id").text
+ ramdisk_id = manifest.find('machine_configuration/ramdisk_id').text
if ramdisk_id == 'true':
image_format = 'ari'
image_type = 'ramdisk'
@@ -162,12 +136,12 @@ class S3ImageService(service.BaseImageService):
ramdisk_id = None
try:
- arch = manifest.find("machine_configuration/architecture").text
+ arch = manifest.find('machine_configuration/architecture').text
except Exception:
arch = 'x86_64'
properties = metadata['properties']
- properties['owner_id'] = context.project_id
+ properties['project_id'] = context.project_id
properties['architecture'] = arch
if kernel_id:
@@ -176,8 +150,6 @@ class S3ImageService(service.BaseImageService):
if ramdisk_id:
properties['ramdisk_id'] = ec2utils.ec2_id_to_id(ramdisk_id)
- properties['is_public'] = False
- properties['type'] = image_type
metadata.update({'disk_format': image_format,
'container_format': image_format,
'status': 'queued',
@@ -190,7 +162,7 @@ class S3ImageService(service.BaseImageService):
def delayed_create():
"""This handles the fetching and decrypting of the part files."""
parts = []
- for fn_element in manifest.find("image").getiterator("filename"):
+ for fn_element in manifest.find('image').getiterator('filename'):
part = self._download_file(bucket, fn_element.text, image_path)
parts.append(part)
@@ -204,9 +176,9 @@ class S3ImageService(service.BaseImageService):
metadata['properties']['image_state'] = 'decrypting'
self.service.update(context, image_id, metadata)
- hex_key = manifest.find("image/ec2_encrypted_key").text
+ hex_key = manifest.find('image/ec2_encrypted_key').text
encrypted_key = binascii.a2b_hex(hex_key)
- hex_iv = manifest.find("image/ec2_encrypted_iv").text
+ hex_iv = manifest.find('image/ec2_encrypted_iv').text
encrypted_iv = binascii.a2b_hex(hex_iv)
# FIXME(vish): grab key from common service so this can run on
@@ -244,7 +216,7 @@ class S3ImageService(service.BaseImageService):
process_input=encrypted_key,
check_exit_code=False)
if err:
- raise exception.Error(_("Failed to decrypt private key: %s")
+ raise exception.Error(_('Failed to decrypt private key: %s')
% err)
iv, err = utils.execute('openssl',
'rsautl',
@@ -253,8 +225,8 @@ class S3ImageService(service.BaseImageService):
process_input=encrypted_iv,
check_exit_code=False)
if err:
- raise exception.Error(_("Failed to decrypt initialization "
- "vector: %s") % err)
+ raise exception.Error(_('Failed to decrypt initialization '
+ 'vector: %s') % err)
_out, err = utils.execute('openssl', 'enc',
'-d', '-aes-128-cbc',
@@ -264,14 +236,14 @@ class S3ImageService(service.BaseImageService):
'-out', '%s' % (decrypted_filename,),
check_exit_code=False)
if err:
- raise exception.Error(_("Failed to decrypt image file "
- "%(image_file)s: %(err)s") %
+ raise exception.Error(_('Failed to decrypt image file '
+ '%(image_file)s: %(err)s') %
{'image_file': encrypted_filename,
'err': err})
@staticmethod
def _untarzip_image(path, filename):
- tar_file = tarfile.open(filename, "r|gz")
+ tar_file = tarfile.open(filename, 'r|gz')
tar_file.extractall(path)
image_file = tar_file.getnames()[0]
tar_file.close()
diff --git a/nova/image/service.py b/nova/image/service.py
index b9897ecae..ab6749049 100644
--- a/nova/image/service.py
+++ b/nova/image/service.py
@@ -20,7 +20,7 @@ from nova import utils
class BaseImageService(object):
- """Base class for providing image search and retrieval services
+ """Base class for providing image search and retrieval services.
ImageService exposes two concepts of metadata:
@@ -35,7 +35,9 @@ class BaseImageService(object):
This means that ImageServices will return BASE_IMAGE_ATTRS as keys in the
metadata dict, all other attributes will be returned as keys in the nested
'properties' dict.
+
"""
+
BASE_IMAGE_ATTRS = ['id', 'name', 'created_at', 'updated_at',
'deleted_at', 'deleted', 'status', 'is_public']
@@ -45,23 +47,18 @@ class BaseImageService(object):
SERVICE_IMAGE_ATTRS = []
def index(self, context):
- """
- Returns a sequence of mappings of id and name information about
- images.
+ """List images.
- :rtype: array
- :retval: a sequence of mappings with the following signature
- {'id': opaque id of image, 'name': name of image}
+ :returns: a sequence of mappings with the following signature
+ {'id': opaque id of image, 'name': name of image}
"""
raise NotImplementedError
def detail(self, context):
- """
- Returns a sequence of mappings of detailed information about images.
+ """Detailed information about an images.
- :rtype: array
- :retval: a sequence of mappings with the following signature
+ :returns: a sequence of mappings with the following signature
{'id': opaque id of image,
'name': name of image,
'created_at': creation datetime object,
@@ -77,15 +74,14 @@ class BaseImageService(object):
NotImplementedError, in which case Nova will emulate this method
with repeated calls to show() for each image received from the
index() method.
+
"""
raise NotImplementedError
def show(self, context, image_id):
- """
- Returns a dict containing image metadata for the given opaque image id.
-
- :retval a mapping with the following signature:
+ """Detailed information about an image.
+ :returns: a mapping with the following signature:
{'id': opaque id of image,
'name': name of image,
'created_at': creation datetime object,
@@ -96,75 +92,107 @@ class BaseImageService(object):
'is_public': boolean indicating if image is public
}, ...
- :raises NotFound if the image does not exist
+ :raises: NotFound if the image does not exist
+
"""
raise NotImplementedError
def get(self, context, data):
- """
- Returns a dict containing image metadata and writes image data to data.
+ """Get an image.
:param data: a file-like object to hold binary image data
+ :returns: a dict containing image metadata, writes image data to data.
+ :raises: NotFound if the image does not exist
- :raises NotFound if the image does not exist
"""
raise NotImplementedError
def create(self, context, metadata, data=None):
- """
- Store the image metadata and data and return the new image metadata.
+ """Store the image metadata and data.
- :raises AlreadyExists if the image already exist.
+ :returns: the new image metadata.
+ :raises: AlreadyExists if the image already exist.
"""
raise NotImplementedError
def update(self, context, image_id, metadata, data=None):
- """Update the given image metadata and data and return the metadata
+ """Update the given image metadata and data and return the metadata.
- :raises NotFound if the image does not exist.
+ :raises: NotFound if the image does not exist.
"""
raise NotImplementedError
def delete(self, context, image_id):
- """
- Delete the given image.
+ """Delete the given image.
- :raises NotFound if the image does not exist.
+ :raises: NotFound if the image does not exist.
"""
raise NotImplementedError
+ @staticmethod
+ def _is_image_available(context, image_meta):
+ """Check image availability.
+
+ Images are always available if they are public or if the user is an
+ admin.
+
+ Otherwise, we filter by project_id (if present) and then fall-back to
+ images owned by user.
+
+ """
+ # FIXME(sirp): We should be filtering by user_id on the Glance side
+ # for security; however, we can't do that until we get authn/authz
+ # sorted out. Until then, filtering in Nova.
+ if image_meta['is_public'] or context.is_admin:
+ return True
+
+ properties = image_meta['properties']
+
+ if context.project_id and ('project_id' in properties):
+ return str(properties['project_id']) == str(context.project_id)
+
+ try:
+ user_id = properties['user_id']
+ except KeyError:
+ return False
+
+ return str(user_id) == str(context.user_id)
+
@classmethod
def _translate_to_base(cls, metadata):
"""Return a metadata dictionary that is BaseImageService compliant.
This is used by subclasses to expose only a metadata dictionary that
is the same across ImageService implementations.
+
"""
return cls._propertify_metadata(metadata, cls.BASE_IMAGE_ATTRS)
@classmethod
def _translate_to_service(cls, metadata):
- """Return a metadata dictionary that is usable by the ImageService
- subclass.
+ """Return a metadata dict that is usable by the ImageService subclass.
As an example, Glance has additional attributes (like 'location'); the
BaseImageService considers these properties, but we need to translate
these back to first-class attrs for sending to Glance. This method
handles this by allowing you to specify the attributes an ImageService
considers first-class.
+
"""
if not cls.SERVICE_IMAGE_ATTRS:
- raise NotImplementedError(_("Cannot use this without specifying "
- "SERVICE_IMAGE_ATTRS for subclass"))
+ raise NotImplementedError(_('Cannot use this without specifying '
+ 'SERVICE_IMAGE_ATTRS for subclass'))
return cls._propertify_metadata(metadata, cls.SERVICE_IMAGE_ATTRS)
@staticmethod
def _propertify_metadata(metadata, keys):
- """Return a dict with any unrecognized keys placed in the nested
- 'properties' dict.
+ """Move unknown keys to a nested 'properties' dict.
+
+ :returns: a new dict with the keys moved.
+
"""
flattened = utils.flatten_dict(metadata)
attributes, properties = utils.partition_dict(flattened, keys)
diff --git a/nova/log.py b/nova/log.py
index d194ab8f0..096279f7c 100644
--- a/nova/log.py
+++ b/nova/log.py
@@ -16,16 +16,15 @@
# License for the specific language governing permissions and limitations
# under the License.
-"""
-Nova logging handler.
+"""Nova logging handler.
This module adds to logging functionality by adding the option to specify
a context object when calling the various log methods. If the context object
is not specified, default formatting is used.
It also allows setting of formatting information through flags.
-"""
+"""
import cStringIO
import inspect
@@ -41,34 +40,28 @@ from nova import version
FLAGS = flags.FLAGS
-
flags.DEFINE_string('logging_context_format_string',
'%(asctime)s %(levelname)s %(name)s '
'[%(request_id)s %(user)s '
'%(project)s] %(message)s',
'format string to use for log messages with context')
-
flags.DEFINE_string('logging_default_format_string',
'%(asctime)s %(levelname)s %(name)s [-] '
'%(message)s',
'format string to use for log messages without context')
-
flags.DEFINE_string('logging_debug_format_suffix',
'from (pid=%(process)d) %(funcName)s'
' %(pathname)s:%(lineno)d',
'data to append to log format when level is DEBUG')
-
flags.DEFINE_string('logging_exception_prefix',
'(%(name)s): TRACE: ',
'prefix each line of exception output with this format')
-
flags.DEFINE_list('default_log_levels',
['amqplib=WARN',
'sqlalchemy=WARN',
'boto=WARN',
'eventlet.wsgi.server=WARN'],
'list of logger=LEVEL pairs')
-
flags.DEFINE_bool('use_syslog', False, 'output to syslog')
flags.DEFINE_string('logfile', None, 'output to named file')
@@ -83,6 +76,8 @@ WARN = logging.WARN
INFO = logging.INFO
DEBUG = logging.DEBUG
NOTSET = logging.NOTSET
+
+
# methods
getLogger = logging.getLogger
debug = logging.debug
@@ -93,6 +88,8 @@ error = logging.error
exception = logging.exception
critical = logging.critical
log = logging.log
+
+
# handlers
StreamHandler = logging.StreamHandler
WatchedFileHandler = logging.handlers.WatchedFileHandler
@@ -106,7 +103,7 @@ logging.addLevelName(AUDIT, 'AUDIT')
def _dictify_context(context):
- if context == None:
+ if context is None:
return None
if not isinstance(context, dict) \
and getattr(context, 'to_dict', None):
@@ -127,17 +124,18 @@ def _get_log_file_path(binary=None):
class NovaLogger(logging.Logger):
- """
- NovaLogger manages request context and formatting.
+ """NovaLogger manages request context and formatting.
This becomes the class that is instanciated by logging.getLogger.
+
"""
+
def __init__(self, name, level=NOTSET):
logging.Logger.__init__(self, name, level)
self.setup_from_flags()
def setup_from_flags(self):
- """Setup logger from flags"""
+ """Setup logger from flags."""
level = NOTSET
for pair in FLAGS.default_log_levels:
logger, _sep, level_name = pair.partition('=')
@@ -148,7 +146,7 @@ class NovaLogger(logging.Logger):
self.setLevel(level)
def _log(self, level, msg, args, exc_info=None, extra=None, context=None):
- """Extract context from any log call"""
+ """Extract context from any log call."""
if not extra:
extra = {}
if context:
@@ -157,17 +155,17 @@ class NovaLogger(logging.Logger):
return logging.Logger._log(self, level, msg, args, exc_info, extra)
def addHandler(self, handler):
- """Each handler gets our custom formatter"""
+ """Each handler gets our custom formatter."""
handler.setFormatter(_formatter)
return logging.Logger.addHandler(self, handler)
def audit(self, msg, *args, **kwargs):
- """Shortcut for our AUDIT level"""
+ """Shortcut for our AUDIT level."""
if self.isEnabledFor(AUDIT):
self._log(AUDIT, msg, args, **kwargs)
def exception(self, msg, *args, **kwargs):
- """Logging.exception doesn't handle kwargs, so breaks context"""
+ """Logging.exception doesn't handle kwargs, so breaks context."""
if not kwargs.get('exc_info'):
kwargs['exc_info'] = 1
self.error(msg, *args, **kwargs)
@@ -181,14 +179,13 @@ class NovaLogger(logging.Logger):
for k in env.keys():
if not isinstance(env[k], str):
env.pop(k)
- message = "Environment: %s" % json.dumps(env)
+ message = 'Environment: %s' % json.dumps(env)
kwargs.pop('exc_info')
self.error(message, **kwargs)
class NovaFormatter(logging.Formatter):
- """
- A nova.context.RequestContext aware formatter configured through flags.
+ """A nova.context.RequestContext aware formatter configured through flags.
The flags used to set format strings are: logging_context_foramt_string
and logging_default_format_string. You can also specify
@@ -197,10 +194,11 @@ class NovaFormatter(logging.Formatter):
For information about what variables are available for the formatter see:
http://docs.python.org/library/logging.html#formatter
+
"""
def format(self, record):
- """Uses contextstring if request_id is set, otherwise default"""
+ """Uses contextstring if request_id is set, otherwise default."""
if record.__dict__.get('request_id', None):
self._fmt = FLAGS.logging_context_format_string
else:
@@ -214,20 +212,21 @@ class NovaFormatter(logging.Formatter):
return logging.Formatter.format(self, record)
def formatException(self, exc_info, record=None):
- """Format exception output with FLAGS.logging_exception_prefix"""
+ """Format exception output with FLAGS.logging_exception_prefix."""
if not record:
return logging.Formatter.formatException(self, exc_info)
stringbuffer = cStringIO.StringIO()
traceback.print_exception(exc_info[0], exc_info[1], exc_info[2],
None, stringbuffer)
- lines = stringbuffer.getvalue().split("\n")
+ lines = stringbuffer.getvalue().split('\n')
stringbuffer.close()
formatted_lines = []
for line in lines:
pl = FLAGS.logging_exception_prefix % record.__dict__
- fl = "%s%s" % (pl, line)
+ fl = '%s%s' % (pl, line)
formatted_lines.append(fl)
- return "\n".join(formatted_lines)
+ return '\n'.join(formatted_lines)
+
_formatter = NovaFormatter()
@@ -241,7 +240,7 @@ class NovaRootLogger(NovaLogger):
NovaLogger.__init__(self, name, level)
def setup_from_flags(self):
- """Setup logger from flags"""
+ """Setup logger from flags."""
global _filelog
if FLAGS.use_syslog:
self.syslog = SysLogHandler(address='/dev/log')
diff --git a/nova/manager.py b/nova/manager.py
index 804a50479..34338ac04 100644
--- a/nova/manager.py
+++ b/nova/manager.py
@@ -16,7 +16,8 @@
# License for the specific language governing permissions and limitations
# under the License.
-"""
+"""Base Manager class.
+
Managers are responsible for a certain aspect of the sytem. It is a logical
grouping of code relating to a portion of the system. In general other
components should be using the manager to make changes to the components that
@@ -49,16 +50,19 @@ Managers will often provide methods for initial setup of a host or periodic
tasksto a wrapping service.
This module provides Manager, a base class for managers.
+
"""
-from nova import utils
from nova import flags
from nova import log as logging
+from nova import utils
from nova.db import base
from nova.scheduler import api
+
FLAGS = flags.FLAGS
+
LOG = logging.getLogger('nova.manager')
@@ -70,23 +74,29 @@ class Manager(base.Base):
super(Manager, self).__init__(db_driver)
def periodic_tasks(self, context=None):
- """Tasks to be run at a periodic interval"""
+ """Tasks to be run at a periodic interval."""
pass
def init_host(self):
- """Do any initialization that needs to be run if this is a standalone
- service. Child classes should override this method."""
+ """Handle initialization if this is a standalone service.
+
+ Child classes should override this method.
+
+ """
pass
class SchedulerDependentManager(Manager):
"""Periodically send capability updates to the Scheduler services.
- Services that need to update the Scheduler of their capabilities
- should derive from this class. Otherwise they can derive from
- manager.Manager directly. Updates are only sent after
- update_service_capabilities is called with non-None values."""
- def __init__(self, host=None, db_driver=None, service_name="undefined"):
+ Services that need to update the Scheduler of their capabilities
+ should derive from this class. Otherwise they can derive from
+ manager.Manager directly. Updates are only sent after
+ update_service_capabilities is called with non-None values.
+
+ """
+
+ def __init__(self, host=None, db_driver=None, service_name='undefined'):
self.last_capabilities = None
self.service_name = service_name
super(SchedulerDependentManager, self).__init__(host, db_driver)
@@ -96,9 +106,9 @@ class SchedulerDependentManager(Manager):
self.last_capabilities = capabilities
def periodic_tasks(self, context=None):
- """Pass data back to the scheduler at a periodic interval"""
+ """Pass data back to the scheduler at a periodic interval."""
if self.last_capabilities:
- LOG.debug(_("Notifying Schedulers of capabilities ..."))
+ LOG.debug(_('Notifying Schedulers of capabilities ...'))
api.update_service_capabilities(context, self.service_name,
self.host, self.last_capabilities)
diff --git a/nova/network/linux_net.py b/nova/network/linux_net.py
index d11d21dad..ec5579dee 100644
--- a/nova/network/linux_net.py
+++ b/nova/network/linux_net.py
@@ -391,6 +391,12 @@ def unbind_floating_ip(floating_ip):
'dev', FLAGS.public_interface)
+def ensure_metadata_ip():
+ """Sets up local metadata ip"""
+ _execute('sudo', 'ip', 'addr', 'add', '169.254.169.254/32',
+ 'scope', 'link', 'dev', 'lo', check_exit_code=False)
+
+
def ensure_vlan_forward(public_ip, port, private_ip):
"""Sets up forwarding rules for vlan"""
iptables_manager.ipv4['filter'].add_rule("FORWARD",
@@ -442,6 +448,7 @@ def ensure_vlan(vlan_num):
return interface
+@utils.synchronized('ensure_bridge', external=True)
def ensure_bridge(bridge, interface, net_attrs=None):
"""Create a bridge unless it already exists.
@@ -495,6 +502,8 @@ def ensure_bridge(bridge, interface, net_attrs=None):
fields = line.split()
if fields and fields[0] == "0.0.0.0" and fields[-1] == interface:
gateway = fields[1]
+ _execute('sudo', 'route', 'del', 'default', 'gw', gateway,
+ 'dev', interface, check_exit_code=False)
out, err = _execute('sudo', 'ip', 'addr', 'show', 'dev', interface,
'scope', 'global')
for line in out.split("\n"):
@@ -504,7 +513,7 @@ def ensure_bridge(bridge, interface, net_attrs=None):
_execute(*_ip_bridge_cmd('del', params, fields[-1]))
_execute(*_ip_bridge_cmd('add', params, bridge))
if gateway:
- _execute('sudo', 'route', 'add', '0.0.0.0', 'gw', gateway)
+ _execute('sudo', 'route', 'add', 'default', 'gw', gateway)
out, err = _execute('sudo', 'brctl', 'addif', bridge, interface,
check_exit_code=False)
diff --git a/nova/network/manager.py b/nova/network/manager.py
index 86ee4fc00..0dd7f2360 100644
--- a/nova/network/manager.py
+++ b/nova/network/manager.py
@@ -126,6 +126,7 @@ class NetworkManager(manager.SchedulerDependentManager):
standalone service.
"""
self.driver.init_host()
+ self.driver.ensure_metadata_ip()
# Set up networking for the projects for which we're already
# the designated network host.
ctxt = context.get_admin_context()
diff --git a/nova/network/xenapi_net.py b/nova/network/xenapi_net.py
index 9a99602d9..8c22a7d4b 100644
--- a/nova/network/xenapi_net.py
+++ b/nova/network/xenapi_net.py
@@ -47,7 +47,7 @@ def ensure_vlan_bridge(vlan_num, bridge, net_attrs=None):
network_ref = network_utils.NetworkHelper.find_network_with_name_label(
session,
bridge)
- if network_ref == None:
+ if network_ref is None:
# If bridge does not exists
# 1 - create network
description = "network for nova bridge %s" % bridge
diff --git a/nova/quota.py b/nova/quota.py
index 2b24c0b5b..d8b5d9a93 100644
--- a/nova/quota.py
+++ b/nova/quota.py
@@ -15,16 +15,15 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
-"""
-Quotas for instances, volumes, and floating ips
-"""
+
+"""Quotas for instances, volumes, and floating ips."""
from nova import db
from nova import exception
from nova import flags
-FLAGS = flags.FLAGS
+FLAGS = flags.FLAGS
flags.DEFINE_integer('quota_instances', 10,
'number of instances allowed per project')
flags.DEFINE_integer('quota_cores', 20,
@@ -64,7 +63,7 @@ def get_quota(context, project_id):
def allowed_instances(context, num_instances, instance_type):
- """Check quota and return min(num_instances, allowed_instances)"""
+ """Check quota and return min(num_instances, allowed_instances)."""
project_id = context.project_id
context = context.elevated()
used_instances, used_cores = db.instance_data_get_for_project(context,
@@ -79,7 +78,7 @@ def allowed_instances(context, num_instances, instance_type):
def allowed_volumes(context, num_volumes, size):
- """Check quota and return min(num_volumes, allowed_volumes)"""
+ """Check quota and return min(num_volumes, allowed_volumes)."""
project_id = context.project_id
context = context.elevated()
used_volumes, used_gigabytes = db.volume_data_get_for_project(context,
@@ -95,7 +94,7 @@ def allowed_volumes(context, num_volumes, size):
def allowed_floating_ips(context, num_floating_ips):
- """Check quota and return min(num_floating_ips, allowed_floating_ips)"""
+ """Check quota and return min(num_floating_ips, allowed_floating_ips)."""
project_id = context.project_id
context = context.elevated()
used_floating_ips = db.floating_ip_count_by_project(context, project_id)
@@ -105,7 +104,7 @@ def allowed_floating_ips(context, num_floating_ips):
def allowed_metadata_items(context, num_metadata_items):
- """Check quota; return min(num_metadata_items,allowed_metadata_items)"""
+ """Check quota; return min(num_metadata_items,allowed_metadata_items)."""
project_id = context.project_id
context = context.elevated()
quota = get_quota(context, project_id)
@@ -114,20 +113,20 @@ def allowed_metadata_items(context, num_metadata_items):
def allowed_injected_files(context):
- """Return the number of injected files allowed"""
+ """Return the number of injected files allowed."""
return FLAGS.quota_max_injected_files
def allowed_injected_file_content_bytes(context):
- """Return the number of bytes allowed per injected file content"""
+ """Return the number of bytes allowed per injected file content."""
return FLAGS.quota_max_injected_file_content_bytes
def allowed_injected_file_path_bytes(context):
- """Return the number of bytes allowed in an injected file path"""
+ """Return the number of bytes allowed in an injected file path."""
return FLAGS.quota_max_injected_file_path_bytes
class QuotaError(exception.ApiError):
- """Quota Exceeeded"""
+ """Quota Exceeeded."""
pass
diff --git a/nova/rpc.py b/nova/rpc.py
index b610cdf9b..2116f22c3 100644
--- a/nova/rpc.py
+++ b/nova/rpc.py
@@ -16,9 +16,12 @@
# License for the specific language governing permissions and limitations
# under the License.
-"""
-AMQP-based RPC. Queues have consumers and publishers.
+"""AMQP-based RPC.
+
+Queues have consumers and publishers.
+
No fan-out support yet.
+
"""
import json
@@ -40,17 +43,19 @@ from nova import log as logging
from nova import utils
-FLAGS = flags.FLAGS
LOG = logging.getLogger('nova.rpc')
+
+FLAGS = flags.FLAGS
flags.DEFINE_integer('rpc_thread_pool_size', 1024, 'Size of RPC thread pool')
class Connection(carrot_connection.BrokerConnection):
- """Connection instance object"""
+ """Connection instance object."""
+
@classmethod
def instance(cls, new=True):
- """Returns the instance"""
+ """Returns the instance."""
if new or not hasattr(cls, '_instance'):
params = dict(hostname=FLAGS.rabbit_host,
port=FLAGS.rabbit_port,
@@ -71,9 +76,11 @@ class Connection(carrot_connection.BrokerConnection):
@classmethod
def recreate(cls):
- """Recreates the connection instance
+ """Recreates the connection instance.
+
+ This is necessary to recover from some network errors/disconnects.
- This is necessary to recover from some network errors/disconnects"""
+ """
try:
del cls._instance
except AttributeError, e:
@@ -84,10 +91,12 @@ class Connection(carrot_connection.BrokerConnection):
class Consumer(messaging.Consumer):
- """Consumer base class
+ """Consumer base class.
+
+ Contains methods for connecting the fetch method to async loops.
- Contains methods for connecting the fetch method to async loops
"""
+
def __init__(self, *args, **kwargs):
for i in xrange(FLAGS.rabbit_max_retries):
if i > 0:
@@ -100,19 +109,18 @@ class Consumer(messaging.Consumer):
fl_host = FLAGS.rabbit_host
fl_port = FLAGS.rabbit_port
fl_intv = FLAGS.rabbit_retry_interval
- LOG.error(_("AMQP server on %(fl_host)s:%(fl_port)d is"
- " unreachable: %(e)s. Trying again in %(fl_intv)d"
- " seconds.")
- % locals())
+ LOG.error(_('AMQP server on %(fl_host)s:%(fl_port)d is'
+ ' unreachable: %(e)s. Trying again in %(fl_intv)d'
+ ' seconds.') % locals())
self.failed_connection = True
if self.failed_connection:
- LOG.error(_("Unable to connect to AMQP server "
- "after %d tries. Shutting down."),
+ LOG.error(_('Unable to connect to AMQP server '
+ 'after %d tries. Shutting down.'),
FLAGS.rabbit_max_retries)
sys.exit(1)
def fetch(self, no_ack=None, auto_ack=None, enable_callbacks=False):
- """Wraps the parent fetch with some logic for failed connections"""
+ """Wraps the parent fetch with some logic for failed connection."""
# TODO(vish): the logic for failed connections and logging should be
# refactored into some sort of connection manager object
try:
@@ -125,14 +133,14 @@ class Consumer(messaging.Consumer):
self.declare()
super(Consumer, self).fetch(no_ack, auto_ack, enable_callbacks)
if self.failed_connection:
- LOG.error(_("Reconnected to queue"))
+ LOG.error(_('Reconnected to queue'))
self.failed_connection = False
# NOTE(vish): This is catching all errors because we really don't
# want exceptions to be logged 10 times a second if some
# persistent failure occurs.
except Exception, e: # pylint: disable=W0703
if not self.failed_connection:
- LOG.exception(_("Failed to fetch message from queue: %s" % e))
+ LOG.exception(_('Failed to fetch message from queue: %s' % e))
self.failed_connection = True
def attach_to_eventlet(self):
@@ -143,8 +151,9 @@ class Consumer(messaging.Consumer):
class AdapterConsumer(Consumer):
- """Calls methods on a proxy object based on method and args"""
- def __init__(self, connection=None, topic="broadcast", proxy=None):
+ """Calls methods on a proxy object based on method and args."""
+
+ def __init__(self, connection=None, topic='broadcast', proxy=None):
LOG.debug(_('Initing the Adapter Consumer for %s') % topic)
self.proxy = proxy
self.pool = greenpool.GreenPool(FLAGS.rpc_thread_pool_size)
@@ -156,13 +165,14 @@ class AdapterConsumer(Consumer):
@exception.wrap_exception
def _receive(self, message_data, message):
- """Magically looks for a method on the proxy object and calls it
+ """Magically looks for a method on the proxy object and calls it.
Message data should be a dictionary with two keys:
method: string representing the method to call
args: dictionary of arg: value
Example: {'method': 'echo', 'args': {'value': 42}}
+
"""
LOG.debug(_('received %s') % message_data)
msg_id = message_data.pop('_msg_id', None)
@@ -189,22 +199,23 @@ class AdapterConsumer(Consumer):
if msg_id:
msg_reply(msg_id, rval, None)
except Exception as e:
- logging.exception("Exception during message handling")
+ logging.exception('Exception during message handling')
if msg_id:
msg_reply(msg_id, None, sys.exc_info())
return
class Publisher(messaging.Publisher):
- """Publisher base class"""
+ """Publisher base class."""
pass
class TopicAdapterConsumer(AdapterConsumer):
- """Consumes messages on a specific topic"""
- exchange_type = "topic"
+ """Consumes messages on a specific topic."""
+
+ exchange_type = 'topic'
- def __init__(self, connection=None, topic="broadcast", proxy=None):
+ def __init__(self, connection=None, topic='broadcast', proxy=None):
self.queue = topic
self.routing_key = topic
self.exchange = FLAGS.control_exchange
@@ -214,27 +225,29 @@ class TopicAdapterConsumer(AdapterConsumer):
class FanoutAdapterConsumer(AdapterConsumer):
- """Consumes messages from a fanout exchange"""
- exchange_type = "fanout"
+ """Consumes messages from a fanout exchange."""
- def __init__(self, connection=None, topic="broadcast", proxy=None):
- self.exchange = "%s_fanout" % topic
+ exchange_type = 'fanout'
+
+ def __init__(self, connection=None, topic='broadcast', proxy=None):
+ self.exchange = '%s_fanout' % topic
self.routing_key = topic
unique = uuid.uuid4().hex
- self.queue = "%s_fanout_%s" % (topic, unique)
+ self.queue = '%s_fanout_%s' % (topic, unique)
self.durable = False
- LOG.info(_("Created '%(exchange)s' fanout exchange "
- "with '%(key)s' routing key"),
- dict(exchange=self.exchange, key=self.routing_key))
+ LOG.info(_('Created "%(exchange)s" fanout exchange '
+ 'with "%(key)s" routing key'),
+ dict(exchange=self.exchange, key=self.routing_key))
super(FanoutAdapterConsumer, self).__init__(connection=connection,
topic=topic, proxy=proxy)
class TopicPublisher(Publisher):
- """Publishes messages on a specific topic"""
- exchange_type = "topic"
+ """Publishes messages on a specific topic."""
+
+ exchange_type = 'topic'
- def __init__(self, connection=None, topic="broadcast"):
+ def __init__(self, connection=None, topic='broadcast'):
self.routing_key = topic
self.exchange = FLAGS.control_exchange
self.durable = False
@@ -243,20 +256,22 @@ class TopicPublisher(Publisher):
class FanoutPublisher(Publisher):
"""Publishes messages to a fanout exchange."""
- exchange_type = "fanout"
+
+ exchange_type = 'fanout'
def __init__(self, topic, connection=None):
- self.exchange = "%s_fanout" % topic
- self.queue = "%s_fanout" % topic
+ self.exchange = '%s_fanout' % topic
+ self.queue = '%s_fanout' % topic
self.durable = False
- LOG.info(_("Creating '%(exchange)s' fanout exchange"),
- dict(exchange=self.exchange))
+ LOG.info(_('Creating "%(exchange)s" fanout exchange'),
+ dict(exchange=self.exchange))
super(FanoutPublisher, self).__init__(connection=connection)
class DirectConsumer(Consumer):
- """Consumes messages directly on a channel specified by msg_id"""
- exchange_type = "direct"
+ """Consumes messages directly on a channel specified by msg_id."""
+
+ exchange_type = 'direct'
def __init__(self, connection=None, msg_id=None):
self.queue = msg_id
@@ -268,8 +283,9 @@ class DirectConsumer(Consumer):
class DirectPublisher(Publisher):
- """Publishes messages directly on a channel specified by msg_id"""
- exchange_type = "direct"
+ """Publishes messages directly on a channel specified by msg_id."""
+
+ exchange_type = 'direct'
def __init__(self, connection=None, msg_id=None):
self.routing_key = msg_id
@@ -279,9 +295,9 @@ class DirectPublisher(Publisher):
def msg_reply(msg_id, reply=None, failure=None):
- """Sends a reply or an error on the channel signified by msg_id
+ """Sends a reply or an error on the channel signified by msg_id.
- failure should be a sys.exc_info() tuple.
+ Failure should be a sys.exc_info() tuple.
"""
if failure:
@@ -303,17 +319,20 @@ def msg_reply(msg_id, reply=None, failure=None):
class RemoteError(exception.Error):
- """Signifies that a remote class has raised an exception
+ """Signifies that a remote class has raised an exception.
Containes a string representation of the type of the original exception,
the value of the original exception, and the traceback. These are
sent to the parent as a joined string so printing the exception
- contains all of the relevent info."""
+ contains all of the relevent info.
+
+ """
+
def __init__(self, exc_type, value, traceback):
self.exc_type = exc_type
self.value = value
self.traceback = traceback
- super(RemoteError, self).__init__("%s %s\n%s" % (exc_type,
+ super(RemoteError, self).__init__('%s %s\n%s' % (exc_type,
value,
traceback))
@@ -339,6 +358,7 @@ def _pack_context(msg, context):
context out into a bunch of separate keys. If we want to support
more arguments in rabbit messages, we may want to do the same
for args at some point.
+
"""
context = dict([('_context_%s' % key, value)
for (key, value) in context.to_dict().iteritems()])
@@ -346,11 +366,11 @@ def _pack_context(msg, context):
def call(context, topic, msg):
- """Sends a message on a topic and wait for a response"""
- LOG.debug(_("Making asynchronous call on %s ..."), topic)
+ """Sends a message on a topic and wait for a response."""
+ LOG.debug(_('Making asynchronous call on %s ...'), topic)
msg_id = uuid.uuid4().hex
msg.update({'_msg_id': msg_id})
- LOG.debug(_("MSG_ID is %s") % (msg_id))
+ LOG.debug(_('MSG_ID is %s') % (msg_id))
_pack_context(msg, context)
class WaitMessage(object):
@@ -387,8 +407,8 @@ def call(context, topic, msg):
def cast(context, topic, msg):
- """Sends a message on a topic without waiting for a response"""
- LOG.debug(_("Making asynchronous cast on %s..."), topic)
+ """Sends a message on a topic without waiting for a response."""
+ LOG.debug(_('Making asynchronous cast on %s...'), topic)
_pack_context(msg, context)
conn = Connection.instance()
publisher = TopicPublisher(connection=conn, topic=topic)
@@ -397,8 +417,8 @@ def cast(context, topic, msg):
def fanout_cast(context, topic, msg):
- """Sends a message on a fanout exchange without waiting for a response"""
- LOG.debug(_("Making asynchronous fanout cast..."))
+ """Sends a message on a fanout exchange without waiting for a response."""
+ LOG.debug(_('Making asynchronous fanout cast...'))
_pack_context(msg, context)
conn = Connection.instance()
publisher = FanoutPublisher(topic, connection=conn)
@@ -407,14 +427,14 @@ def fanout_cast(context, topic, msg):
def generic_response(message_data, message):
- """Logs a result and exits"""
+ """Logs a result and exits."""
LOG.debug(_('response %s'), message_data)
message.ack()
sys.exit(0)
def send_message(topic, message, wait=True):
- """Sends a message for testing"""
+ """Sends a message for testing."""
msg_id = uuid.uuid4().hex
message.update({'_msg_id': msg_id})
LOG.debug(_('topic is %s'), topic)
@@ -425,14 +445,14 @@ def send_message(topic, message, wait=True):
queue=msg_id,
exchange=msg_id,
auto_delete=True,
- exchange_type="direct",
+ exchange_type='direct',
routing_key=msg_id)
consumer.register_callback(generic_response)
publisher = messaging.Publisher(connection=Connection.instance(),
exchange=FLAGS.control_exchange,
durable=False,
- exchange_type="topic",
+ exchange_type='topic',
routing_key=topic)
publisher.send(message)
publisher.close()
@@ -441,8 +461,8 @@ def send_message(topic, message, wait=True):
consumer.wait()
-if __name__ == "__main__":
- # NOTE(vish): you can send messages from the command line using
- # topic and a json sting representing a dictionary
- # for the method
+if __name__ == '__main__':
+ # You can send messages from the command line using
+ # topic and a json string representing a dictionary
+ # for the method
send_message(sys.argv[1], json.loads(sys.argv[2]))
diff --git a/nova/service.py b/nova/service.py
index 47c0b96c0..2532b9df2 100644
--- a/nova/service.py
+++ b/nova/service.py
@@ -17,9 +17,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-"""
-Generic Node baseclass for all workers that run on hosts
-"""
+"""Generic Node baseclass for all workers that run on hosts."""
import inspect
import os
@@ -30,13 +28,11 @@ from eventlet import event
from eventlet import greenthread
from eventlet import greenpool
-from sqlalchemy.exc import OperationalError
-
from nova import context
from nova import db
from nova import exception
-from nova import log as logging
from nova import flags
+from nova import log as logging
from nova import rpc
from nova import utils
from nova import version
@@ -79,7 +75,7 @@ class Service(object):
def start(self):
vcs_string = version.version_string_with_vcs()
- logging.audit(_("Starting %(topic)s node (version %(vcs_string)s)"),
+ logging.audit(_('Starting %(topic)s node (version %(vcs_string)s)'),
{'topic': self.topic, 'vcs_string': vcs_string})
self.manager.init_host()
self.model_disconnected = False
@@ -140,29 +136,24 @@ class Service(object):
return getattr(manager, key)
@classmethod
- def create(cls,
- host=None,
- binary=None,
- topic=None,
- manager=None,
- report_interval=None,
- periodic_interval=None):
+ def create(cls, host=None, binary=None, topic=None, manager=None,
+ report_interval=None, periodic_interval=None):
"""Instantiates class and passes back application object.
- Args:
- host, defaults to FLAGS.host
- binary, defaults to basename of executable
- topic, defaults to bin_name - "nova-" part
- manager, defaults to FLAGS.<topic>_manager
- report_interval, defaults to FLAGS.report_interval
- periodic_interval, defaults to FLAGS.periodic_interval
+ :param host: defaults to FLAGS.host
+ :param binary: defaults to basename of executable
+ :param topic: defaults to bin_name - 'nova-' part
+ :param manager: defaults to FLAGS.<topic>_manager
+ :param report_interval: defaults to FLAGS.report_interval
+ :param periodic_interval: defaults to FLAGS.periodic_interval
+
"""
if not host:
host = FLAGS.host
if not binary:
binary = os.path.basename(inspect.stack()[-1][1])
if not topic:
- topic = binary.rpartition("nova-")[2]
+ topic = binary.rpartition('nova-')[2]
if not manager:
manager = FLAGS.get('%s_manager' % topic, None)
if not report_interval:
@@ -175,12 +166,12 @@ class Service(object):
return service_obj
def kill(self):
- """Destroy the service object in the datastore"""
+ """Destroy the service object in the datastore."""
self.stop()
try:
db.service_destroy(context.get_admin_context(), self.service_id)
except exception.NotFound:
- logging.warn(_("Service killed that has no database entry"))
+ logging.warn(_('Service killed that has no database entry'))
def stop(self):
for x in self.timers:
@@ -198,7 +189,7 @@ class Service(object):
pass
def periodic_tasks(self):
- """Tasks to be run at a periodic interval"""
+ """Tasks to be run at a periodic interval."""
self.manager.periodic_tasks(context.get_admin_context())
def report_state(self):
@@ -208,8 +199,8 @@ class Service(object):
try:
service_ref = db.service_get(ctxt, self.service_id)
except exception.NotFound:
- logging.debug(_("The service database object disappeared, "
- "Recreating it."))
+ logging.debug(_('The service database object disappeared, '
+ 'Recreating it.'))
self._create_service_ref(ctxt)
service_ref = db.service_get(ctxt, self.service_id)
@@ -218,23 +209,24 @@ class Service(object):
{'report_count': service_ref['report_count'] + 1})
# TODO(termie): make this pattern be more elegant.
- if getattr(self, "model_disconnected", False):
+ if getattr(self, 'model_disconnected', False):
self.model_disconnected = False
- logging.error(_("Recovered model server connection!"))
+ logging.error(_('Recovered model server connection!'))
# TODO(vish): this should probably only catch connection errors
except Exception: # pylint: disable=W0702
- if not getattr(self, "model_disconnected", False):
+ if not getattr(self, 'model_disconnected', False):
self.model_disconnected = True
- logging.exception(_("model server went away"))
+ logging.exception(_('model server went away'))
class WsgiService(object):
"""Base class for WSGI based services.
For each api you define, you must also define these flags:
- :<api>_listen: The address on which to listen
- :<api>_listen_port: The port on which to listen
+ :<api>_listen: The address on which to listen
+ :<api>_listen_port: The port on which to listen
+
"""
def __init__(self, conf, apis):
@@ -250,13 +242,14 @@ class WsgiService(object):
class ApiService(WsgiService):
- """Class for our nova-api service"""
+ """Class for our nova-api service."""
+
@classmethod
def create(cls, conf=None):
if not conf:
conf = wsgi.paste_config_file(FLAGS.api_paste_config)
if not conf:
- message = (_("No paste configuration found for: %s"),
+ message = (_('No paste configuration found for: %s'),
FLAGS.api_paste_config)
raise exception.Error(message)
api_endpoints = ['ec2', 'osapi']
@@ -280,11 +273,11 @@ def serve(*services):
FLAGS.ParseNewFlags()
name = '_'.join(x.binary for x in services)
- logging.debug(_("Serving %s"), name)
- logging.debug(_("Full set of FLAGS:"))
+ logging.debug(_('Serving %s'), name)
+ logging.debug(_('Full set of FLAGS:'))
for flag in FLAGS:
flag_get = FLAGS.get(flag, None)
- logging.debug("%(flag)s : %(flag_get)s" % locals())
+ logging.debug('%(flag)s : %(flag_get)s' % locals())
for x in services:
x.start()
@@ -315,20 +308,20 @@ def serve_wsgi(cls, conf=None):
def _run_wsgi(paste_config_file, apis):
- logging.debug(_("Using paste.deploy config at: %s"), paste_config_file)
+ logging.debug(_('Using paste.deploy config at: %s'), paste_config_file)
apps = []
for api in apis:
config = wsgi.load_paste_configuration(paste_config_file, api)
if config is None:
- logging.debug(_("No paste configuration for app: %s"), api)
+ logging.debug(_('No paste configuration for app: %s'), api)
continue
- logging.debug(_("App Config: %(api)s\n%(config)r") % locals())
- logging.info(_("Running %s API"), api)
+ logging.debug(_('App Config: %(api)s\n%(config)r') % locals())
+ logging.info(_('Running %s API'), api)
app = wsgi.load_paste_app(paste_config_file, api)
- apps.append((app, getattr(FLAGS, "%s_listen_port" % api),
- getattr(FLAGS, "%s_listen" % api)))
+ apps.append((app, getattr(FLAGS, '%s_listen_port' % api),
+ getattr(FLAGS, '%s_listen' % api)))
if len(apps) == 0:
- logging.error(_("No known API applications configured in %s."),
+ logging.error(_('No known API applications configured in %s.'),
paste_config_file)
return
diff --git a/nova/test.py b/nova/test.py
index 3b608520a..4deb2a175 100644
--- a/nova/test.py
+++ b/nova/test.py
@@ -16,12 +16,12 @@
# License for the specific language governing permissions and limitations
# under the License.
-"""
-Base classes for our unit tests.
-Allows overriding of flags for use of fakes,
-and some black magic for inline callbacks.
-"""
+"""Base classes for our unit tests.
+Allows overriding of flags for use of fakes, and some black magic for
+inline callbacks.
+
+"""
import datetime
import functools
@@ -52,9 +52,9 @@ flags.DEFINE_bool('fake_tests', True,
def skip_if_fake(func):
- """Decorator that skips a test if running in fake mode"""
+ """Decorator that skips a test if running in fake mode."""
def _skipper(*args, **kw):
- """Wrapped skipper function"""
+ """Wrapped skipper function."""
if FLAGS.fake_tests:
raise unittest.SkipTest('Test cannot be run in fake mode')
else:
@@ -63,9 +63,10 @@ def skip_if_fake(func):
class TestCase(unittest.TestCase):
- """Test case base class for all unit tests"""
+ """Test case base class for all unit tests."""
+
def setUp(self):
- """Run before each test method to initialize test environment"""
+ """Run before each test method to initialize test environment."""
super(TestCase, self).setUp()
# NOTE(vish): We need a better method for creating fixtures for tests
# now that we have some required db setup for the system
@@ -86,8 +87,7 @@ class TestCase(unittest.TestCase):
self._original_flags = FLAGS.FlagValuesDict()
def tearDown(self):
- """Runs after each test method to finalize/tear down test
- environment."""
+ """Runs after each test method to tear down test environment."""
try:
self.mox.UnsetStubs()
self.stubs.UnsetAll()
@@ -121,7 +121,7 @@ class TestCase(unittest.TestCase):
pass
def flags(self, **kw):
- """Override flag variables for a test"""
+ """Override flag variables for a test."""
for k, v in kw.iteritems():
if k in self.flag_overrides:
self.reset_flags()
@@ -131,7 +131,11 @@ class TestCase(unittest.TestCase):
setattr(FLAGS, k, v)
def reset_flags(self):
- """Resets all flag variables for the test. Runs after each test"""
+ """Resets all flag variables for the test.
+
+ Runs after each test.
+
+ """
FLAGS.Reset()
for k, v in self._original_flags.iteritems():
setattr(FLAGS, k, v)
@@ -158,7 +162,6 @@ class TestCase(unittest.TestCase):
def _monkey_patch_wsgi(self):
"""Allow us to kill servers spawned by wsgi.Server."""
- # TODO(termie): change these patterns to use functools
self.original_start = wsgi.Server.start
@functools.wraps(self.original_start)
@@ -189,12 +192,13 @@ class TestCase(unittest.TestCase):
If you don't care (or don't know) a given value, you can specify
the string DONTCARE as the value. This will cause that dict-item
to be skipped.
+
"""
def raise_assertion(msg):
d1str = str(d1)
d2str = str(d2)
- base_msg = ("Dictionaries do not match. %(msg)s d1: %(d1str)s "
- "d2: %(d2str)s" % locals())
+ base_msg = ('Dictionaries do not match. %(msg)s d1: %(d1str)s '
+ 'd2: %(d2str)s' % locals())
raise AssertionError(base_msg)
d1keys = set(d1.keys())
@@ -202,8 +206,8 @@ class TestCase(unittest.TestCase):
if d1keys != d2keys:
d1only = d1keys - d2keys
d2only = d2keys - d1keys
- raise_assertion("Keys in d1 and not d2: %(d1only)s. "
- "Keys in d2 and not d1: %(d2only)s" % locals())
+ raise_assertion('Keys in d1 and not d2: %(d1only)s. '
+ 'Keys in d2 and not d1: %(d2only)s' % locals())
for key in d1keys:
d1value = d1[key]
@@ -217,19 +221,19 @@ class TestCase(unittest.TestCase):
"d2['%(key)s']=%(d2value)s" % locals())
def assertDictListMatch(self, L1, L2):
- """Assert a list of dicts are equivalent"""
+ """Assert a list of dicts are equivalent."""
def raise_assertion(msg):
L1str = str(L1)
L2str = str(L2)
- base_msg = ("List of dictionaries do not match: %(msg)s "
- "L1: %(L1str)s L2: %(L2str)s" % locals())
+ base_msg = ('List of dictionaries do not match: %(msg)s '
+ 'L1: %(L1str)s L2: %(L2str)s' % locals())
raise AssertionError(base_msg)
L1count = len(L1)
L2count = len(L2)
if L1count != L2count:
- raise_assertion("Length mismatch: len(L1)=%(L1count)d != "
- "len(L2)=%(L2count)d" % locals())
+ raise_assertion('Length mismatch: len(L1)=%(L1count)d != '
+ 'len(L2)=%(L2count)d' % locals())
for d1, d2 in zip(L1, L2):
self.assertDictMatch(d1, d2)
diff --git a/nova/tests/api/openstack/test_api.py b/nova/tests/api/openstack/test_api.py
index 5112c486f..c63431a45 100644
--- a/nova/tests/api/openstack/test_api.py
+++ b/nova/tests/api/openstack/test_api.py
@@ -53,13 +53,13 @@ class APITest(test.TestCase):
#api.application = succeed
api = self._wsgi_app(succeed)
resp = Request.blank('/').get_response(api)
- self.assertFalse('computeFault' in resp.body, resp.body)
+ self.assertFalse('cloudServersFault' in resp.body, resp.body)
self.assertEqual(resp.status_int, 200, resp.body)
#api.application = raise_webob_exc
api = self._wsgi_app(raise_webob_exc)
resp = Request.blank('/').get_response(api)
- self.assertFalse('computeFault' in resp.body, resp.body)
+ self.assertFalse('cloudServersFault' in resp.body, resp.body)
self.assertEqual(resp.status_int, 404, resp.body)
#api.application = raise_api_fault
@@ -71,11 +71,11 @@ class APITest(test.TestCase):
#api.application = fail
api = self._wsgi_app(fail)
resp = Request.blank('/').get_response(api)
- self.assertTrue('{"computeFault' in resp.body, resp.body)
+ self.assertTrue('{"cloudServersFault' in resp.body, resp.body)
self.assertEqual(resp.status_int, 500, resp.body)
#api.application = fail
api = self._wsgi_app(fail)
resp = Request.blank('/.xml').get_response(api)
- self.assertTrue('<computeFault' in resp.body, resp.body)
+ self.assertTrue('<cloudServersFault' in resp.body, resp.body)
self.assertEqual(resp.status_int, 500, resp.body)
diff --git a/nova/tests/api/openstack/test_faults.py b/nova/tests/api/openstack/test_faults.py
index 9746e8168..4d86ffb26 100644
--- a/nova/tests/api/openstack/test_faults.py
+++ b/nova/tests/api/openstack/test_faults.py
@@ -22,6 +22,7 @@ import webob.dec
import webob.exc
from nova import test
+from nova.api.openstack import common
from nova.api.openstack import faults
@@ -47,10 +48,10 @@ class TestFaults(test.TestCase):
response = request.get_response(fault)
expected = self._prepare_xml("""
- <badRequest code="400">
+ <badRequest code="400" xmlns="%s">
<message>scram</message>
</badRequest>
- """)
+ """ % common.XML_NS_V10)
actual = self._prepare_xml(response.body)
self.assertEqual(response.content_type, "application/xml")
@@ -91,11 +92,11 @@ class TestFaults(test.TestCase):
response = request.get_response(fault)
expected = self._prepare_xml("""
- <overLimit code="413">
+ <overLimit code="413" xmlns="%s">
<message>sorry</message>
<retryAfter>4</retryAfter>
</overLimit>
- """)
+ """ % common.XML_NS_V10)
actual = self._prepare_xml(response.body)
self.assertEqual(expected, actual)
diff --git a/nova/tests/api/openstack/test_image_metadata.py b/nova/tests/api/openstack/test_image_metadata.py
index 9be753f84..56be0f1cc 100644
--- a/nova/tests/api/openstack/test_image_metadata.py
+++ b/nova/tests/api/openstack/test_image_metadata.py
@@ -45,10 +45,8 @@ class ImageMetaDataTest(unittest.TestCase):
'is_public': True,
'deleted_at': None,
'properties': {
- 'type': 'ramdisk',
'key1': 'value1',
- 'key2': 'value2'
- },
+ 'key2': 'value2'},
'size': 5882349},
{'status': 'active',
'name': 'image2',
@@ -62,10 +60,21 @@ class ImageMetaDataTest(unittest.TestCase):
'is_public': True,
'deleted_at': None,
'properties': {
- 'type': 'ramdisk',
'key1': 'value1',
- 'key2': 'value2'
- },
+ 'key2': 'value2'},
+ 'size': 5882349},
+ {'status': 'active',
+ 'name': 'image3',
+ 'deleted': False,
+ 'container_format': None,
+ 'created_at': '2011-03-22T17:40:15',
+ 'disk_format': None,
+ 'updated_at': '2011-03-22T17:40:15',
+ 'id': '3',
+ 'location': 'file:///var/lib/glance/images/2',
+ 'is_public': True,
+ 'deleted_at': None,
+ 'properties': {},
'size': 5882349},
]
@@ -77,6 +86,10 @@ class ImageMetaDataTest(unittest.TestCase):
fakes.FakeAuthManager.auth_data = {}
fakes.FakeAuthDatabase.data = {}
fakes.stub_out_auth(self.stubs)
+ # NOTE(dprince) max out properties/metadata in image 3 for testing
+ img3 = self.IMAGE_FIXTURES[2]
+ for num in range(FLAGS.quota_metadata_items):
+ img3['properties']['key%i' % num] = "blah"
fakes.stub_out_glance(self.stubs, self.IMAGE_FIXTURES)
def tearDown(self):
@@ -164,3 +177,25 @@ class ImageMetaDataTest(unittest.TestCase):
req.method = 'DELETE'
res = req.get_response(fakes.wsgi_app())
self.assertEqual(404, res.status_int)
+
+ def test_too_many_metadata_items_on_create(self):
+ data = {"metadata": {}}
+ for num in range(FLAGS.quota_metadata_items + 1):
+ data['metadata']['key%i' % num] = "blah"
+ json_string = str(data).replace("\'", "\"")
+ req = webob.Request.blank('/v1.1/images/2/meta')
+ req.environ['api.version'] = '1.1'
+ req.method = 'POST'
+ req.body = json_string
+ req.headers["content-type"] = "application/json"
+ res = req.get_response(fakes.wsgi_app())
+ self.assertEqual(400, res.status_int)
+
+ def test_too_many_metadata_items_on_put(self):
+ req = webob.Request.blank('/v1.1/images/3/meta/blah')
+ req.environ['api.version'] = '1.1'
+ req.method = 'PUT'
+ req.body = '{"blah": "blah"}'
+ req.headers["content-type"] = "application/json"
+ res = req.get_response(fakes.wsgi_app())
+ self.assertEqual(400, res.status_int)
diff --git a/nova/tests/api/openstack/test_images.py b/nova/tests/api/openstack/test_images.py
index 69cc3116d..ae86d0686 100644
--- a/nova/tests/api/openstack/test_images.py
+++ b/nova/tests/api/openstack/test_images.py
@@ -146,7 +146,7 @@ class LocalImageServiceTest(_BaseImageServiceTests):
for x in [1, 2, 3]:
tempfile.mkstemp(prefix='ami-', dir=self.tempdir)
# create some valid image directories names
- for x in ["1485baed", "1a60f0ee", "3123a73d"]:
+ for x in ["1485baed", "1a60f0ee", "3123a73d"]:
os.makedirs(os.path.join(self.tempdir, x))
found_image_ids = self.service._ids()
self.assertEqual(True, isinstance(found_image_ids, list))
@@ -335,7 +335,8 @@ class ImageControllerWithGlanceServiceTest(test.TestCase):
name="public image"
updated="%(expected_now)s"
created="%(expected_now)s"
- status="ACTIVE" />
+ status="ACTIVE"
+ xmlns="http://docs.rackspacecloud.com/servers/api/v1.0" />
""" % (locals()))
self.assertEqual(expected_image.toxml(), actual_image.toxml())
@@ -353,7 +354,8 @@ class ImageControllerWithGlanceServiceTest(test.TestCase):
name="None"
updated="%(expected_now)s"
created="%(expected_now)s"
- status="ACTIVE" />
+ status="ACTIVE"
+ xmlns="http://docs.rackspacecloud.com/servers/api/v1.0" />
""" % (locals()))
self.assertEqual(expected_image.toxml(), actual_image.toxml())
@@ -372,7 +374,8 @@ class ImageControllerWithGlanceServiceTest(test.TestCase):
name="public image"
updated="%(expected_now)s"
created="%(expected_now)s"
- status="ACTIVE">
+ status="ACTIVE"
+ xmlns="http://docs.openstack.org/compute/api/v1.1">
<links>
<link href="%(expected_href)s" rel="self"/>
<link href="%(expected_href)s" rel="bookmark"
@@ -408,7 +411,8 @@ class ImageControllerWithGlanceServiceTest(test.TestCase):
self.assertEqual(404, response.status_int)
expected = minidom.parseString("""
- <itemNotFound code="404">
+ <itemNotFound code="404"
+ xmlns="http://docs.rackspacecloud.com/servers/api/v1.0">
<message>
Image not found.
</message>
@@ -441,8 +445,11 @@ class ImageControllerWithGlanceServiceTest(test.TestCase):
response = request.get_response(fakes.wsgi_app())
self.assertEqual(404, response.status_int)
+ # NOTE(justinsb): I believe this should still use the v1.0 XSD,
+ # because the element hasn't changed definition
expected = minidom.parseString("""
- <itemNotFound code="404">
+ <itemNotFound code="404"
+ xmlns="http://docs.rackspacecloud.com/servers/api/v1.0">
<message>
Image not found.
</message>
diff --git a/nova/tests/api/openstack/test_limits.py b/nova/tests/api/openstack/test_limits.py
index 05cfacc60..df367005d 100644
--- a/nova/tests/api/openstack/test_limits.py
+++ b/nova/tests/api/openstack/test_limits.py
@@ -136,10 +136,17 @@ class LimitsControllerTest(BaseLimitTestSuite):
request = self._get_index_request("application/xml")
response = request.get_response(self.controller)
- expected = "<limits><rate/><absolute/></limits>"
- body = response.body.replace("\n", "").replace(" ", "")
+ expected = parseString("""
+ <limits
+ xmlns="http://docs.rackspacecloud.com/servers/api/v1.0">
+ <rate/>
+ <absolute/>
+ </limits>
+ """.replace(" ", ""))
- self.assertEqual(expected, body)
+ body = parseString(response.body.replace(" ", ""))
+
+ self.assertEqual(expected.toxml(), body.toxml())
def test_index_xml(self):
"""Test getting limit details in XML."""
@@ -148,7 +155,8 @@ class LimitsControllerTest(BaseLimitTestSuite):
response = request.get_response(self.controller)
expected = parseString("""
- <limits>
+ <limits
+ xmlns="http://docs.rackspacecloud.com/servers/api/v1.0">
<rate>
<limit URI="*" regex=".*" remaining="10" resetTime="0"
unit="MINUTE" value="10" verb="GET"/>
diff --git a/nova/tests/api/openstack/test_server_metadata.py b/nova/tests/api/openstack/test_server_metadata.py
index c8d456472..c4d1d4fd8 100644
--- a/nova/tests/api/openstack/test_server_metadata.py
+++ b/nova/tests/api/openstack/test_server_metadata.py
@@ -21,11 +21,19 @@ import unittest
import webob
+from nova import flags
from nova.api import openstack
from nova.tests.api.openstack import fakes
import nova.wsgi
+FLAGS = flags.FLAGS
+
+
+def return_create_instance_metadata_max(context, server_id, metadata):
+ return stub_max_server_metadata()
+
+
def return_create_instance_metadata(context, server_id, metadata):
return stub_server_metadata()
@@ -48,8 +56,14 @@ def stub_server_metadata():
"key2": "value2",
"key3": "value3",
"key4": "value4",
- "key5": "value5"
- }
+ "key5": "value5"}
+ return metadata
+
+
+def stub_max_server_metadata():
+ metadata = {"metadata": {}}
+ for num in range(FLAGS.quota_metadata_items):
+ metadata['metadata']['key%i' % num] = "blah"
return metadata
@@ -69,7 +83,7 @@ class ServerMetaDataTest(unittest.TestCase):
def test_index(self):
self.stubs.Set(nova.db.api, 'instance_metadata_get',
- return_server_metadata)
+ return_server_metadata)
req = webob.Request.blank('/v1.1/servers/1/meta')
req.environ['api.version'] = '1.1'
res = req.get_response(fakes.wsgi_app())
@@ -79,7 +93,7 @@ class ServerMetaDataTest(unittest.TestCase):
def test_index_no_data(self):
self.stubs.Set(nova.db.api, 'instance_metadata_get',
- return_empty_server_metadata)
+ return_empty_server_metadata)
req = webob.Request.blank('/v1.1/servers/1/meta')
req.environ['api.version'] = '1.1'
res = req.get_response(fakes.wsgi_app())
@@ -89,7 +103,7 @@ class ServerMetaDataTest(unittest.TestCase):
def test_show(self):
self.stubs.Set(nova.db.api, 'instance_metadata_get',
- return_server_metadata)
+ return_server_metadata)
req = webob.Request.blank('/v1.1/servers/1/meta/key5')
req.environ['api.version'] = '1.1'
res = req.get_response(fakes.wsgi_app())
@@ -99,7 +113,7 @@ class ServerMetaDataTest(unittest.TestCase):
def test_show_meta_not_found(self):
self.stubs.Set(nova.db.api, 'instance_metadata_get',
- return_empty_server_metadata)
+ return_empty_server_metadata)
req = webob.Request.blank('/v1.1/servers/1/meta/key6')
req.environ['api.version'] = '1.1'
res = req.get_response(fakes.wsgi_app())
@@ -108,7 +122,7 @@ class ServerMetaDataTest(unittest.TestCase):
def test_delete(self):
self.stubs.Set(nova.db.api, 'instance_metadata_delete',
- delete_server_metadata)
+ delete_server_metadata)
req = webob.Request.blank('/v1.1/servers/1/meta/key5')
req.environ['api.version'] = '1.1'
req.method = 'DELETE'
@@ -117,7 +131,7 @@ class ServerMetaDataTest(unittest.TestCase):
def test_create(self):
self.stubs.Set(nova.db.api, 'instance_metadata_update_or_create',
- return_create_instance_metadata)
+ return_create_instance_metadata)
req = webob.Request.blank('/v1.1/servers/1/meta')
req.environ['api.version'] = '1.1'
req.method = 'POST'
@@ -130,7 +144,7 @@ class ServerMetaDataTest(unittest.TestCase):
def test_update_item(self):
self.stubs.Set(nova.db.api, 'instance_metadata_update_or_create',
- return_create_instance_metadata)
+ return_create_instance_metadata)
req = webob.Request.blank('/v1.1/servers/1/meta/key1')
req.environ['api.version'] = '1.1'
req.method = 'PUT'
@@ -143,7 +157,7 @@ class ServerMetaDataTest(unittest.TestCase):
def test_update_item_too_many_keys(self):
self.stubs.Set(nova.db.api, 'instance_metadata_update_or_create',
- return_create_instance_metadata)
+ return_create_instance_metadata)
req = webob.Request.blank('/v1.1/servers/1/meta/key1')
req.environ['api.version'] = '1.1'
req.method = 'PUT'
@@ -154,7 +168,7 @@ class ServerMetaDataTest(unittest.TestCase):
def test_update_item_body_uri_mismatch(self):
self.stubs.Set(nova.db.api, 'instance_metadata_update_or_create',
- return_create_instance_metadata)
+ return_create_instance_metadata)
req = webob.Request.blank('/v1.1/servers/1/meta/bad')
req.environ['api.version'] = '1.1'
req.method = 'PUT'
@@ -162,3 +176,29 @@ class ServerMetaDataTest(unittest.TestCase):
req.headers["content-type"] = "application/json"
res = req.get_response(fakes.wsgi_app())
self.assertEqual(400, res.status_int)
+
+ def test_too_many_metadata_items_on_create(self):
+ self.stubs.Set(nova.db.api, 'instance_metadata_update_or_create',
+ return_create_instance_metadata)
+ data = {"metadata": {}}
+ for num in range(FLAGS.quota_metadata_items + 1):
+ data['metadata']['key%i' % num] = "blah"
+ json_string = str(data).replace("\'", "\"")
+ req = webob.Request.blank('/v1.1/servers/1/meta')
+ req.environ['api.version'] = '1.1'
+ req.method = 'POST'
+ req.body = json_string
+ req.headers["content-type"] = "application/json"
+ res = req.get_response(fakes.wsgi_app())
+ self.assertEqual(400, res.status_int)
+
+ def test_to_many_metadata_items_on_update_item(self):
+ self.stubs.Set(nova.db.api, 'instance_metadata_update_or_create',
+ return_create_instance_metadata_max)
+ req = webob.Request.blank('/v1.1/servers/1/meta/key1')
+ req.environ['api.version'] = '1.1'
+ req.method = 'PUT'
+ req.body = '{"a new key": "a new value"}'
+ req.headers["content-type"] = "application/json"
+ res = req.get_response(fakes.wsgi_app())
+ self.assertEqual(400, res.status_int)
diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py
index 313676e72..556046e9d 100644
--- a/nova/tests/api/openstack/test_servers.py
+++ b/nova/tests/api/openstack/test_servers.py
@@ -32,6 +32,7 @@ from nova import test
import nova.api.openstack
from nova.api.openstack import servers
import nova.compute.api
+from nova.compute import instance_types
import nova.db.api
from nova.db.sqlalchemy.models import Instance
from nova.db.sqlalchemy.models import InstanceMetadata
@@ -71,13 +72,19 @@ def instance_address(context, instance_id):
return None
-def stub_instance(id, user_id=1, private_address=None, public_addresses=None):
+def stub_instance(id, user_id=1, private_address=None, public_addresses=None,
+ host=None):
metadata = []
metadata.append(InstanceMetadata(key='seq', value=id))
- if public_addresses == None:
+ inst_type = instance_types.get_instance_type_by_flavor_id(1)
+
+ if public_addresses is None:
public_addresses = list()
+ if host is not None:
+ host = str(host)
+
instance = {
"id": id,
"admin_pass": "",
@@ -95,8 +102,8 @@ def stub_instance(id, user_id=1, private_address=None, public_addresses=None):
"vcpus": 0,
"local_gb": 0,
"hostname": "",
- "host": None,
- "instance_type": "1",
+ "host": host,
+ "instance_type": dict(inst_type),
"user_data": "",
"reservation_id": "",
"mac_address": "",
@@ -192,6 +199,26 @@ class ServersTest(test.TestCase):
print res_dict['server']
self.assertEqual(res_dict['server']['links'], expected_links)
+ def test_get_server_by_id_with_addresses_xml(self):
+ private = "192.168.0.3"
+ public = ["1.2.3.4"]
+ new_return_server = return_server_with_addresses(private, public)
+ self.stubs.Set(nova.db.api, 'instance_get', new_return_server)
+ req = webob.Request.blank('/v1.0/servers/1')
+ req.headers['Accept'] = 'application/xml'
+ res = req.get_response(fakes.wsgi_app())
+ dom = minidom.parseString(res.body)
+ server = dom.childNodes[0]
+ self.assertEquals(server.nodeName, 'server')
+ self.assertEquals(server.getAttribute('id'), '1')
+ self.assertEquals(server.getAttribute('name'), 'server1')
+ (public,) = server.getElementsByTagName('public')
+ (ip,) = public.getElementsByTagName('ip')
+ self.assertEquals(ip.getAttribute('addr'), '1.2.3.4')
+ (private,) = server.getElementsByTagName('private')
+ (ip,) = private.getElementsByTagName('ip')
+ self.assertEquals(ip.getAttribute('addr'), '192.168.0.3')
+
def test_get_server_by_id_with_addresses(self):
private = "192.168.0.3"
public = ["1.2.3.4"]
@@ -208,6 +235,84 @@ class ServersTest(test.TestCase):
self.assertEqual(len(addresses["private"]), 1)
self.assertEqual(addresses["private"][0], private)
+ def test_get_server_addresses_V10(self):
+ private = '192.168.0.3'
+ public = ['1.2.3.4']
+ new_return_server = return_server_with_addresses(private, public)
+ self.stubs.Set(nova.db.api, 'instance_get', new_return_server)
+ req = webob.Request.blank('/v1.0/servers/1/ips')
+ res = req.get_response(fakes.wsgi_app())
+ res_dict = json.loads(res.body)
+ self.assertEqual(res_dict, {
+ 'addresses': {'public': public, 'private': [private]}})
+
+ def test_get_server_addresses_xml_V10(self):
+ private_expected = "192.168.0.3"
+ public_expected = ["1.2.3.4"]
+ new_return_server = return_server_with_addresses(private_expected,
+ public_expected)
+ self.stubs.Set(nova.db.api, 'instance_get', new_return_server)
+ req = webob.Request.blank('/v1.0/servers/1/ips')
+ req.headers['Accept'] = 'application/xml'
+ res = req.get_response(fakes.wsgi_app())
+ dom = minidom.parseString(res.body)
+ (addresses,) = dom.childNodes
+ self.assertEquals(addresses.nodeName, 'addresses')
+ (public,) = addresses.getElementsByTagName('public')
+ (ip,) = public.getElementsByTagName('ip')
+ self.assertEquals(ip.getAttribute('addr'), public_expected[0])
+ (private,) = addresses.getElementsByTagName('private')
+ (ip,) = private.getElementsByTagName('ip')
+ self.assertEquals(ip.getAttribute('addr'), private_expected)
+
+ def test_get_server_addresses_public_V10(self):
+ private = "192.168.0.3"
+ public = ["1.2.3.4"]
+ new_return_server = return_server_with_addresses(private, public)
+ self.stubs.Set(nova.db.api, 'instance_get', new_return_server)
+ req = webob.Request.blank('/v1.0/servers/1/ips/public')
+ res = req.get_response(fakes.wsgi_app())
+ res_dict = json.loads(res.body)
+ self.assertEqual(res_dict, {'public': public})
+
+ def test_get_server_addresses_private_V10(self):
+ private = "192.168.0.3"
+ public = ["1.2.3.4"]
+ new_return_server = return_server_with_addresses(private, public)
+ self.stubs.Set(nova.db.api, 'instance_get', new_return_server)
+ req = webob.Request.blank('/v1.0/servers/1/ips/private')
+ res = req.get_response(fakes.wsgi_app())
+ res_dict = json.loads(res.body)
+ self.assertEqual(res_dict, {'private': [private]})
+
+ def test_get_server_addresses_public_xml_V10(self):
+ private = "192.168.0.3"
+ public = ["1.2.3.4"]
+ new_return_server = return_server_with_addresses(private, public)
+ self.stubs.Set(nova.db.api, 'instance_get', new_return_server)
+ req = webob.Request.blank('/v1.0/servers/1/ips/public')
+ req.headers['Accept'] = 'application/xml'
+ res = req.get_response(fakes.wsgi_app())
+ dom = minidom.parseString(res.body)
+ (public_node,) = dom.childNodes
+ self.assertEquals(public_node.nodeName, 'public')
+ (ip,) = public_node.getElementsByTagName('ip')
+ self.assertEquals(ip.getAttribute('addr'), public[0])
+
+ def test_get_server_addresses_private_xml_V10(self):
+ private = "192.168.0.3"
+ public = ["1.2.3.4"]
+ new_return_server = return_server_with_addresses(private, public)
+ self.stubs.Set(nova.db.api, 'instance_get', new_return_server)
+ req = webob.Request.blank('/v1.0/servers/1/ips/private')
+ req.headers['Accept'] = 'application/xml'
+ res = req.get_response(fakes.wsgi_app())
+ dom = minidom.parseString(res.body)
+ (private_node,) = dom.childNodes
+ self.assertEquals(private_node.nodeName, 'private')
+ (ip,) = private_node.getElementsByTagName('ip')
+ self.assertEquals(ip.getAttribute('addr'), private)
+
def test_get_server_by_id_with_addresses_v11(self):
private = "192.168.0.3"
public = ["1.2.3.4"]
@@ -508,6 +613,70 @@ class ServersTest(test.TestCase):
res = req.get_response(fakes.wsgi_app())
self.assertEqual(res.status_int, 400)
+ def test_create_instance_with_admin_pass_v10(self):
+ self._setup_for_create_instance()
+
+ body = {
+ 'server': {
+ 'name': 'test-server-create',
+ 'imageId': 3,
+ 'flavorId': 1,
+ 'adminPass': 'testpass',
+ },
+ }
+
+ req = webob.Request.blank('/v1.0/servers')
+ req.method = 'POST'
+ req.body = json.dumps(body)
+ req.headers['content-type'] = "application/json"
+ res = req.get_response(fakes.wsgi_app())
+ res = json.loads(res.body)
+ self.assertNotEqual(res['server']['adminPass'],
+ body['server']['adminPass'])
+
+ def test_create_instance_with_admin_pass_v11(self):
+ self._setup_for_create_instance()
+
+ imageRef = 'http://localhost/v1.1/images/2'
+ flavorRef = 'http://localhost/v1.1/flavors/3'
+ body = {
+ 'server': {
+ 'name': 'server_test',
+ 'imageRef': imageRef,
+ 'flavorRef': flavorRef,
+ 'adminPass': 'testpass',
+ },
+ }
+
+ req = webob.Request.blank('/v1.1/servers')
+ req.method = 'POST'
+ req.body = json.dumps(body)
+ req.headers['content-type'] = "application/json"
+ res = req.get_response(fakes.wsgi_app())
+ server = json.loads(res.body)['server']
+ self.assertEqual(server['adminPass'], body['server']['adminPass'])
+
+ def test_create_instance_with_empty_admin_pass_v11(self):
+ self._setup_for_create_instance()
+
+ imageRef = 'http://localhost/v1.1/images/2'
+ flavorRef = 'http://localhost/v1.1/flavors/3'
+ body = {
+ 'server': {
+ 'name': 'server_test',
+ 'imageRef': imageRef,
+ 'flavorRef': flavorRef,
+ 'adminPass': '',
+ },
+ }
+
+ req = webob.Request.blank('/v1.1/servers')
+ req.method = 'POST'
+ req.body = json.dumps(body)
+ req.headers['content-type'] = "application/json"
+ res = req.get_response(fakes.wsgi_app())
+ self.assertEqual(res.status_int, 400)
+
def test_update_no_body(self):
req = webob.Request.blank('/v1.0/servers/1')
req.method = 'PUT'
@@ -618,6 +787,22 @@ class ServersTest(test.TestCase):
res = req.get_response(fakes.wsgi_app())
self.assertEqual(res.status_int, 404)
+ def test_get_all_server_details_xml_v1_0(self):
+ req = webob.Request.blank('/v1.0/servers/detail')
+ req.headers['Accept'] = 'application/xml'
+ res = req.get_response(fakes.wsgi_app())
+ print res.body
+ dom = minidom.parseString(res.body)
+ for i, server in enumerate(dom.getElementsByTagName('server')):
+ self.assertEqual(server.getAttribute('id'), str(i))
+ self.assertEqual(server.getAttribute('hostId'), '')
+ self.assertEqual(server.getAttribute('name'), 'server%d' % i)
+ self.assertEqual(server.getAttribute('imageId'), '10')
+ self.assertEqual(server.getAttribute('status'), 'BUILD')
+ (meta,) = server.getElementsByTagName('meta')
+ self.assertEqual(meta.getAttribute('key'), 'seq')
+ self.assertEqual(meta.firstChild.data.strip(), str(i))
+
def test_get_all_server_details_v1_0(self):
req = webob.Request.blank('/v1.0/servers/detail')
res = req.get_response(fakes.wsgi_app())
@@ -628,9 +813,9 @@ class ServersTest(test.TestCase):
self.assertEqual(s['hostId'], '')
self.assertEqual(s['name'], 'server%d' % i)
self.assertEqual(s['imageId'], '10')
- self.assertEqual(s['flavorId'], '1')
+ self.assertEqual(s['flavorId'], 1)
self.assertEqual(s['status'], 'BUILD')
- self.assertEqual(s['metadata']['seq'], i)
+ self.assertEqual(s['metadata']['seq'], str(i))
def test_get_all_server_details_v1_1(self):
req = webob.Request.blank('/v1.1/servers/detail')
@@ -644,7 +829,7 @@ class ServersTest(test.TestCase):
self.assertEqual(s['imageRef'], 'http://localhost/v1.1/images/10')
self.assertEqual(s['flavorRef'], 'http://localhost/v1.1/flavors/1')
self.assertEqual(s['status'], 'BUILD')
- self.assertEqual(s['metadata']['seq'], i)
+ self.assertEqual(s['metadata']['seq'], str(i))
def test_get_all_server_details_with_host(self):
'''
@@ -654,12 +839,8 @@ class ServersTest(test.TestCase):
instances - 2 on one host and 3 on another.
'''
- def stub_instance(id, user_id=1):
- return Instance(id=id, state=0, image_id=10, user_id=user_id,
- display_name='server%s' % id, host='host%s' % (id % 2))
-
def return_servers_with_host(context, user_id=1):
- return [stub_instance(i) for i in xrange(5)]
+ return [stub_instance(i, 1, None, None, i % 2) for i in xrange(5)]
self.stubs.Set(nova.db.api, 'instance_get_all_by_user',
return_servers_with_host)
@@ -677,7 +858,8 @@ class ServersTest(test.TestCase):
self.assertEqual(s['id'], i)
self.assertEqual(s['hostId'], host_ids[i % 2])
self.assertEqual(s['name'], 'server%d' % i)
- self.assertEqual(s['imageId'], 10)
+ self.assertEqual(s['imageId'], '10')
+ self.assertEqual(s['flavorId'], 1)
def test_server_pause(self):
FLAGS.allow_admin_api = True
@@ -1525,29 +1707,27 @@ class TestGetKernelRamdiskFromImage(test.TestCase):
def test_not_ami(self):
"""Anything other than ami should return no kernel and no ramdisk"""
- image_meta = {'id': 1, 'status': 'active',
- 'properties': {'disk_format': 'vhd'}}
+ image_meta = {'id': 1, 'status': 'active', 'container_format': 'vhd'}
kernel_id, ramdisk_id = self._get_k_r(image_meta)
self.assertEqual(kernel_id, None)
self.assertEqual(ramdisk_id, None)
def test_ami_no_kernel(self):
"""If an ami is missing a kernel it should raise NotFound"""
- image_meta = {'id': 1, 'status': 'active',
- 'properties': {'disk_format': 'ami', 'ramdisk_id': 1}}
+ image_meta = {'id': 1, 'status': 'active', 'container_format': 'ami',
+ 'properties': {'ramdisk_id': 1}}
self.assertRaises(exception.NotFound, self._get_k_r, image_meta)
def test_ami_no_ramdisk(self):
"""If an ami is missing a ramdisk it should raise NotFound"""
- image_meta = {'id': 1, 'status': 'active',
- 'properties': {'disk_format': 'ami', 'kernel_id': 1}}
+ image_meta = {'id': 1, 'status': 'active', 'container_format': 'ami',
+ 'properties': {'kernel_id': 1}}
self.assertRaises(exception.NotFound, self._get_k_r, image_meta)
def test_ami_kernel_ramdisk_present(self):
"""Return IDs if both kernel and ramdisk are present"""
- image_meta = {'id': 1, 'status': 'active',
- 'properties': {'disk_format': 'ami', 'kernel_id': 1,
- 'ramdisk_id': 2}}
+ image_meta = {'id': 1, 'status': 'active', 'container_format': 'ami',
+ 'properties': {'kernel_id': 1, 'ramdisk_id': 2}}
kernel_id, ramdisk_id = self._get_k_r(image_meta)
self.assertEqual(kernel_id, 1)
self.assertEqual(ramdisk_id, 2)
diff --git a/nova/tests/api/openstack/test_versions.py b/nova/tests/api/openstack/test_versions.py
index 2640a4ddb..fd8d50904 100644
--- a/nova/tests/api/openstack/test_versions.py
+++ b/nova/tests/api/openstack/test_versions.py
@@ -47,8 +47,7 @@ class VersionsTest(test.TestCase):
{
"rel": "self",
"href": "http://localhost/v1.1",
- }
- ],
+ }],
},
{
"id": "v1.0",
@@ -57,8 +56,7 @@ class VersionsTest(test.TestCase):
{
"rel": "self",
"href": "http://localhost/v1.0",
- }
- ],
+ }],
},
]
self.assertEqual(versions, expected)
diff --git a/nova/tests/db/fakes.py b/nova/tests/db/fakes.py
index 7ddfe377a..58d251b1e 100644
--- a/nova/tests/db/fakes.py
+++ b/nova/tests/db/fakes.py
@@ -28,29 +28,34 @@ def stub_out_db_instance_api(stubs, injected=True):
"""Stubs out the db API for creating Instances."""
INSTANCE_TYPES = {
- 'm1.tiny': dict(memory_mb=512,
+ 'm1.tiny': dict(id=2,
+ memory_mb=512,
vcpus=1,
local_gb=0,
flavorid=1,
rxtx_cap=1),
- 'm1.small': dict(memory_mb=2048,
+ 'm1.small': dict(id=5,
+ memory_mb=2048,
vcpus=1,
local_gb=20,
flavorid=2,
rxtx_cap=2),
'm1.medium':
- dict(memory_mb=4096,
+ dict(id=1,
+ memory_mb=4096,
vcpus=2,
local_gb=40,
flavorid=3,
rxtx_cap=3),
- 'm1.large': dict(memory_mb=8192,
+ 'm1.large': dict(id=3,
+ memory_mb=8192,
vcpus=4,
local_gb=80,
flavorid=4,
rxtx_cap=4),
'm1.xlarge':
- dict(memory_mb=16384,
+ dict(id=4,
+ memory_mb=16384,
vcpus=8,
local_gb=160,
flavorid=5,
@@ -107,6 +112,12 @@ def stub_out_db_instance_api(stubs, injected=True):
def fake_instance_type_get_by_name(context, name):
return INSTANCE_TYPES[name]
+ def fake_instance_type_get_by_id(context, id):
+ for name, inst_type in INSTANCE_TYPES.iteritems():
+ if str(inst_type['id']) == str(id):
+ return inst_type
+ return None
+
def fake_network_get_by_instance(context, instance_id):
# Even instance numbers are on vlan networks
if instance_id % 2 == 0:
@@ -136,6 +147,7 @@ def stub_out_db_instance_api(stubs, injected=True):
fake_network_get_all_by_instance)
stubs.Set(db, 'instance_type_get_all', fake_instance_type_get_all)
stubs.Set(db, 'instance_type_get_by_name', fake_instance_type_get_by_name)
+ stubs.Set(db, 'instance_type_get_by_id', fake_instance_type_get_by_id)
stubs.Set(db, 'instance_get_fixed_address',
fake_instance_get_fixed_address)
stubs.Set(db, 'instance_get_fixed_address_v6',
diff --git a/nova/tests/image/test_glance.py b/nova/tests/image/test_glance.py
index 9d0b14613..109905ded 100644
--- a/nova/tests/image/test_glance.py
+++ b/nova/tests/image/test_glance.py
@@ -209,17 +209,17 @@ class TestMutatorDateTimeTests(BaseGlanceTest):
self.assertDateTimesEmpty(image_meta)
def test_update_handles_datetimes(self):
+ self.client.images = {'image1': self._make_datetime_fixture()}
self.client.update_response = self._make_datetime_fixture()
- dummy_id = 'dummy_id'
dummy_meta = {}
- image_meta = self.service.update(self.context, 'dummy_id', dummy_meta)
+ image_meta = self.service.update(self.context, 'image1', dummy_meta)
self.assertDateTimesFilled(image_meta)
def test_update_handles_none_datetimes(self):
+ self.client.images = {'image1': self._make_datetime_fixture()}
self.client.update_response = self._make_none_datetime_fixture()
- dummy_id = 'dummy_id'
dummy_meta = {}
- image_meta = self.service.update(self.context, 'dummy_id', dummy_meta)
+ image_meta = self.service.update(self.context, 'image1', dummy_meta)
self.assertDateTimesEmpty(image_meta)
def _make_datetime_fixture(self):
diff --git a/nova/tests/integrated/test_servers.py b/nova/tests/integrated/test_servers.py
index 749ea8955..e89d0100a 100644
--- a/nova/tests/integrated/test_servers.py
+++ b/nova/tests/integrated/test_servers.py
@@ -134,50 +134,50 @@ class ServersTest(integrated_helpers._IntegratedTestBase):
# Should be gone
self.assertFalse(found_server)
-# TODO(justinsb): Enable this unit test when the metadata bug is fixed
-# def test_create_server_with_metadata(self):
-# """Creates a server with metadata"""
-#
-# # Build the server data gradually, checking errors along the way
-# server = self._build_minimal_create_server_request()
-#
-# for metadata_count in range(30):
-# metadata = {}
-# for i in range(metadata_count):
-# metadata['key_%s' % i] = 'value_%s' % i
-# server['metadata'] = metadata
-#
-# post = {'server': server}
-# created_server = self.api.post_server(post)
-# LOG.debug("created_server: %s" % created_server)
-# self.assertTrue(created_server['id'])
-# created_server_id = created_server['id']
-# # Reenable when bug fixed
-# # self.assertEqual(metadata, created_server.get('metadata'))
-#
-# # Check it's there
-# found_server = self.api.get_server(created_server_id)
-# self.assertEqual(created_server_id, found_server['id'])
-# self.assertEqual(metadata, found_server.get('metadata'))
-#
-# # The server should also be in the all-servers details list
-# servers = self.api.get_servers(detail=True)
-# server_map = dict((server['id'], server) for server in servers)
-# found_server = server_map.get(created_server_id)
-# self.assertTrue(found_server)
-# # Details do include metadata
-# self.assertEqual(metadata, found_server.get('metadata'))
-#
-# # The server should also be in the all-servers summary list
-# servers = self.api.get_servers(detail=False)
-# server_map = dict((server['id'], server) for server in servers)
-# found_server = server_map.get(created_server_id)
-# self.assertTrue(found_server)
-# # Summary should not include metadata
-# self.assertFalse(found_server.get('metadata'))
-#
-# # Cleanup
-# self._delete_server(created_server_id)
+ def test_create_server_with_metadata(self):
+ """Creates a server with metadata."""
+
+ # Build the server data gradually, checking errors along the way
+ server = self._build_minimal_create_server_request()
+
+ metadata = {}
+ for i in range(30):
+ metadata['key_%s' % i] = 'value_%s' % i
+
+ server['metadata'] = metadata
+
+ post = {'server': server}
+ created_server = self.api.post_server(post)
+ LOG.debug("created_server: %s" % created_server)
+ self.assertTrue(created_server['id'])
+ created_server_id = created_server['id']
+
+ # Reenable when bug fixed
+ self.assertEqual(metadata, created_server.get('metadata'))
+ # Check it's there
+
+ found_server = self.api.get_server(created_server_id)
+ self.assertEqual(created_server_id, found_server['id'])
+ self.assertEqual(metadata, found_server.get('metadata'))
+
+ # The server should also be in the all-servers details list
+ servers = self.api.get_servers(detail=True)
+ server_map = dict((server['id'], server) for server in servers)
+ found_server = server_map.get(created_server_id)
+ self.assertTrue(found_server)
+ # Details do include metadata
+ self.assertEqual(metadata, found_server.get('metadata'))
+
+ # The server should also be in the all-servers summary list
+ servers = self.api.get_servers(detail=False)
+ server_map = dict((server['id'], server) for server in servers)
+ found_server = server_map.get(created_server_id)
+ self.assertTrue(found_server)
+ # Summary should not include metadata
+ self.assertFalse(found_server.get('metadata'))
+
+ # Cleanup
+ self._delete_server(created_server_id)
if __name__ == "__main__":
diff --git a/nova/tests/integrated/test_xml.py b/nova/tests/integrated/test_xml.py
new file mode 100644
index 000000000..8a9754777
--- /dev/null
+++ b/nova/tests/integrated/test_xml.py
@@ -0,0 +1,56 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2011 Justin Santa Barbara
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from nova import flags
+from nova.log import logging
+from nova.tests.integrated import integrated_helpers
+from nova.api.openstack import common
+
+
+LOG = logging.getLogger('nova.tests.integrated')
+
+
+FLAGS = flags.FLAGS
+FLAGS.verbose = True
+
+
+class XmlTests(integrated_helpers._IntegratedTestBase):
+ """"Some basic XML sanity checks."""
+
+ def test_namespace_limits(self):
+ """/limits should have v1.0 namespace (hasn't changed in 1.1)."""
+ headers = {}
+ headers['Accept'] = 'application/xml'
+
+ response = self.api.api_request('/limits', headers=headers)
+ data = response.read()
+ LOG.debug("data: %s" % data)
+
+ prefix = '<limits xmlns="%s"' % common.XML_NS_V10
+ self.assertTrue(data.startswith(prefix))
+
+ def test_namespace_servers(self):
+ """/servers should have v1.1 namespace (has changed in 1.1)."""
+ headers = {}
+ headers['Accept'] = 'application/xml'
+
+ response = self.api.api_request('/servers', headers=headers)
+ data = response.read()
+ LOG.debug("data: %s" % data)
+
+ prefix = '<servers xmlns="%s"' % common.XML_NS_V11
+ self.assertTrue(data.startswith(prefix))
diff --git a/nova/tests/test_cloud.py b/nova/tests/test_cloud.py
index 5cb969979..c45bdd12c 100644
--- a/nova/tests/test_cloud.py
+++ b/nova/tests/test_cloud.py
@@ -36,6 +36,7 @@ from nova import rpc
from nova import service
from nova import test
from nova import utils
+from nova import exception
from nova.auth import manager
from nova.compute import power_state
from nova.api.ec2 import cloud
@@ -247,6 +248,37 @@ class CloudTestCase(test.TestCase):
self.assertRaises(NotFound, describe_images,
self.context, ['ami-fake'])
+ def test_describe_image_attribute(self):
+ describe_image_attribute = self.cloud.describe_image_attribute
+
+ def fake_show(meh, context, id):
+ return {'id': 1, 'properties': {'kernel_id': 1, 'ramdisk_id': 1,
+ 'type': 'machine'}, 'is_public': True}
+
+ self.stubs.Set(local.LocalImageService, 'show', fake_show)
+ self.stubs.Set(local.LocalImageService, 'show_by_name', fake_show)
+ result = describe_image_attribute(self.context, 'ami-00000001',
+ 'launchPermission')
+ self.assertEqual([{'group': 'all'}], result['launchPermission'])
+
+ def test_modify_image_attribute(self):
+ modify_image_attribute = self.cloud.modify_image_attribute
+
+ def fake_show(meh, context, id):
+ return {'id': 1, 'properties': {'kernel_id': 1, 'ramdisk_id': 1,
+ 'type': 'machine'}, 'is_public': False}
+
+ def fake_update(meh, context, image_id, metadata, data=None):
+ return metadata
+
+ self.stubs.Set(local.LocalImageService, 'show', fake_show)
+ self.stubs.Set(local.LocalImageService, 'show_by_name', fake_show)
+ self.stubs.Set(local.LocalImageService, 'update', fake_update)
+ result = modify_image_attribute(self.context, 'ami-00000001',
+ 'launchPermission', 'add',
+ user_group=['all'])
+ self.assertEqual(True, result['is_public'])
+
def test_console_output(self):
instance_type = FLAGS.default_instance_type
max_count = 1
@@ -341,6 +373,19 @@ class CloudTestCase(test.TestCase):
LOG.debug(_("Terminating instance %s"), instance_id)
rv = self.compute.terminate_instance(instance_id)
+ def test_terminate_instances(self):
+ inst1 = db.instance_create(self.context, {'reservation_id': 'a',
+ 'image_id': 1,
+ 'host': 'host1'})
+ terminate_instances = self.cloud.terminate_instances
+ # valid instance_id
+ result = terminate_instances(self.context, ['i-00000001'])
+ self.assertTrue(result)
+ # non-existing instance_id
+ self.assertRaises(exception.InstanceNotFound, terminate_instances,
+ self.context, ['i-2'])
+ db.instance_destroy(self.context, inst1['id'])
+
def test_update_of_instance_display_fields(self):
inst = db.instance_create(self.context, {})
ec2_id = ec2utils.id_to_ec2_id(inst['id'])
diff --git a/nova/tests/test_compute.py b/nova/tests/test_compute.py
index 1b0f426d2..393110791 100644
--- a/nova/tests/test_compute.py
+++ b/nova/tests/test_compute.py
@@ -84,7 +84,8 @@ class ComputeTestCase(test.TestCase):
inst['launch_time'] = '10'
inst['user_id'] = self.user.id
inst['project_id'] = self.project.id
- inst['instance_type'] = 'm1.tiny'
+ type_id = instance_types.get_instance_type_by_name('m1.tiny')['id']
+ inst['instance_type_id'] = type_id
inst['mac_address'] = utils.generate_mac()
inst['ami_launch_index'] = 0
inst.update(params)
@@ -132,7 +133,7 @@ class ComputeTestCase(test.TestCase):
cases = [dict(), dict(display_name=None)]
for instance in cases:
ref = self.compute_api.create(self.context,
- FLAGS.default_instance_type, None, **instance)
+ instance_types.get_default_instance_type(), None, **instance)
try:
self.assertNotEqual(ref[0]['display_name'], None)
finally:
@@ -143,7 +144,7 @@ class ComputeTestCase(test.TestCase):
group = self._create_group()
ref = self.compute_api.create(
self.context,
- instance_type=FLAGS.default_instance_type,
+ instance_type=instance_types.get_default_instance_type(),
image_id=None,
security_group=['testgroup'])
try:
@@ -161,7 +162,7 @@ class ComputeTestCase(test.TestCase):
ref = self.compute_api.create(
self.context,
- instance_type=FLAGS.default_instance_type,
+ instance_type=instance_types.get_default_instance_type(),
image_id=None,
security_group=['testgroup'])
try:
@@ -177,7 +178,7 @@ class ComputeTestCase(test.TestCase):
ref = self.compute_api.create(
self.context,
- instance_type=FLAGS.default_instance_type,
+ instance_type=instance_types.get_default_instance_type(),
image_id=None,
security_group=['testgroup'])
@@ -359,8 +360,9 @@ class ComputeTestCase(test.TestCase):
instance_id = self._create_instance()
self.compute.run_instance(self.context, instance_id)
+ inst_type = instance_types.get_instance_type_by_name('m1.xlarge')
db.instance_update(self.context, instance_id,
- {'instance_type': 'm1.xlarge'})
+ {'instance_type_id': inst_type['id']})
self.assertRaises(exception.ApiError, self.compute_api.resize,
context, instance_id, 1)
@@ -380,8 +382,8 @@ class ComputeTestCase(test.TestCase):
self.compute.terminate_instance(context, instance_id)
def test_get_by_flavor_id(self):
- type = instance_types.get_by_flavor_id(1)
- self.assertEqual(type, 'm1.tiny')
+ type = instance_types.get_instance_type_by_flavor_id(1)
+ self.assertEqual(type['name'], 'm1.tiny')
def test_resize_same_source_fails(self):
"""Ensure instance fails to migrate when source and destination are
@@ -664,4 +666,5 @@ class ComputeTestCase(test.TestCase):
instances = db.instance_get_all(context.get_admin_context())
LOG.info(_("After force-killing instances: %s"), instances)
- self.assertEqual(len(instances), 0)
+ self.assertEqual(len(instances), 1)
+ self.assertEqual(power_state.SHUTOFF, instances[0]['state'])
diff --git a/nova/tests/test_console.py b/nova/tests/test_console.py
index d47c70d88..1a9a867ee 100644
--- a/nova/tests/test_console.py
+++ b/nova/tests/test_console.py
@@ -62,7 +62,7 @@ class ConsoleTestCase(test.TestCase):
inst['launch_time'] = '10'
inst['user_id'] = self.user.id
inst['project_id'] = self.project.id
- inst['instance_type'] = 'm1.tiny'
+ inst['instance_type_id'] = 1
inst['mac_address'] = utils.generate_mac()
inst['ami_launch_index'] = 0
return db.instance_create(self.context, inst)['id']
diff --git a/nova/tests/test_instance_types.py b/nova/tests/test_instance_types.py
index edc538879..5d6d5e1f4 100644
--- a/nova/tests/test_instance_types.py
+++ b/nova/tests/test_instance_types.py
@@ -40,7 +40,11 @@ class InstanceTypeTestCase(test.TestCase):
max_flavorid = session.query(models.InstanceTypes).\
order_by("flavorid desc").\
first()
+ max_id = session.query(models.InstanceTypes).\
+ order_by("id desc").\
+ first()
self.flavorid = max_flavorid["flavorid"] + 1
+ self.id = max_id["id"] + 1
self.name = str(int(time.time()))
def test_instance_type_create_then_delete(self):
@@ -53,7 +57,7 @@ class InstanceTypeTestCase(test.TestCase):
'instance type was not created')
instance_types.destroy(self.name)
self.assertEqual(1,
- instance_types.get_instance_type(self.name)["deleted"])
+ instance_types.get_instance_type(self.id)["deleted"])
self.assertEqual(starting_inst_list, instance_types.get_all_types())
instance_types.purge(self.name)
self.assertEqual(len(starting_inst_list),
diff --git a/nova/tests/test_quota.py b/nova/tests/test_quota.py
index c65bc459d..39a123158 100644
--- a/nova/tests/test_quota.py
+++ b/nova/tests/test_quota.py
@@ -67,7 +67,7 @@ class QuotaTestCase(test.TestCase):
inst['reservation_id'] = 'r-fakeres'
inst['user_id'] = self.user.id
inst['project_id'] = self.project.id
- inst['instance_type'] = 'm1.large'
+ inst['instance_type_id'] = '3' # m1.large
inst['vcpus'] = cores
inst['mac_address'] = utils.generate_mac()
return db.instance_create(self.context, inst)['id']
@@ -124,11 +124,12 @@ class QuotaTestCase(test.TestCase):
for i in range(FLAGS.quota_instances):
instance_id = self._create_instance()
instance_ids.append(instance_id)
+ inst_type = instance_types.get_instance_type_by_name('m1.small')
self.assertRaises(quota.QuotaError, compute.API().create,
self.context,
min_count=1,
max_count=1,
- instance_type='m1.small',
+ instance_type=inst_type,
image_id=1)
for instance_id in instance_ids:
db.instance_destroy(self.context, instance_id)
@@ -137,11 +138,12 @@ class QuotaTestCase(test.TestCase):
instance_ids = []
instance_id = self._create_instance(cores=4)
instance_ids.append(instance_id)
+ inst_type = instance_types.get_instance_type_by_name('m1.small')
self.assertRaises(quota.QuotaError, compute.API().create,
self.context,
min_count=1,
max_count=1,
- instance_type='m1.small',
+ instance_type=inst_type,
image_id=1)
for instance_id in instance_ids:
db.instance_destroy(self.context, instance_id)
@@ -192,11 +194,12 @@ class QuotaTestCase(test.TestCase):
metadata = {}
for i in range(FLAGS.quota_metadata_items + 1):
metadata['key%s' % i] = 'value%s' % i
+ inst_type = instance_types.get_instance_type_by_name('m1.small')
self.assertRaises(quota.QuotaError, compute.API().create,
self.context,
min_count=1,
max_count=1,
- instance_type='m1.small',
+ instance_type=inst_type,
image_id='fake',
metadata=metadata)
@@ -207,13 +210,15 @@ class QuotaTestCase(test.TestCase):
def _create_with_injected_files(self, files):
api = compute.API(image_service=self.StubImageService())
+ inst_type = instance_types.get_instance_type_by_name('m1.small')
api.create(self.context, min_count=1, max_count=1,
- instance_type='m1.small', image_id='fake',
+ instance_type=inst_type, image_id='fake',
injected_files=files)
def test_no_injected_files(self):
api = compute.API(image_service=self.StubImageService())
- api.create(self.context, instance_type='m1.small', image_id='fake')
+ inst_type = instance_types.get_instance_type_by_name('m1.small')
+ api.create(self.context, instance_type=inst_type, image_id='fake')
def test_max_injected_files(self):
files = []
diff --git a/nova/tests/test_scheduler.py b/nova/tests/test_scheduler.py
index 6df74dd61..51d987288 100644
--- a/nova/tests/test_scheduler.py
+++ b/nova/tests/test_scheduler.py
@@ -263,7 +263,7 @@ class SimpleDriverTestCase(test.TestCase):
inst['reservation_id'] = 'r-fakeres'
inst['user_id'] = self.user.id
inst['project_id'] = self.project.id
- inst['instance_type'] = 'm1.tiny'
+ inst['instance_type_id'] = '1'
inst['mac_address'] = utils.generate_mac()
inst['vcpus'] = kwargs.get('vcpus', 1)
inst['ami_launch_index'] = 0
@@ -737,7 +737,7 @@ class SimpleDriverTestCase(test.TestCase):
ret = self.scheduler.driver._live_migration_src_check(self.context,
i_ref)
- self.assertTrue(ret == None)
+ self.assertTrue(ret is None)
db.instance_destroy(self.context, instance_id)
db.service_destroy(self.context, s_ref['id'])
@@ -805,7 +805,7 @@ class SimpleDriverTestCase(test.TestCase):
ret = self.scheduler.driver._live_migration_dest_check(self.context,
i_ref,
'somewhere')
- self.assertTrue(ret == None)
+ self.assertTrue(ret is None)
db.instance_destroy(self.context, instance_id)
db.service_destroy(self.context, s_ref['id'])
diff --git a/nova/tests/test_virt.py b/nova/tests/test_virt.py
index 958c8e3e2..aeaea91c7 100644
--- a/nova/tests/test_virt.py
+++ b/nova/tests/test_virt.py
@@ -140,7 +140,7 @@ class LibvirtConnTestCase(test.TestCase):
'vcpus': 2,
'project_id': 'fake',
'bridge': 'br101',
- 'instance_type': 'm1.small'}
+ 'instance_type_id': '5'} # m1.small
def lazy_load_library_exists(self):
"""check if libvirt is available."""
@@ -479,7 +479,7 @@ class LibvirtConnTestCase(test.TestCase):
fake_timer = FakeTime()
- self.create_fake_libvirt_mock(nwfilterLookupByName=fake_raise)
+ self.create_fake_libvirt_mock()
instance_ref = db.instance_create(self.context, self.test_instance)
# Start test
@@ -488,6 +488,7 @@ class LibvirtConnTestCase(test.TestCase):
conn = libvirt_conn.LibvirtConnection(False)
conn.firewall_driver.setattr('setup_basic_filtering', fake_none)
conn.firewall_driver.setattr('prepare_instance_filter', fake_none)
+ conn.firewall_driver.setattr('instance_filter_exists', fake_none)
conn.ensure_filtering_rules_for_instance(instance_ref,
time=fake_timer)
except exception.Error, e:
diff --git a/nova/tests/test_volume.py b/nova/tests/test_volume.py
index d71b75f3f..e9d8289aa 100644
--- a/nova/tests/test_volume.py
+++ b/nova/tests/test_volume.py
@@ -106,7 +106,7 @@ class VolumeTestCase(test.TestCase):
inst['launch_time'] = '10'
inst['user_id'] = 'fake'
inst['project_id'] = 'fake'
- inst['instance_type'] = 'm1.tiny'
+ inst['instance_type_id'] = '2' # m1.tiny
inst['mac_address'] = utils.generate_mac()
inst['ami_launch_index'] = 0
instance_id = db.instance_create(self.context, inst)['id']
diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py
index 17e3f55e9..375480a2e 100644
--- a/nova/tests/test_xenapi.py
+++ b/nova/tests/test_xenapi.py
@@ -80,7 +80,7 @@ class XenAPIVolumeTestCase(test.TestCase):
'image_id': 1,
'kernel_id': 2,
'ramdisk_id': 3,
- 'instance_type': 'm1.large',
+ 'instance_type_id': '3', # m1.large
'mac_address': 'aa:bb:cc:dd:ee:ff',
'os_type': 'linux'}
@@ -289,11 +289,11 @@ class XenAPIVMTestCase(test.TestCase):
'enabled':'1'}],
'ip6s': [{'ip': 'fe80::a8bb:ccff:fedd:eeff',
'netmask': '120',
- 'enabled': '1',
- 'gateway': 'fe80::a00:1'}],
+ 'enabled': '1'}],
'mac': 'aa:bb:cc:dd:ee:ff',
'dns': ['10.0.0.2'],
- 'gateway': '10.0.0.1'})
+ 'gateway': '10.0.0.1',
+ 'gateway6': 'fe80::a00:1'})
def check_vm_params_for_windows(self):
self.assertEquals(self.vm['platform']['nx'], 'true')
@@ -328,7 +328,7 @@ class XenAPIVMTestCase(test.TestCase):
self.assertEquals(self.vm['HVM_boot_policy'], '')
def _test_spawn(self, image_id, kernel_id, ramdisk_id,
- instance_type="m1.large", os_type="linux",
+ instance_type_id="3", os_type="linux",
instance_id=1, check_injection=False):
stubs.stubout_loopingcall_start(self.stubs)
values = {'id': instance_id,
@@ -337,7 +337,7 @@ class XenAPIVMTestCase(test.TestCase):
'image_id': image_id,
'kernel_id': kernel_id,
'ramdisk_id': ramdisk_id,
- 'instance_type': instance_type,
+ 'instance_type_id': instance_type_id,
'mac_address': 'aa:bb:cc:dd:ee:ff',
'os_type': os_type}
instance = db.instance_create(self.context, values)
@@ -349,7 +349,7 @@ class XenAPIVMTestCase(test.TestCase):
FLAGS.xenapi_image_service = 'glance'
self.assertRaises(Exception,
self._test_spawn,
- 1, 2, 3, "m1.xlarge")
+ 1, 2, 3, "4") # m1.xlarge
def test_spawn_raw_objectstore(self):
FLAGS.xenapi_image_service = 'objectstore'
@@ -523,7 +523,7 @@ class XenAPIVMTestCase(test.TestCase):
'image_id': 1,
'kernel_id': 2,
'ramdisk_id': 3,
- 'instance_type': 'm1.large',
+ 'instance_type_id': '3', # m1.large
'mac_address': 'aa:bb:cc:dd:ee:ff',
'os_type': 'linux'}
instance = db.instance_create(self.context, values)
@@ -580,7 +580,7 @@ class XenAPIMigrateInstance(test.TestCase):
'kernel_id': None,
'ramdisk_id': None,
'local_gb': 5,
- 'instance_type': 'm1.large',
+ 'instance_type_id': '3', # m1.large
'mac_address': 'aa:bb:cc:dd:ee:ff',
'os_type': 'linux'}
diff --git a/nova/utils.py b/nova/utils.py
index 3f6f9fc8a..b783f6c14 100644
--- a/nova/utils.py
+++ b/nova/utils.py
@@ -17,9 +17,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-"""
-System-level utilities and helper functions.
-"""
+"""Utilities and helper functions."""
import base64
import datetime
@@ -43,9 +41,8 @@ from eventlet import event
from eventlet import greenthread
from eventlet import semaphore
from eventlet.green import subprocess
-None
+
from nova import exception
-from nova.exception import ProcessExecutionError
from nova import flags
from nova import log as logging
@@ -56,7 +53,7 @@ FLAGS = flags.FLAGS
def import_class(import_str):
- """Returns a class from a string including module and class"""
+ """Returns a class from a string including module and class."""
mod_str, _sep, class_str = import_str.rpartition('.')
try:
__import__(mod_str)
@@ -67,7 +64,7 @@ def import_class(import_str):
def import_object(import_str):
- """Returns an object including a module or module and class"""
+ """Returns an object including a module or module and class."""
try:
__import__(import_str)
return sys.modules[import_str]
@@ -99,11 +96,12 @@ def vpn_ping(address, port, timeout=0.05, session_id=None):
cli_id = 64 bit identifier
? = unknown, probably flags/padding
bit 9 was 1 and the rest were 0 in testing
+
"""
if session_id is None:
session_id = random.randint(0, 0xffffffffffffffff)
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
- data = struct.pack("!BQxxxxxx", 0x38, session_id)
+ data = struct.pack('!BQxxxxxx', 0x38, session_id)
sock.sendto(data, (address, port))
sock.settimeout(timeout)
try:
@@ -112,7 +110,7 @@ def vpn_ping(address, port, timeout=0.05, session_id=None):
return False
finally:
sock.close()
- fmt = "!BQxxxxxQxxxx"
+ fmt = '!BQxxxxxQxxxx'
if len(received) != struct.calcsize(fmt):
print struct.calcsize(fmt)
return False
@@ -122,15 +120,8 @@ def vpn_ping(address, port, timeout=0.05, session_id=None):
def fetchfile(url, target):
- LOG.debug(_("Fetching %s") % url)
-# c = pycurl.Curl()
-# fp = open(target, "wb")
-# c.setopt(c.URL, url)
-# c.setopt(c.WRITEDATA, fp)
-# c.perform()
-# c.close()
-# fp.close()
- execute("curl", "--fail", url, "-o", target)
+ LOG.debug(_('Fetching %s') % url)
+ execute('curl', '--fail', url, '-o', target)
def execute(*cmd, **kwargs):
@@ -147,7 +138,7 @@ def execute(*cmd, **kwargs):
while attempts > 0:
attempts -= 1
try:
- LOG.debug(_("Running cmd (subprocess): %s"), ' '.join(cmd))
+ LOG.debug(_('Running cmd (subprocess): %s'), ' '.join(cmd))
env = os.environ.copy()
if addl_env:
env.update(addl_env)
@@ -157,26 +148,27 @@ def execute(*cmd, **kwargs):
stderr=subprocess.PIPE,
env=env)
result = None
- if process_input != None:
+ if process_input is not None:
result = obj.communicate(process_input)
else:
result = obj.communicate()
obj.stdin.close()
if obj.returncode:
- LOG.debug(_("Result was %s") % obj.returncode)
+ LOG.debug(_('Result was %s') % obj.returncode)
if type(check_exit_code) == types.IntType \
and obj.returncode != check_exit_code:
(stdout, stderr) = result
- raise ProcessExecutionError(exit_code=obj.returncode,
- stdout=stdout,
- stderr=stderr,
- cmd=' '.join(cmd))
+ raise exception.ProcessExecutionError(
+ exit_code=obj.returncode,
+ stdout=stdout,
+ stderr=stderr,
+ cmd=' '.join(cmd))
return result
- except ProcessExecutionError:
+ except exception.ProcessExecutionError:
if not attempts:
raise
else:
- LOG.debug(_("%r failed. Retrying."), cmd)
+ LOG.debug(_('%r failed. Retrying.'), cmd)
if delay_on_retry:
greenthread.sleep(random.randint(20, 200) / 100.0)
finally:
@@ -188,13 +180,13 @@ def execute(*cmd, **kwargs):
def ssh_execute(ssh, cmd, process_input=None,
addl_env=None, check_exit_code=True):
- LOG.debug(_("Running cmd (SSH): %s"), ' '.join(cmd))
+ LOG.debug(_('Running cmd (SSH): %s'), ' '.join(cmd))
if addl_env:
- raise exception.Error("Environment not supported over SSH")
+ raise exception.Error(_('Environment not supported over SSH'))
if process_input:
# This is (probably) fixable if we need it...
- raise exception.Error("process_input not supported over SSH")
+ raise exception.Error(_('process_input not supported over SSH'))
stdin_stream, stdout_stream, stderr_stream = ssh.exec_command(cmd)
channel = stdout_stream.channel
@@ -212,7 +204,7 @@ def ssh_execute(ssh, cmd, process_input=None,
# exit_status == -1 if no exit code was returned
if exit_status != -1:
- LOG.debug(_("Result was %s") % exit_status)
+ LOG.debug(_('Result was %s') % exit_status)
if check_exit_code and exit_status != 0:
raise exception.ProcessExecutionError(exit_code=exit_status,
stdout=stdout,
@@ -251,7 +243,7 @@ def debug(arg):
def runthis(prompt, *cmd, **kwargs):
- LOG.debug(_("Running %s"), (" ".join(cmd)))
+ LOG.debug(_('Running %s'), (' '.join(cmd)))
rv, err = execute(*cmd, **kwargs)
@@ -266,48 +258,49 @@ def generate_mac():
random.randint(0x00, 0x7f),
random.randint(0x00, 0xff),
random.randint(0x00, 0xff)]
- return ':'.join(map(lambda x: "%02x" % x, mac))
+ return ':'.join(map(lambda x: '%02x' % x, mac))
# Default symbols to use for passwords. Avoids visually confusing characters.
# ~6 bits per symbol
-DEFAULT_PASSWORD_SYMBOLS = ("23456789" # Removed: 0,1
- "ABCDEFGHJKLMNPQRSTUVWXYZ" # Removed: I, O
- "abcdefghijkmnopqrstuvwxyz") # Removed: l
+DEFAULT_PASSWORD_SYMBOLS = ('23456789' # Removed: 0,1
+ 'ABCDEFGHJKLMNPQRSTUVWXYZ' # Removed: I, O
+ 'abcdefghijkmnopqrstuvwxyz') # Removed: l
# ~5 bits per symbol
-EASIER_PASSWORD_SYMBOLS = ("23456789" # Removed: 0, 1
- "ABCDEFGHJKLMNPQRSTUVWXYZ") # Removed: I, O
+EASIER_PASSWORD_SYMBOLS = ('23456789' # Removed: 0, 1
+ 'ABCDEFGHJKLMNPQRSTUVWXYZ') # Removed: I, O
def generate_password(length=20, symbols=DEFAULT_PASSWORD_SYMBOLS):
"""Generate a random password from the supplied symbols.
Believed to be reasonably secure (with a reasonable password length!)
+
"""
r = random.SystemRandom()
- return "".join([r.choice(symbols) for _i in xrange(length)])
+ return ''.join([r.choice(symbols) for _i in xrange(length)])
def last_octet(address):
- return int(address.split(".")[-1])
+ return int(address.split('.')[-1])
def get_my_linklocal(interface):
try:
- if_str = execute("ip", "-f", "inet6", "-o", "addr", "show", interface)
- condition = "\s+inet6\s+([0-9a-f:]+)/\d+\s+scope\s+link"
+ if_str = execute('ip', '-f', 'inet6', '-o', 'addr', 'show', interface)
+ condition = '\s+inet6\s+([0-9a-f:]+)/\d+\s+scope\s+link'
links = [re.search(condition, x) for x in if_str[0].split('\n')]
address = [w.group(1) for w in links if w is not None]
if address[0] is not None:
return address[0]
else:
- raise exception.Error(_("Link Local address is not found.:%s")
+ raise exception.Error(_('Link Local address is not found.:%s')
% if_str)
except Exception as ex:
raise exception.Error(_("Couldn't get Link Local IP of %(interface)s"
- " :%(ex)s") % locals())
+ " :%(ex)s") % locals())
def to_global_ipv6(prefix, mac):
@@ -319,15 +312,15 @@ def to_global_ipv6(prefix, mac):
return (mac64_addr ^ netaddr.IPAddress('::0200:0:0:0') | maskIP).\
format()
except TypeError:
- raise TypeError(_("Bad mac for to_global_ipv6: %s") % mac)
+ raise TypeError(_('Bad mac for to_global_ipv6: %s') % mac)
def to_mac(ipv6_address):
address = netaddr.IPAddress(ipv6_address)
- mask1 = netaddr.IPAddress("::ffff:ffff:ffff:ffff")
- mask2 = netaddr.IPAddress("::0200:0:0:0")
+ mask1 = netaddr.IPAddress('::ffff:ffff:ffff:ffff')
+ mask2 = netaddr.IPAddress('::0200:0:0:0')
mac64 = netaddr.EUI(int(address & mask1 ^ mask2)).words
- return ":".join(["%02x" % i for i in mac64[0:3] + mac64[5:8]])
+ return ':'.join(['%02x' % i for i in mac64[0:3] + mac64[5:8]])
def utcnow():
@@ -341,7 +334,7 @@ utcnow.override_time = None
def is_older_than(before, seconds):
- """Return True if before is older than seconds"""
+ """Return True if before is older than seconds."""
return utcnow() - before > datetime.timedelta(seconds=seconds)
@@ -379,7 +372,7 @@ def isotime(at=None):
def parse_isotime(timestr):
- """Turn an iso formatted time back into a datetime"""
+ """Turn an iso formatted time back into a datetime."""
return datetime.datetime.strptime(timestr, TIME_FORMAT)
@@ -433,16 +426,19 @@ class LazyPluggable(object):
class LoopingCallDone(Exception):
- """The poll-function passed to LoopingCall can raise this exception to
+ """Exception to break out and stop a LoopingCall.
+
+ The poll-function passed to LoopingCall can raise this exception to
break out of the loop normally. This is somewhat analogous to
StopIteration.
An optional return-value can be included as the argument to the exception;
this return-value will be returned by LoopingCall.wait()
+
"""
def __init__(self, retvalue=True):
- """:param retvalue: Value that LoopingCall.wait() should return"""
+ """:param retvalue: Value that LoopingCall.wait() should return."""
self.retvalue = retvalue
@@ -493,7 +489,7 @@ def xhtml_escape(value):
http://github.com/facebook/tornado/blob/master/tornado/escape.py
"""
- return saxutils.escape(value, {'"': "&quot;"})
+ return saxutils.escape(value, {'"': '&quot;'})
def utf8(value):
@@ -504,7 +500,7 @@ def utf8(value):
"""
if isinstance(value, unicode):
- return value.encode("utf-8")
+ return value.encode('utf-8')
assert isinstance(value, str)
return value
@@ -554,7 +550,7 @@ class _NoopContextManager(object):
def synchronized(name, external=False):
- """Synchronization decorator
+ """Synchronization decorator.
Decorating a method like so:
@synchronized('mylock')
@@ -578,6 +574,7 @@ def synchronized(name, external=False):
multiple processes. This means that if two different workers both run a
a method decorated with @synchronized('mylock', external=True), only one
of them will execute at a time.
+
"""
def wrap(f):
@@ -590,13 +587,13 @@ def synchronized(name, external=False):
_semaphores[name] = semaphore.Semaphore()
sem = _semaphores[name]
LOG.debug(_('Attempting to grab semaphore "%(lock)s" for method '
- '"%(method)s"...' % {"lock": name,
- "method": f.__name__}))
+ '"%(method)s"...' % {'lock': name,
+ 'method': f.__name__}))
with sem:
if external:
LOG.debug(_('Attempting to grab file lock "%(lock)s" for '
'method "%(method)s"...' %
- {"lock": name, "method": f.__name__}))
+ {'lock': name, 'method': f.__name__}))
lock_file_path = os.path.join(FLAGS.lock_path,
'nova-%s.lock' % name)
lock = lockfile.FileLock(lock_file_path)
@@ -617,21 +614,23 @@ def synchronized(name, external=False):
def get_from_path(items, path):
- """ Returns a list of items matching the specified path. Takes an
- XPath-like expression e.g. prop1/prop2/prop3, and for each item in items,
- looks up items[prop1][prop2][prop3]. Like XPath, if any of the
+ """Returns a list of items matching the specified path.
+
+ Takes an XPath-like expression e.g. prop1/prop2/prop3, and for each item
+ in items, looks up items[prop1][prop2][prop3]. Like XPath, if any of the
intermediate results are lists it will treat each list item individually.
A 'None' in items or any child expressions will be ignored, this function
will not throw because of None (anywhere) in items. The returned list
- will contain no None values."""
+ will contain no None values.
+ """
if path is None:
- raise exception.Error("Invalid mini_xpath")
+ raise exception.Error('Invalid mini_xpath')
- (first_token, sep, remainder) = path.partition("/")
+ (first_token, sep, remainder) = path.partition('/')
- if first_token == "":
- raise exception.Error("Invalid mini_xpath")
+ if first_token == '':
+ raise exception.Error('Invalid mini_xpath')
results = []
@@ -645,7 +644,7 @@ def get_from_path(items, path):
for item in items:
if item is None:
continue
- get_method = getattr(item, "get", None)
+ get_method = getattr(item, 'get', None)
if get_method is None:
continue
child = get_method(first_token)
@@ -666,7 +665,7 @@ def get_from_path(items, path):
def flatten_dict(dict_, flattened=None):
- """Recursively flatten a nested dictionary"""
+ """Recursively flatten a nested dictionary."""
flattened = flattened or {}
for key, value in dict_.iteritems():
if hasattr(value, 'iteritems'):
@@ -677,9 +676,7 @@ def flatten_dict(dict_, flattened=None):
def partition_dict(dict_, keys):
- """Return two dicts, one containing only `keys` the other containing
- everything but `keys`
- """
+ """Return two dicts, one with `keys` the other with everything else."""
intersection = {}
difference = {}
for key, value in dict_.iteritems():
@@ -691,9 +688,7 @@ def partition_dict(dict_, keys):
def map_dict_keys(dict_, key_map):
- """Return a dictionary in which the dictionaries keys are mapped to
- new keys.
- """
+ """Return a dict in which the dictionaries keys are mapped to new keys."""
mapped = {}
for key, value in dict_.iteritems():
mapped_key = key_map[key] if key in key_map else key
@@ -702,15 +697,15 @@ def map_dict_keys(dict_, key_map):
def subset_dict(dict_, keys):
- """Return a dict that only contains a subset of keys"""
+ """Return a dict that only contains a subset of keys."""
subset = partition_dict(dict_, keys)[0]
return subset
def check_isinstance(obj, cls):
- """Checks that obj is of type cls, and lets PyLint infer types"""
+ """Checks that obj is of type cls, and lets PyLint infer types."""
if isinstance(obj, cls):
return obj
- raise Exception(_("Expected object of type: %s") % (str(cls)))
+ raise Exception(_('Expected object of type: %s') % (str(cls)))
# TODO(justinsb): Can we make this better??
return cls() # Ugly PyLint hack
diff --git a/nova/version.py b/nova/version.py
index c3ecc2245..1f8d08e8c 100644
--- a/nova/version.py
+++ b/nova/version.py
@@ -21,9 +21,9 @@ except ImportError:
'revision_id': 'LOCALREVISION',
'revno': 0}
-NOVA_VERSION = ['2011', '2']
-YEAR, COUNT = NOVA_VERSION
+NOVA_VERSION = ['2011', '3']
+YEAR, COUNT = NOVA_VERSION
FINAL = False # This becomes true at Release Candidate time
@@ -39,8 +39,8 @@ def version_string():
def vcs_version_string():
- return "%s:%s" % (version_info['branch_nick'], version_info['revision_id'])
+ return '%s:%s' % (version_info['branch_nick'], version_info['revision_id'])
def version_string_with_vcs():
- return "%s-%s" % (canonical_version_string(), vcs_version_string())
+ return '%s-%s' % (canonical_version_string(), vcs_version_string())
diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py
index 2be190256..d212be3c9 100644
--- a/nova/virt/libvirt_conn.py
+++ b/nova/virt/libvirt_conn.py
@@ -58,7 +58,6 @@ from nova import db
from nova import exception
from nova import flags
from nova import log as logging
-#from nova import test
from nova import utils
from nova import vnc
from nova.auth import manager
@@ -117,6 +116,8 @@ flags.DEFINE_integer('live_migration_bandwidth', 0,
'Define live migration behavior')
flags.DEFINE_string('qemu_img', 'qemu-img',
'binary to use for qemu-img commands')
+flags.DEFINE_bool('start_guests_on_host_boot', False,
+ 'Whether to restart guests when the host reboots')
def get_connection(read_only):
@@ -169,34 +170,34 @@ def _get_network_info(instance):
instance['id'])
network_info = []
- def ip_dict(ip):
- return {
- "ip": ip.address,
- "netmask": network["netmask"],
- "enabled": "1"}
-
- def ip6_dict(ip6):
- prefix = ip6.network.cidr_v6
- mac = instance.mac_address
- return {
- "ip": utils.to_global_ipv6(prefix, mac),
- "netmask": ip6.network.netmask_v6,
- "gateway": ip6.network.gateway_v6,
- "enabled": "1"}
-
for network in networks:
network_ips = [ip for ip in ip_addresses
- if ip.network_id == network.id]
+ if ip['network_id'] == network['id']]
+
+ def ip_dict(ip):
+ return {
+ 'ip': ip['address'],
+ 'netmask': network['netmask'],
+ 'enabled': '1'}
+
+ def ip6_dict():
+ prefix = network['cidr_v6']
+ mac = instance['mac_address']
+ return {
+ 'ip': utils.to_global_ipv6(prefix, mac),
+ 'netmask': network['netmask_v6'],
+ 'enabled': '1'}
mapping = {
'label': network['label'],
'gateway': network['gateway'],
- 'mac': instance.mac_address,
+ 'mac': instance['mac_address'],
'dns': [network['dns']],
'ips': [ip_dict(ip) for ip in network_ips]}
if FLAGS.use_ipv6:
- mapping['ip6s'] = [ip6_dict(ip) for ip in network_ips]
+ mapping['ip6s'] = [ip6_dict()]
+ mapping['gateway6'] = network['gateway_v6']
network_info.append((network, mapping))
return network_info
@@ -209,7 +210,6 @@ class LibvirtConnection(driver.ComputeDriver):
self.libvirt_uri = self.get_uri()
self.libvirt_xml = open(FLAGS.libvirt_xml_template).read()
- self.interfaces_xml = open(FLAGS.injected_network_template).read()
self.cpuinfo_xml = open(FLAGS.cpuinfo_xml_template).read()
self._wrapped_conn = None
self.read_only = read_only
@@ -231,12 +231,8 @@ class LibvirtConnection(driver.ComputeDriver):
{'name': instance['name'], 'state': state})
db.instance_set_state(ctxt, instance['id'], state)
- if state == power_state.SHUTOFF:
- # TODO(soren): This is what the compute manager does when you
- # terminate # an instance. At some point I figure we'll have a
- # "terminated" state and some sort of cleanup job that runs
- # occasionally, cleaning them out.
- db.instance_destroy(ctxt, instance['id'])
+ # NOTE(justinsb): We no longer delete SHUTOFF instances,
+ # the user may want to power them back on
if state != power_state.RUNNING:
continue
@@ -311,12 +307,57 @@ class LibvirtConnection(driver.ComputeDriver):
return infos
def destroy(self, instance, cleanup=True):
+ instance_name = instance['name']
+
+ # TODO(justinsb): Refactor all lookupByName calls for error-handling
try:
- virt_dom = self._conn.lookupByName(instance['name'])
- virt_dom.destroy()
- except Exception as _err:
- pass
- # If the instance is already terminated, we're still happy
+ virt_dom = self._conn.lookupByName(instance_name)
+ except libvirt.libvirtError as e:
+ errcode = e.get_error_code()
+ if errcode == libvirt.VIR_ERR_NO_DOMAIN:
+ virt_dom = None
+ else:
+ LOG.warning(_("Error from libvirt during lookup of "
+ "%(instance_name)s. Code=%(errcode)s "
+ "Error=%(e)s") %
+ locals())
+ raise
+
+ # If the instance is already terminated, we're still happy
+ # Otherwise, destroy it
+ if virt_dom is not None:
+ try:
+ virt_dom.destroy()
+ except libvirt.libvirtError as e:
+ is_okay = False
+ errcode = e.get_error_code()
+ if errcode == libvirt.VIR_ERR_OPERATION_INVALID:
+ # If the instance if already shut off, we get this:
+ # Code=55 Error=Requested operation is not valid:
+ # domain is not running
+ (state, _max_mem, _mem, _cpus, _t) = virt_dom.info()
+ if state == power_state.SHUTOFF:
+ is_okay = True
+
+ if not is_okay:
+ LOG.warning(_("Error from libvirt during destroy of "
+ "%(instance_name)s. Code=%(errcode)s "
+ "Error=%(e)s") %
+ locals())
+ raise
+
+ try:
+ # NOTE(justinsb): We remove the domain definition. We probably
+ # would do better to keep it if cleanup=False (e.g. volumes?)
+ # (e.g. #2 - not losing machines on failure)
+ virt_dom.undefine()
+ except libvirt.libvirtError as e:
+ errcode = e.get_error_code()
+ LOG.warning(_("Error from libvirt during undefine of "
+ "%(instance_name)s. Code=%(errcode)s "
+ "Error=%(e)s") %
+ locals())
+ raise
# We'll save this for when we do shutdown,
# instead of destroy - but destroy returns immediately
@@ -327,12 +368,18 @@ class LibvirtConnection(driver.ComputeDriver):
state = self.get_info(instance['name'])['state']
db.instance_set_state(context.get_admin_context(),
instance['id'], state)
- if state == power_state.SHUTDOWN:
+ if state == power_state.SHUTOFF:
break
- except Exception:
+
+ # Let's not hammer on the DB
+ time.sleep(1)
+ except Exception as ex:
+ msg = _("Error encountered when destroying instance '%(id)s': "
+ "%(ex)s") % {"id": instance["id"], "ex": ex}
+ LOG.debug(msg)
db.instance_set_state(context.get_admin_context(),
instance['id'],
- power_state.SHUTDOWN)
+ power_state.SHUTOFF)
break
self.firewall_driver.unfilter_instance(instance)
@@ -391,9 +438,9 @@ class LibvirtConnection(driver.ComputeDriver):
if child.prop('dev') == device:
return str(node)
finally:
- if ctx != None:
+ if ctx is not None:
ctx.xpathFreeContext()
- if doc != None:
+ if doc is not None:
doc.freeDoc()
@exception.wrap_exception
@@ -423,9 +470,8 @@ class LibvirtConnection(driver.ComputeDriver):
metadata = {'disk_format': base['disk_format'],
'container_format': base['container_format'],
'is_public': False,
+ 'name': '%s.%s' % (base['name'], image_id),
'properties': {'architecture': base['architecture'],
- 'type': base['type'],
- 'name': '%s.%s' % (base['name'], image_id),
'kernel_id': instance['kernel_id'],
'image_location': 'snapshot',
'image_state': 'available',
@@ -452,12 +498,17 @@ class LibvirtConnection(driver.ComputeDriver):
# Export the snapshot to a raw image
temp_dir = tempfile.mkdtemp()
out_path = os.path.join(temp_dir, snapshot_name)
- qemu_img_cmd = '%s convert -f qcow2 -O raw -s %s %s %s' % (
- FLAGS.qemu_img,
- snapshot_name,
- disk_path,
- out_path)
- utils.execute(qemu_img_cmd)
+ qemu_img_cmd = (FLAGS.qemu_img,
+ 'convert',
+ '-f',
+ 'qcow2',
+ '-O',
+ 'raw',
+ '-s',
+ snapshot_name,
+ disk_path,
+ out_path)
+ utils.execute(*qemu_img_cmd)
# Upload that image to the image service
with open(out_path) as image_file:
@@ -475,7 +526,7 @@ class LibvirtConnection(driver.ComputeDriver):
xml = self.to_xml(instance)
self.firewall_driver.setup_basic_filtering(instance)
self.firewall_driver.prepare_instance_filter(instance)
- self._conn.createXML(xml, 0)
+ self._create_new_domain(xml)
self.firewall_driver.apply_instance_filter(instance)
timer = utils.LoopingCall(f=None)
@@ -523,7 +574,7 @@ class LibvirtConnection(driver.ComputeDriver):
'kernel_id': FLAGS.rescue_kernel_id,
'ramdisk_id': FLAGS.rescue_ramdisk_id}
self._create_image(instance, xml, '.rescue', rescue_images)
- self._conn.createXML(xml, 0)
+ self._create_new_domain(xml)
timer = utils.LoopingCall(f=None)
@@ -558,7 +609,7 @@ class LibvirtConnection(driver.ComputeDriver):
# for xenapi(tr3buchet)
@exception.wrap_exception
def spawn(self, instance, network_info=None):
- xml = self.to_xml(instance, network_info)
+ xml = self.to_xml(instance, False, network_info)
db.instance_set_state(context.get_admin_context(),
instance['id'],
power_state.NOSTATE,
@@ -566,10 +617,15 @@ class LibvirtConnection(driver.ComputeDriver):
self.firewall_driver.setup_basic_filtering(instance, network_info)
self.firewall_driver.prepare_instance_filter(instance, network_info)
self._create_image(instance, xml, network_info)
- self._conn.createXML(xml, 0)
+ domain = self._create_new_domain(xml)
LOG.debug(_("instance %s: is running"), instance['name'])
self.firewall_driver.apply_instance_filter(instance)
+ if FLAGS.start_guests_on_host_boot:
+ LOG.debug(_("instance %s: setting autostart ON") %
+ instance['name'])
+ domain.setAutostart(1)
+
timer = utils.LoopingCall(f=None)
def _wait_for_boot():
@@ -797,7 +853,10 @@ class LibvirtConnection(driver.ComputeDriver):
root_fname = '%08x' % int(disk_images['image_id'])
size = FLAGS.minimum_root_size
- if inst['instance_type'] == 'm1.tiny' or suffix == '.rescue':
+
+ inst_type_id = inst['instance_type_id']
+ inst_type = instance_types.get_instance_type(inst_type_id)
+ if inst_type['name'] == 'm1.tiny' or suffix == '.rescue':
size = None
root_fname += "_sm"
@@ -809,14 +868,13 @@ class LibvirtConnection(driver.ComputeDriver):
user=user,
project=project,
size=size)
- type_data = instance_types.get_instance_type(inst['instance_type'])
- if type_data['local_gb']:
+ if inst_type['local_gb']:
self._cache_image(fn=self._create_local,
target=basepath('disk.local'),
- fname="local_%s" % type_data['local_gb'],
+ fname="local_%s" % inst_type['local_gb'],
cow=FLAGS.use_cow_images,
- local_gb=type_data['local_gb'])
+ local_gb=inst_type['local_gb'])
# For now, we assume that if we're not using a kernel, we're using a
# partitioned disk image where the target partition is the first
@@ -950,8 +1008,8 @@ class LibvirtConnection(driver.ComputeDriver):
nics.append(self._get_nic_for_xml(network,
mapping))
# FIXME(vish): stick this in db
- instance_type_name = instance['instance_type']
- instance_type = instance_types.get_instance_type(instance_type_name)
+ inst_type_id = instance['instance_type_id']
+ inst_type = instance_types.get_instance_type(inst_type_id)
if FLAGS.use_cow_images:
driver_type = 'qcow2'
@@ -962,10 +1020,10 @@ class LibvirtConnection(driver.ComputeDriver):
'name': instance['name'],
'basepath': os.path.join(FLAGS.instances_path,
instance['name']),
- 'memory_kb': instance_type['memory_mb'] * 1024,
- 'vcpus': instance_type['vcpus'],
+ 'memory_kb': inst_type['memory_mb'] * 1024,
+ 'vcpus': inst_type['vcpus'],
'rescue': rescue,
- 'local': instance_type['local_gb'],
+ 'local': inst_type['local_gb'],
'driver_type': driver_type,
'nics': nics}
@@ -987,11 +1045,22 @@ class LibvirtConnection(driver.ComputeDriver):
return xml
def get_info(self, instance_name):
+ # NOTE(justinsb): When libvirt isn't running / can't connect, we get:
+ # libvir: Remote error : unable to connect to
+ # '/var/run/libvirt/libvirt-sock', libvirtd may need to be started:
+ # No such file or directory
try:
virt_dom = self._conn.lookupByName(instance_name)
- except:
- raise exception.NotFound(_("Instance %s not found")
- % instance_name)
+ except libvirt.libvirtError as e:
+ errcode = e.get_error_code()
+ if errcode == libvirt.VIR_ERR_NO_DOMAIN:
+ raise exception.NotFound(_("Instance %s not found")
+ % instance_name)
+ LOG.warning(_("Error from libvirt during lookup. "
+ "Code=%(errcode)s Error=%(e)s") %
+ locals())
+ raise
+
(state, max_mem, mem, num_cpu, cpu_time) = virt_dom.info()
return {'state': state,
'max_mem': max_mem,
@@ -999,6 +1068,24 @@ class LibvirtConnection(driver.ComputeDriver):
'num_cpu': num_cpu,
'cpu_time': cpu_time}
+ def _create_new_domain(self, xml, persistent=True, launch_flags=0):
+ # NOTE(justinsb): libvirt has two types of domain:
+ # * a transient domain disappears when the guest is shutdown
+ # or the host is rebooted.
+ # * a permanent domain is not automatically deleted
+ # NOTE(justinsb): Even for ephemeral instances, transient seems risky
+
+ if persistent:
+ # To create a persistent domain, first define it, then launch it.
+ domain = self._conn.defineXML(xml)
+
+ domain.createWithFlags(launch_flags)
+ else:
+ # createXML call creates a transient domain
+ domain = self._conn.createXML(xml, launch_flags)
+
+ return domain
+
def get_diagnostics(self, instance_name):
raise exception.ApiError(_("diagnostics are not supported "
"for libvirt"))
@@ -1033,14 +1120,14 @@ class LibvirtConnection(driver.ComputeDriver):
if child.name == 'target':
devdst = child.prop('dev')
- if devdst == None:
+ if devdst is None:
continue
disks.append(devdst)
finally:
- if ctx != None:
+ if ctx is not None:
ctx.xpathFreeContext()
- if doc != None:
+ if doc is not None:
doc.freeDoc()
return disks
@@ -1075,14 +1162,14 @@ class LibvirtConnection(driver.ComputeDriver):
if child.name == 'target':
devdst = child.prop('dev')
- if devdst == None:
+ if devdst is None:
continue
interfaces.append(devdst)
finally:
- if ctx != None:
+ if ctx is not None:
ctx.xpathFreeContext()
- if doc != None:
+ if doc is not None:
doc.freeDoc()
return interfaces
@@ -1402,18 +1489,13 @@ class LibvirtConnection(driver.ComputeDriver):
# wait for completion
timeout_count = range(FLAGS.live_migration_retry_count)
while timeout_count:
- try:
- filter_name = 'nova-instance-%s' % instance_ref.name
- self._conn.nwfilterLookupByName(filter_name)
+ if self.firewall_driver.instance_filter_exists(instance_ref):
break
- except libvirt.libvirtError:
- timeout_count.pop()
- if len(timeout_count) == 0:
- ec2_id = instance_ref['hostname']
- iname = instance_ref.name
- msg = _('Timeout migrating for %(ec2_id)s(%(iname)s)')
- raise exception.Error(msg % locals())
- time.sleep(1)
+ timeout_count.pop()
+ if len(timeout_count) == 0:
+ msg = _('Timeout migrating for %s. nwfilter not found.')
+ raise exception.Error(msg % instance_ref.name)
+ time.sleep(1)
def live_migration(self, ctxt, instance_ref, dest,
post_method, recover_method):
@@ -1542,6 +1624,10 @@ class FirewallDriver(object):
"""
raise NotImplementedError()
+ def instance_filter_exists(self, instance):
+ """Check nova-instance-instance-xxx exists"""
+ raise NotImplementedError()
+
class NWFilterFirewall(FirewallDriver):
"""
@@ -1849,6 +1935,21 @@ class NWFilterFirewall(FirewallDriver):
return 'nova-instance-%s' % (instance['name'])
return 'nova-instance-%s-%s' % (instance['name'], nic_id)
+ def instance_filter_exists(self, instance):
+ """Check nova-instance-instance-xxx exists"""
+ network_info = _get_network_info(instance)
+ for (network, mapping) in network_info:
+ nic_id = mapping['mac'].replace(':', '')
+ instance_filter_name = self._instance_filter_name(instance, nic_id)
+ try:
+ self._conn.nwfilterLookupByName(instance_filter_name)
+ except libvirt.libvirtError:
+ name = instance.name
+ LOG.debug(_('The nwfilter(%(instance_filter_name)s) for'
+ '%(name)s is not found.') % locals())
+ return False
+ return True
+
class IptablesFirewallDriver(FirewallDriver):
def __init__(self, execute=None, **kwargs):
@@ -2038,6 +2139,10 @@ class IptablesFirewallDriver(FirewallDriver):
return ipv4_rules, ipv6_rules
+ def instance_filter_exists(self, instance):
+ """Check nova-instance-instance-xxx exists"""
+ return self.nwfilter.instance_filter_exists(instance)
+
def refresh_security_group_members(self, security_group):
pass
diff --git a/nova/virt/vmwareapi/vim.py b/nova/virt/vmwareapi/vim.py
index 159e16a80..0cbdba363 100644
--- a/nova/virt/vmwareapi/vim.py
+++ b/nova/virt/vmwareapi/vim.py
@@ -43,6 +43,7 @@ flags.DEFINE_string('vmwareapi_wsdl_loc',
if suds:
+
class VIMMessagePlugin(suds.plugin.MessagePlugin):
def addAttributeForValue(self, node):
diff --git a/nova/virt/vmwareapi_conn.py b/nova/virt/vmwareapi_conn.py
index 20c1b2b45..1c6d2572d 100644
--- a/nova/virt/vmwareapi_conn.py
+++ b/nova/virt/vmwareapi_conn.py
@@ -42,6 +42,7 @@ from nova import exception
from nova import flags
from nova import log as logging
from nova import utils
+from nova.virt import driver
from nova.virt.vmwareapi import error_util
from nova.virt.vmwareapi import vim
from nova.virt.vmwareapi import vim_util
@@ -104,11 +105,12 @@ def get_connection(_):
api_retry_count)
-class VMWareESXConnection(object):
+class VMWareESXConnection(driver.ComputeDriver):
"""The ESX host connection object."""
def __init__(self, host_ip, host_username, host_password,
api_retry_count, scheme="https"):
+ super(VMWareESXConnection, self).__init__()
session = VMWareAPISession(host_ip, host_username, host_password,
api_retry_count, scheme=scheme)
self._vmops = VMWareVMOps(session)
diff --git a/nova/virt/xenapi/fake.py b/nova/virt/xenapi/fake.py
index 4434dbf0b..e36ef3288 100644
--- a/nova/virt/xenapi/fake.py
+++ b/nova/virt/xenapi/fake.py
@@ -294,7 +294,7 @@ class Failure(Exception):
def __str__(self):
try:
return str(self.details)
- except Exception, exc:
+ except Exception:
return "XenAPI Fake Failure: %s" % str(self.details)
def _details_map(self):
diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py
index d07d60800..1927500ad 100644
--- a/nova/virt/xenapi/vm_utils.py
+++ b/nova/virt/xenapi/vm_utils.py
@@ -28,10 +28,7 @@ import urllib
import uuid
from xml.dom import minidom
-from eventlet import event
import glance.client
-from nova import context
-from nova import db
from nova import exception
from nova import flags
from nova import log as logging
@@ -49,6 +46,8 @@ LOG = logging.getLogger("nova.virt.xenapi.vm_utils")
FLAGS = flags.FLAGS
flags.DEFINE_string('default_os_type', 'linux', 'Default OS type')
+flags.DEFINE_integer('block_device_creation_timeout', 10,
+ 'time to wait for a block device to be created')
XENAPI_POWER_STATE = {
'Halted': power_state.SHUTDOWN,
@@ -101,8 +100,8 @@ class VMHelper(HelperBase):
3. Using hardware virtualization
"""
- instance_type = instance_types.\
- get_instance_type(instance.instance_type)
+ inst_type_id = instance.instance_type_id
+ instance_type = instance_types.get_instance_type(inst_type_id)
mem = str(long(instance_type['memory_mb']) * 1024 * 1024)
vcpus = str(instance_type['vcpus'])
rec = {
@@ -169,8 +168,8 @@ class VMHelper(HelperBase):
@classmethod
def ensure_free_mem(cls, session, instance):
- instance_type = instance_types.get_instance_type(
- instance.instance_type)
+ inst_type_id = instance.instance_type_id
+ instance_type = instance_types.get_instance_type(inst_type_id)
mem = long(instance_type['memory_mb']) * 1024 * 1024
#get free memory from host
host = session.get_xenapi_host()
@@ -304,7 +303,6 @@ class VMHelper(HelperBase):
% locals())
vm_vdi_ref, vm_vdi_rec = cls.get_vdi_for_vm_safely(session, vm_ref)
- vm_vdi_uuid = vm_vdi_rec["uuid"]
sr_ref = vm_vdi_rec["SR"]
original_parent_uuid = get_vhd_parent_uuid(session, vm_vdi_ref)
@@ -753,14 +751,14 @@ class VMHelper(HelperBase):
session.call_xenapi('SR.scan', sr_ref)
-def get_rrd(host, uuid):
+def get_rrd(host, vm_uuid):
"""Return the VM RRD XML as a string"""
try:
xml = urllib.urlopen("http://%s:%s@%s/vm_rrd?uuid=%s" % (
FLAGS.xenapi_connection_username,
FLAGS.xenapi_connection_password,
host,
- uuid))
+ vm_uuid))
return xml.read()
except IOError:
return None
@@ -896,6 +894,16 @@ def remap_vbd_dev(dev):
return remapped_dev
+def _wait_for_device(dev):
+ """Wait for device node to appear"""
+ for i in xrange(0, FLAGS.block_device_creation_timeout):
+ if os.path.exists('/dev/%s' % dev):
+ return
+ time.sleep(1)
+
+ raise StorageError(_('Timeout waiting for device %s to be created') % dev)
+
+
def with_vdi_attached_here(session, vdi_ref, read_only, f):
this_vm_ref = get_this_vm_ref(session)
vbd_rec = {}
@@ -924,6 +932,11 @@ def with_vdi_attached_here(session, vdi_ref, read_only, f):
if dev != orig_dev:
LOG.debug(_('VBD %(vbd_ref)s plugged into wrong dev, '
'remapping to %(dev)s') % locals())
+ if dev != 'autodetect':
+ # NOTE(johannes): Unit tests will end up with a device called
+ # 'autodetect' which obviously won't exist. It's not ideal,
+ # but the alternatives were much messier
+ _wait_for_device(dev)
return f(dev)
finally:
LOG.debug(_('Destroying VBD for VDI %s ... '), vdi_ref)
@@ -1003,7 +1016,6 @@ def _stream_disk(dev, image_type, virtual_size, image_file):
def _write_partition(virtual_size, dev):
dest = '/dev/%s' % dev
- mbr_last = MBR_SIZE_SECTORS - 1
primary_first = MBR_SIZE_SECTORS
primary_last = MBR_SIZE_SECTORS + (virtual_size / SECTOR_SIZE) - 1
@@ -1130,7 +1142,7 @@ def _prepare_injectables(inst, networks_info):
'dns': dns,
'address_v6': ip_v6 and ip_v6['ip'] or '',
'netmask_v6': ip_v6 and ip_v6['netmask'] or '',
- 'gateway_v6': ip_v6 and ip_v6['gateway'] or '',
+ 'gateway_v6': ip_v6 and info['gateway6'] or '',
'use_ipv6': FLAGS.use_ipv6}
interfaces_info.append(interface_info)
diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py
index c96c35a6e..8b6a35f74 100644
--- a/nova/virt/xenapi/vmops.py
+++ b/nova/virt/xenapi/vmops.py
@@ -176,7 +176,7 @@ class VMOps(object):
vdi_ref, network_info)
self.create_vifs(vm_ref, network_info)
- self.inject_network_info(instance, vm_ref, network_info)
+ self.inject_network_info(instance, network_info, vm_ref)
return vm_ref
def _spawn(self, instance, vm_ref):
@@ -387,7 +387,6 @@ class VMOps(object):
def link_disks(self, instance, base_copy_uuid, cow_uuid):
"""Links the base copy VHD to the COW via the XAPI plugin."""
- vm_ref = VMHelper.lookup(self._session, instance.name)
new_base_copy_uuid = str(uuid.uuid4())
new_cow_uuid = str(uuid.uuid4())
params = {'instance_id': instance.id,
@@ -760,7 +759,6 @@ class VMOps(object):
instance)))
for vm in rescue_vms:
- rescue_name = vm["name"]
rescue_vm_ref = vm["vm_ref"]
self._destroy_rescue_instance(rescue_vm_ref)
@@ -798,15 +796,17 @@ class VMOps(object):
def _get_network_info(self, instance):
"""Creates network info list for instance."""
admin_context = context.get_admin_context()
- IPs = db.fixed_ip_get_all_by_instance(admin_context,
+ ips = db.fixed_ip_get_all_by_instance(admin_context,
instance['id'])
networks = db.network_get_all_by_instance(admin_context,
instance['id'])
- flavor = db.instance_type_get_by_name(admin_context,
- instance['instance_type'])
+
+ inst_type = db.instance_type_get_by_id(admin_context,
+ instance['instance_type_id'])
+
network_info = []
for network in networks:
- network_IPs = [ip for ip in IPs if ip.network_id == network.id]
+ network_ips = [ip for ip in ips if ip.network_id == network.id]
def ip_dict(ip):
return {
@@ -814,12 +814,11 @@ class VMOps(object):
"netmask": network["netmask"],
"enabled": "1"}
- def ip6_dict(ip6):
+ def ip6_dict():
return {
"ip": utils.to_global_ipv6(network['cidr_v6'],
instance['mac_address']),
"netmask": network['netmask_v6'],
- "gateway": network['gateway_v6'],
"enabled": "1"}
info = {
@@ -827,23 +826,41 @@ class VMOps(object):
'gateway': network['gateway'],
'broadcast': network['broadcast'],
'mac': instance.mac_address,
- 'rxtx_cap': flavor['rxtx_cap'],
+ 'rxtx_cap': inst_type['rxtx_cap'],
'dns': [network['dns']],
- 'ips': [ip_dict(ip) for ip in network_IPs]}
+ 'ips': [ip_dict(ip) for ip in network_ips]}
if network['cidr_v6']:
- info['ip6s'] = [ip6_dict(ip) for ip in network_IPs]
+ info['ip6s'] = [ip6_dict()]
+ if network['gateway_v6']:
+ info['gateway6'] = network['gateway_v6']
network_info.append((network, info))
return network_info
- def inject_network_info(self, instance, vm_ref, network_info):
+ #TODO{tr3buchet) remove this shim with nova-multi-nic
+ def inject_network_info(self, instance, network_info=None, vm_ref=None):
+ """
+ shim in place which makes inject_network_info work without being
+ passed network_info.
+ shim goes away after nova-multi-nic
+ """
+ if not network_info:
+ network_info = self._get_network_info(instance)
+ self._inject_network_info(instance, network_info, vm_ref)
+
+ def _inject_network_info(self, instance, network_info, vm_ref=None):
"""
Generate the network info and make calls to place it into the
xenstore and the xenstore param list.
+ vm_ref can be passed in because it will sometimes be different than
+ what VMHelper.lookup(session, instance.name) will find (ex: rescue)
"""
logging.debug(_("injecting network info to xs for vm: |%s|"), vm_ref)
- # this function raises if vm_ref is not a vm_opaque_ref
- self._session.get_xenapi().VM.get_record(vm_ref)
+ if vm_ref:
+ # this function raises if vm_ref is not a vm_opaque_ref
+ self._session.get_xenapi().VM.get_record(vm_ref)
+ else:
+ vm_ref = VMHelper.lookup(self._session, instance.name)
for (network, info) in network_info:
location = 'vm-data/networking/%s' % info['mac'].replace(':', '')
@@ -875,8 +892,10 @@ class VMOps(object):
VMHelper.create_vif(self._session, vm_ref, network_ref,
mac_address, device, rxtx_cap)
- def reset_network(self, instance, vm_ref):
+ def reset_network(self, instance, vm_ref=None):
"""Creates uuid arg to pass to make_agent_call and calls it."""
+ if not vm_ref:
+ vm_ref = VMHelper.lookup(self._session, instance.name)
args = {'id': str(uuid.uuid4())}
# TODO(tr3buchet): fix function call after refactor
#resp = self._make_agent_call('resetnetwork', instance, '', args)
@@ -902,7 +921,7 @@ class VMOps(object):
try:
ret = self._make_xenstore_call('read_record', vm, path,
{'ignore_missing_path': 'True'})
- except self.XenAPI.Failure, e:
+ except self.XenAPI.Failure:
return None
ret = json.loads(ret)
if ret == "None":
diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py
index 99fd35c61..0cabccf08 100644
--- a/nova/virt/xenapi_conn.py
+++ b/nova/virt/xenapi_conn.py
@@ -63,6 +63,7 @@ import xmlrpclib
from eventlet import event
from eventlet import tpool
+from eventlet import timeout
from nova import context
from nova import db
@@ -140,6 +141,9 @@ flags.DEFINE_bool('xenapi_remap_vbd_dev', False,
flags.DEFINE_string('xenapi_remap_vbd_dev_prefix', 'sd',
'Specify prefix to remap VBD dev to '
'(ex. /dev/xvdb -> /dev/sdb)')
+flags.DEFINE_integer('xenapi_login_timeout',
+ 10,
+ 'Timeout in seconds for XenAPI login.')
def get_connection(_):
@@ -318,7 +322,10 @@ class XenAPISession(object):
def __init__(self, url, user, pw):
self.XenAPI = self.get_imported_xenapi()
self._session = self._create_session(url)
- self._session.login_with_password(user, pw)
+ exception = self.XenAPI.Failure(_("Unable to log in to XenAPI "
+ "(is the Dom0 disk full?)"))
+ with timeout.Timeout(FLAGS.xenapi_login_timeout, exception):
+ self._session.login_with_password(user, pw)
self.loop = None
def get_imported_xenapi(self):
diff --git a/nova/volume/driver.py b/nova/volume/driver.py
index 850893914..55307ad9b 100644
--- a/nova/volume/driver.py
+++ b/nova/volume/driver.py
@@ -112,6 +112,12 @@ class VolumeDriver(object):
# If the volume isn't present, then don't attempt to delete
return True
+ # zero out old volumes to prevent data leaking between users
+ # TODO(ja): reclaiming space should be done lazy and low priority
+ self._execute('sudo', 'dd', 'if=/dev/zero',
+ 'of=%s' % self.local_path(volume),
+ 'count=%d' % (volume['size'] * 1024),
+ 'bs=1M')
self._try_execute('sudo', 'lvremove', '-f', "%s/%s" %
(FLAGS.volume_group,
volume['name']))
@@ -557,7 +563,7 @@ class RBDDriver(VolumeDriver):
"""Returns the path of the rbd volume."""
# This is the same as the remote path
# since qemu accesses it directly.
- return self.discover_volume(volume)
+ return "rbd:%s/%s" % (FLAGS.rbd_pool, volume['name'])
def ensure_export(self, context, volume):
"""Synchronously recreates an export for a logical volume."""
@@ -571,10 +577,8 @@ class RBDDriver(VolumeDriver):
"""Removes an export for a logical volume"""
pass
- def discover_volume(self, volume):
+ def discover_volume(self, context, volume):
"""Discover volume on a remote host"""
- # NOTE(justinsb): This is messed up... discover_volume takes 3 args
- # but then that would break local_path
return "rbd:%s/%s" % (FLAGS.rbd_pool, volume['name'])
def undiscover_volume(self, volume):
diff --git a/nova/wsgi.py b/nova/wsgi.py
index ba0819466..418087641 100644
--- a/nova/wsgi.py
+++ b/nova/wsgi.py
@@ -17,9 +17,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-"""
-Utility methods for working with WSGI servers
-"""
+"""Utility methods for working with WSGI servers."""
import os
import sys
@@ -33,7 +31,6 @@ import routes.middleware
import webob
import webob.dec
import webob.exc
-
from paste import deploy
from nova import exception
@@ -43,6 +40,7 @@ from nova import utils
FLAGS = flags.FLAGS
+LOG = logging.getLogger('nova.wsgi')
class WritableLogger(object):
@@ -65,7 +63,7 @@ class Server(object):
def start(self, application, port, host='0.0.0.0', backlog=128):
"""Run a WSGI server with the given application."""
arg0 = sys.argv[0]
- logging.audit(_("Starting %(arg0)s on %(host)s:%(port)s") % locals())
+ logging.audit(_('Starting %(arg0)s on %(host)s:%(port)s') % locals())
socket = eventlet.listen((host, port), backlog=backlog)
self.pool.spawn_n(self._run, application, socket)
@@ -86,30 +84,30 @@ class Server(object):
class Request(webob.Request):
def best_match_content_type(self):
- """
- Determine the most acceptable content-type based on the
- query extension then the Accept header
- """
+ """Determine the most acceptable content-type.
- parts = self.path.rsplit(".", 1)
+ Based on the query extension then the Accept header.
+
+ """
+ parts = self.path.rsplit('.', 1)
if len(parts) > 1:
format = parts[1]
- if format in ["json", "xml"]:
- return "application/{0}".format(parts[1])
+ if format in ['json', 'xml']:
+ return 'application/{0}'.format(parts[1])
- ctypes = ["application/json", "application/xml"]
+ ctypes = ['application/json', 'application/xml']
bm = self.accept.best_match(ctypes)
- return bm or "application/json"
+ return bm or 'application/json'
def get_content_type(self):
try:
- ct = self.headers["Content-Type"]
- assert ct in ("application/xml", "application/json")
+ ct = self.headers['Content-Type']
+ assert ct in ('application/xml', 'application/json')
return ct
except Exception:
- raise webob.exc.HTTPBadRequest("Invalid content type")
+ raise webob.exc.HTTPBadRequest('Invalid content type')
class Application(object):
@@ -117,7 +115,7 @@ class Application(object):
@classmethod
def factory(cls, global_config, **local_config):
- """Used for paste app factories in paste.deploy config fles.
+ """Used for paste app factories in paste.deploy config files.
Any local configuration (that is, values under the [app:APPNAME]
section of the paste config) will be passed into the `__init__` method
@@ -172,8 +170,9 @@ class Application(object):
See the end of http://pythonpaste.org/webob/modules/dec.html
for more info.
+
"""
- raise NotImplementedError(_("You must implement __call__"))
+ raise NotImplementedError(_('You must implement __call__'))
class Middleware(Application):
@@ -183,11 +182,12 @@ class Middleware(Application):
initialized that will be called next. By default the middleware will
simply call its wrapped app, or you can override __call__ to customize its
behavior.
+
"""
@classmethod
def factory(cls, global_config, **local_config):
- """Used for paste app factories in paste.deploy config fles.
+ """Used for paste app factories in paste.deploy config files.
Any local configuration (that is, values under the [filter:APPNAME]
section of the paste config) will be passed into the `__init__` method
@@ -239,20 +239,24 @@ class Middleware(Application):
class Debug(Middleware):
- """Helper class that can be inserted into any WSGI application chain
- to get information about the request and response."""
+ """Helper class for debugging a WSGI application.
+
+ Can be inserted into any WSGI application chain to get information
+ about the request and response.
+
+ """
@webob.dec.wsgify(RequestClass=Request)
def __call__(self, req):
- print ("*" * 40) + " REQUEST ENVIRON"
+ print ('*' * 40) + ' REQUEST ENVIRON'
for key, value in req.environ.items():
- print key, "=", value
+ print key, '=', value
print
resp = req.get_response(self.application)
- print ("*" * 40) + " RESPONSE HEADERS"
+ print ('*' * 40) + ' RESPONSE HEADERS'
for (key, value) in resp.headers.iteritems():
- print key, "=", value
+ print key, '=', value
print
resp.app_iter = self.print_generator(resp.app_iter)
@@ -261,11 +265,8 @@ class Debug(Middleware):
@staticmethod
def print_generator(app_iter):
- """
- Iterator that prints the contents of a wrapper string iterator
- when iterated.
- """
- print ("*" * 40) + " BODY"
+ """Iterator that prints the contents of a wrapper string."""
+ print ('*' * 40) + ' BODY'
for part in app_iter:
sys.stdout.write(part)
sys.stdout.flush()
@@ -274,13 +275,10 @@ class Debug(Middleware):
class Router(object):
- """
- WSGI middleware that maps incoming requests to WSGI apps.
- """
+ """WSGI middleware that maps incoming requests to WSGI apps."""
def __init__(self, mapper):
- """
- Create a router for the given routes.Mapper.
+ """Create a router for the given routes.Mapper.
Each route in `mapper` must specify a 'controller', which is a
WSGI app to call. You'll probably want to specify an 'action' as
@@ -292,15 +290,16 @@ class Router(object):
sc = ServerController()
# Explicit mapping of one route to a controller+action
- mapper.connect(None, "/svrlist", controller=sc, action="list")
+ mapper.connect(None, '/svrlist', controller=sc, action='list')
# Actions are all implicitly defined
- mapper.resource("server", "servers", controller=sc)
+ mapper.resource('server', 'servers', controller=sc)
# Pointing to an arbitrary WSGI app. You can specify the
# {path_info:.*} parameter so the target app can be handed just that
# section of the URL.
- mapper.connect(None, "/v1.0/{path_info:.*}", controller=BlogApp())
+ mapper.connect(None, '/v1.0/{path_info:.*}', controller=BlogApp())
+
"""
self.map = mapper
self._router = routes.middleware.RoutesMiddleware(self._dispatch,
@@ -308,19 +307,22 @@ class Router(object):
@webob.dec.wsgify(RequestClass=Request)
def __call__(self, req):
- """
- Route the incoming request to a controller based on self.map.
+ """Route the incoming request to a controller based on self.map.
+
If no match, return a 404.
+
"""
return self._router
@staticmethod
@webob.dec.wsgify(RequestClass=Request)
def _dispatch(req):
- """
+ """Dispatch the request to the appropriate controller.
+
Called by self._router after matching the incoming request to a route
and putting the information into req.environ. Either returns 404
or the routed WSGI app's response.
+
"""
match = req.environ['wsgiorg.routing_args'][1]
if not match:
@@ -330,22 +332,23 @@ class Router(object):
class Controller(object):
- """
+ """WSGI app that dispatched to methods.
+
WSGI app that reads routing information supplied by RoutesMiddleware
and calls the requested action method upon itself. All action methods
must, in addition to their normal parameters, accept a 'req' argument
which is the incoming wsgi.Request. They raise a webob.exc exception,
or return a dict which will be serialized by requested content type.
+
"""
@webob.dec.wsgify(RequestClass=Request)
def __call__(self, req):
- """
- Call the method specified in req.environ by RoutesMiddleware.
- """
+ """Call the method specified in req.environ by RoutesMiddleware."""
arg_dict = req.environ['wsgiorg.routing_args'][1]
action = arg_dict['action']
method = getattr(self, action)
+ LOG.debug("%s %s" % (req.method, req.url))
del arg_dict['controller']
del arg_dict['action']
if 'format' in arg_dict:
@@ -355,57 +358,67 @@ class Controller(object):
if type(result) is dict:
content_type = req.best_match_content_type()
- body = self._serialize(result, content_type)
+ default_xmlns = self.get_default_xmlns(req)
+ body = self._serialize(result, content_type, default_xmlns)
response = webob.Response()
- response.headers["Content-Type"] = content_type
+ response.headers['Content-Type'] = content_type
response.body = body
+ msg_dict = dict(url=req.url, status=response.status_int)
+ msg = _("%(url)s returned with HTTP %(status)d") % msg_dict
+ LOG.debug(msg)
return response
-
else:
return result
- def _serialize(self, data, content_type):
- """
- Serialize the given dict to the provided content_type.
+ def _serialize(self, data, content_type, default_xmlns):
+ """Serialize the given dict to the provided content_type.
+
Uses self._serialization_metadata if it exists, which is a dict mapping
MIME types to information needed to serialize to that type.
+
"""
- _metadata = getattr(type(self), "_serialization_metadata", {})
- serializer = Serializer(_metadata)
+ _metadata = getattr(type(self), '_serialization_metadata', {})
+
+ serializer = Serializer(_metadata, default_xmlns)
try:
return serializer.serialize(data, content_type)
except exception.InvalidContentType:
raise webob.exc.HTTPNotAcceptable()
def _deserialize(self, data, content_type):
- """
- Deserialize the request body to the specefied content type.
+ """Deserialize the request body to the specefied content type.
+
Uses self._serialization_metadata if it exists, which is a dict mapping
MIME types to information needed to serialize to that type.
+
"""
- _metadata = getattr(type(self), "_serialization_metadata", {})
+ _metadata = getattr(type(self), '_serialization_metadata', {})
serializer = Serializer(_metadata)
return serializer.deserialize(data, content_type)
+ def get_default_xmlns(self, req):
+ """Provide the XML namespace to use if none is otherwise specified."""
+ return None
+
class Serializer(object):
- """
- Serializes and deserializes dictionaries to certain MIME types.
- """
+ """Serializes and deserializes dictionaries to certain MIME types."""
+
+ def __init__(self, metadata=None, default_xmlns=None):
+ """Create a serializer based on the given WSGI environment.
- def __init__(self, metadata=None):
- """
- Create a serializer based on the given WSGI environment.
'metadata' is an optional dict mapping MIME types to information
needed to serialize a dictionary to that type.
+
"""
self.metadata = metadata or {}
+ self.default_xmlns = default_xmlns
def _get_serialize_handler(self, content_type):
handlers = {
- "application/json": self._to_json,
- "application/xml": self._to_xml,
+ 'application/json': self._to_json,
+ 'application/xml': self._to_xml,
}
try:
@@ -414,29 +427,27 @@ class Serializer(object):
raise exception.InvalidContentType()
def serialize(self, data, content_type):
- """
- Serialize a dictionary into a string of the specified content type.
- """
+ """Serialize a dictionary into the specified content type."""
return self._get_serialize_handler(content_type)(data)
def deserialize(self, datastring, content_type):
- """
- Deserialize a string to a dictionary.
+ """Deserialize a string to a dictionary.
The string must be in the format of a supported MIME type.
+
"""
return self.get_deserialize_handler(content_type)(datastring)
def get_deserialize_handler(self, content_type):
handlers = {
- "application/json": self._from_json,
- "application/xml": self._from_xml,
+ 'application/json': self._from_json,
+ 'application/xml': self._from_xml,
}
try:
return handlers[content_type]
except Exception:
- raise exception.InvalidContentType(_("Invalid content type %s"
+ raise exception.InvalidContentType(_('Invalid content type %s'
% content_type))
def _from_json(self, datastring):
@@ -449,11 +460,11 @@ class Serializer(object):
return {node.nodeName: self._from_xml_node(node, plurals)}
def _from_xml_node(self, node, listnames):
- """
- Convert a minidom node to a simple Python type.
+ """Convert a minidom node to a simple Python type.
listnames is a collection of names of XML nodes whose subnodes should
be considered list items.
+
"""
if len(node.childNodes) == 1 and node.childNodes[0].nodeType == 3:
return node.childNodes[0].nodeValue
@@ -478,12 +489,32 @@ class Serializer(object):
root_key = data.keys()[0]
doc = minidom.Document()
node = self._to_xml_node(doc, metadata, root_key, data[root_key])
+
+ xmlns = node.getAttribute('xmlns')
+ if not xmlns and self.default_xmlns:
+ node.setAttribute('xmlns', self.default_xmlns)
+
return node.toprettyxml(indent=' ')
def _to_xml_node(self, doc, metadata, nodename, data):
"""Recursive method to convert data members to XML nodes."""
result = doc.createElement(nodename)
+
+ # Set the xml namespace if one is specified
+ # TODO(justinsb): We could also use prefixes on the keys
+ xmlns = metadata.get('xmlns', None)
+ if xmlns:
+ result.setAttribute('xmlns', xmlns)
+
if type(data) is list:
+ collections = metadata.get('list_collections', {})
+ if nodename in collections:
+ metadata = collections[nodename]
+ for item in data:
+ node = doc.createElement(metadata['item_name'])
+ node.setAttribute(metadata['item_key'], str(item))
+ result.appendChild(node)
+ return result
singular = metadata.get('plurals', {}).get(nodename, None)
if singular is None:
if nodename.endswith('s'):
@@ -494,6 +525,16 @@ class Serializer(object):
node = self._to_xml_node(doc, metadata, singular, item)
result.appendChild(node)
elif type(data) is dict:
+ collections = metadata.get('dict_collections', {})
+ if nodename in collections:
+ metadata = collections[nodename]
+ for k, v in data.items():
+ node = doc.createElement(metadata['item_name'])
+ node.setAttribute(metadata['item_key'], str(k))
+ text = doc.createTextNode(str(v))
+ node.appendChild(text)
+ result.appendChild(node)
+ return result
attrs = metadata.get('attributes', {}).get(nodename, {})
for k, v in data.items():
if k in attrs:
@@ -530,8 +571,8 @@ def paste_config_file(basename):
* /etc/nova, which may not be diffrerent from state_path on your distro
"""
-
configfiles = [basename,
+ os.path.join(FLAGS.state_path, 'etc', 'nova', basename),
os.path.join(FLAGS.state_path, 'etc', basename),
os.path.join(FLAGS.state_path, basename),
'/etc/nova/%s' % basename]
@@ -545,7 +586,7 @@ def load_paste_configuration(filename, appname):
filename = os.path.abspath(filename)
config = None
try:
- config = deploy.appconfig("config:%s" % filename, name=appname)
+ config = deploy.appconfig('config:%s' % filename, name=appname)
except LookupError:
pass
return config
@@ -556,7 +597,7 @@ def load_paste_app(filename, appname):
filename = os.path.abspath(filename)
app = None
try:
- app = deploy.loadapp("config:%s" % filename, name=appname)
+ app = deploy.loadapp('config:%s' % filename, name=appname)
except LookupError:
pass
return app