1) Merged from trunk
2) 'type' parameter in VMHelper.fetch_image converted in enum 3) Fixed pep8 errors 4) Passed unit tests
This commit is contained in:
21 changed files with 241 additions and 22 deletions
1
.mailmap
1
.mailmap
@@ -19,6 +19,7 @@
<mordred@inaugust.com> <mordred@hudson>
<paul@openstack.org> <pvoccio@castor.local>
<paul@openstack.org> <paul.voccio@rackspace.com>
<soren.hansen@rackspace.com> <soren@linux2go.dk>
<todd@ansolabs.com> <todd@lapex>
<todd@ansolabs.com> <todd@rubidine.com>
<vishvananda@gmail.com> <vishvananda@yahoo.com>
4
Authors
4
Authors
@@ -25,12 +25,12 @@ Monty Taylor <mordred@inaugust.com>
Paul Voccio <paul@openstack.org>
Rick Clark <rick@openstack.org>
Ryan Lucio <rlucio@internap.com>
Salvatore Orlando <salvatore.orlando@eu.citrix.com>
Sandy Walsh <sandy.walsh@rackspace.com>
Soren Hansen <soren.hansen@rackspace.com>
Soren Hansen <soren@linux2go.dk>
Todd Willey <todd@ansolabs.com>
Trey Morris <trey.morris@rackspace.com>
Vishvananda Ishaya <vishvananda@gmail.com>
Youcef Laribi <Youcef.Laribi@eu.citrix.com>
Zhixue Wu <Zhixue.Wu@citrix.com>
Salvatore Orlando <salvatore.orlando@eu.citrix.com>
@@ -194,6 +194,7 @@ class HostInfo(object):
class NovaAdminClient(object):
def __init__(
self,
clc_url=DEFAULT_CLC_URL,
@@ -168,6 +168,7 @@ class AdminController(object):
# FIXME(vish): these host commands don't work yet, perhaps some of the
# required data can be retrieved from service objects?
def describe_hosts(self, _context, **_kwargs):
"""Returns status info for all nodes. Includes:
* Disk Space
@@ -170,9 +170,16 @@ class APIRouter(wsgi.Router):
def __init__(self):
mapper = routes.Mapper()
server_members = {'action': 'POST'}
if FLAGS.allow_admin_api:
logging.debug("Including admin operations in API.")
server_members['pause'] = 'POST'
server_members['unpause'] = 'POST'
mapper.resource("server", "servers", controller=servers.Controller(),
collection={'detail': 'GET'},
member={'action': 'POST'})
member=server_members)
mapper.resource("backup_schedule", "backup_schedules",
controller=backup_schedules.Controller(),
@@ -186,10 +193,6 @@ class APIRouter(wsgi.Router):
mapper.resource("sharedipgroup", "sharedipgroups",
controller=sharedipgroups.Controller())
if FLAGS.allow_admin_api:
logging.debug("Including admin operations in API.")
# TODO: Place routes for admin operations here.
super(APIRouter, self).__init__(mapper)
@@ -24,6 +24,7 @@ import nova.image.service
class Controller(wsgi.Controller):
def __init__(self):
pass
@@ -15,6 +15,9 @@
# License for the specific language governing permissions and limitations
# under the License.
import logging
import traceback
from webob import exc
from nova import exception
@@ -27,6 +30,10 @@ from nova.compute import power_state
import nova.api.openstack
LOG = logging.getLogger('server')
LOG.setLevel(logging.DEBUG)
def _entity_list(entities):
""" Coerces a list of servers into proper dictionary format """
return dict(servers=entities)
@@ -166,3 +173,25 @@ class Controller(wsgi.Controller):
except:
return faults.Fault(exc.HTTPUnprocessableEntity())
return exc.HTTPAccepted()
def pause(self, req, id):
""" Permit Admins to Pause the server. """
ctxt = req.environ['nova.context']
try:
self.compute_api.pause(ctxt, id)
except:
readable = traceback.format_exc()
logging.error("Compute.api::pause %s", readable)
return faults.Fault(exc.HTTPUnprocessableEntity())
return exc.HTTPAccepted()
def unpause(self, req, id):
""" Permit Admins to Unpause the server. """
ctxt = req.environ['nova.context']
try:
self.compute_api.unpause(ctxt, id)
except:
readable = traceback.format_exc()
logging.error("Compute.api::unpause %s", readable)
return faults.Fault(exc.HTTPUnprocessableEntity())
return exc.HTTPAccepted()
@@ -77,7 +77,7 @@ class ComputeAPI(base.Base):
kernel_id = image.get('kernelId', None)
if ramdisk_id is None:
ramdisk_id = image.get('ramdiskId', None)
#Salvatore - No kernel and ramdisk for raw images
#No kernel and ramdisk for raw images
if kernel_id == str(FLAGS.null_kernel):
kernel_id = None
ramdisk_id = None
@@ -281,6 +281,24 @@ class ComputeAPI(base.Base):
{"method": "reboot_instance",
"args": {"instance_id": instance['id']}})
def pause(self, context, instance_id):
"""Pause the given instance."""
instance = self.db.instance_get_by_internal_id(context, instance_id)
host = instance['host']
rpc.cast(context,
self.db.queue_get_for(context, FLAGS.compute_topic, host),
{"method": "pause_instance",
"args": {"instance_id": instance['id']}})
def unpause(self, context, instance_id):
"""Unpause the given instance."""
instance = self.db.instance_get_by_internal_id(context, instance_id)
host = instance['host']
rpc.cast(context,
self.db.queue_get_for(context, FLAGS.compute_topic, host),
{"method": "unpause_instance",
"args": {"instance_id": instance['id']}})
def rescue(self, context, instance_id):
"""Rescue the given instance."""
instance = self.db.instance_get_by_internal_id(context, instance_id)
@@ -186,6 +186,47 @@ class ComputeManager(manager.Manager):
self.driver.unrescue(instance_ref)
self._update_state(context, instance_id)
@staticmethod
def _update_state_callback(self, context, instance_id, result):
"""Update instance state when async task completes."""
self._update_state(context, instance_id)
@exception.wrap_exception
def pause_instance(self, context, instance_id):
"""Pause an instance on this server."""
context = context.elevated()
instance_ref = self.db.instance_get(context, instance_id)
logging.debug('instance %s: pausing',
instance_ref['internal_id'])
self.db.instance_set_state(context,
instance_id,
power_state.NOSTATE,
'pausing')
self.driver.pause(instance_ref,
lambda result: self._update_state_callback(self,
context,
instance_id,
result))
@exception.wrap_exception
def unpause_instance(self, context, instance_id):
"""Unpause a paused instance on this server."""
context = context.elevated()
instance_ref = self.db.instance_get(context, instance_id)
logging.debug('instance %s: unpausing',
instance_ref['internal_id'])
self.db.instance_set_state(context,
instance_id,
power_state.NOSTATE,
'unpausing')
self.driver.unpause(instance_ref,
lambda result: self._update_state_callback(self,
context,
instance_id,
result))
@exception.wrap_exception
def get_console_output(self, context, instance_id):
"""Send the console output for an instance."""
@@ -528,6 +528,8 @@ def fixed_ip_update(context, address, values):
#TODO(gundlach): instance_create and volume_create are nearly identical
#and should be refactored. I expect there are other copy-and-paste
#functions between the two of them as well.
@require_context
def instance_create(context, values):
"""Create a new Instance record in the database.
@@ -913,6 +915,8 @@ def network_get(context, network_id, session=None):
# NOTE(vish): pylint complains because of the long method name, but
# it fits with the names of the rest of the methods
# pylint: disable-msg=C0103
@require_admin_context
def network_get_associated_fixed_ips(context, network_id):
session = get_session()
@@ -27,6 +27,7 @@ import traceback
class ProcessExecutionError(IOError):
def __init__(self, stdout=None, stderr=None, exit_code=None, cmd=None,
description=None):
if description is None:
@@ -39,11 +40,13 @@ class ProcessExecutionError(IOError):
class Error(Exception):
def __init__(self, message=None):
super(Error, self).__init__(message)
class ApiError(Error):
def __init__(self, message='Unknown', code='Unknown'):
self.message = message
self.code = code
@@ -56,11 +56,16 @@ def instance_address(context, instance_id):
def stub_instance(id, user_id=1):
return Instance(id=id + 123456, state=0, image_id=10, user_id=user_id,
return Instance(id=int(id) + 123456, state=0, image_id=10, user_id=user_id,
display_name='server%s' % id, internal_id=id)
def fake_compute_api(cls, req, id):
return True
class ServersTest(unittest.TestCase):
def setUp(self):
self.stubs = stubout.StubOutForTesting()
fakes.FakeAuthManager.auth_data = {}
@@ -82,9 +87,15 @@ class ServersTest(unittest.TestCase):
instance_address)
self.stubs.Set(nova.db.api, 'instance_get_floating_address',
instance_address)
self.stubs.Set(nova.compute.api.ComputeAPI, 'pause',
fake_compute_api)
self.stubs.Set(nova.compute.api.ComputeAPI, 'unpause',
fake_compute_api)
self.allow_admin = FLAGS.allow_admin_api
def tearDown(self):
self.stubs.UnsetAll()
FLAGS.allow_admin_api = self.allow_admin
def test_get_server_by_id(self):
req = webob.Request.blank('/v1.0/servers/1')
@@ -211,6 +222,30 @@ class ServersTest(unittest.TestCase):
self.assertEqual(s['imageId'], 10)
i += 1
def test_server_pause(self):
FLAGS.allow_admin_api = True
body = dict(server=dict(
name='server_test', imageId=2, flavorId=2, metadata={},
personality={}))
req = webob.Request.blank('/v1.0/servers/1/pause')
req.method = 'POST'
req.content_type = 'application/json'
req.body = json.dumps(body)
res = req.get_response(nova.api.API('os'))
self.assertEqual(res.status_int, 202)
def test_server_unpause(self):
FLAGS.allow_admin_api = True
body = dict(server=dict(
name='server_test', imageId=2, flavorId=2, metadata={},
personality={}))
req = webob.Request.blank('/v1.0/servers/1/unpause')
req.method = 'POST'
req.content_type = 'application/json'
req.body = json.dumps(body)
res = req.get_response(nova.api.API('os'))
self.assertEqual(res.status_int, 202)
def test_server_reboot(self):
body = dict(server=dict(
name='server_test', imageId=2, flavorId=2, metadata={},
@@ -127,6 +127,14 @@ class ComputeTestCase(test.TestCase):
self.assert_(instance_ref['launched_at'] < terminate)
self.assert_(instance_ref['deleted_at'] > terminate)
def test_pause(self):
"""Ensure instance can be paused"""
instance_id = self._create_instance()
self.compute.run_instance(self.context, instance_id)
self.compute.pause_instance(self.context, instance_id)
self.compute.unpause_instance(self.context, instance_id)
self.compute.terminate_instance(self.context, instance_id)
def test_reboot(self):
"""Ensure instance can be rebooted"""
instance_id = self._create_instance()
@@ -43,7 +43,7 @@ else:
FLAGS = flags.FLAGS
flags.DEFINE_string('logdir', None, 'directory to keep log files in '
flags.DEFINE_string('logdir', None, 'directory to keep log files in '
'(will be prepended to $logfile)')
@@ -130,6 +130,18 @@ class FakeConnection(object):
"""
pass
def pause(self, instance, callback):
"""
Pause the specified instance.
"""
pass
def unpause(self, instance, callback):
"""
Unpause the specified instance.
"""
pass
def destroy(self, instance):
"""
Destroy (shutdown and delete) the specified instance.
@@ -243,5 +255,6 @@ class FakeConnection(object):
class FakeInstance(object):
def __init__(self):
self._state = power_state.NOSTATE
@@ -97,6 +97,7 @@ def get_connection(read_only):
class LibvirtConnection(object):
def __init__(self, read_only):
self.libvirt_uri = self.get_uri()
@@ -260,6 +261,14 @@ class LibvirtConnection(object):
timer.f = _wait_for_reboot
return timer.start(interval=0.5, now=True)
@exception.wrap_exception
def pause(self, instance, callback):
raise exception.APIError("pause not supported for libvirt.")
@exception.wrap_exception
def unpause(self, instance, callback):
raise exception.APIError("unpause not supported for libvirt.")
@exception.wrap_exception
def rescue(self, instance):
self.destroy(instance, False)
@@ -25,6 +25,7 @@ class NetworkHelper():
"""
The class that wraps the helper methods together.
"""
def __init__(self):
return
@@ -43,10 +43,24 @@ XENAPI_POWER_STATE = {
XenAPI = None
class ImageType:
"""
Enumeration class for distinguishing different image types
0 - kernel/ramdisk image (goes on dom0's filesystem)
1 - disk image (local SR, partitioned by objectstore plugin)
2 - raw disk image (local SR, NOT partitioned by plugin)
"""
KERNEL_RAMDISK = 0
DISK = 1
DISK_RAW = 2
class VMHelper():
"""
The class that wraps the helper methods together.
"""
def __init__(self):
return
@@ -170,24 +184,22 @@ class VMHelper():
@classmethod
def fetch_image(cls, session, image, user, project, type):
"""type: integer field for specifying how to handle the image
0 - kernel/ramdisk image (goes on dom0's filesystem)
1 - disk image (local SR, partitioned by objectstore plugin)
2 - raw disk image (local SR, NOT partitioned by plugin)"""
"""
type is interpreted as an ImageType instance
"""
url = images.image_url(image)
access = AuthManager().get_access_key(user, project)
logging.debug("Asking xapi to fetch %s as %s", url, access)
fn = (type != 0) and 'get_vdi' or 'get_kernel'
fn = (type != ImageType.KERNEL_RAMDISK) and 'get_vdi' or 'get_kernel'
args = {}
args['src_url'] = url
args['username'] = access
args['password'] = user.secret
args['add_partition'] = 'false'
args['raw'] = 'false'
if type != 0:
if type != ImageType.KERNEL_RAMDISK:
args['add_partition'] = 'true'
if type == 2:
if type == ImageType.DISK_RAW:
args['raw'] = 'true'
task = session.async_call_plugin('objectstore', fn, args)
uuid = session.wait_for_task(task)
@@ -26,6 +26,7 @@ from nova import context
from nova.auth.manager import AuthManager
from nova.virt.xenapi.network_utils import NetworkHelper
from nova.virt.xenapi.vm_utils import VMHelper
from nova.virt.xenapi.vm_utils import ImageType
XenAPI = None
@@ -34,6 +35,7 @@ class VMOps(object):
"""
Management class for VM-related tasks
"""
def __init__(self, session):
global XenAPI
if XenAPI is None:
@@ -63,9 +65,9 @@ class VMOps(object):
project = AuthManager().get_project(instance.project_id)
#if kernel is not present we must download a raw disk
if instance.kernel_id:
disk_image_type = 1
disk_image_type = ImageType.DISK
else:
disk_image_type = 2
disk_image_type = ImageType.DISK_RAW
vdi_uuid = VMHelper.fetch_image(self._session,
instance.image_id, user, project, disk_image_type)
vdi_ref = self._session.call_xenapi('VDI.get_by_uuid', vdi_uuid)
@@ -76,11 +78,11 @@ class VMOps(object):
kernel = None
if instance.kernel_id:
kernel = VMHelper.fetch_image(self._session,
instance.kernel_id, user, project, 0)
instance.kernel_id, user, project, ImageType.KERNEL_RAMDISK)
ramdisk = None
if instance.ramdisk_id:
ramdisk = VMHelper.fetch_image(self._session,
instance.ramdisk_id, user, project, 0)
instance.ramdisk_id, user, project, ImageType.KERNEL_RAMDISK)
vm_ref = VMHelper.create_vm(self._session,
instance, kernel, ramdisk, pv_kernel)
VMHelper.create_vbd(self._session, vm_ref, vdi_ref, 0, True)
@@ -131,6 +133,32 @@ class VMOps(object):
except XenAPI.Failure, exc:
logging.warn(exc)
def _wait_with_callback(self, task, callback):
ret = None
try:
ret = self._session.wait_for_task(task)
except XenAPI.Failure, exc:
logging.warn(exc)
callback(ret)
def pause(self, instance, callback):
""" Pause VM instance """
instance_name = instance.name
vm = VMHelper.lookup(self._session, instance_name)
if vm is None:
raise Exception('instance not present %s' % instance_name)
task = self._session.call_xenapi('Async.VM.pause', vm)
self._wait_with_callback(task, callback)
def unpause(self, instance, callback):
""" Unpause VM instance """
instance_name = instance.name
vm = VMHelper.lookup(self._session, instance_name)
if vm is None:
raise Exception('instance not present %s' % instance_name)
task = self._session.call_xenapi('Async.VM.unpause', vm)
self._wait_with_callback(task, callback)
def get_info(self, instance_id):
""" Return data about VM instance """
vm = VMHelper.lookup_blocking(self._session, instance_id)
@@ -20,6 +20,7 @@ Management class for Storage-related functions (attach, detach, etc).
class VolumeOps(object):
def __init__(self, session):
self._session = session
@@ -102,6 +102,7 @@ def get_connection(_):
class XenAPIConnection(object):
""" A connection to XenServer or Xen Cloud Platform """
def __init__(self, url, user, pw):
session = XenAPISession(url, user, pw)
self._vmops = VMOps(session)
@@ -123,6 +124,14 @@ class XenAPIConnection(object):
""" Destroy VM instance """
self._vmops.destroy(instance)
def pause(self, instance, callback):
""" Pause VM instance """
self._vmops.pause(instance, callback)
def unpause(self, instance, callback):
""" Unpause paused VM instance """
self._vmops.unpause(instance, callback)
def get_info(self, instance_id):
""" Return data about VM instance """
return self._vmops.get_info(instance_id)
@@ -148,6 +157,7 @@ class XenAPIConnection(object):
class XenAPISession(object):
""" The session to invoke XenAPI SDK calls """
def __init__(self, url, user, pw):
self._session = XenAPI.Session(url)
self._session.login_with_password(user, pw)
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.