diff options
| author | Salvatore Orlando <salvatore.orlando@eu.citrix.com> | 2010-12-21 11:43:06 +0000 |
|---|---|---|
| committer | Salvatore Orlando <salvatore.orlando@eu.citrix.com> | 2010-12-21 11:43:06 +0000 |
| commit | 301dd942b533f8efbe55a74def7ae79de3a11f48 (patch) | |
| tree | aee41c5cfda5b1e8960ae11e7c3032a0f096fc9f | |
| parent | c7dada9b2f5811afbc7a181636d6be3ab8ab98cc (diff) | |
| parent | 086f2d87be3c56ac8dafaf4551096868d57454db (diff) | |
1) Merged from trunk
2) 'type' parameter in VMHelper.fetch_image converted in enum
3) Fixed pep8 errors
4) Passed unit tests
| -rw-r--r-- | .mailmap | 1 | ||||
| -rw-r--r-- | Authors | 4 | ||||
| -rw-r--r-- | nova/adminclient.py | 1 | ||||
| -rw-r--r-- | nova/api/ec2/admin.py | 1 | ||||
| -rw-r--r-- | nova/api/openstack/__init__.py | 13 | ||||
| -rw-r--r-- | nova/api/openstack/backup_schedules.py | 1 | ||||
| -rw-r--r-- | nova/api/openstack/servers.py | 29 | ||||
| -rw-r--r-- | nova/compute/api.py | 20 | ||||
| -rw-r--r-- | nova/compute/manager.py | 41 | ||||
| -rw-r--r-- | nova/db/sqlalchemy/api.py | 4 | ||||
| -rw-r--r-- | nova/exception.py | 3 | ||||
| -rw-r--r-- | nova/tests/api/openstack/test_servers.py | 37 | ||||
| -rw-r--r-- | nova/tests/compute_unittest.py | 8 | ||||
| -rw-r--r-- | nova/twistd.py | 2 | ||||
| -rw-r--r-- | nova/virt/fake.py | 13 | ||||
| -rw-r--r-- | nova/virt/libvirt_conn.py | 9 | ||||
| -rw-r--r-- | nova/virt/xenapi/network_utils.py | 1 | ||||
| -rw-r--r-- | nova/virt/xenapi/vm_utils.py | 28 | ||||
| -rw-r--r-- | nova/virt/xenapi/vmops.py | 36 | ||||
| -rw-r--r-- | nova/virt/xenapi/volumeops.py | 1 | ||||
| -rw-r--r-- | nova/virt/xenapi_conn.py | 10 |
21 files changed, 241 insertions, 22 deletions
@@ -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> @@ -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> + diff --git a/nova/adminclient.py b/nova/adminclient.py index 5a62cce7d..6ae9f0c0f 100644 --- a/nova/adminclient.py +++ b/nova/adminclient.py @@ -194,6 +194,7 @@ class HostInfo(object): class NovaAdminClient(object): + def __init__( self, clc_url=DEFAULT_CLC_URL, diff --git a/nova/api/ec2/admin.py b/nova/api/ec2/admin.py index 1c6ab688d..fac01369e 100644 --- a/nova/api/ec2/admin.py +++ b/nova/api/ec2/admin.py @@ -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 diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index b9ecbd9b8..210df8d24 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -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) diff --git a/nova/api/openstack/backup_schedules.py b/nova/api/openstack/backup_schedules.py index 3ed691d7b..fc70b5c6c 100644 --- a/nova/api/openstack/backup_schedules.py +++ b/nova/api/openstack/backup_schedules.py @@ -24,6 +24,7 @@ import nova.image.service class Controller(wsgi.Controller): + def __init__(self): pass diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 7704f48f1..5c3322f7c 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -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() diff --git a/nova/compute/api.py b/nova/compute/api.py index aa64b8c5f..046b0fa86 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -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) diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 7eb60e262..a84af6bb9 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -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.""" diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 55036d1d1..935063609 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -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() diff --git a/nova/exception.py b/nova/exception.py index 6d6c37338..9af4017ba 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -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 diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 8444b6fce..3820f5f27 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -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={}, diff --git a/nova/tests/compute_unittest.py b/nova/tests/compute_unittest.py index c6353d357..187ca31de 100644 --- a/nova/tests/compute_unittest.py +++ b/nova/tests/compute_unittest.py @@ -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() diff --git a/nova/twistd.py b/nova/twistd.py index cb5648ce6..e6c3101f1 100644 --- a/nova/twistd.py +++ b/nova/twistd.py @@ -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)') diff --git a/nova/virt/fake.py b/nova/virt/fake.py index 77bc926c2..55c6dcef9 100644 --- a/nova/virt/fake.py +++ b/nova/virt/fake.py @@ -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 diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 75c8eaa4c..ad101db2a 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -97,6 +97,7 @@ def get_connection(read_only): class LibvirtConnection(object): + def __init__(self, read_only): self.libvirt_uri = self.get_uri() @@ -261,6 +262,14 @@ class LibvirtConnection(object): 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) diff --git a/nova/virt/xenapi/network_utils.py b/nova/virt/xenapi/network_utils.py index 012954394..ce2c68ce0 100644 --- a/nova/virt/xenapi/network_utils.py +++ b/nova/virt/xenapi/network_utils.py @@ -25,6 +25,7 @@ class NetworkHelper(): """ The class that wraps the helper methods together. """ + def __init__(self): return diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index 4c58598c1..d92ca235a 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -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) diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 37c64ee93..cd704d9f8 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -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) diff --git a/nova/virt/xenapi/volumeops.py b/nova/virt/xenapi/volumeops.py index a4c7a3861..1943ccab0 100644 --- a/nova/virt/xenapi/volumeops.py +++ b/nova/virt/xenapi/volumeops.py @@ -20,6 +20,7 @@ Management class for Storage-related functions (attach, detach, etc). class VolumeOps(object): + def __init__(self, session): self._session = session diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index 6beb08f5e..21ed2cd65 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -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) |
