Merge from trunk.

This commit is contained in:
Naveed Massjouni 2011-08-08 20:25:31 -04:00
commit 72dc7939f4
52 changed files with 1348 additions and 205 deletions

View File

@ -56,6 +56,7 @@
import gettext
import glob
import json
import math
import netaddr
import os
import sys
@ -591,6 +592,31 @@ class FixedIpCommands(object):
fixed_ip['address'],
mac_address, hostname, host)
@args('--address', dest="address", metavar='<ip address>',
help='IP address')
def reserve(self, address):
"""Mark fixed ip as reserved
arguments: address"""
self._set_reserved(address, True)
@args('--address', dest="address", metavar='<ip address>',
help='IP address')
def unreserve(self, address):
"""Mark fixed ip as free to use
arguments: address"""
self._set_reserved(address, False)
def _set_reserved(self, address, reserved):
ctxt = context.get_admin_context()
try:
fixed_ip = db.fixed_ip_get_by_address(ctxt, address)
db.fixed_ip_update(ctxt, fixed_ip['address'],
{'reserved': reserved})
except exception.NotFound as ex:
print "error: %s" % ex
sys.exit(2)
class FloatingIpCommands(object):
"""Class for managing floating ip."""
@ -694,7 +720,17 @@ class NetworkCommands(object):
if not num_networks:
num_networks = FLAGS.num_networks
if not network_size:
network_size = FLAGS.network_size
fixnet = netaddr.IPNetwork(fixed_range_v4)
each_subnet_size = fixnet.size / int(num_networks)
if each_subnet_size > FLAGS.network_size:
network_size = FLAGS.network_size
subnet = 32 - int(math.log(network_size, 2))
oversize_msg = _('Subnet(s) too large, defaulting to /%s.'
' To override, specify network_size flag.'
) % subnet
print oversize_msg
else:
network_size = fixnet.size
if not multi_host:
multi_host = FLAGS.multi_host
else:
@ -1224,11 +1260,12 @@ class ImageCommands(object):
is_public, architecture)
def _lookup(self, old_image_id):
elevated = context.get_admin_context()
try:
internal_id = ec2utils.ec2_id_to_id(old_image_id)
image = self.image_service.show(context, internal_id)
image = self.image_service.show(elevated, internal_id)
except (exception.InvalidEc2Id, exception.ImageNotFound):
image = self.image_service.show_by_name(context, old_image_id)
image = self.image_service.show_by_name(elevated, old_image_id)
return image['id']
def _old_to_new(self, old):

View File

@ -296,8 +296,8 @@ class ServiceWrapper(object):
'application/json': nova.api.openstack.wsgi.JSONDictSerializer(),
}[content_type]
return serializer.serialize(result)
except:
raise exception.Error("returned non-serializable type: %s"
except Exception, e:
raise exception.Error(_("Returned non-serializable type: %s")
% result)

View File

@ -147,7 +147,7 @@ class Authenticate(wsgi.Middleware):
try:
signature = req.params['Signature']
access = req.params['AWSAccessKeyId']
except:
except KeyError, e:
raise webob.exc.HTTPBadRequest()
# Make a copy of args for authentication and signature verification.
@ -211,7 +211,7 @@ class Requestify(wsgi.Middleware):
for non_arg in non_args:
# Remove, but raise KeyError if omitted
args.pop(non_arg)
except:
except KeyError, e:
raise webob.exc.HTTPBadRequest()
LOG.debug(_('action: %s'), action)

View File

@ -104,7 +104,7 @@ class APIRequest(object):
for key in data.keys():
val = data[key]
el.appendChild(self._render_data(xml, key, val))
except:
except Exception:
LOG.debug(data)
raise

View File

@ -50,6 +50,9 @@ FLAGS = flags.FLAGS
flags.DEFINE_bool('allow_admin_api',
False,
'When True, this API service will accept admin operations.')
flags.DEFINE_bool('allow_instance_snapshots',
True,
'When True, this API service will permit instance snapshot operations.')
class FaultWrapper(base_wsgi.Middleware):

View File

@ -15,8 +15,9 @@
# License for the specific language governing permissions and limitations
# under the License.
import functools
import re
from urlparse import urlparse
import urlparse
from xml.dom import minidom
import webob
@ -137,8 +138,8 @@ def get_id_from_href(href):
if re.match(r'\d+$', str(href)):
return int(href)
try:
return int(urlparse(href).path.split('/')[-1])
except:
return int(urlparse.urlsplit(href).path.split('/')[-1])
except ValueError, e:
LOG.debug(_("Error extracting id from href: %s") % href)
raise ValueError(_('could not parse id from href'))
@ -153,22 +154,18 @@ def remove_version_from_href(href):
Returns: 'http://www.nova.com'
"""
try:
#removes the first instance that matches /v#.#/
new_href = re.sub(r'[/][v][0-9]+\.[0-9]+[/]', '/', href, count=1)
parsed_url = urlparse.urlsplit(href)
new_path = re.sub(r'^/v[0-9]+\.[0-9]+(/|$)', r'\1', parsed_url.path,
count=1)
#if no version was found, try finding /v#.# at the end of the string
if new_href == href:
new_href = re.sub(r'[/][v][0-9]+\.[0-9]+$', '', href, count=1)
except:
LOG.debug(_("Error removing version from href: %s") % href)
msg = _('could not parse version from href')
if new_path == parsed_url.path:
msg = _('href %s does not contain version') % href
LOG.debug(msg)
raise ValueError(msg)
if new_href == href:
msg = _('href does not contain version')
raise ValueError(msg)
return new_href
parsed_url = list(parsed_url)
parsed_url[2] = new_path
return urlparse.urlunsplit(parsed_url)
def get_version_from_href(href):
@ -284,3 +281,15 @@ class MetadataXMLSerializer(wsgi.XMLDictSerializer):
def default(self, *args, **kwargs):
return ''
def check_snapshots_enabled(f):
@functools.wraps(f)
def inner(*args, **kwargs):
if not FLAGS.allow_instance_snapshots:
LOG.warn(_('Rejecting snapshot request, snapshots currently'
' disabled'))
msg = _("Instance snapshots are not permitted at this time.")
raise webob.exc.HTTPBadRequest(explanation=msg)
return f(*args, **kwargs)
return inner

View File

@ -0,0 +1,30 @@
# Copyright (c) 2011 Openstack, LLC.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""Decorator for limiting extensions that should be admin-only."""
from functools import wraps
from nova import flags
FLAGS = flags.FLAGS
def admin_only(fnc):
@wraps(fnc)
def _wrapped(self, *args, **kwargs):
if FLAGS.allow_admin_api:
return fnc(self, *args, **kwargs)
return []
_wrapped.func_name = fnc.func_name
return _wrapped

View File

@ -24,6 +24,7 @@ from nova import log as logging
from nova.api.openstack import common
from nova.api.openstack import extensions
from nova.api.openstack import faults
from nova.api.openstack.contrib import admin_only
from nova.scheduler import api as scheduler_api
@ -70,7 +71,7 @@ class HostController(object):
key = raw_key.lower().strip()
val = raw_val.lower().strip()
# NOTE: (dabo) Right now only 'status' can be set, but other
# actions may follow.
# settings may follow.
if key == "status":
if val[:6] in ("enable", "disabl"):
return self._set_enabled_status(req, id,
@ -89,8 +90,30 @@ class HostController(object):
LOG.audit(_("Setting host %(host)s to %(state)s.") % locals())
result = self.compute_api.set_host_enabled(context, host=host,
enabled=enabled)
if result not in ("enabled", "disabled"):
# An error message was returned
raise webob.exc.HTTPBadRequest(explanation=result)
return {"host": host, "status": result}
def _host_power_action(self, req, host, action):
"""Reboots, shuts down or powers up the host."""
context = req.environ['nova.context']
try:
result = self.compute_api.host_power_action(context, host=host,
action=action)
except NotImplementedError as e:
raise webob.exc.HTTPBadRequest(explanation=e.msg)
return {"host": host, "power_action": result}
def startup(self, req, id):
return self._host_power_action(req, host=id, action="startup")
def shutdown(self, req, id):
return self._host_power_action(req, host=id, action="shutdown")
def reboot(self, req, id):
return self._host_power_action(req, host=id, action="reboot")
class Hosts(extensions.ExtensionDescriptor):
def get_name(self):
@ -108,7 +131,10 @@ class Hosts(extensions.ExtensionDescriptor):
def get_updated(self):
return "2011-06-29T00:00:00+00:00"
@admin_only.admin_only
def get_resources(self):
resources = [extensions.ResourceExtension('os-hosts', HostController(),
collection_actions={'update': 'PUT'}, member_actions={})]
resources = [extensions.ResourceExtension('os-hosts',
HostController(), collection_actions={'update': 'PUT'},
member_actions={"startup": "GET", "shutdown": "GET",
"reboot": "GET"})]
return resources

View File

@ -304,38 +304,6 @@ class ServerXMLDeserializer(wsgi.XMLDeserializer):
metadata_deserializer = common.MetadataXMLDeserializer()
def action(self, string):
dom = minidom.parseString(string)
action_node = dom.childNodes[0]
action_name = action_node.tagName
action_deserializer = {
'createImage': self._action_create_image,
'createBackup': self._action_create_backup,
}.get(action_name, self.default)
action_data = action_deserializer(action_node)
return {'body': {action_name: action_data}}
def _action_create_image(self, node):
return self._deserialize_image_action(node, ('name',))
def _action_create_backup(self, node):
attributes = ('name', 'backup_type', 'rotation')
return self._deserialize_image_action(node, attributes)
def _deserialize_image_action(self, node, allowed_attributes):
data = {}
for attribute in allowed_attributes:
value = node.getAttribute(attribute)
if value:
data[attribute] = value
metadata_node = self.find_first_child_named(node, 'metadata')
metadata = self.metadata_deserializer.extract_metadata(metadata_node)
data['metadata'] = metadata
return data
def create(self, string):
"""Deserialize an xml-formatted server create request"""
dom = minidom.parseString(string)
@ -347,15 +315,14 @@ class ServerXMLDeserializer(wsgi.XMLDeserializer):
server = {}
server_node = self.find_first_child_named(node, 'server')
attributes = ["name", "imageId", "flavorId", "imageRef",
"flavorRef", "adminPass"]
attributes = ["name", "imageId", "flavorId", "adminPass"]
for attr in attributes:
if server_node.getAttribute(attr):
server[attr] = server_node.getAttribute(attr)
metadata_node = self.find_first_child_named(server_node, "metadata")
server["metadata"] = self.metadata_deserializer.extract_metadata(
metadata_node)
metadata_node)
server["personality"] = self._extract_personality(server_node)
@ -373,3 +340,135 @@ class ServerXMLDeserializer(wsgi.XMLDeserializer):
item["contents"] = self.extract_text(file_node)
personality.append(item)
return personality
class ServerXMLDeserializerV11(wsgi.MetadataXMLDeserializer):
"""
Deserializer to handle xml-formatted server create requests.
Handles standard server attributes as well as optional metadata
and personality attributes
"""
metadata_deserializer = common.MetadataXMLDeserializer()
def action(self, string):
dom = minidom.parseString(string)
action_node = dom.childNodes[0]
action_name = action_node.tagName
action_deserializer = {
'createImage': self._action_create_image,
'createBackup': self._action_create_backup,
'changePassword': self._action_change_password,
'reboot': self._action_reboot,
'rebuild': self._action_rebuild,
'resize': self._action_resize,
'confirmResize': self._action_confirm_resize,
'revertResize': self._action_revert_resize,
}.get(action_name, self.default)
action_data = action_deserializer(action_node)
return {'body': {action_name: action_data}}
def _action_create_image(self, node):
return self._deserialize_image_action(node, ('name',))
def _action_create_backup(self, node):
attributes = ('name', 'backup_type', 'rotation')
return self._deserialize_image_action(node, attributes)
def _action_change_password(self, node):
if not node.hasAttribute("adminPass"):
raise AttributeError("No adminPass was specified in request")
return {"adminPass": node.getAttribute("adminPass")}
def _action_reboot(self, node):
if not node.hasAttribute("type"):
raise AttributeError("No reboot type was specified in request")
return {"type": node.getAttribute("type")}
def _action_rebuild(self, node):
rebuild = {}
if node.hasAttribute("name"):
rebuild['name'] = node.getAttribute("name")
metadata_node = self.find_first_child_named(node, "metadata")
if metadata_node is not None:
rebuild["metadata"] = self.extract_metadata(metadata_node)
personality = self._extract_personality(node)
if personality is not None:
rebuild["personality"] = personality
if not node.hasAttribute("imageRef"):
raise AttributeError("No imageRef was specified in request")
rebuild["imageRef"] = node.getAttribute("imageRef")
return rebuild
def _action_resize(self, node):
if not node.hasAttribute("flavorRef"):
raise AttributeError("No flavorRef was specified in request")
return {"flavorRef": node.getAttribute("flavorRef")}
def _action_confirm_resize(self, node):
return None
def _action_revert_resize(self, node):
return None
def _deserialize_image_action(self, node, allowed_attributes):
data = {}
for attribute in allowed_attributes:
value = node.getAttribute(attribute)
if value:
data[attribute] = value
metadata_node = self.find_first_child_named(node, 'metadata')
if metadata_node is not None:
metadata = self.metadata_deserializer.extract_metadata(
metadata_node)
data['metadata'] = metadata
return data
def create(self, string):
"""Deserialize an xml-formatted server create request"""
dom = minidom.parseString(string)
server = self._extract_server(dom)
return {'body': {'server': server}}
def _extract_server(self, node):
"""Marshal the server attribute of a parsed request"""
server = {}
server_node = self.find_first_child_named(node, 'server')
attributes = ["name", "imageRef", "flavorRef", "adminPass"]
for attr in attributes:
if server_node.getAttribute(attr):
server[attr] = server_node.getAttribute(attr)
metadata_node = self.find_first_child_named(server_node, "metadata")
if metadata_node is not None:
server["metadata"] = self.extract_metadata(metadata_node)
personality = self._extract_personality(server_node)
if personality is not None:
server["personality"] = personality
return server
def _extract_personality(self, server_node):
"""Marshal the personality attribute of a parsed request"""
node = self.find_first_child_named(server_node, "personality")
if node is not None:
personality = []
for file_node in self.find_children_named(node, "file"):
item = {}
if file_node.hasAttribute("path"):
item["path"] = file_node.getAttribute("path")
item["contents"] = self.extract_text(file_node)
personality.append(item)
return personality
else:
return None

View File

@ -106,6 +106,7 @@ class Controller(object):
class ControllerV10(Controller):
"""Version 1.0 specific controller logic."""
@common.check_snapshots_enabled
def create(self, req, body):
"""Snapshot a server instance and save the image."""
try:
@ -143,7 +144,7 @@ class ControllerV10(Controller):
"""
context = req.environ['nova.context']
filters = self._get_filters(req)
images = self._image_service.index(context, filters)
images = self._image_service.index(context, filters=filters)
images = common.limited(images, req)
builder = self.get_builder(req).build
return dict(images=[builder(image, detail=False) for image in images])
@ -156,7 +157,7 @@ class ControllerV10(Controller):
"""
context = req.environ['nova.context']
filters = self._get_filters(req)
images = self._image_service.detail(context, filters)
images = self._image_service.detail(context, filters=filters)
images = common.limited(images, req)
builder = self.get_builder(req).build
return dict(images=[builder(image, detail=True) for image in images])

View File

@ -240,6 +240,7 @@ class Controller(object):
resp.headers['Location'] = image_ref
return resp
@common.check_snapshots_enabled
def _action_create_image(self, input_dict, req, id):
return exc.HTTPNotImplemented()
@ -267,10 +268,16 @@ class Controller(object):
def _action_reboot(self, input_dict, req, id):
if 'reboot' in input_dict and 'type' in input_dict['reboot']:
reboot_type = input_dict['reboot']['type']
valid_reboot_types = ['HARD', 'SOFT']
reboot_type = input_dict['reboot']['type'].upper()
if not valid_reboot_types.count(reboot_type):
msg = _("Argument 'type' for reboot is not HARD or SOFT")
LOG.exception(msg)
raise exc.HTTPBadRequest(explanation=msg)
else:
LOG.exception(_("Missing argument 'type' for reboot"))
raise exc.HTTPUnprocessableEntity()
msg = _("Missing argument 'type' for reboot")
LOG.exception(msg)
raise exc.HTTPBadRequest(explanation=msg)
try:
# TODO(gundlach): pass reboot_type, support soft reboot in
# virt driver
@ -290,7 +297,7 @@ class Controller(object):
context = req.environ['nova.context']
try:
self.compute_api.lock(context, id)
except:
except Exception:
readable = traceback.format_exc()
LOG.exception(_("Compute.api::lock %s"), readable)
raise exc.HTTPUnprocessableEntity()
@ -306,7 +313,7 @@ class Controller(object):
context = req.environ['nova.context']
try:
self.compute_api.unlock(context, id)
except:
except Exception:
readable = traceback.format_exc()
LOG.exception(_("Compute.api::unlock %s"), readable)
raise exc.HTTPUnprocessableEntity()
@ -321,7 +328,7 @@ class Controller(object):
context = req.environ['nova.context']
try:
self.compute_api.get_lock(context, id)
except:
except Exception:
readable = traceback.format_exc()
LOG.exception(_("Compute.api::get_lock %s"), readable)
raise exc.HTTPUnprocessableEntity()
@ -336,7 +343,7 @@ class Controller(object):
context = req.environ['nova.context']
try:
self.compute_api.reset_network(context, id)
except:
except Exception:
readable = traceback.format_exc()
LOG.exception(_("Compute.api::reset_network %s"), readable)
raise exc.HTTPUnprocessableEntity()
@ -351,7 +358,7 @@ class Controller(object):
context = req.environ['nova.context']
try:
self.compute_api.inject_network_info(context, id)
except:
except Exception:
readable = traceback.format_exc()
LOG.exception(_("Compute.api::inject_network_info %s"), readable)
raise exc.HTTPUnprocessableEntity()
@ -363,7 +370,7 @@ class Controller(object):
ctxt = req.environ['nova.context']
try:
self.compute_api.pause(ctxt, id)
except:
except Exception:
readable = traceback.format_exc()
LOG.exception(_("Compute.api::pause %s"), readable)
raise exc.HTTPUnprocessableEntity()
@ -375,7 +382,7 @@ class Controller(object):
ctxt = req.environ['nova.context']
try:
self.compute_api.unpause(ctxt, id)
except:
except Exception:
readable = traceback.format_exc()
LOG.exception(_("Compute.api::unpause %s"), readable)
raise exc.HTTPUnprocessableEntity()
@ -387,7 +394,7 @@ class Controller(object):
context = req.environ['nova.context']
try:
self.compute_api.suspend(context, id)
except:
except Exception:
readable = traceback.format_exc()
LOG.exception(_("compute.api::suspend %s"), readable)
raise exc.HTTPUnprocessableEntity()
@ -399,7 +406,7 @@ class Controller(object):
context = req.environ['nova.context']
try:
self.compute_api.resume(context, id)
except:
except Exception:
readable = traceback.format_exc()
LOG.exception(_("compute.api::resume %s"), readable)
raise exc.HTTPUnprocessableEntity()
@ -420,7 +427,7 @@ class Controller(object):
context = req.environ["nova.context"]
try:
self.compute_api.rescue(context, id)
except:
except Exception:
readable = traceback.format_exc()
LOG.exception(_("compute.api::rescue %s"), readable)
raise exc.HTTPUnprocessableEntity()
@ -432,7 +439,7 @@ class Controller(object):
context = req.environ["nova.context"]
try:
self.compute_api.unrescue(context, id)
except:
except Exception:
readable = traceback.format_exc()
LOG.exception(_("compute.api::unrescue %s"), readable)
raise exc.HTTPUnprocessableEntity()
@ -646,6 +653,9 @@ class ControllerV11(Controller):
""" Resizes a given instance to the flavor size requested """
try:
flavor_ref = input_dict["resize"]["flavorRef"]
if not flavor_ref:
msg = _("Resize request has invalid 'flavorRef' attribute.")
raise exc.HTTPBadRequest(explanation=msg)
except (KeyError, TypeError):
msg = _("Resize requests require 'flavorRef' attribute.")
raise exc.HTTPBadRequest(explanation=msg)
@ -680,6 +690,7 @@ class ControllerV11(Controller):
return webob.Response(status_int=202)
@common.check_snapshots_enabled
def _action_create_image(self, input_dict, req, instance_id):
"""Snapshot a server instance."""
entity = input_dict.get("createImage", {})
@ -891,8 +902,13 @@ def create_resource(version='1.0'):
'application/xml': xml_serializer,
}
xml_deserializer = {
'1.0': helper.ServerXMLDeserializer(),
'1.1': helper.ServerXMLDeserializerV11(),
}[version]
body_deserializers = {
'application/xml': helper.ServerXMLDeserializer(),
'application/xml': xml_deserializer,
}
serializer = wsgi.ResponseSerializer(body_serializers, headers_serializer)

View File

@ -77,7 +77,9 @@ class ViewBuilder(object):
"status": image_obj.get("status"),
})
if image["status"] == "SAVING":
if image["status"].upper() == "ACTIVE":
image["progress"] = 100
else:
image["progress"] = 0
return image

View File

@ -166,7 +166,7 @@ class Controller(object):
return self.helper._get_server_admin_password_old_style(server)
class ControllerV11(object):
class ControllerV11(Controller):
"""Controller for 1.1 Zone resources."""
def _get_server_admin_password(self, server):

View File

@ -16,3 +16,4 @@ export NOVA_API_KEY="%(access)s"
export NOVA_USERNAME="%(user)s"
export NOVA_PROJECT_ID="%(project)s"
export NOVA_URL="%(os)s"
export NOVA_VERSION="1.1"

View File

@ -141,15 +141,12 @@ class CloudPipe(object):
try:
result = cloud._gen_key(context, context.user_id, key_name)
private_key = result['private_key']
try:
key_dir = os.path.join(FLAGS.keys_path, context.user_id)
if not os.path.exists(key_dir):
os.makedirs(key_dir)
key_path = os.path.join(key_dir, '%s.pem' % key_name)
with open(key_path, 'w') as f:
f.write(private_key)
except:
pass
except exception.Duplicate:
key_dir = os.path.join(FLAGS.keys_path, context.user_id)
if not os.path.exists(key_dir):
os.makedirs(key_dir)
key_path = os.path.join(key_dir, '%s.pem' % key_name)
with open(key_path, 'w') as f:
f.write(private_key)
except (exception.Duplicate, os.error, IOError):
pass
return key_name

View File

@ -360,6 +360,7 @@ class API(base.Base):
instance_type, zone_blob,
availability_zone, injected_files,
admin_password,
image,
instance_id=None, num_instances=1):
"""Send the run_instance request to the schedulers for processing."""
pid = context.project_id
@ -373,6 +374,7 @@ class API(base.Base):
filter_class = 'nova.scheduler.host_filter.InstanceTypeFilter'
request_spec = {
'image': image,
'instance_properties': base_options,
'instance_type': instance_type,
'filter': filter_class,
@ -415,6 +417,7 @@ class API(base.Base):
instance_type, zone_blob,
availability_zone, injected_files,
admin_password,
image,
num_instances=num_instances)
return base_options['reservation_id']
@ -463,6 +466,7 @@ class API(base.Base):
instance_type, zone_blob,
availability_zone, injected_files,
admin_password,
image,
instance_id=instance_id)
return [dict(x.iteritems()) for x in instances]
@ -993,7 +997,12 @@ class API(base.Base):
def set_host_enabled(self, context, host, enabled):
"""Sets the specified host's ability to accept new instances."""
return self._call_compute_message("set_host_enabled", context,
instance_id=None, host=host, params={"enabled": enabled})
host=host, params={"enabled": enabled})
def host_power_action(self, context, host, action):
"""Reboots, shuts down or powers up the host."""
return self._call_compute_message("host_power_action", context,
host=host, params={"action": action})
@scheduler_api.reroute_compute("diagnostics")
def get_diagnostics(self, context, instance_id):

View File

@ -748,7 +748,8 @@ class ComputeManager(manager.SchedulerDependentManager):
instance_ref['host'])
rpc.cast(context, topic,
{'method': 'finish_revert_resize',
'args': {'migration_id': migration_ref['id']},
'args': {'instance_id': instance_ref['uuid'],
'migration_id': migration_ref['id']},
})
@exception.wrap_exception(notifier=notifier, publisher_id=publisher_id())
@ -957,8 +958,12 @@ class ComputeManager(manager.SchedulerDependentManager):
result))
@exception.wrap_exception(notifier=notifier, publisher_id=publisher_id())
def set_host_enabled(self, context, instance_id=None, host=None,
enabled=None):
def host_power_action(self, context, host=None, action=None):
"""Reboots, shuts down or powers up the host."""
return self.driver.host_power_action(host, action)
@exception.wrap_exception(notifier=notifier, publisher_id=publisher_id())
def set_host_enabled(self, context, host=None, enabled=None):
"""Sets the specified host's ability to accept new instances."""
return self.driver.set_host_enabled(host, enabled)

View File

@ -1426,9 +1426,14 @@ def instance_action_create(context, values):
def instance_get_actions(context, instance_id):
"""Return the actions associated to the given instance id"""
session = get_session()
if utils.is_uuid_like(instance_id):
instance = instance_get_by_uuid(context, instance_id, session)
instance_id = instance.id
return session.query(models.InstanceActions).\
filter_by(instance_id=instance_id).\
all()
all()
###################
@ -3178,8 +3183,9 @@ def instance_metadata_delete_all(context, instance_id):
@require_context
@require_instance_exists
def instance_metadata_get_item(context, instance_id, key):
session = get_session()
def instance_metadata_get_item(context, instance_id, key, session=None):
if not session:
session = get_session()
meta_result = session.query(models.InstanceMetadata).\
filter_by(instance_id=instance_id).\
@ -3205,7 +3211,7 @@ def instance_metadata_update_or_create(context, instance_id, metadata):
try:
meta_ref = instance_metadata_get_item(context, instance_id, key,
session)
except:
except exception.InstanceMetadataNotFound, e:
meta_ref = models.InstanceMetadata()
meta_ref.update({"key": key, "value": value,
"instance_id": instance_id,
@ -3300,8 +3306,10 @@ def instance_type_extra_specs_delete(context, instance_type_id, key):
@require_context
def instance_type_extra_specs_get_item(context, instance_type_id, key):
session = get_session()
def instance_type_extra_specs_get_item(context, instance_type_id, key,
session=None):
if not session:
session = get_session()
spec_result = session.query(models.InstanceTypeExtraSpecs).\
filter_by(instance_type_id=instance_type_id).\
@ -3327,7 +3335,7 @@ def instance_type_extra_specs_update_or_create(context, instance_type_id,
instance_type_id,
key,
session)
except:
except exception.InstanceTypeExtraSpecsNotFound, e:
spec_ref = models.InstanceTypeExtraSpecs()
spec_ref.update({"key": key, "value": value,
"instance_type_id": instance_type_id,

View File

@ -25,6 +25,7 @@ SHOULD include dedicated exception logging.
"""
from functools import wraps
import sys
from nova import log as logging
@ -96,6 +97,10 @@ def wrap_exception(notifier=None, publisher_id=None, event_type=None,
try:
return f(*args, **kw)
except Exception, e:
# Save exception since it can be clobbered during processing
# below before we can re-raise
exc_info = sys.exc_info()
if notifier:
payload = dict(args=args, exception=e)
payload.update(kw)
@ -122,7 +127,9 @@ def wrap_exception(notifier=None, publisher_id=None, event_type=None,
LOG.exception(_('Uncaught exception'))
#logging.error(traceback.extract_stack(exc_traceback))
raise Error(str(e))
raise
# re-raise original exception since it may have been clobbered
raise exc_info[0], exc_info[1], exc_info[2]
return wraps(f)(wrapped)
return inner
@ -150,6 +157,10 @@ class NovaException(Exception):
return self._error_string
class ImagePaginationFailed(NovaException):
message = _("Failed to paginate through images from image service")
class VirtualInterfaceCreateException(NovaException):
message = _("Virtual Interface creation failed")

View File

@ -317,7 +317,7 @@ DEFINE_string('osapi_extensions_path', '/var/lib/nova/extensions',
DEFINE_string('osapi_host', '$my_ip', 'ip of api server')
DEFINE_string('osapi_scheme', 'http', 'prefix for openstack')
DEFINE_integer('osapi_port', 8774, 'OpenStack API port')
DEFINE_string('osapi_path', '/v1.0/', 'suffix for openstack')
DEFINE_string('osapi_path', '/v1.1/', 'suffix for openstack')
DEFINE_integer('osapi_max_limit', 1000,
'max number of items returned in a collection response')

View File

@ -35,6 +35,7 @@ def _parse_image_ref(image_href):
:param image_href: href of an image
:returns: a tuple of the form (image_id, host, port)
:raises ValueError
"""
o = urlparse(image_href)
@ -72,7 +73,7 @@ def get_glance_client(image_href):
try:
(image_id, host, port) = _parse_image_ref(image_href)
except:
except ValueError:
raise exception.InvalidImageRef(image_href=image_href)
glance_client = GlanceClient(host, port)
return (glance_client, image_id)

View File

@ -45,9 +45,12 @@ class _FakeImageService(service.BaseImageService):
'name': 'fakeimage123456',
'created_at': timestamp,
'updated_at': timestamp,
'deleted_at': None,
'deleted': False,
'status': 'active',
'container_format': 'ami',
'disk_format': 'raw',
'is_public': False,
# 'container_format': 'ami',
# 'disk_format': 'raw',
'properties': {'kernel_id': FLAGS.null_kernel,
'ramdisk_id': FLAGS.null_kernel,
'architecture': 'x86_64'}}
@ -56,9 +59,12 @@ class _FakeImageService(service.BaseImageService):
'name': 'fakeimage123456',
'created_at': timestamp,
'updated_at': timestamp,
'deleted_at': None,
'deleted': False,
'status': 'active',
'container_format': 'ami',
'disk_format': 'raw',
'is_public': True,
# 'container_format': 'ami',
# 'disk_format': 'raw',
'properties': {'kernel_id': FLAGS.null_kernel,
'ramdisk_id': FLAGS.null_kernel}}
@ -66,9 +72,12 @@ class _FakeImageService(service.BaseImageService):
'name': 'fakeimage123456',
'created_at': timestamp,
'updated_at': timestamp,
'deleted_at': None,
'deleted': False,
'status': 'active',
'container_format': 'ami',
'disk_format': 'raw',
'is_public': True,
# 'container_format': 'ami',
# 'disk_format': 'raw',
'properties': {'kernel_id': FLAGS.null_kernel,
'ramdisk_id': FLAGS.null_kernel}}
@ -76,9 +85,12 @@ class _FakeImageService(service.BaseImageService):
'name': 'fakeimage123456',
'created_at': timestamp,
'updated_at': timestamp,
'deleted_at': None,
'deleted': False,
'status': 'active',
'container_format': 'ami',
'disk_format': 'raw',
'is_public': True,
# 'container_format': 'ami',
# 'disk_format': 'raw',
'properties': {'kernel_id': FLAGS.null_kernel,
'ramdisk_id': FLAGS.null_kernel}}
@ -86,9 +98,12 @@ class _FakeImageService(service.BaseImageService):
'name': 'fakeimage123456',
'created_at': timestamp,
'updated_at': timestamp,
'deleted_at': None,
'deleted': False,
'status': 'active',
'container_format': 'ami',
'disk_format': 'raw',
'is_public': True,
# 'container_format': 'ami',
# 'disk_format': 'raw',
'properties': {'kernel_id': FLAGS.null_kernel,
'ramdisk_id': FLAGS.null_kernel}}
@ -101,7 +116,11 @@ class _FakeImageService(service.BaseImageService):
def index(self, context, filters=None, marker=None, limit=None):
"""Returns list of images."""
return copy.deepcopy(self.images.values())
retval = []
for img in self.images.values():
retval += [dict([(k, v) for k, v in img.iteritems()
if k in ['id', 'name']])]
return retval
def detail(self, context, filters=None, marker=None, limit=None):
"""Return list of detailed image information."""

View File

@ -87,42 +87,71 @@ class GlanceImageService(service.BaseImageService):
"""Sets the client's auth token."""
self.client.set_auth_token(context.auth_token)
def index(self, context, filters=None, marker=None, limit=None):
def index(self, context, **kwargs):
"""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
self._set_client_context(context)
filtered = []
filters = filters or {}
if 'is_public' not in filters:
# NOTE(vish): don't filter out private images
filters['is_public'] = 'none'
image_metas = self.client.get_images_detailed(filters=filters,
marker=marker,
limit=limit)
params = self._extract_query_params(kwargs)
image_metas = self._get_images(context, **params)
images = []
for image_meta in image_metas:
# 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
if self._is_image_available(context, image_meta):
meta_subset = utils.subset_dict(image_meta, ('id', 'name'))
filtered.append(meta_subset)
return filtered
images.append(meta_subset)
return images
def detail(self, context, filters=None, marker=None, limit=None):
def detail(self, context, **kwargs):
"""Calls out to Glance for a list of detailed image information."""
self._set_client_context(context)
filtered = []
filters = filters or {}
if 'is_public' not in filters:
# NOTE(vish): don't filter out private images
filters['is_public'] = 'none'
image_metas = self.client.get_images_detailed(filters=filters,
marker=marker,
limit=limit)
params = self._extract_query_params(kwargs)
image_metas = self._get_images(context, **params)
images = []
for image_meta in image_metas:
if self._is_image_available(context, image_meta):
base_image_meta = self._translate_to_base(image_meta)
filtered.append(base_image_meta)
return filtered
images.append(base_image_meta)
return images
def _extract_query_params(self, params):
_params = {}
accepted_params = ('filters', 'marker', 'limit',
'sort_key', 'sort_dir')
for param in accepted_params:
if param in params:
_params[param] = params.get(param)
return _params
def _get_images(self, context, **kwargs):
"""Get image entitites from images service"""
self._set_client_context(context)
# ensure filters is a dict
kwargs['filters'] = kwargs.get('filters') or {}
# NOTE(vish): don't filter out private images
kwargs['filters'].setdefault('is_public', 'none')
return self._fetch_images(self.client.get_images_detailed, **kwargs)
def _fetch_images(self, fetch_func, **kwargs):
"""Paginate through results from glance server"""
images = fetch_func(**kwargs)
for image in images:
yield image
else:
# break out of recursive loop to end pagination
return
try:
# attempt to advance the marker in order to fetch next page
kwargs['marker'] = images[-1]['id']
except KeyError:
raise exception.ImagePaginationFailed()
self._fetch_images(fetch_func, **kwargs)
def show(self, context, image_id):
"""Returns a dict with image data for the given opaque image id."""

View File

@ -17,7 +17,8 @@
Handles all requests relating to schedulers.
"""
import novaclient
from novaclient import v1_1 as novaclient
from novaclient import exceptions as novaclient_exceptions
from nova import db
from nova import exception
@ -112,7 +113,7 @@ def _wrap_method(function, self):
def _process(func, zone):
"""Worker stub for green thread pool. Give the worker
an authenticated nova client and zone info."""
nova = novaclient.OpenStack(zone.username, zone.password, None,
nova = novaclient.Client(zone.username, zone.password, None,
zone.api_url)
nova.authenticate()
return func(nova, zone)
@ -132,10 +133,10 @@ def call_zone_method(context, method_name, errors_to_ignore=None,
zones = db.zone_get_all(context)
for zone in zones:
try:
nova = novaclient.OpenStack(zone.username, zone.password, None,
nova = novaclient.Client(zone.username, zone.password, None,
zone.api_url)
nova.authenticate()
except novaclient.exceptions.BadRequest, e:
except novaclient_exceptions.BadRequest, e:
url = zone.api_url
LOG.warn(_("Failed request to zone; URL=%(url)s: %(e)s")
% locals())
@ -188,7 +189,7 @@ def _issue_novaclient_command(nova, zone, collection,
if method_name in ['find', 'findall']:
try:
return getattr(manager, method_name)(**kwargs)
except novaclient.NotFound:
except novaclient_exceptions.NotFound:
url = zone.api_url
LOG.debug(_("%(collection)s.%(method_name)s didn't find "
"anything matching '%(kwargs)s' on '%(url)s'" %
@ -200,7 +201,7 @@ def _issue_novaclient_command(nova, zone, collection,
item = args.pop(0)
try:
result = manager.get(item)
except novaclient.NotFound:
except novaclient_exceptions.NotFound:
url = zone.api_url
LOG.debug(_("%(collection)s '%(item)s' not found on '%(url)s'" %
locals()))

View File

@ -24,7 +24,9 @@ import operator
import json
import M2Crypto
import novaclient
from novaclient import v1_1 as novaclient
from novaclient import exceptions as novaclient_exceptions
from nova import crypto
from nova import db
@ -58,12 +60,13 @@ class ZoneAwareScheduler(driver.Scheduler):
"""Create the requested resource in this Zone."""
host = build_plan_item['hostname']
base_options = request_spec['instance_properties']
image = request_spec['image']
# TODO(sandy): I guess someone needs to add block_device_mapping
# support at some point? Also, OS API has no concept of security
# groups.
instance = compute_api.API().create_db_entry_for_new_instance(context,
base_options, None, [])
image, base_options, None, [])
instance_id = instance['id']
kwargs['instance_id'] = instance_id
@ -117,10 +120,9 @@ class ZoneAwareScheduler(driver.Scheduler):
% locals())
nova = None
try:
nova = novaclient.OpenStack(zone.username, zone.password, None,
url)
nova = novaclient.Client(zone.username, zone.password, None, url)
nova.authenticate()
except novaclient.exceptions.BadRequest, e:
except novaclient_exceptions.BadRequest, e:
raise exception.NotAuthorized(_("Bad credentials attempting "
"to talk to zone at %(url)s.") % locals())

View File

@ -18,10 +18,11 @@ ZoneManager oversees all communications with child Zones.
"""
import datetime
import novaclient
import thread
import traceback
from novaclient import v1_1 as novaclient
from eventlet import greenpool
from nova import db
@ -89,8 +90,8 @@ class ZoneState(object):
def _call_novaclient(zone):
"""Call novaclient. Broken out for testing purposes."""
client = novaclient.OpenStack(zone.username, zone.password, None,
zone.api_url)
client = novaclient.Client(zone.username, zone.password, None,
zone.api_url)
return client.zones.info()._info

View File

@ -60,11 +60,42 @@ class skip_test(object):
self.message = msg
def __call__(self, func):
@functools.wraps(func)
def _skipper(*args, **kw):
"""Wrapped skipper function."""
raise nose.SkipTest(self.message)
_skipper.__name__ = func.__name__
_skipper.__doc__ = func.__doc__
return _skipper
class skip_if(object):
"""Decorator that skips a test if contition is true."""
def __init__(self, condition, msg):
self.condition = condition
self.message = msg
def __call__(self, func):
@functools.wraps(func)
def _skipper(*args, **kw):
"""Wrapped skipper function."""
if self.condition:
raise nose.SkipTest(self.message)
func(*args, **kw)
return _skipper
class skip_unless(object):
"""Decorator that skips a test if condition is not true."""
def __init__(self, condition, msg):
self.condition = condition
self.message = msg
def __call__(self, func):
@functools.wraps(func)
def _skipper(*args, **kw):
"""Wrapped skipper function."""
if not self.condition:
raise nose.SkipTest(self.message)
func(*args, **kw)
return _skipper

View File

@ -379,6 +379,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase):
"updated": self.NOW_API_FORMAT,
"created": self.NOW_API_FORMAT,
"status": "ACTIVE",
"progress": 100,
},
}
@ -402,6 +403,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase):
"updated": self.NOW_API_FORMAT,
"created": self.NOW_API_FORMAT,
"status": "QUEUED",
"progress": 0,
'server': {
'id': 42,
"links": [{
@ -444,6 +446,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase):
updated="%(expected_now)s"
created="%(expected_now)s"
status="ACTIVE"
progress="100"
xmlns="http://docs.rackspacecloud.com/servers/api/v1.0" />
""" % (locals()))
@ -463,6 +466,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase):
updated="%(expected_now)s"
created="%(expected_now)s"
status="ACTIVE"
progress="100"
xmlns="http://docs.rackspacecloud.com/servers/api/v1.0" />
""" % (locals()))
@ -587,6 +591,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase):
'updated': self.NOW_API_FORMAT,
'created': self.NOW_API_FORMAT,
'status': 'ACTIVE',
'progress': 100,
},
{
'id': 124,
@ -594,6 +599,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase):
'updated': self.NOW_API_FORMAT,
'created': self.NOW_API_FORMAT,
'status': 'QUEUED',
'progress': 0,
},
{
'id': 125,
@ -608,7 +614,8 @@ class ImageControllerWithGlanceServiceTest(test.TestCase):
'name': 'active snapshot',
'updated': self.NOW_API_FORMAT,
'created': self.NOW_API_FORMAT,
'status': 'ACTIVE'
'status': 'ACTIVE',
'progress': 100,
},
{
'id': 127,
@ -616,6 +623,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase):
'updated': self.NOW_API_FORMAT,
'created': self.NOW_API_FORMAT,
'status': 'FAILED',
'progress': 0,
},
{
'id': 129,
@ -623,6 +631,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase):
'updated': self.NOW_API_FORMAT,
'created': self.NOW_API_FORMAT,
'status': 'ACTIVE',
'progress': 100,
}]
self.assertDictListMatch(expected, response_list)
@ -643,6 +652,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase):
'updated': self.NOW_API_FORMAT,
'created': self.NOW_API_FORMAT,
'status': 'ACTIVE',
'progress': 100,
"links": [{
"rel": "self",
"href": "http://localhost/v1.1/images/123",
@ -662,6 +672,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase):
'updated': self.NOW_API_FORMAT,
'created': self.NOW_API_FORMAT,
'status': 'QUEUED',
'progress': 0,
'server': {
'id': 42,
"links": [{
@ -723,6 +734,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase):
'updated': self.NOW_API_FORMAT,
'created': self.NOW_API_FORMAT,
'status': 'ACTIVE',
'progress': 100,
'server': {
'id': 42,
"links": [{
@ -753,6 +765,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase):
'updated': self.NOW_API_FORMAT,
'created': self.NOW_API_FORMAT,
'status': 'FAILED',
'progress': 0,
'server': {
'id': 42,
"links": [{
@ -780,6 +793,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase):
'updated': self.NOW_API_FORMAT,
'created': self.NOW_API_FORMAT,
'status': 'ACTIVE',
'progress': 100,
"links": [{
"rel": "self",
"href": "http://localhost/v1.1/images/129",
@ -1001,7 +1015,8 @@ class ImageControllerWithGlanceServiceTest(test.TestCase):
image_meta = json.loads(res.body)['image']
expected = {'id': 123, 'name': 'public image',
'updated': self.NOW_API_FORMAT,
'created': self.NOW_API_FORMAT, 'status': 'ACTIVE'}
'created': self.NOW_API_FORMAT, 'status': 'ACTIVE',
'progress': 100}
self.assertDictMatch(image_meta, expected)
def test_get_image_non_existent(self):
@ -1049,6 +1064,16 @@ class ImageControllerWithGlanceServiceTest(test.TestCase):
response = req.get_response(fakes.wsgi_app())
self.assertEqual(400, response.status_int)
def test_create_image_snapshots_disabled(self):
self.flags(allow_instance_snapshots=False)
body = dict(image=dict(serverId='123', name='Snapshot 1'))
req = webob.Request.blank('/v1.0/images')
req.method = 'POST'
req.body = json.dumps(body)
req.headers["content-type"] = "application/json"
response = req.get_response(fakes.wsgi_app())
self.assertEqual(400, response.status_int)
@classmethod
def _make_image_fixtures(cls):
image_id = 123

View File

@ -458,6 +458,7 @@ class ServerActionsTestV11(test.TestCase):
self.service.delete_all()
self.sent_to_glance = {}
fakes.stub_out_glance_add_image(self.stubs, self.sent_to_glance)
self.flags(allow_instance_snapshots=True)
def tearDown(self):
self.stubs.UnsetAll()
@ -475,6 +476,21 @@ class ServerActionsTestV11(test.TestCase):
self.assertEqual(mock_method.instance_id, '1')
self.assertEqual(mock_method.password, '1234pass')
def test_server_change_password_xml(self):
mock_method = MockSetAdminPassword()
self.stubs.Set(nova.compute.api.API, 'set_admin_password', mock_method)
req = webob.Request.blank('/v1.1/servers/1/action')
req.method = 'POST'
req.content_type = "application/xml"
req.body = """<?xml version="1.0" encoding="UTF-8"?>
<changePassword
xmlns="http://docs.openstack.org/compute/api/v1.1"
adminPass="1234pass"/>"""
res = req.get_response(fakes.wsgi_app())
self.assertEqual(res.status_int, 202)
self.assertEqual(mock_method.instance_id, '1')
self.assertEqual(mock_method.password, '1234pass')
def test_server_change_password_not_a_string(self):
body = {'changePassword': {'adminPass': 1234}}
req = webob.Request.blank('/v1.1/servers/1/action')
@ -511,6 +527,42 @@ class ServerActionsTestV11(test.TestCase):
res = req.get_response(fakes.wsgi_app())
self.assertEqual(res.status_int, 400)
def test_server_reboot_hard(self):
body = dict(reboot=dict(type="HARD"))
req = webob.Request.blank('/v1.1/servers/1/action')
req.method = 'POST'
req.content_type = 'application/json'
req.body = json.dumps(body)
res = req.get_response(fakes.wsgi_app())
self.assertEqual(res.status_int, 202)
def test_server_reboot_soft(self):
body = dict(reboot=dict(type="SOFT"))
req = webob.Request.blank('/v1.1/servers/1/action')
req.method = 'POST'
req.content_type = 'application/json'
req.body = json.dumps(body)
res = req.get_response(fakes.wsgi_app())
self.assertEqual(res.status_int, 202)
def test_server_reboot_incorrect_type(self):
body = dict(reboot=dict(type="NOT_A_TYPE"))
req = webob.Request.blank('/v1.1/servers/1/action')
req.method = 'POST'
req.content_type = 'application/json'
req.body = json.dumps(body)
res = req.get_response(fakes.wsgi_app())
self.assertEqual(res.status_int, 400)
def test_server_reboot_missing_type(self):
body = dict(reboot=dict())
req = webob.Request.blank('/v1.1/servers/1/action')
req.method = 'POST'
req.content_type = 'application/json'
req.body = json.dumps(body)
res = req.get_response(fakes.wsgi_app())
self.assertEqual(res.status_int, 400)
def test_server_rebuild_accepted_minimum(self):
body = {
"rebuild": {
@ -653,6 +705,62 @@ class ServerActionsTestV11(test.TestCase):
self.assertEqual(res.status_int, 202)
self.assertEqual(self.resize_called, True)
def test_resize_server_no_flavor(self):
req = webob.Request.blank('/v1.1/servers/1/action')
req.content_type = 'application/json'
req.method = 'POST'
body_dict = dict(resize=dict())
req.body = json.dumps(body_dict)
res = req.get_response(fakes.wsgi_app())
self.assertEqual(res.status_int, 400)
def test_resize_server_no_flavor_ref(self):
req = webob.Request.blank('/v1.1/servers/1/action')
req.content_type = 'application/json'
req.method = 'POST'
body_dict = dict(resize=dict(flavorRef=None))
req.body = json.dumps(body_dict)
res = req.get_response(fakes.wsgi_app())
self.assertEqual(res.status_int, 400)
def test_confirm_resize_server(self):
req = webob.Request.blank('/v1.1/servers/1/action')
req.content_type = 'application/json'
req.method = 'POST'
body_dict = dict(confirmResize=None)
req.body = json.dumps(body_dict)
self.confirm_resize_called = False
def cr_mock(*args):
self.confirm_resize_called = True
self.stubs.Set(nova.compute.api.API, 'confirm_resize', cr_mock)
res = req.get_response(fakes.wsgi_app())
self.assertEqual(res.status_int, 204)
self.assertEqual(self.confirm_resize_called, True)
def test_revert_resize_server(self):
req = webob.Request.blank('/v1.1/servers/1/action')
req.content_type = 'application/json'
req.method = 'POST'
body_dict = dict(revertResize=None)
req.body = json.dumps(body_dict)
self.revert_resize_called = False
def revert_mock(*args):
self.revert_resize_called = True
self.stubs.Set(nova.compute.api.API, 'revert_resize', revert_mock)
res = req.get_response(fakes.wsgi_app())
self.assertEqual(res.status_int, 202)
self.assertEqual(self.revert_resize_called, True)
def test_create_image(self):
body = {
'createImage': {
@ -668,6 +776,23 @@ class ServerActionsTestV11(test.TestCase):
location = response.headers['Location']
self.assertEqual('http://localhost/v1.1/images/123', location)
def test_create_image_snapshots_disabled(self):
"""Don't permit a snapshot if the allow_instance_snapshots flag is
False
"""
self.flags(allow_instance_snapshots=False)
body = {
'createImage': {
'name': 'Snapshot 1',
},
}
req = webob.Request.blank('/v1.1/servers/1/action')
req.method = 'POST'
req.body = json.dumps(body)
req.headers["content-type"] = "application/json"
response = req.get_response(fakes.wsgi_app())
self.assertEqual(400, response.status_int)
def test_create_image_with_metadata(self):
body = {
'createImage': {
@ -730,10 +855,10 @@ class ServerActionsTestV11(test.TestCase):
self.assertTrue(response.headers['Location'])
class TestServerActionXMLDeserializer(test.TestCase):
class TestServerActionXMLDeserializerV11(test.TestCase):
def setUp(self):
self.deserializer = create_instance_helper.ServerXMLDeserializer()
self.deserializer = create_instance_helper.ServerXMLDeserializerV11()
def tearDown(self):
pass
@ -746,7 +871,6 @@ class TestServerActionXMLDeserializer(test.TestCase):
expected = {
"createImage": {
"name": "new-server-test",
"metadata": {},
},
}
self.assertEquals(request['body'], expected)
@ -767,3 +891,147 @@ class TestServerActionXMLDeserializer(test.TestCase):
},
}
self.assertEquals(request['body'], expected)
def test_change_pass(self):
serial_request = """<?xml version="1.0" encoding="UTF-8"?>
<changePassword
xmlns="http://docs.openstack.org/compute/api/v1.1"
adminPass="1234pass"/> """
request = self.deserializer.deserialize(serial_request, 'action')
expected = {
"changePassword": {
"adminPass": "1234pass",
},
}
self.assertEquals(request['body'], expected)
def test_change_pass_no_pass(self):
serial_request = """<?xml version="1.0" encoding="UTF-8"?>
<changePassword
xmlns="http://docs.openstack.org/compute/api/v1.1"/> """
self.assertRaises(AttributeError,
self.deserializer.deserialize,
serial_request,
'action')
def test_reboot(self):
serial_request = """<?xml version="1.0" encoding="UTF-8"?>
<reboot
xmlns="http://docs.openstack.org/compute/api/v1.1"
type="HARD"/>"""
request = self.deserializer.deserialize(serial_request, 'action')
expected = {
"reboot": {
"type": "HARD",
},
}
self.assertEquals(request['body'], expected)
def test_reboot_no_type(self):
serial_request = """<?xml version="1.0" encoding="UTF-8"?>
<reboot
xmlns="http://docs.openstack.org/compute/api/v1.1"/>"""
self.assertRaises(AttributeError,
self.deserializer.deserialize,
serial_request,
'action')
def test_resize(self):
serial_request = """<?xml version="1.0" encoding="UTF-8"?>
<resize
xmlns="http://docs.openstack.org/compute/api/v1.1"
flavorRef="http://localhost/flavors/3"/>"""
request = self.deserializer.deserialize(serial_request, 'action')
expected = {
"resize": {
"flavorRef": "http://localhost/flavors/3"
},
}
self.assertEquals(request['body'], expected)
def test_resize_no_flavor_ref(self):
serial_request = """<?xml version="1.0" encoding="UTF-8"?>
<resize
xmlns="http://docs.openstack.org/compute/api/v1.1"/>"""
self.assertRaises(AttributeError,
self.deserializer.deserialize,
serial_request,
'action')
def test_confirm_resize(self):
serial_request = """<?xml version="1.0" encoding="UTF-8"?>
<confirmResize
xmlns="http://docs.openstack.org/compute/api/v1.1"/>"""
request = self.deserializer.deserialize(serial_request, 'action')
expected = {
"confirmResize": None,
}
self.assertEquals(request['body'], expected)
def test_revert_resize(self):
serial_request = """<?xml version="1.0" encoding="UTF-8"?>
<revertResize
xmlns="http://docs.openstack.org/compute/api/v1.1"/>"""
request = self.deserializer.deserialize(serial_request, 'action')
expected = {
"revertResize": None,
}
self.assertEquals(request['body'], expected)
def test_rebuild(self):
serial_request = """<?xml version="1.0" encoding="UTF-8"?>
<rebuild
xmlns="http://docs.openstack.org/compute/api/v1.1"
name="new-server-test"
imageRef="http://localhost/images/1">
<metadata>
<meta key="My Server Name">Apache1</meta>
</metadata>
<personality>
<file path="/etc/banner.txt">Mg==</file>
</personality>
</rebuild>"""
request = self.deserializer.deserialize(serial_request, 'action')
expected = {
"rebuild": {
"name": "new-server-test",
"imageRef": "http://localhost/images/1",
"metadata": {
"My Server Name": "Apache1",
},
"personality": [
{"path": "/etc/banner.txt", "contents": "Mg=="},
],
},
}
self.assertDictMatch(request['body'], expected)
def test_rebuild_minimum(self):
serial_request = """<?xml version="1.0" encoding="UTF-8"?>
<rebuild
xmlns="http://docs.openstack.org/compute/api/v1.1"
imageRef="http://localhost/images/1"/>"""
request = self.deserializer.deserialize(serial_request, 'action')
expected = {
"rebuild": {
"imageRef": "http://localhost/images/1",
},
}
self.assertDictMatch(request['body'], expected)
def test_rebuild_no_imageRef(self):
serial_request = """<?xml version="1.0" encoding="UTF-8"?>
<rebuild
xmlns="http://docs.openstack.org/compute/api/v1.1"
name="new-server-test">
<metadata>
<meta key="My Server Name">Apache1</meta>
</metadata>
<personality>
<file path="/etc/banner.txt">Mg==</file>
</personality>
</rebuild>"""
self.assertRaises(AttributeError,
self.deserializer.deserialize,
serial_request,
'action')

View File

@ -2177,7 +2177,7 @@ class TestServerCreateRequestXMLDeserializerV11(test.TestCase):
def setUp(self):
super(TestServerCreateRequestXMLDeserializerV11, self).setUp()
self.deserializer = create_instance_helper.ServerXMLDeserializer()
self.deserializer = create_instance_helper.ServerXMLDeserializerV11()
def test_minimal_request(self):
serial_request = """
@ -2191,8 +2191,6 @@ class TestServerCreateRequestXMLDeserializerV11(test.TestCase):
"name": "new-server-test",
"imageRef": "1",
"flavorRef": "2",
"metadata": {},
"personality": [],
},
}
self.assertEquals(request['body'], expected)
@ -2211,8 +2209,6 @@ class TestServerCreateRequestXMLDeserializerV11(test.TestCase):
"imageRef": "1",
"flavorRef": "2",
"adminPass": "1234",
"metadata": {},
"personality": [],
},
}
self.assertEquals(request['body'], expected)
@ -2229,8 +2225,6 @@ class TestServerCreateRequestXMLDeserializerV11(test.TestCase):
"name": "new-server-test",
"imageRef": "http://localhost:8774/v1.1/images/2",
"flavorRef": "3",
"metadata": {},
"personality": [],
},
}
self.assertEquals(request['body'], expected)
@ -2247,8 +2241,6 @@ class TestServerCreateRequestXMLDeserializerV11(test.TestCase):
"name": "new-server-test",
"imageRef": "1",
"flavorRef": "http://localhost:8774/v1.1/flavors/3",
"metadata": {},
"personality": [],
},
}
self.assertEquals(request['body'], expected)
@ -2292,7 +2284,6 @@ class TestServerCreateRequestXMLDeserializerV11(test.TestCase):
"imageRef": "1",
"flavorRef": "2",
"metadata": {"one": "two", "open": "snack"},
"personality": [],
},
}
self.assertEquals(request['body'], expected)
@ -2314,7 +2305,6 @@ class TestServerCreateRequestXMLDeserializerV11(test.TestCase):
"name": "new-server-test",
"imageRef": "1",
"flavorRef": "2",
"metadata": {},
"personality": [
{"path": "/etc/banner.txt", "contents": "MQ=="},
{"path": "/etc/hosts", "contents": "Mg=="},

View File

@ -21,9 +21,11 @@ Tests For Scheduler
import datetime
import mox
import novaclient.exceptions
import stubout
from novaclient import v1_1 as novaclient
from novaclient import exceptions as novaclient_exceptions
from mox import IgnoreArg
from nova import context
from nova import db
@ -1036,10 +1038,10 @@ class FakeServerCollection(object):
class FakeEmptyServerCollection(object):
def get(self, f):
raise novaclient.NotFound(1)
raise novaclient_exceptions.NotFound(1)
def find(self, name):
raise novaclient.NotFound(2)
raise novaclient_exceptions.NotFound(2)
class FakeNovaClient(object):
@ -1085,7 +1087,7 @@ class FakeZonesProxy(object):
raise Exception('testing')
class FakeNovaClientOpenStack(object):
class FakeNovaClientZones(object):
def __init__(self, *args, **kwargs):
self.zones = FakeZonesProxy()
@ -1098,7 +1100,7 @@ class CallZoneMethodTest(test.TestCase):
super(CallZoneMethodTest, self).setUp()
self.stubs = stubout.StubOutForTesting()
self.stubs.Set(db, 'zone_get_all', zone_get_all)
self.stubs.Set(novaclient, 'OpenStack', FakeNovaClientOpenStack)
self.stubs.Set(novaclient, 'Client', FakeNovaClientZones)
def tearDown(self):
self.stubs.UnsetAll()

View File

@ -21,7 +21,9 @@ import json
import nova.db
from nova import exception
from nova import rpc
from nova import test
from nova.compute import api as compute_api
from nova.scheduler import driver
from nova.scheduler import zone_aware_scheduler
from nova.scheduler import zone_manager
@ -114,7 +116,7 @@ def fake_provision_resource_from_blob(context, item, instance_id,
def fake_decrypt_blob_returns_local_info(blob):
return {'foo': True} # values aren't important.
return {'hostname': 'foooooo'} # values aren't important.
def fake_decrypt_blob_returns_child_info(blob):
@ -283,14 +285,29 @@ class ZoneAwareSchedulerTestCase(test.TestCase):
global was_called
sched = FakeZoneAwareScheduler()
was_called = False
def fake_create_db_entry_for_new_instance(self, context,
image, base_options, security_group,
block_device_mapping, num=1):
global was_called
was_called = True
# return fake instances
return {'id': 1, 'uuid': 'f874093c-7b17-49c0-89c3-22a5348497f9'}
def fake_rpc_cast(*args, **kwargs):
pass
self.stubs.Set(sched, '_decrypt_blob',
fake_decrypt_blob_returns_local_info)
self.stubs.Set(sched, '_provision_resource_locally',
fake_provision_resource_locally)
self.stubs.Set(compute_api.API,
'create_db_entry_for_new_instance',
fake_create_db_entry_for_new_instance)
self.stubs.Set(rpc, 'cast', fake_rpc_cast)
request_spec = {'blob': "Non-None blob data"}
build_plan_item = {'blob': "Non-None blob data"}
request_spec = {'image': {}, 'instance_properties': {}}
sched._provision_resource_from_blob(None, request_spec, 1,
sched._provision_resource_from_blob(None, build_plan_item, 1,
request_spec, {})
self.assertTrue(was_called)

View File

@ -48,6 +48,10 @@ def stub_set_host_enabled(context, host, enabled):
return status
def stub_host_power_action(context, host, action):
return action
class FakeRequest(object):
environ = {"nova.context": context.get_admin_context()}
@ -62,6 +66,8 @@ class HostTestCase(test.TestCase):
self.stubs.Set(scheduler_api, 'get_host_list', stub_get_host_list)
self.stubs.Set(self.controller.compute_api, 'set_host_enabled',
stub_set_host_enabled)
self.stubs.Set(self.controller.compute_api, 'host_power_action',
stub_host_power_action)
def test_list_hosts(self):
"""Verify that the compute hosts are returned."""
@ -87,6 +93,18 @@ class HostTestCase(test.TestCase):
result_c2 = self.controller.update(self.req, "host_c2", body=en_body)
self.assertEqual(result_c2["status"], "disabled")
def test_host_startup(self):
result = self.controller.startup(self.req, "host_c1")
self.assertEqual(result["power_action"], "startup")
def test_host_shutdown(self):
result = self.controller.shutdown(self.req, "host_c1")
self.assertEqual(result["power_action"], "shutdown")
def test_host_reboot(self):
result = self.controller.reboot(self.req, "host_c1")
self.assertEqual(result["power_action"], "reboot")
def test_bad_status_value(self):
bad_body = {"status": "bad"}
self.assertRaises(webob.exc.HTTPBadRequest, self.controller.update,

134
nova/tests/test_image.py Normal file
View File

@ -0,0 +1,134 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2011 OpenStack LLC
# Author: Soren Hansen
#
# 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 datetime
from nova import context
from nova import exception
from nova import test
import nova.image
class _ImageTestCase(test.TestCase):
def setUp(self):
super(_ImageTestCase, self).setUp()
self.context = context.get_admin_context()
def test_index(self):
res = self.image_service.index(self.context)
for image in res:
self.assertEquals(set(image.keys()), set(['id', 'name']))
def test_detail(self):
res = self.image_service.detail(self.context)
for image in res:
keys = set(image.keys())
self.assertEquals(keys, set(['id', 'name', 'created_at',
'updated_at', 'deleted_at', 'deleted',
'status', 'is_public', 'properties']))
self.assertTrue(isinstance(image['created_at'], datetime.datetime))
self.assertTrue(isinstance(image['updated_at'], datetime.datetime))
if not (isinstance(image['deleted_at'], datetime.datetime) or
image['deleted_at'] is None):
self.fail('image\'s "deleted_at" attribute was neither a '
'datetime object nor None')
def check_is_bool(image, key):
val = image.get('deleted')
if not isinstance(val, bool):
self.fail('image\'s "%s" attribute wasn\'t '
'a bool: %r' % (key, val))
check_is_bool(image, 'deleted')
check_is_bool(image, 'is_public')
def test_index_and_detail_have_same_results(self):
index = self.image_service.index(self.context)
detail = self.image_service.detail(self.context)
index_set = set([(i['id'], i['name']) for i in index])
detail_set = set([(i['id'], i['name']) for i in detail])
self.assertEqual(index_set, detail_set)
def test_show_raises_imagenotfound_for_invalid_id(self):
self.assertRaises(exception.ImageNotFound,
self.image_service.show,
self.context,
'this image does not exist')
def test_show_by_name(self):
self.assertRaises(exception.ImageNotFound,
self.image_service.show_by_name,
self.context,
'this image does not exist')
def test_create_adds_id(self):
index = self.image_service.index(self.context)
image_count = len(index)
self.image_service.create(self.context, {})
index = self.image_service.index(self.context)
self.assertEquals(len(index), image_count + 1)
self.assertTrue(index[0]['id'])
def test_create_keeps_id(self):
self.image_service.create(self.context, {'id': '34'})
self.image_service.show(self.context, '34')
def test_create_rejects_duplicate_ids(self):
self.image_service.create(self.context, {'id': '34'})
self.assertRaises(exception.Duplicate,
self.image_service.create,
self.context,
{'id': '34'})
# Make sure there's still one left
self.image_service.show(self.context, '34')
def test_update(self):
self.image_service.create(self.context,
{'id': '34', 'foo': 'bar'})
self.image_service.update(self.context, '34',
{'id': '34', 'foo': 'baz'})
img = self.image_service.show(self.context, '34')
self.assertEquals(img['foo'], 'baz')
def test_delete(self):
self.image_service.create(self.context, {'id': '34', 'foo': 'bar'})
self.image_service.delete(self.context, '34')
self.assertRaises(exception.NotFound,
self.image_service.show,
self.context,
'34')
def test_delete_all(self):
self.image_service.create(self.context, {'id': '32', 'foo': 'bar'})
self.image_service.create(self.context, {'id': '33', 'foo': 'bar'})
self.image_service.create(self.context, {'id': '34', 'foo': 'bar'})
self.image_service.delete_all()
index = self.image_service.index(self.context)
self.assertEquals(len(index), 0)
class FakeImageTestCase(_ImageTestCase):
def setUp(self):
super(FakeImageTestCase, self).setUp()
self.image_service = nova.image.fake.FakeImageService()

View File

@ -0,0 +1,82 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2011 OpenStack LLC
# Copyright 2011 Ilya Alekseyev
#
# 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 os
import sys
TOPDIR = os.path.normpath(os.path.join(
os.path.dirname(os.path.abspath(__file__)),
os.pardir,
os.pardir))
NOVA_MANAGE_PATH = os.path.join(TOPDIR, 'bin', 'nova-manage')
sys.dont_write_bytecode = True
import imp
nova_manage = imp.load_source('nova_manage.py', NOVA_MANAGE_PATH)
sys.dont_write_bytecode = False
import netaddr
from nova import context
from nova import db
from nova import flags
from nova import test
FLAGS = flags.FLAGS
class FixedIpCommandsTestCase(test.TestCase):
def setUp(self):
super(FixedIpCommandsTestCase, self).setUp()
cidr = '10.0.0.0/24'
net = netaddr.IPNetwork(cidr)
net_info = {'bridge': 'fakebr',
'bridge_interface': 'fakeeth',
'dns': FLAGS.flat_network_dns,
'cidr': cidr,
'netmask': str(net.netmask),
'gateway': str(net[1]),
'broadcast': str(net.broadcast),
'dhcp_start': str(net[2])}
self.network = db.network_create_safe(context.get_admin_context(),
net_info)
num_ips = len(net)
for index in range(num_ips):
address = str(net[index])
reserved = (index == 1 or index == 2)
db.fixed_ip_create(context.get_admin_context(),
{'network_id': self.network['id'],
'address': address,
'reserved': reserved})
self.commands = nova_manage.FixedIpCommands()
def tearDown(self):
db.network_delete_safe(context.get_admin_context(), self.network['id'])
super(FixedIpCommandsTestCase, self).tearDown()
def test_reserve(self):
self.commands.reserve('10.0.0.100')
address = db.fixed_ip_get_by_address(context.get_admin_context(),
'10.0.0.100')
self.assertEqual(address['reserved'], True)
def test_unreserve(self):
db.fixed_ip_update(context.get_admin_context(), '10.0.0.100',
{'reserved': True})
self.commands.unreserve('10.0.0.100')
address = db.fixed_ip_get_by_address(context.get_admin_context(),
'10.0.0.100')
self.assertEqual(address['reserved'], False)

View File

@ -0,0 +1,47 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2010 United States Government as represented by the
# Administrator of the National Aeronautics and Space Administration.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from nova import test
class ExampleSkipTestCase(test.TestCase):
test_counter = 0
@test.skip_test("Example usage of @test.skip_test()")
def test_skip_test_example(self):
self.fail("skip_test failed to work properly.")
@test.skip_if(True, "Example usage of @test.skip_if()")
def test_skip_if_example(self):
self.fail("skip_if failed to work properly.")
@test.skip_unless(False, "Example usage of @test.skip_unless()")
def test_skip_unless_example(self):
self.fail("skip_unless failed to work properly.")
@test.skip_if(False, "This test case should never be skipped.")
def test_001_increase_test_counter(self):
ExampleSkipTestCase.test_counter += 1
@test.skip_unless(True, "This test case should never be skipped.")
def test_002_increase_test_counter(self):
ExampleSkipTestCase.test_counter += 1
def test_003_verify_test_counter(self):
self.assertEquals(ExampleSkipTestCase.test_counter, 2,
"Tests were not skipped appropriately")

View File

@ -767,6 +767,52 @@ class XenAPIMigrateInstance(test.TestCase):
conn = xenapi_conn.get_connection(False)
conn.migrate_disk_and_power_off(instance, '127.0.0.1')
def test_revert_migrate(self):
instance = db.instance_create(self.context, self.values)
self.called = False
self.fake_vm_start_called = False
self.fake_revert_migration_called = False
def fake_vm_start(*args, **kwargs):
self.fake_vm_start_called = True
def fake_vdi_resize(*args, **kwargs):
self.called = True
def fake_revert_migration(*args, **kwargs):
self.fake_revert_migration_called = True
self.stubs.Set(stubs.FakeSessionForMigrationTests,
"VDI_resize_online", fake_vdi_resize)
self.stubs.Set(vmops.VMOps, '_start', fake_vm_start)
self.stubs.Set(vmops.VMOps, 'revert_migration', fake_revert_migration)
stubs.stubout_session(self.stubs, stubs.FakeSessionForMigrationTests)
stubs.stubout_loopingcall_start(self.stubs)
conn = xenapi_conn.get_connection(False)
network_info = [({'bridge': 'fa0', 'id': 0, 'injected': False},
{'broadcast': '192.168.0.255',
'dns': ['192.168.0.1'],
'gateway': '192.168.0.1',
'gateway6': 'dead:beef::1',
'ip6s': [{'enabled': '1',
'ip': 'dead:beef::dcad:beff:feef:0',
'netmask': '64'}],
'ips': [{'enabled': '1',
'ip': '192.168.0.100',
'netmask': '255.255.255.0'}],
'label': 'fake',
'mac': 'DE:AD:BE:EF:00:00',
'rxtx_cap': 3})]
conn.finish_migration(self.context, instance,
dict(base_copy='hurr', cow='durr'),
network_info, resize_instance=True)
self.assertEqual(self.called, True)
self.assertEqual(self.fake_vm_start_called, True)
conn.revert_migration(instance)
self.assertEqual(self.fake_revert_migration_called, True)
def test_finish_migrate(self):
instance = db.instance_create(self.context, self.values)
self.called = False

View File

@ -18,7 +18,6 @@ Tests For ZoneManager
import datetime
import mox
import novaclient
from nova import context
from nova import db

View File

@ -126,6 +126,22 @@ def fetchfile(url, target):
def execute(*cmd, **kwargs):
"""
Helper method to execute command with optional retry.
:cmd Passed to subprocess.Popen.
:process_input Send to opened process.
:addl_env Added to the processes env.
:check_exit_code Defaults to 0. Raise exception.ProcessExecutionError
unless program exits with this code.
:delay_on_retry True | False. Defaults to True. If set to True, wait a
short amount of time before retrying.
:attempts How many times to retry cmd.
:raises exception.Error on receiving unknown arguments
:raises exception.ProcessExecutionError
"""
process_input = kwargs.pop('process_input', None)
addl_env = kwargs.pop('addl_env', None)
check_exit_code = kwargs.pop('check_exit_code', 0)
@ -790,7 +806,7 @@ def parse_server_string(server_str):
(address, port) = server_str.split(':')
return (address, port)
except:
except Exception:
LOG.debug(_('Invalid server_string: %s' % server_str))
return ('', '')

View File

@ -282,6 +282,10 @@ class ComputeDriver(object):
# TODO(Vek): Need to pass context in for access to auth_token
raise NotImplementedError()
def host_power_action(self, host, action):
"""Reboots, shuts down or powers up the host."""
raise NotImplementedError()
def set_host_enabled(self, host, enabled):
"""Sets the specified host's ability to accept new instances."""
# TODO(Vek): Need to pass context in for access to auth_token

View File

@ -512,6 +512,10 @@ class FakeConnection(driver.ComputeDriver):
"""Return fake Host Status of ram, disk, network."""
return self.host_status
def host_power_action(self, host, action):
"""Reboots, shuts down or powers up the host."""
pass
def set_host_enabled(self, host, enabled):
"""Sets the specified host's ability to accept new instances."""
pass

View File

@ -499,6 +499,10 @@ class HyperVConnection(driver.ComputeDriver):
"""See xenapi_conn.py implementation."""
pass
def host_power_action(self, host, action):
"""Reboots, shuts down or powers up the host."""
pass
def set_host_enabled(self, host, enabled):
"""Sets the specified host's ability to accept new instances."""
pass

View File

@ -349,7 +349,7 @@ class LibvirtConnection(driver.ComputeDriver):
"""Returns the xml for the disk mounted at device"""
try:
doc = libxml2.parseDoc(xml)
except:
except Exception:
return None
ctx = doc.xpathNewContext()
try:
@ -392,9 +392,7 @@ class LibvirtConnection(driver.ComputeDriver):
nova.image.get_image_service(image_href)
snapshot = snapshot_image_service.show(context, snapshot_image_id)
metadata = {'disk_format': base['disk_format'],
'container_format': base['container_format'],
'is_public': False,
metadata = {'is_public': False,
'status': 'active',
'name': snapshot['name'],
'properties': {
@ -409,6 +407,12 @@ class LibvirtConnection(driver.ComputeDriver):
arch = base['properties']['architecture']
metadata['properties']['architecture'] = arch
if 'disk_format' in base:
metadata['disk_format'] = base['disk_format']
if 'container_format' in base:
metadata['container_format'] = base['container_format']
# Make the snapshot
snapshot_name = uuid.uuid4().hex
snapshot_xml = """
@ -880,7 +884,7 @@ class LibvirtConnection(driver.ComputeDriver):
'netmask': netmask,
'gateway': mapping['gateway'],
'broadcast': mapping['broadcast'],
'dns': mapping['dns'],
'dns': ' '.join(mapping['dns']),
'address_v6': address_v6,
'gateway6': gateway_v6,
'netmask_v6': netmask_v6}
@ -1077,7 +1081,7 @@ class LibvirtConnection(driver.ComputeDriver):
try:
doc = libxml2.parseDoc(xml)
except:
except Exception:
return []
ctx = doc.xpathNewContext()
@ -1118,7 +1122,7 @@ class LibvirtConnection(driver.ComputeDriver):
try:
doc = libxml2.parseDoc(xml)
except:
except Exception:
return []
ctx = doc.xpathNewContext()
@ -1558,6 +1562,10 @@ class LibvirtConnection(driver.ComputeDriver):
"""See xenapi_conn.py implementation."""
pass
def host_power_action(self, host, action):
"""Reboots, shuts down or powers up the host."""
pass
def set_host_enabled(self, host, enabled):
"""Sets the specified host's ability to accept new instances."""
pass

View File

@ -25,6 +25,7 @@ from nova.network import linux_net
from nova.virt.libvirt import netutils
from nova import utils
from nova.virt.vif import VIFDriver
from nova import exception
LOG = logging.getLogger('nova.virt.libvirt.vif')
@ -128,7 +129,7 @@ class LibvirtOpenVswitchDriver(VIFDriver):
utils.execute('sudo', 'ovs-vsctl', 'del-port',
network['bridge'], dev)
utils.execute('sudo', 'ip', 'link', 'delete', dev)
except:
except exception.ProcessExecutionError:
LOG.warning(_("Failed while unplugging vif of instance '%s'"),
instance['name'])
raise

View File

@ -191,6 +191,10 @@ class VMWareESXConnection(driver.ComputeDriver):
"""This method is supported only by libvirt."""
return
def host_power_action(self, host, action):
"""Reboots, shuts down or powers up the host."""
pass
def set_host_enabled(self, host, enabled):
"""Sets the specified host's ability to accept new instances."""
pass

View File

@ -1031,11 +1031,23 @@ class VMOps(object):
# TODO: implement this!
return 'http://fakeajaxconsole/fake_url'
def host_power_action(self, host, action):
"""Reboots or shuts down the host."""
args = {"action": json.dumps(action)}
methods = {"reboot": "host_reboot", "shutdown": "host_shutdown"}
json_resp = self._call_xenhost(methods[action], args)
resp = json.loads(json_resp)
return resp["power_action"]
def set_host_enabled(self, host, enabled):
"""Sets the specified host's ability to accept new instances."""
args = {"enabled": json.dumps(enabled)}
json_resp = self._call_xenhost("set_host_enabled", args)
resp = json.loads(json_resp)
xenapi_resp = self._call_xenhost("set_host_enabled", args)
try:
resp = json.loads(xenapi_resp)
except TypeError as e:
# Already logged; return the message
return xenapi_resp.details[-1]
return resp["status"]
def _call_xenhost(self, method, arg_dict):
@ -1051,7 +1063,7 @@ class VMOps(object):
#args={"params": arg_dict})
ret = self._session.wait_for_task(task, task_id)
except self.XenAPI.Failure as e:
ret = None
ret = e
LOG.error(_("The call to %(method)s returned an error: %(e)s.")
% locals())
return ret

View File

@ -191,7 +191,7 @@ class XenAPIConnection(driver.ComputeDriver):
def revert_migration(self, instance):
"""Reverts a resize, powering back on the instance"""
self._vmops.revert_resize(instance)
self._vmops.revert_migration(instance)
def finish_migration(self, context, instance, disk_info, network_info,
resize_instance=False):
@ -332,6 +332,19 @@ class XenAPIConnection(driver.ComputeDriver):
True, run the update first."""
return self.HostState.get_host_stats(refresh=refresh)
def host_power_action(self, host, action):
"""The only valid values for 'action' on XenServer are 'reboot' or
'shutdown', even though the API also accepts 'startup'. As this is
not technically possible on XenServer, since the host is the same
physical machine as the hypervisor, if this is requested, we need to
raise an exception.
"""
if action in ("reboot", "shutdown"):
return self._vmops.host_power_action(host, action)
else:
msg = _("Host startup on XenServer is not supported.")
raise NotImplementedError(msg)
def set_host_enabled(self, host, enabled):
"""Sets the specified host's ability to accept new instances."""
return self._vmops.set_host_enabled(host, enabled)
@ -440,7 +453,7 @@ class XenAPISession(object):
params = None
try:
params = eval(exc.details[3])
except:
except Exception:
raise exc
raise self.XenAPI.Failure(params)
else:

View File

@ -60,7 +60,7 @@ class WebsocketVNCProxy(object):
break
d = base64.b64encode(d)
dest.send(d)
except:
except Exception:
source.close()
dest.close()
@ -72,7 +72,7 @@ class WebsocketVNCProxy(object):
break
d = base64.b64decode(d)
dest.sendall(d)
except:
except Exception:
source.close()
dest.close()

View File

@ -261,6 +261,9 @@ def _upload_tarball(staging_path, image_id, glance_host, glance_port, os_type,
if header.lower().startswith("x-image-meta-property-"):
headers[header.lower()] = value
# Toss body so connection state-machine is ready for next request/response
resp.read()
# NOTE(sirp): httplib under python2.4 won't accept a file-like object
# to request
conn.putrequest('PUT', '/v1/images/%s' % image_id)

View File

@ -39,6 +39,7 @@ import pluginlib_nova as pluginlib
pluginlib.configure_logging("xenhost")
host_data_pattern = re.compile(r"\s*(\S+) \([^\)]+\) *: ?(.*)")
config_file_path = "/usr/etc/xenhost.conf"
def jsonify(fnc):
@ -103,6 +104,104 @@ def set_host_enabled(self, arg_dict):
return {"status": status}
def _write_config_dict(dct):
conf_file = file(config_file_path, "w")
json.dump(dct, conf_file)
conf_file.close()
def _get_config_dict():
"""Returns a dict containing the key/values in the config file.
If the file doesn't exist, it is created, and an empty dict
is returned.
"""
try:
conf_file = file(config_file_path)
config_dct = json.load(conf_file)
conf_file.close()
except IOError:
# File doesn't exist
config_dct = {}
# Create the file
_write_config_dict(config_dct)
return config_dct
@jsonify
def get_config(self, arg_dict):
"""Return the value stored for the specified key, or None if no match."""
conf = _get_config_dict()
params = arg_dict["params"]
try:
dct = json.loads(params)
except Exception, e:
dct = params
key = dct["key"]
ret = conf.get(key)
if ret is None:
# Can't jsonify None
return "None"
return ret
@jsonify
def set_config(self, arg_dict):
"""Write the specified key/value pair, overwriting any existing value."""
conf = _get_config_dict()
params = arg_dict["params"]
try:
dct = json.loads(params)
except Exception, e:
dct = params
key = dct["key"]
val = dct["value"]
if val is None:
# Delete the key, if present
conf.pop(key, None)
else:
conf.update({key: val})
_write_config_dict(conf)
def _power_action(action):
host_uuid = _get_host_uuid()
# Host must be disabled first
result = _run_command("xe host-disable")
if result:
raise pluginlib.PluginError(result)
# All running VMs must be shutdown
result = _run_command("xe vm-shutdown --multiple power-state=running")
if result:
raise pluginlib.PluginError(result)
cmds = {"reboot": "xe host-reboot", "startup": "xe host-power-on",
"shutdown": "xe host-shutdown"}
result = _run_command(cmds[action])
# Should be empty string
if result:
raise pluginlib.PluginError(result)
return {"power_action": action}
@jsonify
def host_reboot(self, arg_dict):
"""Reboots the host."""
return _power_action("reboot")
@jsonify
def host_shutdown(self, arg_dict):
"""Reboots the host."""
return _power_action("shutdown")
@jsonify
def host_start(self, arg_dict):
"""Starts the host. Currently not feasible, since the host
runs on the same machine as Xen.
"""
return _power_action("startup")
@jsonify
def host_data(self, arg_dict):
"""Runs the commands on the xenstore host to return the current status
@ -115,6 +214,9 @@ def host_data(self, arg_dict):
# We have the raw dict of values. Extract those that we need,
# and convert the data types as needed.
ret_dict = cleanup(parsed_data)
# Add any config settings
config = _get_config_dict()
ret_dict.update(config)
return ret_dict
@ -217,4 +319,9 @@ def cleanup(dct):
if __name__ == "__main__":
XenAPIPlugin.dispatch(
{"host_data": host_data,
"set_host_enabled": set_host_enabled})
"set_host_enabled": set_host_enabled,
"host_shutdown": host_shutdown,
"host_reboot": host_reboot,
"host_start": host_start,
"get_config": get_config,
"set_config": set_config})

View File

@ -115,7 +115,8 @@ class SecurityGroupTests(base.UserSmokeTestCase):
if not instance_id:
return False
if instance_id != self.data['instance'].id:
raise Exception("Wrong instance id")
raise Exception("Wrong instance id. Expected: %s, Got: %s" %
(self.data['instance'].id, instance_id))
return True
def test_001_can_create_security_group(self):

View File

@ -10,7 +10,7 @@ carrot==0.10.5
eventlet
lockfile==0.8
lxml==2.3
python-novaclient==2.5.9
python-novaclient==2.6.0
python-daemon==1.5.5
python-gflags==1.3
redis==2.0.0