Merge from trunk.
This commit is contained in:
commit
72dc7939f4
@ -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):
|
||||
|
@ -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)
|
||||
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
||||
|
@ -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):
|
||||
|
@ -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
|
||||
|
30
nova/api/openstack/contrib/admin_only.py
Normal file
30
nova/api/openstack/contrib/admin_only.py
Normal 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
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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])
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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):
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
|
@ -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):
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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")
|
||||
|
||||
|
@ -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')
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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."""
|
||||
|
@ -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."""
|
||||
|
@ -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()))
|
||||
|
@ -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())
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
||||
|
35
nova/test.py
35
nova/test.py
@ -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
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
@ -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')
|
||||
|
@ -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=="},
|
||||
|
@ -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()
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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
134
nova/tests/test_image.py
Normal 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()
|
82
nova/tests/test_nova_manage.py
Normal file
82
nova/tests/test_nova_manage.py
Normal 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)
|
47
nova/tests/test_skip_examples.py
Normal file
47
nova/tests/test_skip_examples.py
Normal 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")
|
@ -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
|
||||
|
@ -18,7 +18,6 @@ Tests For ZoneManager
|
||||
|
||||
import datetime
|
||||
import mox
|
||||
import novaclient
|
||||
|
||||
from nova import context
|
||||
from nova import db
|
||||
|
@ -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 ('', '')
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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})
|
||||
|
@ -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):
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user