From 005a4e645f8e913c673c6ba07e7b0c8c54f33e1c Mon Sep 17 00:00:00 2001 From: Ed Leafe Date: Tue, 21 Dec 2010 14:17:29 -0600 Subject: Refactored duplicate rpc.cast() calls in nova/compute/api.py. Cleaned up some formatting issues. --- nova/compute/api.py | 47 ++++++++++++--------------------- nova/compute/manager.py | 32 +++++++++++++++++++++++ nova/tests/compute_unittest.py | 7 +++++ nova/virt/fake.py | 12 +++++++++ nova/virt/xenapi/vmops.py | 15 ++++++++--- nova/virt/xenapi_conn.py | 59 ++++++++++++++++++++++-------------------- 6 files changed, 111 insertions(+), 61 deletions(-) diff --git a/nova/compute/api.py b/nova/compute/api.py index c740814da..ae7c84bc5 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -206,8 +206,7 @@ class ComputeAPI(base.Base): def delete_instance(self, context, instance_id): logging.debug("Going to try and terminate %d" % instance_id) try: - instance = self.db.instance_get_by_internal_id(context, - instance_id) + instance = self.get_instance(context, instance_id) except exception.NotFound as e: logging.warning("Instance %d was not found during terminate", instance_id) @@ -271,50 +270,38 @@ class ComputeAPI(base.Base): def get_instance(self, context, instance_id): return self.db.instance_get_by_internal_id(context, instance_id) - def reboot(self, context, instance_id): - """Reboot the given instance.""" - instance = self.db.instance_get_by_internal_id(context, instance_id) + def _cast_compute_message(method, context, instance_id): + """Generic handler for RPC calls.""" + instance = self.get_instance(context, instance_id) host = instance['host'] rpc.cast(context, self.db.queue_get_for(context, FLAGS.compute_topic, host), - {"method": "reboot_instance", + {"method": method, "args": {"instance_id": instance['id']}}) + def reboot(self, context, instance_id): + """Reboot the given instance.""" + self._cast_compute_message("reboot_instance", context, 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']}}) + self._cast_compute_message("pause_instance", context, 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']}}) + self._cast_compute_message("unpause_instance", context, instance_id) def rescue(self, context, instance_id): """Rescue 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": "rescue_instance", - "args": {"instance_id": instance['id']}}) + self._cast_compute_message("rescue_instance", context, instance_id) def unrescue(self, context, instance_id): """Unrescue 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": "unrescue_instance", - "args": {"instance_id": instance['id']}}) + self._cast_compute_message("unrescue_instance", context, instance_id) + + def reset_root_password(self, context, instance_id): + """Reset the root/admin pw for the given instance.""" + self._cast_compute_message("reset_root_password", context, instance_id) def _get_network_topic(self, context): """Retrieves the network host for a project""" diff --git a/nova/compute/manager.py b/nova/compute/manager.py index a84af6bb9..cb64cd39d 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -156,6 +156,38 @@ class ComputeManager(manager.Manager): self.driver.reboot(instance_ref) self._update_state(context, instance_id) + + + + # WORKING CODE + @exception.wrap_exception + def reset_root_password(self, context, instance_id): + """Reset the root/admin password for an instance on this server.""" + context = context.elevated() + instance_ref = self.db.instance_get(context, instance_id) + self._update_state(context, instance_id) + + if instance_ref['state'] != power_state.RUNNING: + logging.warn('trying to reset the password on a non-running ' + 'instance: %s (state: %s excepted: %s)', + instance_ref['internal_id'], + instance_ref['state'], + power_state.RUNNING) + + logging.debug('instance %s: resetting root password', + instance_ref['name']) + self.db.instance_set_state(context, + instance_id, + power_state.NOSTATE, + 'resetting_password') + #TODO: (dabo) not sure how we will implement this yet. + self.driver.reset_root_password(instance_ref) + self._update_state(context, instance_id) + + + + + @exception.wrap_exception def rescue_instance(self, context, instance_id): """Rescue an instance on this server.""" diff --git a/nova/tests/compute_unittest.py b/nova/tests/compute_unittest.py index 187ca31de..16e577c56 100644 --- a/nova/tests/compute_unittest.py +++ b/nova/tests/compute_unittest.py @@ -142,6 +142,13 @@ class ComputeTestCase(test.TestCase): self.compute.reboot_instance(self.context, instance_id) self.compute.terminate_instance(self.context, instance_id) + def test_reset_root_password(self): + """Ensure instance can have its root password reset""" + instance_id = self._create_instance() + self.compute.run_instance(self.context, instance_id) + self.compute.reset_root_password(self.context, instance_id) + self.compute.terminate_instance(self.context, instance_id) + def test_console_output(self): """Make sure we can get console output from instance""" instance_id = self._create_instance() diff --git a/nova/virt/fake.py b/nova/virt/fake.py index 55c6dcef9..ff3f61838 100644 --- a/nova/virt/fake.py +++ b/nova/virt/fake.py @@ -118,6 +118,18 @@ class FakeConnection(object): """ pass + def reset_root_password(self, instance): + """ + Reset the root password on the specified instance. + + The given parameter is an instance of nova.compute.service.Instance, + and so the instance is being specified as instance.name. + + The work will be done asynchronously. This function returns a + Deferred that allows the caller to detect when it is complete. + """ + pass + def rescue(self, instance): """ Rescue the specified instance. diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index a18eacf07..c5ae52add 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -34,7 +34,6 @@ class VMOps(object): """ Management class for VM-related tasks """ - def __init__(self, session): global XenAPI if XenAPI is None: @@ -45,8 +44,9 @@ class VMOps(object): def list_instances(self): """ List VM instances """ - return [self._session.get_xenapi().VM.get_name_label(vm) \ - for vm in self._session.get_xenapi().VM.get_all()] + xVM = self._session.get_xenapi().VM + return [xVM.get_name_label(vm) + for vm in xVM.get_all()] def spawn(self, instance): """ Create VM instance """ @@ -89,6 +89,15 @@ class VMOps(object): task = self._session.call_xenapi('Async.VM.clean_reboot', vm) self._session.wait_for_task(task) + def reset_root_password(self, instance): + """ Reset the root/admin password on the 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.reset_root_password', vm) + self._session.wait_for_task(task) + def destroy(self, instance): """ Destroy VM instance """ vm = VMHelper.lookup(self._session, instance.name) diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index 21ed2cd65..199c0b862 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -19,15 +19,15 @@ A connection to XenServer or Xen Cloud Platform. The concurrency model for this class is as follows: -All XenAPI calls are on a thread (using t.i.t.deferToThread, via the decorator -deferredToThread). They are remote calls, and so may hang for the usual -reasons. They should not be allowed to block the reactor thread. +All XenAPI calls are on a green thread (using eventlet's "tpool" +thread pool). They are remote calls, and so may hang for the usual +reasons. All long-running XenAPI calls (VM.start, VM.reboot, etc) are called async -(using XenAPI.VM.async_start etc). These return a task, which can then be -polled for completion. Polling is handled using reactor.callLater. +(using XenAPI.VM.async_start etc). These return a task, which can then be +polled for completion. -This combination of techniques means that we don't block the reactor thread at +This combination of techniques means that we don't block the main thread at all, and at the same time we don't hold lots of threads waiting for long-running operations. @@ -75,7 +75,7 @@ flags.DEFINE_string('xenapi_connection_password', flags.DEFINE_float('xenapi_task_poll_interval', 0.5, 'The interval used for polling of remote tasks ' - '(Async.VM.start, etc). Used only if ' + '(Async.VM.start, etc). Used only if ' 'connection_type=xenapi.') XenAPI = None @@ -101,7 +101,7 @@ def get_connection(_): class XenAPIConnection(object): - """ A connection to XenServer or Xen Cloud Platform """ + """A connection to XenServer or Xen Cloud Platform""" def __init__(self, url, user, pw): session = XenAPISession(url, user, pw) @@ -109,31 +109,35 @@ class XenAPIConnection(object): self._volumeops = VolumeOps(session) def list_instances(self): - """ List VM instances """ + """List VM instances""" return self._vmops.list_instances() def spawn(self, instance): - """ Create VM instance """ + """Create VM instance""" self._vmops.spawn(instance) def reboot(self, instance): - """ Reboot VM instance """ + """Reboot VM instance""" self._vmops.reboot(instance) + def reset_root_password(self, instance): + """Reset the root/admin password on the VM instance""" + self._vmops.reset_root_password(instance) + def destroy(self, instance): - """ Destroy VM instance """ + """Destroy VM instance""" self._vmops.destroy(instance) def pause(self, instance, callback): - """ Pause VM instance """ + """Pause VM instance""" self._vmops.pause(instance, callback) def unpause(self, instance, callback): - """ Unpause paused VM instance """ + """Unpause paused VM instance""" self._vmops.unpause(instance, callback) def get_info(self, instance_id): - """ Return data about VM instance """ + """Return data about VM instance""" return self._vmops.get_info(instance_id) def get_diagnostics(self, instance_id): @@ -141,33 +145,33 @@ class XenAPIConnection(object): return self._vmops.get_diagnostics(instance_id) def get_console_output(self, instance): - """ Return snapshot of console """ + """Return snapshot of console""" return self._vmops.get_console_output(instance) def attach_volume(self, instance_name, device_path, mountpoint): - """ Attach volume storage to VM instance """ + """Attach volume storage to VM instance""" return self._volumeops.attach_volume(instance_name, - device_path, - mountpoint) + device_path, + mountpoint) def detach_volume(self, instance_name, mountpoint): - """ Detach volume storage to VM instance """ + """Detach volume storage to VM instance""" return self._volumeops.detach_volume(instance_name, mountpoint) class XenAPISession(object): - """ The session to invoke XenAPI SDK calls """ + """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) def get_xenapi(self): - """ Return the xenapi object """ + """Return the xenapi object""" return self._session.xenapi def get_xenapi_host(self): - """ Return the xenapi host """ + """Return the xenapi host""" return self._session.xenapi.session.get_this_host(self._session.handle) def call_xenapi(self, method, *args): @@ -184,9 +188,8 @@ class XenAPISession(object): self.get_xenapi_host(), plugin, fn, args) def wait_for_task(self, task): - """Return a Deferred that will give the result of the given task. + """Return the result of the given task. The task is polled until it completes.""" - done = event.Event() loop = utils.LoopingCall(self._poll_task, task, done) loop.start(FLAGS.xenapi_task_poll_interval, now=True) @@ -195,7 +198,7 @@ class XenAPISession(object): return rv def _poll_task(self, task, done): - """Poll the given XenAPI task, and fire the given Deferred if we + """Poll the given XenAPI task, and fire the given action if we get a result.""" try: #logging.debug('Polling task %s...', task) @@ -218,7 +221,7 @@ class XenAPISession(object): def _unwrap_plugin_exceptions(func, *args, **kwargs): - """ Parse exception details """ + """Parse exception details""" try: return func(*args, **kwargs) except XenAPI.Failure, exc: @@ -240,7 +243,7 @@ def _unwrap_plugin_exceptions(func, *args, **kwargs): def _parse_xmlrpc_value(val): - """Parse the given value as if it were an XML-RPC value. This is + """Parse the given value as if it were an XML-RPC value. This is sometimes used as the format for the task.result field.""" if not val: return val -- cgit From 269ab03f74ea94a586f6af5b7d61847443522ba1 Mon Sep 17 00:00:00 2001 From: Ed Leafe Date: Wed, 22 Dec 2010 11:20:30 -0600 Subject: committing so that I can merge trunk changes --- nova/compute/manager.py | 10 +--------- nova/virt/xenapi/vmops.py | 22 ++++++++++++---------- 2 files changed, 13 insertions(+), 19 deletions(-) diff --git a/nova/compute/manager.py b/nova/compute/manager.py index cb64cd39d..86f8d9216 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -156,10 +156,6 @@ class ComputeManager(manager.Manager): self.driver.reboot(instance_ref) self._update_state(context, instance_id) - - - - # WORKING CODE @exception.wrap_exception def reset_root_password(self, context, instance_id): """Reset the root/admin password for an instance on this server.""" @@ -180,14 +176,10 @@ class ComputeManager(manager.Manager): instance_id, power_state.NOSTATE, 'resetting_password') - #TODO: (dabo) not sure how we will implement this yet. + #### TODO: (dabo) not sure how we will implement this yet. self.driver.reset_root_password(instance_ref) self._update_state(context, instance_id) - - - - @exception.wrap_exception def rescue_instance(self, context, instance_id): """Rescue an instance on this server.""" diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index c5ae52add..9dca55e26 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -43,13 +43,13 @@ class VMOps(object): VMHelper.late_import() def list_instances(self): - """ List VM instances """ + """List VM instances""" xVM = self._session.get_xenapi().VM return [xVM.get_name_label(vm) for vm in xVM.get_all()] def spawn(self, instance): - """ Create VM instance """ + """Create VM instance""" vm = VMHelper.lookup(self._session, instance.name) if vm is not None: raise Exception('Attempted to create non-unique name %s' % @@ -81,7 +81,7 @@ class VMOps(object): vm_ref) def reboot(self, instance): - """ Reboot VM instance """ + """Reboot VM instance""" instance_name = instance.name vm = VMHelper.lookup(self._session, instance_name) if vm is None: @@ -90,16 +90,18 @@ class VMOps(object): self._session.wait_for_task(task) def reset_root_password(self, instance): - """ Reset the root/admin password on the VM instance """ + """Reset the root/admin password on the 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.reset_root_password', vm) + #### TODO: (dabo) Need to figure out the correct command to + #### write to the xenstore. + task = self._session.call_xenapi('VM.get xenstore data', vm) self._session.wait_for_task(task) def destroy(self, instance): - """ Destroy VM instance """ + """Destroy VM instance""" vm = VMHelper.lookup(self._session, instance.name) if vm is None: # Don't complain, just return. This lets us clean up instances @@ -136,7 +138,7 @@ class VMOps(object): callback(ret) def pause(self, instance, callback): - """ Pause VM instance """ + """Pause VM instance""" instance_name = instance.name vm = VMHelper.lookup(self._session, instance_name) if vm is None: @@ -145,7 +147,7 @@ class VMOps(object): self._wait_with_callback(task, callback) def unpause(self, instance, callback): - """ Unpause VM instance """ + """Unpause VM instance""" instance_name = instance.name vm = VMHelper.lookup(self._session, instance_name) if vm is None: @@ -154,7 +156,7 @@ class VMOps(object): self._wait_with_callback(task, callback) def get_info(self, instance_id): - """ Return data about VM instance """ + """Return data about VM instance""" vm = VMHelper.lookup_blocking(self._session, instance_id) if vm is None: raise Exception('instance not present %s' % instance_id) @@ -170,6 +172,6 @@ class VMOps(object): return VMHelper.compile_diagnostics(self._session, rec) def get_console_output(self, instance): - """ Return snapshot of console """ + """Return snapshot of console""" # TODO: implement this to fix pylint! return 'FAKE CONSOLE OUTPUT of instance' -- cgit From 6c8fe1963e6d64ba76698dbbaeb7ef9f63cfda95 Mon Sep 17 00:00:00 2001 From: Ed Leafe Date: Wed, 22 Dec 2010 17:33:21 -0600 Subject: Got basic xenstore operations working --- nova/virt/xenapi/vmops.py | 63 +++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 55 insertions(+), 8 deletions(-) diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 1c76d2ccc..e36770cfc 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -92,16 +92,63 @@ class VMOps(object): task = self._session.call_xenapi('Async.VM.clean_reboot', vm) self._session.wait_for_task(instance.id, task) - def reset_root_password(self, instance): - """Reset the root/admin password on the VM instance""" - instance_name = instance.name - vm = VMHelper.lookup(self._session, instance_name) + def _get_vm_opaque_ref(self, instance_or_vm): + try: + instance_name = instance_or_vm.name + vm = VMHelper.lookup(self._session, instance_name) + except AttributeError: + # A vm opaque ref was passed + vm = instance_or_vm if vm is None: raise Exception('instance not present %s' % instance_name) - #### TODO: (dabo) Need to figure out the correct command to - #### write to the xenstore. - task = self._session.call_xenapi('VM.get_xenstore_data', vm) - self._session.wait_for_task(task) + return vm + + def remove_from_xenstore(self, instance_or_vm, keys): + vm = self._get_vm_opaque_ref(instance_or_vm) + for key in keys: + self._session._session.xenapi_request('VM.remove_from_xenstore_data', + (vm, key)) + + def read_from_xenstore(self, instance_or_vm, keys=None): + """Returns the xenstore data for the specified VM instance as + a dict. Accepts an optional list of keys; if the list of keys is + passed, the returned dict is filtered to only return the values + for those keys. + """ + vm = self._get_vm_opaque_ref(instance_or_vm) + ret = self._session._session.xenapi_request('VM.get_xenstore_data', (vm, )) + if keys: + allkeys = set(ret.keys()) + badkeys = allkeys.difference(keys) + for k in badkeys: + ret.pop(k) + return ret + + def add_to_xenstore(self, instance_or_vm, mapping): + """Takes a dict and adds it to the xenstore record for + the given vm instance. Existing data is preserved, but any + existing values for the mapping's keys are overwritten. + """ + vm = self._get_vm_opaque_ref(instance_or_vm) + current_data = self.read_from_xenstore(vm) + current_data.update(mapping) + self.write_to_xenstore(vm, current_data) + + def write_to_xenstore(self, instance_or_vm, mapping): + """Takes a dict and writes it to the xenstore record for + the given vm instance. Any existing data is overwritten. + """ + vm = self._get_vm_opaque_ref(instance_or_vm) + self._session._session.xenapi_request('VM.set_xenstore_data', + (vm, mapping)) + + def reset_root_password(self, instance): + """Reset the root/admin password on the VM instance""" + self.add_to_xenstore(instance, {"reset_root_password": "requested"}) + self.add_to_xenstore(instance, {"TEST": "OMG!"}) + import time + self.add_to_xenstore(instance, {"timestamp": time.ctime()}) + def destroy(self, instance): """Destroy VM instance""" -- cgit From ef8e4495f5ed195a08be6c02b3eb3326f6403bb6 Mon Sep 17 00:00:00 2001 From: Ed Leafe Date: Thu, 23 Dec 2010 16:56:21 -0600 Subject: updated the xenstore methods to reflect that they write to the param record of xenstore, not the actual xenstore itself. --- nova/virt/xenapi/vmops.py | 142 ++++++++++++++++++++++++++-------------------- 1 file changed, 82 insertions(+), 60 deletions(-) diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 7bff47507..0e22ce306 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -18,7 +18,10 @@ Management class for VM-related functions (spawn, reboot, etc). """ +import json import logging +import random +import uuid from nova import db from nova import context @@ -53,13 +56,12 @@ class VMOps(object): """Create VM instance""" vm = VMHelper.lookup(self._session, instance.name) if vm is not None: - raise exception.Duplicate(_('Attempted to create' - ' non-unique name %s') % instance.name) + raise exception.Duplicate(_('Attempted to create non-unique name %s') + % instance.name) bridge = db.network_get_by_instance(context.get_admin_context(), instance['id'])['bridge'] - network_ref = \ - NetworkHelper.find_network_with_bridge(self._session, bridge) + network_ref = NetworkHelper.find_network_with_bridge(self._session, bridge) user = AuthManager().get_user(instance.user_id) project = AuthManager().get_project(instance.project_id) @@ -104,16 +106,6 @@ class VMOps(object): timer.f = _wait_for_boot return timer.start(interval=0.5, now=True) - def reboot(self, instance): - """Reboot VM instance""" - instance_name = instance.name - vm = VMHelper.lookup(self._session, instance_name) - if vm is None: - raise exception.NotFound(_('instance not' - ' found %s') % instance_name) - task = self._session.call_xenapi('Async.VM.clean_reboot', vm) - self._session.wait_for_task(instance.id, task) - def _get_vm_opaque_ref(self, instance_or_vm): try: instance_name = instance_or_vm.name @@ -122,15 +114,21 @@ class VMOps(object): # A vm opaque ref was passed vm = instance_or_vm if vm is None: - raise Exception('instance not present %s' % instance_name) + raise Exception(_('Instance not present %s') % instance_name) return vm + def reboot(self, instance): + """Reboot VM instance""" + vm = self._get_vm_opaque_ref(instance) + task = self._session.call_xenapi('Async.VM.clean_reboot', vm) + self._session.wait_for_task(instance.id, task) + def reset_root_password(self, instance): """Reset the root/admin password on the VM instance""" - self.add_to_xenstore(instance, {"reset_root_password": "requested"}) - self.add_to_xenstore(instance, {"TEST": "OMG!"}) + self.add_to_param_xenstore(instance, "reset_root_password", "requested") + self.add_to_param_xenstore(instance, "TEST", "OMG!") import time - self.add_to_xenstore(instance, {"timestamp": time.ctime()}) + self.add_to_param_xenstore(instance, "timestamp", time.ctime()) def destroy(self, instance): @@ -173,38 +171,27 @@ class VMOps(object): 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.NotFound(_('Instance not' - ' found %s') % instance_name) + vm = self._get_vm_opaque_ref(instance) task = self._session.call_xenapi('Async.VM.pause', vm) self._wait_with_callback(instance.id, 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.NotFound(_('Instance not' - ' found %s') % instance_name) + vm = self._get_vm_opaque_ref(instance) task = self._session.call_xenapi('Async.VM.unpause', vm) self._wait_with_callback(instance.id, task, callback) def get_info(self, instance_id): """Return data about VM instance""" - vm = VMHelper.lookup(self._session, instance_id) + vm = VMHelper.lookup_blocking(self._session, instance_id) if vm is None: - raise exception.NotFound(_('Instance not' - ' found %s') % instance_id) + raise Exception(_('Instance not present %s') % instance_id) rec = self._session.get_xenapi().VM.get_record(vm) return VMHelper.compile_info(rec) def get_diagnostics(self, instance_id): """Return data about VM diagnostics""" - vm = VMHelper.lookup(self._session, instance_id) - if vm is None: - raise exception.NotFound(_("Instance not found %s") % instance_id) + vm = self._get_vm_opaque_ref(instance) rec = self._session.get_xenapi().VM.get_record(vm) return VMHelper.compile_diagnostics(self._session, rec) @@ -213,40 +200,75 @@ class VMOps(object): # TODO: implement this to fix pylint! return 'FAKE CONSOLE OUTPUT of instance' - def read_from_xenstore(self, instance_or_vm, keys=None): + def dh_keyinit(self, instance): + """Initiates a Diffie-Hellman (or, more precisely, a + Diffie-Hellman-Merkle) key exchange with the agent. It will + compute one side of the exchange and write it to xenstore. + When a response is received, it will then compute the shared + secret key, which is returned. + NOTE: the base and prime are pre-set; this may change in + the future. + """ + base = 5 + prime = 162259276829213363391578010288127 + secret_int = random.randint(100,1000) + val = (base ** secret_int) % prime + msgname = str(uuid.uuid4()) + key = "/data/host/%s" % msgname + self.add_to_param_xenstore(instance, key=key, + value={"name": "keyinit", "value": val}) + + def read_partial_from_param_xenstore(self, instance_or_vm, key_prefix): + """Returns a dict of all the keys in the xenstore for the given instance + that begin with the key_prefix. + """ + data = self.read_from_param_xenstore(instance_or_vm) + badkeys = [k for k in data.keys() + if not k.startswith(key_prefix)] + for badkey in badkeys: + del data[badkey] + return data + + def read_from_param_xenstore(self, instance_or_vm, keys=None): """Returns the xenstore data for the specified VM instance as - a dict. Accepts an optional list of keys; if the list of keys is - passed, the returned dict is filtered to only return the values + a dict. Accepts an optional key or list of keys; if a value for 'keys' + is passed, the returned dict is filtered to only return the values for those keys. """ vm = self._get_vm_opaque_ref(instance_or_vm) - ret = self._session.call_xenapi_request('VM.get_xenstore_data', (vm, )) - if keys: - allkeys = set(ret.keys()) - badkeys = allkeys.difference(keys) - for k in badkeys: - del ret[k] + data = self._session.call_xenapi_request('VM.get_xenstore_data', (vm, )) + ret = {} + if keys is None: + keys = data.keys() + elif isinstance(keys, basestring): + keys = [keys] + for key in keys: + raw = data.get(key) + if raw: + ret[key] = json.loads(raw) + else: + ret[key] = raw return ret - def add_to_xenstore(self, instance_or_vm, mapping): - """Takes a dict and adds it to the xenstore record for - the given vm instance. Existing data is preserved, but any - existing values for the mapping's keys are overwritten. - """ + def add_to_param_xenstore(self, instance_or_vm, key, val): + """Takes a key/value pair and adds it to the xenstore record + for the given vm instance. If the key exists in xenstore, it is + overwritten""" vm = self._get_vm_opaque_ref(instance_or_vm) - current_data = self.read_from_xenstore(vm) - current_data.update(mapping) - self.write_to_xenstore(vm, current_data) + self.remove_from_param_xenstore(instance_or_vm, key) + jsonval = json.dumps(val) + self._session.call_xenapi_request('VM.add_to_xenstore_data', + (vm, key, jsonval)) - def write_to_xenstore(self, instance_or_vm, mapping): - """Takes a dict and writes it to the xenstore record for - the given vm instance. Any existing data is overwritten. + def write_to_param_xenstore(self, instance_or_vm, mapping): + """Takes a dict and writes each key/value pair to the xenstore + record for the given vm instance. Any existing data for those + keys is overwritten. """ - vm = self._get_vm_opaque_ref(instance_or_vm) - self._session.call_xenapi_request('VM.set_xenstore_data', - (vm, mapping)) + for k, v in mapping.iteritems(): + self.add_to_param_xenstore(instance_or_vm, k, v) - def remove_from_xenstore(self, instance_or_vm, key_or_keys): + def remove_from_param_xenstore(self, instance_or_vm, key_or_keys): """Takes either a single key or a list of keys and removes them from the xenstore data for the given VM. If the key doesn't exist, the request is ignored. @@ -270,6 +292,6 @@ class VMOps(object): for key in keys: self._session.call_xenapi_request('VM.remove_from_xenstore_data', (vm, key)) - def clear_xenstore(self, instance_or_vm): + def clear_param_xenstore(self, instance_or_vm): """Removes all data from the xenstore record for this VM.""" - self.write_to_xenstore(instance_or_vm, {}) + self.write_to_param_xenstore(instance_or_vm, {}) -- cgit From b50433d77207c542ee63b7858eb465bb51ba56ea Mon Sep 17 00:00:00 2001 From: Ed Leafe Date: Fri, 31 Dec 2010 05:37:30 -0600 Subject: Before merge with xenstore-plugin code --- nova/compute/manager.py | 19 ++- nova/virt/xenapi/vmops.py | 290 ++++++++++++++++++++++++++++++++++++++-------- nova/virt/xenapi_conn.py | 18 ++- 3 files changed, 267 insertions(+), 60 deletions(-) diff --git a/nova/compute/manager.py b/nova/compute/manager.py index c46587adc..4d8c5e1a5 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -36,6 +36,7 @@ terminating it. import datetime import logging +import string from nova import exception from nova import flags @@ -239,15 +240,21 @@ class ComputeManager(manager.Manager): power_state.RUNNING) logging.debug('instance %s: resetting root password', - instance_ref['name']) - self.db.instance_set_state(context, - instance_id, - power_state.NOSTATE, - 'resetting_password') + instance_ref['name']) + self.db.instance_set_state(context, instance_id, + power_state.NOSTATE, 'resetting_password') #### TODO: (dabo) not sure how we will implement this yet. - self.driver.reset_root_password(instance_ref) + new_pass = self._generate_password(12) + self.driver.reset_root_password(instance_ref, new_pass) self._update_state(context, instance_id) + def _generate_password(self, length=20): + """Generate a random sequence of letters and digits + to be used as a password. + """ + chrs = string.letters + string.digits + return "".join([random.choice(chrs) for i in xrange(length)]) + @exception.wrap_exception def rescue_instance(self, context, instance_id): """Rescue an instance on this server.""" diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 3f8f0da69..304ff7232 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -20,7 +20,10 @@ Management class for VM-related functions (spawn, reboot, etc). import json import logging +import os import random +import subprocess +import tempfile import uuid from nova import db @@ -138,14 +141,38 @@ class VMOps(object): task = self._session.call_xenapi('Async.VM.clean_reboot', vm) self._session.wait_for_task(instance.id, task) - def reset_root_password(self, instance): - """Reset the root/admin password on the VM instance""" - self.add_to_param_xenstore(instance, "reset_root_password", "requested") - self.add_to_param_xenstore(instance, "TEST", "OMG!") - import time - self.add_to_param_xenstore(instance, "timestamp", time.ctime()) - - + def reset_root_password(self, instance, new_pass): + """Reset the root/admin password on the VM instance. This is + done via an agent running on the VM. Communication between + nova and the agent is done via writing xenstore records. Since + communication is done over the XenAPI RPC calls, we need to + encrypt the password. We're using a simple Diffie-Hellman class + instead of the more advanced one in M2Crypto for compatibility + with the agent code. + """ + # Need to uniquely identify this request. + transaction_id = str(uuid.uuid4()) + # The simple Diffie-Hellman class is used to manage key exchange. + dh = SimpleDH() + args = {'id': transaction_id, 'pub': str(dh.get_public())} + resp = self._make_agent_call('key_init', instance, '', args) + resp_dict = json.loads(resp) + # Successful return code from key_init is 'D0' + if resp_dict['returncode'] != 'D0': + # There was some sort of error + raise RuntimeError(resp_dict['message']) + agent_pub = int(resp_dict['message']) + dh.compute_shared(agent_pub) + enc_pass = dh.encrypt(new_pass) + # Send the encrypted password + args['enc_pass'] = enc_pass + resp = self._make_agent_call('password', instance, '', args) + resp_dict = json.loads(resp) + # Successful return code from password is '0' + if resp_dict['returncode'] != '0': + raise RuntimeError(resp_dict['message']) + return resp_dict['message'] + def destroy(self, instance): """Destroy VM instance""" vm = VMHelper.lookup(self._session, instance.name) @@ -159,7 +186,7 @@ class VMOps(object): task = self._session.call_xenapi('Async.VM.hard_shutdown', vm) self._session.wait_for_task(instance.id, task) - except XenAPI.Failure, exc: + except self.XenAPI.Failure, exc: logging.warn(exc) # Disk clean-up if vdis: @@ -167,20 +194,20 @@ class VMOps(object): try: task = self._session.call_xenapi('Async.VDI.destroy', vdi) self._session.wait_for_task(instance.id, task) - except XenAPI.Failure, exc: + except self.XenAPI.Failure, exc: logging.warn(exc) # VM Destroy try: task = self._session.call_xenapi('Async.VM.destroy', vm) self._session.wait_for_task(instance.id, task) - except XenAPI.Failure, exc: + except self.XenAPI.Failure, exc: logging.warn(exc) def _wait_with_callback(self, instance_id, task, callback): ret = None try: ret = self._session.wait_for_task(instance_id, task) - except XenAPI.Failure, exc: + except self.XenAPI.Failure, exc: logging.warn(exc) callback(ret) @@ -235,27 +262,119 @@ class VMOps(object): # TODO: implement this to fix pylint! return 'FAKE CONSOLE OUTPUT of instance' - def dh_keyinit(self, instance): - """Initiates a Diffie-Hellman (or, more precisely, a - Diffie-Hellman-Merkle) key exchange with the agent. It will - compute one side of the exchange and write it to xenstore. - When a response is received, it will then compute the shared - secret key, which is returned. - NOTE: the base and prime are pre-set; this may change in - the future. + def list_from_xenstore(self, vm, path): + """Runs the xenstore-ls command to get a listing of all records + from 'path' downward. Returns a dict with the sub-paths as keys, + and the value stored in those paths as values. If nothing is + found at that path, returns None. + """ + ret = self._make_xenstore_call('list_records', vm, path) + try: + return json.loads(ret) + except ValueError: + # Not a valid JSON value + return ret + + def read_from_xenstore(self, vm, path): + """Returns the value stored in the xenstore record for the given VM + at the specified location. A XenAPIPlugin.PluginError will be raised + if any error is encountered in the read process. + """ + try: + ret = self._make_xenstore_call('read_record', vm, path, + {'ignore_missing_path': 'True'}) + except self.XenAPI.Failure, e: + print "XENERR", e + return None + except StandardError, e: + print "ERR", type(e), e, e.msg + return None + try: + return json.loads(ret) + except ValueError: + # Not a JSON object + if ret == "None": + # Can't marshall None over RPC calls. + return None + return ret + + def write_to_xenstore(self, vm, path, value): + """Writes the passed value to the xenstore record for the given VM + at the specified location. A XenAPIPlugin.PluginError will be raised + if any error is encountered in the write process. + """ + return self._make_xenstore_call('write_record', vm, path, {'value': json.dumps(value)}) + + def clear_xenstore(self, vm, path): + """Deletes the VM's xenstore record for the specified path. + If there is no such record, the request is ignored. + """ + self._make_xenstore_call('delete_record', vm, path) + + def _make_xenstore_call(self, method, vm, path, addl_args={}): + """Abstracts out the interaction with the xenstore xenapi plugin.""" + return self._make_plugin_call('xenstore.py', method=method, vm=vm, path=path, + addl_args=addl_args) + + def _make_agent_call(self, method, vm, path, addl_args={}): + """Abstracts out the interaction with the agent xenapi plugin.""" + return self._make_plugin_call('agent.py', method=method, vm=vm, path=path, + addl_args=addl_args) + + def _make_plugin_call(self, plugin, method, vm, path, addl_args={}): + vm = self._get_vm_opaque_ref(vm) + rec = self._session.get_xenapi().VM.get_record(vm) + args = {'dom_id': rec['domid'], 'path': path} + args.update(addl_args) + # If the 'testing_mode' attribute is set, add that to the args. + if getattr(self, 'testing_mode', False): + args['testing_mode'] = 'true' + try: + task = self._session.async_call_plugin(plugin, method, args) + ret = self._session.wait_for_task(0, task) + except self.XenAPI.Failure, e: + raise RuntimeError("%s" % e.details[-1]) + return ret + + def add_to_xenstore(self, vm, path, key, value): + """Adds the passed key/value pair to the xenstore record for + the given VM at the specified location. A XenAPIPlugin.PluginError + will be raised if any error is encountered in the write process. """ - base = 5 - prime = 162259276829213363391578010288127 - secret_int = random.randint(100,1000) - val = (base ** secret_int) % prime - msgname = str(uuid.uuid4()) - key = "/data/host/%s" % msgname - self.add_to_param_xenstore(instance, key=key, - value={"name": "keyinit", "value": val}) + current = self.read_from_xenstore(vm, path) + current[key] = value + self.write_to_xenstore(vm, path, current) + def remove_from_xenstore(self, vm, path, key_or_keys): + """Takes either a single key or a list of keys and removes + them from the xenstoreirecord data for the given VM. + If the key doesn't exist, the request is ignored. + """ + current = self.list_from_xenstore(vm, path) + if not current: + return + if isinstance(key_or_keys, basestring): + keys = [key_or_keys] + else: + keys = key_or_keys + keys.sort(lambda x,y: cmp(y.count('/'), x.count('/'))) + for key in keys: + if path: + keypath = "%s/%s" % (path, key) + else: + keypath = key + self._make_xenstore_call('delete_record', vm, keypath) + + + ######################################################################## + ###### The following methods interact with the xenstore parameter + ###### record, not the live xenstore. They were created before I + ###### knew the difference, and are left in here in case they prove + ###### to be useful. + ######################################################################## def read_partial_from_param_xenstore(self, instance_or_vm, key_prefix): - """Returns a dict of all the keys in the xenstore for the given instance - that begin with the key_prefix. + """Returns a dict of all the keys in the xenstore parameter record + for the given instance that begin with the key_prefix. """ data = self.read_from_param_xenstore(instance_or_vm) badkeys = [k for k in data.keys() @@ -265,8 +384,8 @@ class VMOps(object): return data def read_from_param_xenstore(self, instance_or_vm, keys=None): - """Returns the xenstore data for the specified VM instance as - a dict. Accepts an optional key or list of keys; if a value for 'keys' + """Returns the xenstore parameter record data for the specified VM instance + as a dict. Accepts an optional key or list of keys; if a value for 'keys' is passed, the returned dict is filtered to only return the values for those keys. """ @@ -286,9 +405,9 @@ class VMOps(object): return ret def add_to_param_xenstore(self, instance_or_vm, key, val): - """Takes a key/value pair and adds it to the xenstore record - for the given vm instance. If the key exists in xenstore, it is - overwritten""" + """Takes a key/value pair and adds it to the xenstore parameter + record for the given vm instance. If the key exists in xenstore, + it is overwritten""" vm = self._get_vm_opaque_ref(instance_or_vm) self.remove_from_param_xenstore(instance_or_vm, key) jsonval = json.dumps(val) @@ -297,27 +416,16 @@ class VMOps(object): def write_to_param_xenstore(self, instance_or_vm, mapping): """Takes a dict and writes each key/value pair to the xenstore - record for the given vm instance. Any existing data for those - keys is overwritten. + parameter record for the given vm instance. Any existing data for + those keys is overwritten. """ for k, v in mapping.iteritems(): self.add_to_param_xenstore(instance_or_vm, k, v) def remove_from_param_xenstore(self, instance_or_vm, key_or_keys): """Takes either a single key or a list of keys and removes - them from the xenstore data for the given VM. If the key - doesn't exist, the request is ignored. - """ - vm = self._get_vm_opaque_ref(instance_or_vm) - if isinstance(key_or_keys, basestring): - keys = [key_or_keys] - else: - keys = key_or_keys - for key in keys: - self._session.call_xenapi_request('VM.remove_from_xenstore_data', (vm, key)) - """Takes either a single key or a list of keys and removes - them from the xenstore data for the given VM. If the key - doesn't exist, the request is ignored. + them from the xenstore parameter record data for the given VM. + If the key doesn't exist, the request is ignored. """ vm = self._get_vm_opaque_ref(instance_or_vm) if isinstance(key_or_keys, basestring): @@ -328,5 +436,85 @@ class VMOps(object): self._session.call_xenapi_request('VM.remove_from_xenstore_data', (vm, key)) def clear_param_xenstore(self, instance_or_vm): - """Removes all data from the xenstore record for this VM.""" + """Removes all data from the xenstore parameter record for this VM.""" self.write_to_param_xenstore(instance_or_vm, {}) + ######################################################################## + + +def _runproc(cmd): + pipe = subprocess.PIPE + return subprocess.Popen([cmd], shell=True, stdin=pipe, stdout=pipe, stderr=pipe, close_fds=True) + +class SimpleDH(object): + """This class wraps all the functionality needed to implement + basic Diffie-Hellman-Merkle key exchange in Python. It features + intelligent defaults for the prime and base numbers needed for the + calculation, while allowing you to supply your own. It requires that + the openssl binary be installed on the system on which this is run, + as it uses that to handle the encryption and decryption. If openssl + is not available, a RuntimeError will be raised. + """ +# def __init__(self, prime=None, base=None): +# """You can specify the values for prime and base if you wish; +# otherwise, reasonable default values will be used. +# """ +# if prime is None: +# self._prime = 162259276829213363391578010288127 +# else: +# self._prime = prime +# if base is None: +# self._base = 5 +# else: +# self._base = base +# self._secret = random.randint(5000, 15000) +# self._shared = self._public = None + def __init__(self, prime=None, base=None, secret=None): + """You can specify the values for prime and base if you wish; + otherwise, reasonable default values will be used. + """ + if prime is None: + self._prime = 162259276829213363391578010288127 + else: + self._prime = prime + if base is None: + self._base = 5 + else: + self._base = base + if secret is None: + self._secret = random.randint(5000, 15000) + else: + self._secret = secret + self._shared = self._public = None + + def get_public(self): + self._public = (self._base ** self._secret) % self._prime + return self._public + + def compute_shared(self, other): + self._shared = (other ** self._secret) % self._prime + return self._shared + + def _run_ssl(self, text, which): + base_cmd = ('cat %(tmpfile)s | openssl enc -aes-128-cbc ' + '-a -pass pass:%(shared)s -nosalt %(dec_flag)s') + if which.lower()[0] == 'd': + dec_flag = ' -d' + else: + dec_flag = '' + fd, tmpfile = tempfile.mkstemp() + os.close(fd) + file(tmpfile, 'w').write(text) + shared = self._shared + cmd = base_cmd % locals() + proc = _runproc(cmd) + proc.wait() + err = proc.stderr.read() + if err: + raise RuntimeError(_('OpenSSL error: %s') % err) + return proc.stdout.read() + + def encrypt(self, text): + return self._run_ssl(text, 'enc') + + def decrypt(self, text): + return self._run_ssl(text, 'dec') diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index 7fb7ff15d..6b65a8c48 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -135,9 +135,9 @@ class XenAPIConnection(object): """Reboot VM instance""" self._vmops.reboot(instance) - def reset_root_password(self, instance): + def reset_root_password(self, instance, new_pass): """Reset the root/admin password on the VM instance""" - self._vmops.reset_root_password(instance) + self._vmops.reset_root_password(instance, new_pass) def destroy(self, instance): """Destroy VM instance""" @@ -264,7 +264,19 @@ class XenAPISession(object): status, error_info)) done.send_exception(self.XenAPI.Failure(error_info)) - db.instance_action_create(context.get_admin_context(), action) + + #db.instance_action_create(context.get_admin_context(), action) + import sqlalchemy + from sqlalchemy.exc import IntegrityError as IntegrityError + try: + db.instance_action_create(context.get_admin_context(), action) + except IntegrityError: + # Some methods don't pass unique IDs, so the call to + # instance_action_create() will raise IntegrityErrors. Rather + # than bomb out, I'm explicitly silencing them so that the + # code can continue to work until they fix that method. + pass + except self.XenAPI.Failure, exc: logging.warn(exc) done.send_exception(*sys.exc_info()) -- cgit From 4f77545cb1ae58484669028fbddb06592b1ee7e4 Mon Sep 17 00:00:00 2001 From: Ed Leafe Date: Fri, 31 Dec 2010 08:54:10 -0600 Subject: fixed pep8 issues --- nova/virt/xenapi/vmops.py | 64 +++++++++++++++++++++-------------------------- nova/virt/xenapi_conn.py | 20 +++++++-------- 2 files changed, 39 insertions(+), 45 deletions(-) diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index b0269d960..49857e3d9 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -61,12 +61,13 @@ class VMOps(object): """Create VM instance""" vm = VMHelper.lookup(self._session, instance.name) if vm is not None: - raise exception.Duplicate(_('Attempted to create non-unique name %s') - % instance.name) + msg = _('Attempted to create non-unique name %s') % instance.name) + raise exception.Duplicate( bridge = db.network_get_by_instance(context.get_admin_context(), instance['id'])['bridge'] - network_ref = NetworkHelper.find_network_with_bridge(self._session, bridge) + network_ref = NetworkHelper.find_network_with_bridge(self._session, + bridge) user = AuthManager().get_user(instance.user_id) project = AuthManager().get_project(instance.project_id) @@ -222,7 +223,7 @@ class VMOps(object): if resp_dict['returncode'] != '0': raise RuntimeError(resp_dict['message']) return resp_dict['message'] - + def destroy(self, instance): """Destroy VM instance""" vm = VMHelper.lookup(self._session, instance.name) @@ -321,7 +322,7 @@ class VMOps(object): def list_from_xenstore(self, vm, path): """Runs the xenstore-ls command to get a listing of all records from 'path' downward. Returns a dict with the sub-paths as keys, - and the value stored in those paths as values. If nothing is + and the value stored in those paths as values. If nothing is found at that path, returns None. """ ret = self._make_xenstore_call('list_records', vm, path) @@ -355,7 +356,8 @@ class VMOps(object): at the specified location. A XenAPIPlugin.PluginError will be raised if any error is encountered in the write process. """ - return self._make_xenstore_call('write_record', vm, path, {'value': json.dumps(value)}) + return self._make_xenstore_call('write_record', vm, path, + {'value': json.dumps(value)}) def clear_xenstore(self, vm, path): """Deletes the VM's xenstore record for the specified path. @@ -365,13 +367,13 @@ class VMOps(object): def _make_xenstore_call(self, method, vm, path, addl_args={}): """Handles calls to the xenstore xenapi plugin.""" - return self._make_plugin_call('xenstore.py', method=method, vm=vm, path=path, - addl_args=addl_args) + return self._make_plugin_call('xenstore.py', method=method, vm=vm, + path=path, addl_args=addl_args) def _make_agent_call(self, method, vm, path, addl_args={}): """Abstracts out the interaction with the agent xenapi plugin.""" - return self._make_plugin_call('agent.py', method=method, vm=vm, path=path, - addl_args=addl_args) + return self._make_plugin_call('agent.py', method=method, vm=vm, + path=path, addl_args=addl_args) def _make_plugin_call(self, plugin, method, vm, path, addl_args={}): """Abstracts out the process of calling a method of a xenapi plugin. @@ -416,7 +418,7 @@ class VMOps(object): keys = [key_or_keys] else: keys = key_or_keys - keys.sort(lambda x,y: cmp(y.count('/'), x.count('/'))) + keys.sort(lambda x, y: cmp(y.count('/'), x.count('/'))) for key in keys: if path: keypath = "%s/%s" % (path, key) @@ -424,9 +426,8 @@ class VMOps(object): keypath = key self._make_xenstore_call('delete_record', vm, keypath) - ######################################################################## - ###### The following methods interact with the xenstore parameter + ###### The following methods interact with the xenstore parameter ###### record, not the live xenstore. They were created before I ###### knew the difference, and are left in here in case they prove ###### to be useful. They all have '_param' added to their method @@ -444,13 +445,14 @@ class VMOps(object): return data def read_from_param_xenstore(self, instance_or_vm, keys=None): - """Returns the xenstore parameter record data for the specified VM instance - as a dict. Accepts an optional key or list of keys; if a value for 'keys' - is passed, the returned dict is filtered to only return the values - for those keys. + """Returns the xenstore parameter record data for the specified VM + instance as a dict. Accepts an optional key or list of keys; if a + value for 'keys' is passed, the returned dict is filtered to only + return the values for those keys. """ vm = self._get_vm_opaque_ref(instance_or_vm) - data = self._session.call_xenapi_request('VM.get_xenstore_data', (vm, )) + data = self._session.call_xenapi_request('VM.get_xenstore_data', + (vm, )) ret = {} if keys is None: keys = data.keys() @@ -493,7 +495,8 @@ class VMOps(object): else: keys = key_or_keys for key in keys: - self._session.call_xenapi_request('VM.remove_from_xenstore_data', (vm, key)) + self._session.call_xenapi_request('VM.remove_from_xenstore_data', + (vm, key)) def clear_param_xenstore(self, instance_or_vm): """Removes all data from the xenstore parameter record for this VM.""" @@ -503,7 +506,9 @@ class VMOps(object): def _runproc(cmd): pipe = subprocess.PIPE - return subprocess.Popen([cmd], shell=True, stdin=pipe, stdout=pipe, stderr=pipe, close_fds=True) + return subprocess.Popen([cmd], shell=True, stdin=pipe, stdout=pipe, + stderr=pipe, close_fds=True) + class SimpleDH(object): """This class wraps all the functionality needed to implement @@ -514,23 +519,12 @@ class SimpleDH(object): as it uses that to handle the encryption and decryption. If openssl is not available, a RuntimeError will be raised. """ -# def __init__(self, prime=None, base=None): -# """You can specify the values for prime and base if you wish; -# otherwise, reasonable default values will be used. -# """ -# if prime is None: -# self._prime = 162259276829213363391578010288127 -# else: -# self._prime = prime -# if base is None: -# self._base = 5 -# else: -# self._base = base -# self._secret = random.randint(5000, 15000) -# self._shared = self._public = None def __init__(self, prime=None, base=None, secret=None): """You can specify the values for prime and base if you wish; - otherwise, reasonable default values will be used. + otherwise, reasonable default values will be used. You may also + specify the integer value for 'secret', but this should only be + done while testing when you need reproducible values. Otherwise, + any security benefits are lost. """ if prime is None: self._prime = 162259276829213363391578010288127 diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index fe302c450..3baae6188 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -275,16 +275,16 @@ class XenAPISession(object): error_info)) done.send_exception(self.XenAPI.Failure(error_info)) db.instance_action_create(context.get_admin_context(), action) -# import sqlalchemy -# from sqlalchemy.exc import IntegrityError as IntegrityError -# try: -# db.instance_action_create(context.get_admin_context(), action) -# except IntegrityError: -# # Some methods don't pass unique IDs, so the call to -# # instance_action_create() will raise IntegrityErrors. Rather -# # than bomb out, I'm explicitly silencing them so that the -# # code can continue to work until they fix that method. -# pass +# import sqlalchemy +# from sqlalchemy.exc import IntegrityError as IntegrityError +# try: +# db.instance_action_create(context.get_admin_context(), action) +# except IntegrityError: +# # Some methods don't pass unique IDs, so the call to +# # instance_action_create() will raise IntegrityErrors. Rather +# # than bomb out, I'm explicitly silencing them so that the +# # code can continue to work until they fix that method. +# pass except self.XenAPI.Failure, exc: logging.warn(exc) -- cgit From 108352d5c132f6accc79974d8c646a2bc7d4f127 Mon Sep 17 00:00:00 2001 From: Ed Leafe Date: Fri, 31 Dec 2010 12:21:04 -0600 Subject: Updated the password generation code --- nova/compute/manager.py | 8 +++++--- nova/virt/fake.py | 2 +- nova/virt/xenapi/vmops.py | 4 ++-- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 8ebd87f28..583fabe42 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -36,6 +36,7 @@ terminating it. import datetime import logging +import random import string from nova import exception @@ -247,7 +248,7 @@ class ComputeManager(manager.Manager): self.driver.snapshot(instance_ref, name) @exception.wrap_exception - def reset_root_password(self, context, instance_id): + def reset_root_password(self, context, instance_id, new_pass=None): """Reset the root/admin password for an instance on this server.""" context = context.elevated() instance_ref = self.db.instance_get(context, instance_id) @@ -264,8 +265,9 @@ class ComputeManager(manager.Manager): instance_ref['name']) self.db.instance_set_state(context, instance_id, power_state.NOSTATE, 'resetting_password') - #### TODO: (dabo) not sure how we will implement this yet. - new_pass = self._generate_password(12) + if new_pass is None: + # Generate a random, 12-character password + new_pass = self._generate_password(12) self.driver.reset_root_password(instance_ref, new_pass) self._update_state(context, instance_id) diff --git a/nova/virt/fake.py b/nova/virt/fake.py index 13c67c4ee..fdc8ac5fb 100644 --- a/nova/virt/fake.py +++ b/nova/virt/fake.py @@ -138,7 +138,7 @@ class FakeConnection(object): """ pass - def reset_root_password(self, instance): + def reset_root_password(self, instance, new_pass): """ Reset the root password on the specified instance. diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 49857e3d9..4ce8d819b 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -61,8 +61,8 @@ class VMOps(object): """Create VM instance""" vm = VMHelper.lookup(self._session, instance.name) if vm is not None: - msg = _('Attempted to create non-unique name %s') % instance.name) - raise exception.Duplicate( + msg = _('Attempted to create non-unique name %s') % instance.name + raise exception.Duplicate(msg) bridge = db.network_get_by_instance(context.get_admin_context(), instance['id'])['bridge'] -- cgit From f67802d62ee530b4e81aaf108dfd3813c84550b2 Mon Sep 17 00:00:00 2001 From: Ed Leafe Date: Wed, 5 Jan 2011 16:41:50 -0600 Subject: intermediate work --- nova/api/__init__.py | 14 ++++++++++++-- nova/api/openstack/servers.py | 7 ++++--- nova/compute/api.py | 2 +- nova/virt/xenapi/vmops.py | 19 ++++++++++++++----- 4 files changed, 31 insertions(+), 11 deletions(-) diff --git a/nova/api/__init__.py b/nova/api/__init__.py index 26fed847b..ff936bed2 100644 --- a/nova/api/__init__.py +++ b/nova/api/__init__.py @@ -59,13 +59,23 @@ class API(wsgi.Router): mapper.connect("/", controller=self.osapi_versions, conditions=osapi_subdomain) - mapper.connect("/v1.0/{path_info:.*}", controller=openstack.API(), - conditions=osapi_subdomain) + mapper.connect("/v1.0/{path_info:.*}", controller=openstack.API()) mapper.connect("/", controller=self.ec2api_versions, conditions=ec2api_subdomain) mapper.connect("/services/{path_info:.*}", controller=ec2.API(), conditions=ec2api_subdomain) + +# mapper.connect("/", controller=self.osapi_versions, +# conditions=osapi_subdomain) +# mapper.connect("/v1.0/{path_info:.*}", controller=openstack.API(), +# conditions=osapi_subdomain) +# +# mapper.connect("/", controller=self.ec2api_versions, +# conditions=ec2api_subdomain) +# mapper.connect("/services/{path_info:.*}", controller=ec2.API(), +# conditions=ec2api_subdomain) + mrh = metadatarequesthandler.MetadataRequestHandler() for s in ['/latest', '/2009-04-04', diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index c5cbe21ef..4bdb14d04 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -152,9 +152,10 @@ class Controller(wsgi.Controller): try: ctxt = req.environ['nova.context'] - self.compute_api.update_instance(ctxt, - id, - **update_dict) + # The ID passed in is actually the internal_id of the + # instance, not the value of the id column in the DB. + instance = self.compute_api.get_instance(ctxt, id) + self.compute_api.update_instance(ctxt, instance.id, **update_dict) except exception.NotFound: return faults.Fault(exc.HTTPNotFound()) return exc.HTTPNoContent() diff --git a/nova/compute/api.py b/nova/compute/api.py index 76a571d61..1d21a4668 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -257,7 +257,7 @@ class ComputeAPI(base.Base): def get_instance(self, context, instance_id): return self.db.instance_get_by_internal_id(context, instance_id) - def _cast_compute_message(method, context, instance_id): + def _cast_compute_message(self, method, context, instance_id): """Generic handler for RPC calls.""" instance = self.get_instance(context, instance_id) host = instance['host'] diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 78f2d4704..eaf8c7dff 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -130,12 +130,18 @@ class VMOps(object): """Refactored out the common code of many methods that receive either a vm name or a vm instance, and want a vm instance in return. """ + vm = None try: - instance_name = instance_or_vm.name - vm = VMHelper.lookup(self._session, instance_name) + if instance_or_vm.startswith("OpaqueRef:"): + # Got passed an opaque ref; return it + return instance_or_vm + else: + # Must be the instance name + instance_name = instance_or_vm except AttributeError: - # A vm opaque ref was passed - vm = instance_or_vm + # Not a string; must be a vm instance + instance_name = instance_or_vm.name + vm = VMHelper.lookup(self._session, instance_name) if vm is None: raise Exception(_('Instance not present %s') % instance_name) return vm @@ -201,6 +207,9 @@ class VMOps(object): instead of the more advanced one in M2Crypto for compatibility with the agent code. """ + + logging.error("ZZZZ RESET PASS CALLED") + # Need to uniquely identify this request. transaction_id = str(uuid.uuid4()) # The simple Diffie-Hellman class is used to manage key exchange. @@ -291,7 +300,7 @@ class VMOps(object): task = self._session.call_xenapi('Async.VM.resume', vm, False, True) self._wait_with_callback(task, callback) - def get_info(self, instance_id): + def get_info(self, instance): """Return data about VM instance""" vm = self._get_vm_opaque_ref(instance) rec = self._session.get_xenapi().VM.get_record(vm) -- cgit From a3e12f5eb92921acc622ea7bd9097edeea0d40fd Mon Sep 17 00:00:00 2001 From: Ed Leafe Date: Thu, 6 Jan 2011 06:45:14 -0600 Subject: Renamed 'set_root_password' to 'set_admin_password' globally. --- nova/compute/api.py | 50 ++++++++++++++++------------------------------ nova/compute/manager.py | 24 ++++++++++++---------- nova/tests/test_compute.py | 6 +++--- nova/virt/fake.py | 19 +++++++++--------- nova/virt/xenapi/vmops.py | 20 +++++++++---------- nova/virt/xenapi_conn.py | 6 +++--- 6 files changed, 56 insertions(+), 69 deletions(-) diff --git a/nova/compute/api.py b/nova/compute/api.py index 1d21a4668..0a7b802d6 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -190,7 +190,7 @@ class ComputeAPI(base.Base): """ try: db.security_group_get_by_name(context, context.project_id, - 'default') + 'default') except exception.NotFound: values = {'name': 'default', 'description': 'default', @@ -258,70 +258,54 @@ class ComputeAPI(base.Base): return self.db.instance_get_by_internal_id(context, instance_id) def _cast_compute_message(self, method, context, instance_id): - """Generic handler for RPC calls.""" + """Generic handler for RPC calls to compute.""" instance = self.get_instance(context, instance_id) host = instance['host'] rpc.cast(context, - self.db.queue_get_for(context, FLAGS.compute_topic, host), - {"method": method, - "args": {"instance_id": instance['id']}}) + self.db.queue_get_for(context, FLAGS.compute_topic, host), + {'method': method, 'args': {'instance_id': instance['id']}}) def snapshot(self, context, instance_id, name): """Snapshot the given instance.""" - self._cast_compute_message("snapshot_instance", context, instance_id) + self._cast_compute_message('snapshot_instance', context, instance_id) def reboot(self, context, instance_id): """Reboot the given instance.""" - self._cast_compute_message("reboot_instance", context, instance_id) + self._cast_compute_message('reboot_instance', context, instance_id) def pause(self, context, instance_id): """Pause the given instance.""" - self._cast_compute_message("pause_instance", context, instance_id) + self._cast_compute_message('pause_instance', context, instance_id) def unpause(self, context, instance_id): """Unpause the given instance.""" - self._cast_compute_message("unpause_instance", context, instance_id) + self._cast_compute_message('unpause_instance', context, instance_id) def get_diagnostics(self, context, instance_id): """Retrieve diagnostics for the given instance.""" - instance = self.db.instance_get_by_internal_id(context, instance_id) - host = instance["host"] - return rpc.call(context, - self.db.queue_get_for(context, FLAGS.compute_topic, host), - {"method": "get_diagnostics", - "args": {"instance_id": instance["id"]}}) + self._cast_compute_message('get_diagnostics', context, instance_id) def get_actions(self, context, instance_id): """Retrieve actions for the given instance.""" instance = self.db.instance_get_by_internal_id(context, instance_id) - return self.db.instance_get_actions(context, instance["id"]) + return self.db.instance_get_actions(context, instance['id']) def suspend(self, context, instance_id): """suspend the instance with instance_id""" - 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": "suspend_instance", - "args": {"instance_id": instance['id']}}) + self._cast_compute_message('suspend_instance', context, instance_id) def resume(self, context, instance_id): """resume the instance with instance_id""" - 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": "resume_instance", - "args": {"instance_id": instance['id']}}) + self._cast_compute_message('resume_instance', context, instance_id) def rescue(self, context, instance_id): """Rescue the given instance.""" - self._cast_compute_message("rescue_instance", context, instance_id) + self._cast_compute_message('rescue_instance', context, instance_id) def unrescue(self, context, instance_id): """Unrescue the given instance.""" - self._cast_compute_message("unrescue_instance", context, instance_id) + self._cast_compute_message('unrescue_instance', context, instance_id) - def reset_root_password(self, context, instance_id): - """Reset the root/admin pw for the given instance.""" - self._cast_compute_message("reset_root_password", context, instance_id) + def set_admin_password(self, context, instance_id): + """Set the root/admin password for the given instance.""" + self._cast_compute_message('set_admin_password', context, instance_id) diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 6f4d14589..b8bf91530 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -53,6 +53,8 @@ flags.DEFINE_string('compute_driver', 'nova.virt.connection.get_connection', 'Driver to use for controlling virtualization') flags.DEFINE_string('stub_network', False, 'Stub network related code') +flags.DEFINE_integer('password_length', 12, + 'Length of generated admin passwords') class ComputeManager(manager.Manager): @@ -248,27 +250,27 @@ class ComputeManager(manager.Manager): self.driver.snapshot(instance_ref, name) @exception.wrap_exception - def reset_root_password(self, context, instance_id, new_pass=None): - """Reset the root/admin password for an instance on this server.""" + def set_admin_password(self, context, instance_id, new_pass=None): + """Set the root/admin password for an instance on this server.""" context = context.elevated() instance_ref = self.db.instance_get(context, instance_id) self._update_state(context, instance_id) if instance_ref['state'] != power_state.RUNNING: logging.warn('trying to reset the password on a non-running ' - 'instance: %s (state: %s excepted: %s)', - instance_ref['internal_id'], - instance_ref['state'], - power_state.RUNNING) + 'instance: %s (state: %s expected: %s)', + instance_ref['internal_id'], + instance_ref['state'], + power_state.RUNNING) - logging.debug('instance %s: resetting root password', + logging.debug('instance %s: setting admin password', instance_ref['name']) self.db.instance_set_state(context, instance_id, - power_state.NOSTATE, 'resetting_password') + power_state.NOSTATE, 'setting_password') if new_pass is None: - # Generate a random, 12-character password - new_pass = self._generate_password(12) - self.driver.reset_root_password(instance_ref, new_pass) + # Generate a random password + new_pass = self._generate_password(FLAGS.password_length) + self.driver.set_admin_password(instance_ref, new_pass) self._update_state(context, instance_id) def _generate_password(self, length=20): diff --git a/nova/tests/test_compute.py b/nova/tests/test_compute.py index 7c258e636..88e14d7df 100644 --- a/nova/tests/test_compute.py +++ b/nova/tests/test_compute.py @@ -151,11 +151,11 @@ class ComputeTestCase(test.TestCase): self.compute.reboot_instance(self.context, instance_id) self.compute.terminate_instance(self.context, instance_id) - def test_reset_root_password(self): - """Ensure instance can have its root password reset""" + def test_set_admin_password(self): + """Ensure instance can have its admin password set""" instance_id = self._create_instance() self.compute.run_instance(self.context, instance_id) - self.compute.reset_root_password(self.context, instance_id) + self.compute.set_admin_password(self.context, instance_id) self.compute.terminate_instance(self.context, instance_id) def test_snapshot(self): diff --git a/nova/virt/fake.py b/nova/virt/fake.py index 2b9cf1ca3..2d4b0a3d7 100644 --- a/nova/virt/fake.py +++ b/nova/virt/fake.py @@ -98,7 +98,7 @@ class FakeConnection(object): the new instance. The work will be done asynchronously. This function returns a - Deferred that allows the caller to detect when it is complete. + task that allows the caller to detect when it is complete. Once this successfully completes, the instance should be running (power_state.RUNNING). @@ -122,7 +122,7 @@ class FakeConnection(object): The second parameter is the name of the snapshot. The work will be done asynchronously. This function returns a - Deferred that allows the caller to detect when it is complete. + task that allows the caller to detect when it is complete. """ pass @@ -134,19 +134,20 @@ class FakeConnection(object): and so the instance is being specified as instance.name. The work will be done asynchronously. This function returns a - Deferred that allows the caller to detect when it is complete. + task that allows the caller to detect when it is complete. """ pass - def reset_root_password(self, instance, new_pass): + def set_admin_password(self, instance, new_pass): """ - Reset the root password on the specified instance. + Set the root password on the specified instance. - The given parameter is an instance of nova.compute.service.Instance, - and so the instance is being specified as instance.name. + The first parameter is an instance of nova.compute.service.Instance, + and so the instance is being specified as instance.name. The second + parameter is the value of the new password. The work will be done asynchronously. This function returns a - Deferred that allows the caller to detect when it is complete. + task that allows the caller to detect when it is complete. """ pass @@ -194,7 +195,7 @@ class FakeConnection(object): and so the instance is being specified as instance.name. The work will be done asynchronously. This function returns a - Deferred that allows the caller to detect when it is complete. + task that allows the caller to detect when it is complete. """ del self.instances[instance.name] diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index eaf8c7dff..64855001b 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -198,17 +198,16 @@ class VMOps(object): task = self._session.call_xenapi('Async.VM.clean_reboot', vm) self._session.wait_for_task(instance.id, task) - def reset_root_password(self, instance, new_pass): - """Reset the root/admin password on the VM instance. This is - done via an agent running on the VM. Communication between - nova and the agent is done via writing xenstore records. Since - communication is done over the XenAPI RPC calls, we need to - encrypt the password. We're using a simple Diffie-Hellman class - instead of the more advanced one in M2Crypto for compatibility - with the agent code. + def set_admin_password(self, instance, new_pass): + """Set the root/admin password on the VM instance. This is done via + an agent running on the VM. Communication between nova and the agent + is done via writing xenstore records. Since communication is done over + the XenAPI RPC calls, we need to encrypt the password. We're using a + simple Diffie-Hellman class instead of the more advanced one in + M2Crypto for compatibility with the agent code. """ - logging.error("ZZZZ RESET PASS CALLED") + logging.error("ZZZZ SET PASS CALLED") # Need to uniquely identify this request. transaction_id = str(uuid.uuid4()) @@ -219,7 +218,8 @@ class VMOps(object): resp_dict = json.loads(resp) # Successful return code from key_init is 'D0' if resp_dict['returncode'] != 'D0': - # There was some sort of error + # There was some sort of error; the message will contain + # a description of the error. raise RuntimeError(resp_dict['message']) agent_pub = int(resp_dict['message']) dh.compute_shared(agent_pub) diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index f4dd7055c..0cba2812d 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -144,9 +144,9 @@ class XenAPIConnection(object): """Reboot VM instance""" self._vmops.reboot(instance) - def reset_root_password(self, instance, new_pass): - """Reset the root/admin password on the VM instance""" - self._vmops.reset_root_password(instance, new_pass) + def set_admin_password(self, instance, new_pass): + """Set the root/admin password on the VM instance""" + self._vmops.set_admin_password(instance, new_pass) def destroy(self, instance): """Destroy VM instance""" -- cgit From 0209ad587b2d8d35a7abdf60ca9b33391cab4a83 Mon Sep 17 00:00:00 2001 From: Ed Leafe Date: Thu, 6 Jan 2011 07:21:11 -0600 Subject: merged trunk changes --- nova/compute/api.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/nova/compute/api.py b/nova/compute/api.py index 476667fa8..19cdf2d0a 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -61,13 +61,11 @@ class API(base.Base): def get_network_topic(self, context, instance_id): try: -<<<<<<< TREE instance = self.get_instance(context, instance_id) + # TODO (dabo) Need to verify whether an internal_id or a db id + # id being passed; use get_instance or get, respectively. + #instance = self.get(context, instance_id) except exception.NotFound, e: -======= - instance = self.get(context, instance_id) - except exception.NotFound as e: ->>>>>>> MERGE-SOURCE logging.warning("Instance %d was not found in get_network_topic", instance_id) raise e -- cgit From 3d70b49a1c17bccfc6163198b2d99efb9a9829a7 Mon Sep 17 00:00:00 2001 From: Ed Leafe Date: Thu, 6 Jan 2011 13:02:32 -0600 Subject: commit before merging trunk --- nova/api/ec2/cloud.py | 2 +- nova/api/openstack/servers.py | 14 +++++++++++--- nova/compute/api.py | 18 ++++-------------- nova/compute/manager.py | 5 +++-- nova/db/api.py | 5 +++++ nova/db/sqlalchemy/api.py | 23 +++++++++++++++++++++++ nova/virt/xenapi/vmops.py | 7 +++++++ nova/virt/xenapi_conn.py | 3 +++ 8 files changed, 57 insertions(+), 20 deletions(-) diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 0c0027287..6619b5452 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -592,7 +592,7 @@ class CloudController(object): return {'reservationSet': self._format_instances(context)} def _format_run_instances(self, context, reservation_id): - i = self._format_instances(context, reservation_id) + i = self._format_instances(context, reservation_id=reservation_id) assert len(i) == 1 return i[0] diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 05ba56c79..e0513e4c1 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -104,7 +104,7 @@ class Controller(wsgi.Controller): def show(self, req, id): """ Returns server details by server id """ try: - instance = self.compute_api.get(req.environ['nova.context'], id) + instance = self.compute_api.get_instance(req.environ['nova.context'], id) return _translate_detail_keys(instance) except exception.NotFound: return faults.Fault(exc.HTTPNotFound()) @@ -142,8 +142,10 @@ class Controller(wsgi.Controller): return faults.Fault(exc.HTTPUnprocessableEntity()) update_dict = {} + func = None if 'adminPass' in inst_dict['server']: update_dict['admin_pass'] = inst_dict['server']['adminPass'] + func = self.compute_api.set_admin_password if 'name' in inst_dict['server']: update_dict['display_name'] = inst_dict['server']['name'] @@ -152,9 +154,15 @@ class Controller(wsgi.Controller): # The ID passed in is actually the internal_id of the # instance, not the value of the id column in the DB. instance = self.compute_api.get_instance(ctxt, id) - self.compute_api.update_instance(ctxt, instance.id, **update_dict) + self.compute_api.update(ctxt, instance.id, **update_dict) except exception.NotFound: return faults.Fault(exc.HTTPNotFound()) + + logging.error("ZZZZ func=%s" % func) + logging.error("ZZZZ UPD=%s" % id) + + if func: + func(ctxt, id) return exc.HTTPNoContent() def action(self, req, id): @@ -225,4 +233,4 @@ class Controller(wsgi.Controller): def actions(self, req, id): """Permit Admins to retrieve server actions.""" ctxt = req.environ["nova.context"] - return self.compute_api.get_actions(ctxt, id) + return self.compute_api.get_actions(ctxt, id_val) diff --git a/nova/compute/api.py b/nova/compute/api.py index 19cdf2d0a..a4345f337 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -62,9 +62,6 @@ class API(base.Base): def get_network_topic(self, context, instance_id): try: instance = self.get_instance(context, instance_id) - # TODO (dabo) Need to verify whether an internal_id or a db id - # id being passed; use get_instance or get, respectively. - #instance = self.get(context, instance_id) except exception.NotFound, e: logging.warning("Instance %d was not found in get_network_topic", instance_id) @@ -223,10 +220,6 @@ class API(base.Base): logging.debug('Going to try and terminate %s' % instance_id) try: instance = self.get_instance(context, instance_id) - #TODO: (dabo) resolve that this is the correct call: - # get_instance vs. get. Depends on whether we get an internal_id - # or an actual db id. - #instance = self.get(context, instance_id) except exception.NotFound, e: logging.warning(_('Instance % was not found during terminate'), instance_id) @@ -252,7 +245,7 @@ class API(base.Base): else: self.db.instance_destroy(context, instance_id) - def get(self, context, instance_id): + def get_instance(self, context, instance_id): """Get a single instance with the given ID.""" return self.db.instance_get_by_id(context, instance_id) @@ -276,9 +269,6 @@ class API(base.Base): project_id) return self.db.instance_get_all(context) - def get_instance(self, context, instance_id): - return self.db.instance_get_by_internal_id(context, instance_id) - def _cast_compute_message(self, method, context, instance_id): """Generic handler for RPC calls to compute.""" instance = self.get_instance(context, instance_id) @@ -309,7 +299,7 @@ class API(base.Base): def get_actions(self, context, instance_id): """Retrieve actions for the given instance.""" - instance = self.db.instance_get_by_internal_id(context, instance_id) + instance = self.db.instance_get_by_id(context, instance_id) return self.db.instance_get_actions(context, instance['id']) def suspend(self, context, instance_id): @@ -337,7 +327,7 @@ class API(base.Base): raise exception.ApiError(_("Invalid device specified: %s. " "Example device: /dev/vdb") % device) self.volume_api.check_attach(context, volume_id) - instance = self.get(context, instance_id) + instance = self.get_instance(context, instance_id) host = instance['host'] rpc.cast(context, self.db.queue_get_for(context, FLAGS.compute_topic, host), @@ -360,6 +350,6 @@ class API(base.Base): return instance def associate_floating_ip(self, context, instance_id, address): - instance = self.get(context, instance_id) + instance = self.get_instance(context, instance_id) self.network_api.associate_floating_ip(context, address, instance['fixed_ip']) diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 3e6d3eab0..201fffc68 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -83,7 +83,8 @@ class ComputeManager(manager.Manager): # FIXME(ja): include other fields from state? instance_ref = self.db.instance_get(context, instance_id) try: - info = self.driver.get_info(instance_ref['name']) + #info = self.driver.get_info(instance_ref['name']) + info = self.driver.get_info(instance_ref) state = info['state'] except exception.NotFound: state = power_state.NOSTATE @@ -259,7 +260,7 @@ class ComputeManager(manager.Manager): if instance_ref['state'] != power_state.RUNNING: logging.warn('trying to reset the password on a non-running ' 'instance: %s (state: %s expected: %s)', - instance_ref['internal_id'], + instance_ref['id'], instance_ref['state'], power_state.RUNNING) diff --git a/nova/db/api.py b/nova/db/api.py index 0fa5eb1e8..f5c7eab91 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -358,6 +358,11 @@ def instance_get_by_id(context, instance_id): return IMPL.instance_get_by_id(context, instance_id) +def instance_get_by_internal_id(context, internal_id): + """Get an instance by internal id.""" + return IMPL.instance_get_by_internal_id(context, internal_id) + + def instance_is_vpn(context, instance_id): """True if instance is a vpn.""" return IMPL.instance_is_vpn(context, instance_id) diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index aaa07e3c9..34c73490e 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -670,6 +670,29 @@ def instance_get(context, instance_id, session=None): return result +@require_context +def instance_get_by_internal_id(context, internal_id): + session = get_session() + + if is_admin_context(context): + result = session.query(models.Instance).\ + options(joinedload('security_groups')).\ + filter_by(internal_id=internal_id).\ + filter_by(deleted=can_read_deleted(context)).\ + first() + elif is_user_context(context): + result = session.query(models.Instance).\ + options(joinedload('security_groups')).\ + filter_by(project_id=context.project_id).\ + filter_by(internal_id=internal_id).\ + filter_by(deleted=False).\ + first() + if not result: + raise exception.NotFound(_('Instance %s not found') % (internal_id)) + + return result + + @require_admin_context def instance_get_all(context): session = get_session() diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 64855001b..e092601b9 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -130,17 +130,21 @@ class VMOps(object): """Refactored out the common code of many methods that receive either a vm name or a vm instance, and want a vm instance in return. """ + logging.error("ZZZZ opaq instance_or_vm=%s" % instance_or_vm) vm = None try: if instance_or_vm.startswith("OpaqueRef:"): + logging.error("ZZZZ opaq startswith") # Got passed an opaque ref; return it return instance_or_vm else: # Must be the instance name + logging.error("ZZZZ opaq inst name") instance_name = instance_or_vm except AttributeError: # Not a string; must be a vm instance instance_name = instance_or_vm.name + logging.error("ZZZZ opaq instance, name=%s" % instance_name) vm = VMHelper.lookup(self._session, instance_name) if vm is None: raise Exception(_('Instance not present %s') % instance_name) @@ -302,6 +306,9 @@ class VMOps(object): def get_info(self, instance): """Return data about VM instance""" + + logging.error("ZZZZ get_info instance=%s" % instance) + vm = self._get_vm_opaque_ref(instance) rec = self._session.get_xenapi().VM.get_record(vm) return VMHelper.compile_info(rec) diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index 0cba2812d..0ceac8c97 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -170,6 +170,9 @@ class XenAPIConnection(object): def get_info(self, instance_id): """Return data about VM instance""" + + logging.error("ZZZZ conn get_info id=%s" % instance_id) + return self._vmops.get_info(instance_id) def get_diagnostics(self, instance): -- cgit From e66f3017373dcf9135c53ae4d510b0b2a5dcecf0 Mon Sep 17 00:00:00 2001 From: Ed Leafe Date: Thu, 6 Jan 2011 15:53:11 -0600 Subject: Got the basic 'set admin password' stuff working --- nova/api/openstack/servers.py | 17 +- nova/compute/manager.py | 3 - nova/exception.py | 4 + nova/virt/xenapi/vmops.py | 22 +- nova/virt/xenapi_conn.py | 16 +- plugins/xenserver/xenapi/etc/xapi.d/plugins/agent | 235 +++++++++++++++++++++ .../xenserver/xenapi/etc/xapi.d/plugins/agent.py | 221 ------------------- 7 files changed, 258 insertions(+), 260 deletions(-) create mode 100755 plugins/xenserver/xenapi/etc/xapi.d/plugins/agent delete mode 100644 plugins/xenserver/xenapi/etc/xapi.d/plugins/agent.py diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index e0513e4c1..bc89f696c 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -104,7 +104,8 @@ class Controller(wsgi.Controller): def show(self, req, id): """ Returns server details by server id """ try: - instance = self.compute_api.get_instance(req.environ['nova.context'], id) + instance = self.compute_api.get_instance( + req.environ['nova.context'], id) return _translate_detail_keys(instance) except exception.NotFound: return faults.Fault(exc.HTTPNotFound()) @@ -141,6 +142,7 @@ class Controller(wsgi.Controller): if not inst_dict: return faults.Fault(exc.HTTPUnprocessableEntity()) + ctxt = req.environ['nova.context'] update_dict = {} func = None if 'adminPass' in inst_dict['server']: @@ -148,21 +150,18 @@ class Controller(wsgi.Controller): func = self.compute_api.set_admin_password if 'name' in inst_dict['server']: update_dict['display_name'] = inst_dict['server']['name'] - + if func: + try: + func(ctxt, id) + except exception.TimeoutException, e: + return exc.HTTPRequestTimeout() try: - ctxt = req.environ['nova.context'] # The ID passed in is actually the internal_id of the # instance, not the value of the id column in the DB. instance = self.compute_api.get_instance(ctxt, id) self.compute_api.update(ctxt, instance.id, **update_dict) except exception.NotFound: return faults.Fault(exc.HTTPNotFound()) - - logging.error("ZZZZ func=%s" % func) - logging.error("ZZZZ UPD=%s" % id) - - if func: - func(ctxt, id) return exc.HTTPNoContent() def action(self, req, id): diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 201fffc68..52acfebea 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -83,7 +83,6 @@ class ComputeManager(manager.Manager): # FIXME(ja): include other fields from state? instance_ref = self.db.instance_get(context, instance_id) try: - #info = self.driver.get_info(instance_ref['name']) info = self.driver.get_info(instance_ref) state = info['state'] except exception.NotFound: @@ -266,8 +265,6 @@ class ComputeManager(manager.Manager): logging.debug('instance %s: setting admin password', instance_ref['name']) - self.db.instance_set_state(context, instance_id, - power_state.NOSTATE, 'setting_password') if new_pass is None: # Generate a random password new_pass = self._generate_password(FLAGS.password_length) diff --git a/nova/exception.py b/nova/exception.py index 277033e0f..52bf2a2a7 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -77,6 +77,10 @@ class InvalidInputException(Error): pass +class TimeoutException(Error): + pass + + def wrap_exception(f): def _wrap(*args, **kw): try: diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index e092601b9..5f1654a49 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -130,21 +130,17 @@ class VMOps(object): """Refactored out the common code of many methods that receive either a vm name or a vm instance, and want a vm instance in return. """ - logging.error("ZZZZ opaq instance_or_vm=%s" % instance_or_vm) vm = None try: if instance_or_vm.startswith("OpaqueRef:"): - logging.error("ZZZZ opaq startswith") # Got passed an opaque ref; return it return instance_or_vm else: # Must be the instance name - logging.error("ZZZZ opaq inst name") instance_name = instance_or_vm except AttributeError: # Not a string; must be a vm instance instance_name = instance_or_vm.name - logging.error("ZZZZ opaq instance, name=%s" % instance_name) vm = VMHelper.lookup(self._session, instance_name) if vm is None: raise Exception(_('Instance not present %s') % instance_name) @@ -210,9 +206,6 @@ class VMOps(object): simple Diffie-Hellman class instead of the more advanced one in M2Crypto for compatibility with the agent code. """ - - logging.error("ZZZZ SET PASS CALLED") - # Need to uniquely identify this request. transaction_id = str(uuid.uuid4()) # The simple Diffie-Hellman class is used to manage key exchange. @@ -306,9 +299,6 @@ class VMOps(object): def get_info(self, instance): """Return data about VM instance""" - - logging.error("ZZZZ get_info instance=%s" % instance) - vm = self._get_vm_opaque_ref(instance) rec = self._session.get_xenapi().VM.get_record(vm) return VMHelper.compile_info(rec) @@ -370,13 +360,14 @@ class VMOps(object): def _make_agent_call(self, method, vm, path, addl_args={}): """Abstracts out the interaction with the agent xenapi plugin.""" - return self._make_plugin_call('agent.py', method=method, vm=vm, + return self._make_plugin_call('agent', method=method, vm=vm, path=path, addl_args=addl_args) def _make_plugin_call(self, plugin, method, vm, path, addl_args={}): """Abstracts out the process of calling a method of a xenapi plugin. Any errors raised by the plugin will in turn raise a RuntimeError here. """ + instance_id = vm.id vm = self._get_vm_opaque_ref(vm) rec = self._session.get_xenapi().VM.get_record(vm) args = {'dom_id': rec['domid'], 'path': path} @@ -386,9 +377,14 @@ class VMOps(object): args['testing_mode'] = 'true' try: task = self._session.async_call_plugin(plugin, method, args) - ret = self._session.wait_for_task(0, task) + ret = self._session.wait_for_task(instance_id, task) except self.XenAPI.Failure, e: - raise RuntimeError("%s" % e.details[-1]) + err_trace = e.details[-1] + err_msg = err_trace.splitlines()[-1] + if 'TIMEOUT:' in err_msg: + raise exception.TimeoutException(err_msg) + else: + raise RuntimeError("%s" % e.details[-1]) return ret def add_to_xenstore(self, vm, path, key, value): diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index 0ceac8c97..c9428e3a6 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -170,9 +170,6 @@ class XenAPIConnection(object): def get_info(self, instance_id): """Return data about VM instance""" - - logging.error("ZZZZ conn get_info id=%s" % instance_id) - return self._vmops.get_info(instance_id) def get_diagnostics(self, instance): @@ -251,7 +248,8 @@ class XenAPISession(object): def _poll_task(self, id, task, done): """Poll the given XenAPI task, and fire the given action if we - get a result.""" + get a result. + """ try: name = self._session.xenapi.task.get_name_label(task) status = self._session.xenapi.task.get_status(task) @@ -278,16 +276,6 @@ class XenAPISession(object): error_info)) done.send_exception(self.XenAPI.Failure(error_info)) db.instance_action_create(context.get_admin_context(), action) -# import sqlalchemy -# from sqlalchemy.exc import IntegrityError as IntegrityError -# try: -# db.instance_action_create(context.get_admin_context(), action) -# except IntegrityError: -# # Some methods don't pass unique IDs, so the call to -# # instance_action_create() will raise IntegrityErrors. Rather -# # than bomb out, I'm explicitly silencing them so that the -# # code can continue to work until they fix that method. -# pass except self.XenAPI.Failure, exc: logging.warn(exc) diff --git a/plugins/xenserver/xenapi/etc/xapi.d/plugins/agent b/plugins/xenserver/xenapi/etc/xapi.d/plugins/agent new file mode 100755 index 000000000..244509f3f --- /dev/null +++ b/plugins/xenserver/xenapi/etc/xapi.d/plugins/agent @@ -0,0 +1,235 @@ +#!/usr/bin/env python + +# Copyright (c) 2010 Citrix Systems, Inc. +# 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. + +# +# XenAPI plugin for reading/writing information to xenstore +# + +try: + import json +except ImportError: + import simplejson as json +import os +import random +import subprocess +import tempfile +import time + +import XenAPIPlugin + +from pluginlib_nova import * +configure_logging("xenstore") +import xenstore + +AGENT_TIMEOUT = 30 +# Used for simulating an external agent for testing +PRETEND_SECRET = 11111 + + +def jsonify(fnc): + def wrapper(*args, **kwargs): + return json.dumps(fnc(*args, **kwargs)) + return wrapper + + +class TimeoutError(StandardError): + pass + + +class SimpleDH(object): + """This class wraps all the functionality needed to implement + basic Diffie-Hellman-Merkle key exchange in Python. It features + intelligent defaults for the prime and base numbers needed for the + calculation, while allowing you to supply your own. It requires that + the openssl binary be installed on the system on which this is run, + as it uses that to handle the encryption and decryption. If openssl + is not available, a RuntimeError will be raised. + + Please note that nova already uses the M2Crypto library for most + cryptographic functions, and that it includes a Diffie-Hellman + implementation. However, that is a much more complex implementation, + and is not compatible with the DH algorithm that the agent uses. Hence + the need for this 'simple' version. + """ + def __init__(self, prime=None, base=None, secret=None): + """You can specify the values for prime and base if you wish; + otherwise, reasonable default values will be used. + """ + if prime is None: + self._prime = 162259276829213363391578010288127 + else: + self._prime = prime + if base is None: + self._base = 5 + else: + self._base = base + if secret is None: + self._secret = random.randint(5000, 15000) + else: + self._secret = secret + self._shared = self._public = None + + def get_public(self): + """Return the public key""" + self._public = (self._base ** self._secret) % self._prime + return self._public + + def compute_shared(self, other): + """Given the other end's public key, compute the + shared secret. + """ + self._shared = (other ** self._secret) % self._prime + return self._shared + + def _run_ssl(self, text, which): + """The encryption/decryption methods require running the openssl + installed on the system. This method abstracts out the common + code required. + """ + base_cmd = ("cat %(tmpfile)s | openssl enc -aes-128-cbc " + "-a -pass pass:%(shared)s -nosalt %(dec_flag)s") + if which.lower()[0] == "d": + dec_flag = " -d" + else: + dec_flag = "" + # Note: instead of using 'cat' and a tempfile, it is also + # possible to just 'echo' the value. However, we can not assume + # that the value is 'safe'; i.e., it may contain semi-colons, + # octothorpes, or other characters that would not be allowed + # in an 'echo' construct. + fd, tmpfile = tempfile.mkstemp() + os.close(fd) + file(tmpfile, "w").write(text) + shared = self._shared + cmd = base_cmd % locals() + try: + return _run_command(cmd) + except PluginError, e: + raise RuntimeError("OpenSSL error: %s" % e) + + def encrypt(self, text): + """Uses the shared key to encrypt the given text.""" + return self._run_ssl(text, "enc") + + def decrypt(self, text): + """Uses the shared key to decrypt the given text.""" + return self._run_ssl(text, "dec") + + +def _run_command(cmd): + """Abstracts out the basics of issuing system commands. If the command + returns anything in stderr, a PluginError is raised with that information. + Otherwise, the output from stdout is returned. + """ + pipe = subprocess.PIPE + proc = subprocess.Popen([cmd], shell=True, stdin=pipe, stdout=pipe, stderr=pipe, close_fds=True) + proc.wait() + err = proc.stderr.read() + if err: + raise PluginError(err) + return proc.stdout.read() + + +@jsonify +def key_init(self, arg_dict): + """Handles the Diffie-Hellman key exchange with the agent to + establish the shared secret key used to encrypt/decrypt sensitive + info to be passed, such as passwords. Returns the shared + secret key value. + """ + pub = int(arg_dict["pub"]) + arg_dict["value"] = json.dumps({"name": "keyinit", "value": pub}) + request_id = arg_dict["id"] + if arg_dict.get("testing_mode"): + # Pretend! + pretend = SimpleDH(secret=PRETEND_SECRET) + shared = pretend.compute_shared(pub) + # Simulate the agent's response + ret = {"returncode": "D0", "message": "%s", "shared": "%s"} % (pretend.get_public(), shared) + return ret + arg_dict["path"] = "data/host/%s" % request_id + xenstore.write_record(self, arg_dict) + try: + resp = _wait_for_agent(self, request_id, arg_dict) + except TimeoutError, e: + raise PluginError("%s" % e) + return resp + + +@jsonify +def password(self, arg_dict): + """Writes a request to xenstore that tells the agent to set + the root password for the given VM. The password should be + encrypted using the shared secret key that was returned by a + previous call to key_init. The encrypted password value should + be passed as the value for the 'enc_pass' key in arg_dict. + """ + pub = int(arg_dict["pub"]) + enc_pass = arg_dict["enc_pass"] + if arg_dict.get("testing_mode"): + # Decrypt the password, and send it back to verify + pretend = SimpleDH(secret=PRETEND_SECRET) + pretend.compute_shared(pub) + pw = pretend.decrypt(enc_pass) + ret = {"returncode": "0", "message": "%s"} % pw + return ret + arg_dict["value"] = json.dumps({"name": "password", "value": enc_pass}) + request_id = arg_dict["id"] + arg_dict["path"] = "data/host/%s" % request_id + xenstore.write_record(self, arg_dict) + try: + resp = _wait_for_agent(self, request_id, arg_dict) + except TimeoutError, e: + raise PluginError("%s" % e) + return resp + + +def _wait_for_agent(self, request_id, arg_dict): + """Periodically checks xenstore for a response from the agent. + The request is always written to 'data/host/{id}', and + the agent's response for that request will be in 'data/guest/{id}'. + If no value appears from the agent within the time specified by + AGENT_TIMEOUT, the original request is deleted and a TimeoutError + is returned. + """ + arg_dict["path"] = "data/guest/%s" % request_id + arg_dict["ignore_missing_path"] = True + start = time.time() + while True: + if time.time() - start > AGENT_TIMEOUT: + # No response within the timeout period; bail out + # First, delete the request record + arg_dict["path"] = "data/host/%s" % request_id + xenstore.delete_record(self, arg_dict) + raise TimeoutError("TIMEOUT: No response from agent within %s seconds." % + AGENT_TIMEOUT) + ret = xenstore.read_record(self, arg_dict) + # Note: the response for None with be a string that includes + # double quotes. + if ret != '"None"': + # The agent responded + return ret + else: + time.sleep(3) + + +if __name__ == "__main__": + XenAPIPlugin.dispatch( + {"key_init": key_init, + "password": password}) diff --git a/plugins/xenserver/xenapi/etc/xapi.d/plugins/agent.py b/plugins/xenserver/xenapi/etc/xapi.d/plugins/agent.py deleted file mode 100644 index 4b072ce67..000000000 --- a/plugins/xenserver/xenapi/etc/xapi.d/plugins/agent.py +++ /dev/null @@ -1,221 +0,0 @@ -#!/usr/bin/env python - -# Copyright (c) 2010 Citrix Systems, Inc. -# 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. - -# -# XenAPI plugin for reading/writing information to xenstore -# - -try: - import json -except ImportError: - import simplejson as json -import os -import random -import subprocess -import tempfile -import time - -import XenAPIPlugin - -from pluginlib_nova import * -configure_logging("xenstore") -import xenstore - -AGENT_TIMEOUT = 30 -# Used for simulating an external agent for testing -PRETEND_SECRET = 11111 - - -class TimeoutError(StandardError): - pass - -class SimpleDH(object): - """This class wraps all the functionality needed to implement - basic Diffie-Hellman-Merkle key exchange in Python. It features - intelligent defaults for the prime and base numbers needed for the - calculation, while allowing you to supply your own. It requires that - the openssl binary be installed on the system on which this is run, - as it uses that to handle the encryption and decryption. If openssl - is not available, a RuntimeError will be raised. - - Please note that nova already uses the M2Crypto library for most - cryptographic functions, and that it includes a Diffie-Hellman - implementation. However, that is a much more complex implementation, - and is not compatible with the DH algorithm that the agent uses. Hence - the need for this 'simple' version. - """ - def __init__(self, prime=None, base=None, secret=None): - """You can specify the values for prime and base if you wish; - otherwise, reasonable default values will be used. - """ - if prime is None: - self._prime = 162259276829213363391578010288127 - else: - self._prime = prime - if base is None: - self._base = 5 - else: - self._base = base - if secret is None: - self._secret = random.randint(5000, 15000) - else: - self._secret = secret - self._shared = self._public = None - - def get_public(self): - """Return the public key""" - self._public = (self._base ** self._secret) % self._prime - return self._public - - def compute_shared(self, other): - """Given the other end's public key, compute the - shared secret. - """ - self._shared = (other ** self._secret) % self._prime - return self._shared - - def _run_ssl(self, text, which): - """The encryption/decryption methods require running the openssl - installed on the system. This method abstracts out the common - code required. - """ - base_cmd = ("cat %(tmpfile)s | openssl enc -aes-128-cbc " - "-a -pass pass:%(shared)s -nosalt %(dec_flag)s") - if which.lower()[0] == "d": - dec_flag = " -d" - else: - dec_flag = "" - # Note: instead of using 'cat' and a tempfile, it is also - # possible to just 'echo' the value. However, we can not assume - # that the value is 'safe'; i.e., it may contain semi-colons, - # octothorpes, or other characters that would not be allowed - # in an 'echo' construct. - fd, tmpfile = tempfile.mkstemp() - os.close(fd) - file(tmpfile, "w").write(text) - shared = self._shared - cmd = base_cmd % locals() - try: - return _run_command(cmd) - except PluginError, e: - raise RuntimeError("OpenSSL error: %s" % e) - - def encrypt(self, text): - """Uses the shared key to encrypt the given text.""" - return self._run_ssl(text, "enc") - - def decrypt(self, text): - """Uses the shared key to decrypt the given text.""" - return self._run_ssl(text, "dec") - - -def _run_command(cmd): - """Abstracts out the basics of issuing system commands. If the command - returns anything in stderr, a PluginError is raised with that information. - Otherwise, the output from stdout is returned. - """ - pipe = subprocess.PIPE - proc = subprocess.Popen([cmd], shell=True, stdin=pipe, stdout=pipe, stderr=pipe, close_fds=True) - proc.wait() - err = proc.stderr.read() - if err: - raise PluginError(err) - return proc.stdout.read() - -def key_init(self, arg_dict): - """Handles the Diffie-Hellman key exchange with the agent to - establish the shared secret key used to encrypt/decrypt sensitive - info to be passed, such as passwords. Returns the shared - secret key value. - """ - pub = int(arg_dict["pub"]) - arg_dict["value"] = json.dumps({"name": "keyinit", "value": pub}) - request_id = arg_dict["id"] - if arg_dict.get("testing_mode"): - # Pretend! - pretend = SimpleDH(secret=PRETEND_SECRET) - shared = pretend.compute_shared(pub) - # Simulate the agent's response - ret = '{ "returncode": "D0", "message": "%s", "shared": "%s" }' % (pretend.get_public(), shared) - return ret - arg_dict["path"] = "data/host/%s" % request_id - xenstore.write_record(self, arg_dict) - try: - resp = _wait_for_agent(self, request_id, arg_dict) - except TimeoutError, e: - raise PluginError("%s" % e) - return resp - -def password(self, arg_dict): - """Writes a request to xenstore that tells the agent to set - the root password for the given VM. The password should be - encrypted using the shared secret key that was returned by a - previous call to key_init. The encrypted password value should - be passed as the value for the 'enc_pass' key in arg_dict. - """ - pub = int(arg_dict["pub"]) - enc_pass = arg_dict["enc_pass"] - if arg_dict.get("testing_mode"): - # Decrypt the password, and send it back to verify - pretend = SimpleDH(secret=PRETEND_SECRET) - pretend.compute_shared(pub) - pw = pretend.decrypt(enc_pass) - ret = '{ "returncode": "0", "message": "%s" }' % pw - return ret - arg_dict["value"] = json.dumps({"name": "password", "value": enc_pass}) - request_id = arg_dict["id"] - arg_dict["path"] = "data/host/%s" % request_id - xenstore.write_record(self, arg_dict) - try: - resp = _wait_for_agent(self, request_id, arg_dict) - except TimeoutError, e: - raise PluginError("%s" % e) - return resp - -def _wait_for_agent(self, request_id, arg_dict): - """Periodically checks xenstore for a response from the agent. - The request is always written to 'data/host/{id}', and - the agent's response for that request will be in 'data/guest/{id}'. - If no value appears from the agent within the time specified by - AGENT_TIMEOUT, the original request is deleted and a TimeoutError - is returned. - """ - arg_dict["path"] = "data/guest/%s" % request_id - arg_dict["ignore_missing_path"] = True - start = time.time() - while True: - if time.time() - start > AGENT_TIMEOUT: - # No response within the timeout period; bail out - # First, delete the request record - arg_dict["path"] = "data/host/%s" % request_id - xenstore.delete_record(self, arg_dict) - raise TimeoutError("No response from agent within %s seconds." % - AGENT_TIMEOUT) - ret = xenstore.read_record(self, arg_dict) - if ret != "None": - # The agent responded - return ret - else: - time.sleep(3) - - -if __name__ == "__main__": - XenAPIPlugin.dispatch( - {"key_init": key_init, - "password": password}) -- cgit From dea9f51d65ce0d5c3c4ea328a2231499c71719d6 Mon Sep 17 00:00:00 2001 From: Ed Leafe Date: Thu, 6 Jan 2011 17:37:02 -0500 Subject: various cleanup and fixes --- nova/api/openstack/servers.py | 7 ++----- nova/compute/api.py | 2 +- nova/db/sqlalchemy/api.py | 23 ----------------------- 3 files changed, 3 insertions(+), 29 deletions(-) diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index bc89f696c..9eaef2b40 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -156,10 +156,7 @@ class Controller(wsgi.Controller): except exception.TimeoutException, e: return exc.HTTPRequestTimeout() try: - # The ID passed in is actually the internal_id of the - # instance, not the value of the id column in the DB. - instance = self.compute_api.get_instance(ctxt, id) - self.compute_api.update(ctxt, instance.id, **update_dict) + self.compute_api.update(ctxt, id, **update_dict) except exception.NotFound: return faults.Fault(exc.HTTPNotFound()) return exc.HTTPNoContent() @@ -232,4 +229,4 @@ class Controller(wsgi.Controller): def actions(self, req, id): """Permit Admins to retrieve server actions.""" ctxt = req.environ["nova.context"] - return self.compute_api.get_actions(ctxt, id_val) + return self.compute_api.get_actions(ctxt, id) diff --git a/nova/compute/api.py b/nova/compute/api.py index a4345f337..232eddb64 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -217,7 +217,7 @@ class API(base.Base): return self.db.instance_update(context, instance_id, kwargs) def delete(self, context, instance_id): - logging.debug('Going to try and terminate %s' % instance_id) + logging.debug('Going to try to terminate %s' % instance_id) try: instance = self.get_instance(context, instance_id) except exception.NotFound, e: diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 34c73490e..aaa07e3c9 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -670,29 +670,6 @@ def instance_get(context, instance_id, session=None): return result -@require_context -def instance_get_by_internal_id(context, internal_id): - session = get_session() - - if is_admin_context(context): - result = session.query(models.Instance).\ - options(joinedload('security_groups')).\ - filter_by(internal_id=internal_id).\ - filter_by(deleted=can_read_deleted(context)).\ - first() - elif is_user_context(context): - result = session.query(models.Instance).\ - options(joinedload('security_groups')).\ - filter_by(project_id=context.project_id).\ - filter_by(internal_id=internal_id).\ - filter_by(deleted=False).\ - first() - if not result: - raise exception.NotFound(_('Instance %s not found') % (internal_id)) - - return result - - @require_admin_context def instance_get_all(context): session = get_session() -- cgit From e01123943e7fbe81d7cb40325cde6c517bb2ffd9 Mon Sep 17 00:00:00 2001 From: Ed Leafe Date: Fri, 7 Jan 2011 08:36:11 -0500 Subject: grabbed the get_info fix from my other branch --- nova/virt/xenapi/vmops.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 5f1654a49..65d0360c8 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -138,8 +138,9 @@ class VMOps(object): else: # Must be the instance name instance_name = instance_or_vm - except AttributeError: + except (AttributeError, KeyError): # Not a string; must be a vm instance + # Note the the KeyError will only happen with fakes.py instance_name = instance_or_vm.name vm = VMHelper.lookup(self._session, instance_name) if vm is None: @@ -297,9 +298,12 @@ class VMOps(object): task = self._session.call_xenapi('Async.VM.resume', vm, False, True) self._wait_with_callback(task, callback) - def get_info(self, instance): + def get_info(self, instance_id): """Return data about VM instance""" - vm = self._get_vm_opaque_ref(instance) + vm = VMHelper.lookup(self._session, instance_id) + if vm is None: + raise exception.NotFound(_('Instance not' + ' found %s') % instance_id) rec = self._session.get_xenapi().VM.get_record(vm) return VMHelper.compile_info(rec) -- cgit From eaa5b5994891eee0280b750dff221a4b54932eb9 Mon Sep 17 00:00:00 2001 From: Ed Leafe Date: Fri, 7 Jan 2011 10:23:48 -0600 Subject: getting ready to push for merge prop --- nova/api/openstack/servers.py | 4 ++-- nova/compute/api.py | 18 +++++++++--------- nova/compute/manager.py | 3 +-- nova/virt/xenapi/vmops.py | 16 ++++++++++------ plugins/xenserver/xenapi/etc/xapi.d/plugins/agent | 4 ++-- 5 files changed, 24 insertions(+), 21 deletions(-) diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index a426a721d..280e2349e 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -104,7 +104,7 @@ class Controller(wsgi.Controller): def show(self, req, id): """ Returns server details by server id """ try: - instance = self.compute_api.get_instance( + instance = self.compute_api.get( req.environ['nova.context'], id) return _translate_detail_keys(instance) except exception.NotFound: @@ -158,7 +158,7 @@ class Controller(wsgi.Controller): try: # The ID passed in is actually the internal_id of the # instance, not the value of the id column in the DB. - instance = self.compute_api.get_instance(ctxt, id) + instance = self.compute_api.get(ctxt, id) self.compute_api.update(ctxt, instance.id, **update_dict) except exception.NotFound: return faults.Fault(exc.HTTPNotFound()) diff --git a/nova/compute/api.py b/nova/compute/api.py index 78ffcca7a..106c3f7f0 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -61,7 +61,7 @@ class API(base.Base): def get_network_topic(self, context, instance_id): try: - instance = self.get_instance(context, instance_id) + instance = self.get(context, instance_id) except exception.NotFound, e: logging.warning("Instance %d was not found in get_network_topic", instance_id) @@ -220,7 +220,7 @@ class API(base.Base): def delete(self, context, instance_id): logging.debug('Going to try and terminate %s' % instance_id) try: - instance = self.get_instance(context, instance_id) + instance = self.get(context, instance_id) except exception.NotFound, e: logging.warning(_('Instance % was not found during terminate'), instance_id) @@ -246,7 +246,7 @@ class API(base.Base): else: self.db.instance_destroy(context, instance_id) - def get_instance(self, context, instance_id): + def get(self, context, instance_id): """Get a single instance with the given ID.""" return self.db.instance_get_by_id(context, instance_id) @@ -272,7 +272,7 @@ class API(base.Base): def _cast_compute_message(self, method, context, instance_id): """Generic handler for RPC calls to compute.""" - instance = self.get_instance(context, instance_id) + instance = self.get(context, instance_id) host = instance['host'] rpc.cast(context, self.db.queue_get_for(context, FLAGS.compute_topic, host), @@ -328,7 +328,7 @@ class API(base.Base): lock the instance with instance_id """ - instance = self.get_instance(context, instance_id) + instance = self.get(context, instance_id) host = instance['host'] rpc.cast(context, self.db.queue_get_for(context, FLAGS.compute_topic, host), @@ -340,7 +340,7 @@ class API(base.Base): unlock the instance with instance_id """ - instance = self.get_instance(context, instance_id) + instance = self.get(context, instance_id) host = instance['host'] rpc.cast(context, self.db.queue_get_for(context, FLAGS.compute_topic, host), @@ -352,7 +352,7 @@ class API(base.Base): return the boolean state of (instance with instance_id)'s lock """ - instance = self.get_instance(context, instance_id) + instance = self.get(context, instance_id) return instance['locked'] def attach_volume(self, context, instance_id, volume_id, device): @@ -360,7 +360,7 @@ class API(base.Base): raise exception.ApiError(_("Invalid device specified: %s. " "Example device: /dev/vdb") % device) self.volume_api.check_attach(context, volume_id) - instance = self.get_instance(context, instance_id) + instance = self.get(context, instance_id) host = instance['host'] rpc.cast(context, self.db.queue_get_for(context, FLAGS.compute_topic, host), @@ -383,6 +383,6 @@ class API(base.Base): return instance def associate_floating_ip(self, context, instance_id, address): - instance = self.get_instance(context, instance_id) + instance = self.get(context, instance_id) self.network_api.associate_floating_ip(context, address, instance['fixed_ip']) diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 10219833b..5d677b023 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -289,8 +289,6 @@ class ComputeManager(manager.Manager): """Set the root/admin password for an instance on this server.""" context = context.elevated() instance_ref = self.db.instance_get(context, instance_id) - self._update_state(context, instance_id) - if instance_ref['state'] != power_state.RUNNING: logging.warn('trying to reset the password on a non-running ' 'instance: %s (state: %s expected: %s)', @@ -303,6 +301,7 @@ class ComputeManager(manager.Manager): if new_pass is None: # Generate a random password new_pass = self._generate_password(FLAGS.password_length) + self.driver.set_admin_password(instance_ref, new_pass) self._update_state(context, instance_id) diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 5f1654a49..3df561e7c 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -137,13 +137,17 @@ class VMOps(object): return instance_or_vm else: # Must be the instance name - instance_name = instance_or_vm - except AttributeError: - # Not a string; must be a vm instance - instance_name = instance_or_vm.name - vm = VMHelper.lookup(self._session, instance_name) + instance = instance_or_vm + except (AttributeError, KeyError): + # Note the the KeyError will only happen with fakes.py + # Not a string; must be an ID or a vm instance + if isinstance(instance_or_vm, (int, long)): + instance = instance_or_vm + else: + instance = instance_or_vm.name + vm = VMHelper.lookup(self._session, instance) if vm is None: - raise Exception(_('Instance not present %s') % instance_name) + raise Exception(_('Instance not present %s') % instance) return vm def snapshot(self, instance, name): diff --git a/plugins/xenserver/xenapi/etc/xapi.d/plugins/agent b/plugins/xenserver/xenapi/etc/xapi.d/plugins/agent index 244509f3f..ab5b98d1c 100755 --- a/plugins/xenserver/xenapi/etc/xapi.d/plugins/agent +++ b/plugins/xenserver/xenapi/etc/xapi.d/plugins/agent @@ -161,7 +161,7 @@ def key_init(self, arg_dict): pretend = SimpleDH(secret=PRETEND_SECRET) shared = pretend.compute_shared(pub) # Simulate the agent's response - ret = {"returncode": "D0", "message": "%s", "shared": "%s"} % (pretend.get_public(), shared) + ret = {"returncode": "D0", "message": pretend.get_public(), "shared": shared} return ret arg_dict["path"] = "data/host/%s" % request_id xenstore.write_record(self, arg_dict) @@ -187,7 +187,7 @@ def password(self, arg_dict): pretend = SimpleDH(secret=PRETEND_SECRET) pretend.compute_shared(pub) pw = pretend.decrypt(enc_pass) - ret = {"returncode": "0", "message": "%s"} % pw + ret = {"returncode": "0", "message": pw} return ret arg_dict["value"] = json.dumps({"name": "password", "value": enc_pass}) request_id = arg_dict["id"] -- cgit From 147693e45c7be174c54e39160869ca9a83bb4fff Mon Sep 17 00:00:00 2001 From: Ed Leafe Date: Fri, 7 Jan 2011 11:04:53 -0600 Subject: Additional cleanup prior to pushing --- nova/api/__init__.py | 14 +----- nova/api/openstack/servers.py | 2 - nova/compute/api.py | 54 +++++++---------------- nova/db/api.py | 5 --- nova/db/sqlalchemy/api.py | 23 ---------- nova/virt/xenapi/vmops.py | 6 +++ nova/virt/xenapi_conn.py | 1 - plugins/xenserver/xenapi/etc/xapi.d/plugins/agent | 8 ++-- 8 files changed, 29 insertions(+), 84 deletions(-) diff --git a/nova/api/__init__.py b/nova/api/__init__.py index ff936bed2..26fed847b 100644 --- a/nova/api/__init__.py +++ b/nova/api/__init__.py @@ -59,23 +59,13 @@ class API(wsgi.Router): mapper.connect("/", controller=self.osapi_versions, conditions=osapi_subdomain) - mapper.connect("/v1.0/{path_info:.*}", controller=openstack.API()) + mapper.connect("/v1.0/{path_info:.*}", controller=openstack.API(), + conditions=osapi_subdomain) mapper.connect("/", controller=self.ec2api_versions, conditions=ec2api_subdomain) mapper.connect("/services/{path_info:.*}", controller=ec2.API(), conditions=ec2api_subdomain) - -# mapper.connect("/", controller=self.osapi_versions, -# conditions=osapi_subdomain) -# mapper.connect("/v1.0/{path_info:.*}", controller=openstack.API(), -# conditions=osapi_subdomain) -# -# mapper.connect("/", controller=self.ec2api_versions, -# conditions=ec2api_subdomain) -# mapper.connect("/services/{path_info:.*}", controller=ec2.API(), -# conditions=ec2api_subdomain) - mrh = metadatarequesthandler.MetadataRequestHandler() for s in ['/latest', '/2009-04-04', diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 280e2349e..6a35567ff 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -156,8 +156,6 @@ class Controller(wsgi.Controller): except exception.TimeoutException, e: return exc.HTTPRequestTimeout() try: - # The ID passed in is actually the internal_id of the - # instance, not the value of the id column in the DB. instance = self.compute_api.get(ctxt, id) self.compute_api.update(ctxt, instance.id, **update_dict) except exception.NotFound: diff --git a/nova/compute/api.py b/nova/compute/api.py index 106c3f7f0..a894a0ce3 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -62,7 +62,7 @@ class API(base.Base): def get_network_topic(self, context, instance_id): try: instance = self.get(context, instance_id) - except exception.NotFound, e: + except exception.NotFound as e: logging.warning("Instance %d was not found in get_network_topic", instance_id) raise e @@ -195,7 +195,7 @@ class API(base.Base): """ try: db.security_group_get_by_name(context, context.project_id, - 'default') + 'default') except exception.NotFound: values = {'name': 'default', 'description': 'default', @@ -218,12 +218,12 @@ class API(base.Base): return self.db.instance_update(context, instance_id, kwargs) def delete(self, context, instance_id): - logging.debug('Going to try and terminate %s' % instance_id) + logging.debug('Going to try to terminate %s' % instance_id) try: instance = self.get(context, instance_id) - except exception.NotFound, e: + except exception.NotFound as e: logging.warning(_('Instance % was not found during terminate'), - instance_id) + instance_id) raise e if (instance['state_description'] == 'terminating'): @@ -239,10 +239,8 @@ class API(base.Base): host = instance['host'] if host: - rpc.cast(context, - self.db.queue_get_for(context, FLAGS.compute_topic, host), - {"method": "terminate_instance", - "args": {"instance_id": instance_id}}) + self._cast_compute_message('terminate_instance', context, + instance_id) else: self.db.instance_destroy(context, instance_id) @@ -274,9 +272,9 @@ class API(base.Base): """Generic handler for RPC calls to compute.""" instance = self.get(context, instance_id) host = instance['host'] - rpc.cast(context, - self.db.queue_get_for(context, FLAGS.compute_topic, host), - {'method': method, 'args': {'instance_id': instance['id']}}) + queue = self.db.queue_get_for(context, FLAGS.compute_topic, host) + kwargs = {'method': method, 'args': {'instance_id': instance['id']}} + rpc.cast(context, queue, kwargs) def snapshot(self, context, instance_id, name): """Snapshot the given instance.""" @@ -300,8 +298,7 @@ class API(base.Base): def get_actions(self, context, instance_id): """Retrieve actions for the given instance.""" - instance = self.db.instance_get_by_id(context, instance_id) - return self.db.instance_get_actions(context, instance['id']) + return self.db.instance_get_actions(context, instance_id) def suspend(self, context, instance_id): """suspend the instance with instance_id""" @@ -324,34 +321,15 @@ class API(base.Base): self._cast_compute_message('set_admin_password', context, instance_id) def lock(self, context, instance_id): - """ - lock the instance with instance_id - - """ - instance = self.get(context, instance_id) - host = instance['host'] - rpc.cast(context, - self.db.queue_get_for(context, FLAGS.compute_topic, host), - {"method": "lock_instance", - "args": {"instance_id": instance['id']}}) + """lock the instance with instance_id""" + self._cast_compute_message('lock_instance', context, instance_id) def unlock(self, context, instance_id): - """ - unlock the instance with instance_id - - """ - instance = self.get(context, instance_id) - host = instance['host'] - rpc.cast(context, - self.db.queue_get_for(context, FLAGS.compute_topic, host), - {"method": "unlock_instance", - "args": {"instance_id": instance['id']}}) + """unlock the instance with instance_id""" + self._cast_compute_message('unlock_instance', context, instance_id) def get_lock(self, context, instance_id): - """ - return the boolean state of (instance with instance_id)'s lock - - """ + """return the boolean state of (instance with instance_id)'s lock""" instance = self.get(context, instance_id) return instance['locked'] diff --git a/nova/db/api.py b/nova/db/api.py index f5c7eab91..0fa5eb1e8 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -358,11 +358,6 @@ def instance_get_by_id(context, instance_id): return IMPL.instance_get_by_id(context, instance_id) -def instance_get_by_internal_id(context, internal_id): - """Get an instance by internal id.""" - return IMPL.instance_get_by_internal_id(context, internal_id) - - def instance_is_vpn(context, instance_id): """True if instance is a vpn.""" return IMPL.instance_is_vpn(context, instance_id) diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 0e5c14275..45427597a 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -670,29 +670,6 @@ def instance_get(context, instance_id, session=None): return result -@require_context -def instance_get_by_internal_id(context, internal_id): - session = get_session() - - if is_admin_context(context): - result = session.query(models.Instance).\ - options(joinedload('security_groups')).\ - filter_by(internal_id=internal_id).\ - filter_by(deleted=can_read_deleted(context)).\ - first() - elif is_user_context(context): - result = session.query(models.Instance).\ - options(joinedload('security_groups')).\ - filter_by(project_id=context.project_id).\ - filter_by(internal_id=internal_id).\ - filter_by(deleted=False).\ - first() - if not result: - raise exception.NotFound(_('Instance %s not found') % (internal_id)) - - return result - - @require_admin_context def instance_get_all(context): session = get_session() diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 3df561e7c..210129771 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -516,6 +516,12 @@ class SimpleDH(object): the openssl binary be installed on the system on which this is run, as it uses that to handle the encryption and decryption. If openssl is not available, a RuntimeError will be raised. + + Please note that nova already uses the M2Crypto library for most + cryptographic functions, and that it includes a Diffie-Hellman + implementation. However, that is a much more complex implementation, + and is not compatible with the DH algorithm that the agent uses. Hence + the need for this 'simple' version. """ def __init__(self, prime=None, base=None, secret=None): """You can specify the values for prime and base if you wish; diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index c9428e3a6..3ea81474b 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -276,7 +276,6 @@ class XenAPISession(object): error_info)) done.send_exception(self.XenAPI.Failure(error_info)) db.instance_action_create(context.get_admin_context(), action) - except self.XenAPI.Failure, exc: logging.warn(exc) done.send_exception(*sys.exc_info()) diff --git a/plugins/xenserver/xenapi/etc/xapi.d/plugins/agent b/plugins/xenserver/xenapi/etc/xapi.d/plugins/agent index ab5b98d1c..70726bf6f 100755 --- a/plugins/xenserver/xenapi/etc/xapi.d/plugins/agent +++ b/plugins/xenserver/xenapi/etc/xapi.d/plugins/agent @@ -1,7 +1,8 @@ #!/usr/bin/env python -# Copyright (c) 2010 Citrix Systems, Inc. -# Copyright 2010 United States Government as represented by the +# Copyright (c) 2011 Citrix Systems, Inc. +# Copyright 2011 OpenStack LLC. +# Copyright 2011 United States Government as represented by the # Administrator of the National Aeronautics and Space Administration. # All Rights Reserved. # @@ -138,7 +139,8 @@ def _run_command(cmd): Otherwise, the output from stdout is returned. """ pipe = subprocess.PIPE - proc = subprocess.Popen([cmd], shell=True, stdin=pipe, stdout=pipe, stderr=pipe, close_fds=True) + proc = subprocess.Popen([cmd], shell=True, stdin=pipe, stdout=pipe, + stderr=pipe, close_fds=True) proc.wait() err = proc.stderr.read() if err: -- cgit From 5d9ad54cc38283d0b946779f4235f54370b12489 Mon Sep 17 00:00:00 2001 From: Ed Leafe Date: Fri, 7 Jan 2011 15:50:43 -0500 Subject: incorporated changes suggested by eday --- nova/api/openstack/servers.py | 9 +++------ nova/compute/api.py | 13 +++++++------ nova/compute/manager.py | 1 + 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index c71cb8562..c97be86a2 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -144,17 +144,14 @@ class Controller(wsgi.Controller): ctxt = req.environ['nova.context'] update_dict = {} - func = None if 'adminPass' in inst_dict['server']: update_dict['admin_pass'] = inst_dict['server']['adminPass'] - func = self.compute_api.set_admin_password - if 'name' in inst_dict['server']: - update_dict['display_name'] = inst_dict['server']['name'] - if func: try: - func(ctxt, id) + self.compute_api.set_admin_password(ctxt, id) except exception.TimeoutException, e: return exc.HTTPRequestTimeout() + if 'name' in inst_dict['server']: + update_dict['display_name'] = inst_dict['server']['name'] try: self.compute_api.update(ctxt, id, **update_dict) except exception.NotFound: diff --git a/nova/compute/api.py b/nova/compute/api.py index a894a0ce3..d90b59de6 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -239,8 +239,8 @@ class API(base.Base): host = instance['host'] if host: - self._cast_compute_message('terminate_instance', context, - instance_id) + self._cast_compute_message('snapshot_instance', context, + instance_id, host) else: self.db.instance_destroy(context, instance_id) @@ -268,12 +268,13 @@ class API(base.Base): project_id) return self.db.instance_get_all(context) - def _cast_compute_message(self, method, context, instance_id): + def _cast_compute_message(self, method, context, instance_id, host=None): """Generic handler for RPC calls to compute.""" - instance = self.get(context, instance_id) - host = instance['host'] + if not host: + instance = self.get(context, instance_id) + host = instance['host'] queue = self.db.queue_get_for(context, FLAGS.compute_topic, host) - kwargs = {'method': method, 'args': {'instance_id': instance['id']}} + kwargs = {'method': method, 'args': {'instance_id': instance_id}} rpc.cast(context, queue, kwargs) def snapshot(self, context, instance_id, name): diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 5d677b023..7d4a097bd 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -285,6 +285,7 @@ class ComputeManager(manager.Manager): self.driver.snapshot(instance_ref, name) @exception.wrap_exception + @checks_instance_lock def set_admin_password(self, context, instance_id, new_pass=None): """Set the root/admin password for an instance on this server.""" context = context.elevated() -- cgit From 18b8d8307d0fc008f62dd8eeeedb351a954a3471 Mon Sep 17 00:00:00 2001 From: Ed Leafe Date: Fri, 7 Jan 2011 15:51:28 -0600 Subject: removed a merge conflict line I missed before --- nova/compute/manager.py | 1 - 1 file changed, 1 deletion(-) diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 8b5646a54..21b09e443 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -37,7 +37,6 @@ terminating it. import datetime import random import string ->>>>>>> MERGE-SOURCE import functools from nova import exception -- cgit From a0ec77b597713fd9a4be5bb7b892eba4ac53e625 Mon Sep 17 00:00:00 2001 From: Ed Leafe Date: Fri, 7 Jan 2011 15:56:32 -0600 Subject: Reverted formatting change no longer necessary --- nova/api/openstack/servers.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index d8a7ca161..764c843ac 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -104,8 +104,7 @@ class Controller(wsgi.Controller): def show(self, req, id): """ Returns server details by server id """ try: - instance = self.compute_api.get( - req.environ['nova.context'], id) + instance = self.compute_api.get(req.environ['nova.context'], id) return _translate_detail_keys(instance) except exception.NotFound: return faults.Fault(exc.HTTPNotFound()) -- cgit From bae57e82767b4877bae5c2dcb6fe052291d16b32 Mon Sep 17 00:00:00 2001 From: Ed Leafe Date: Mon, 10 Jan 2011 15:33:10 -0600 Subject: Fixed issues raised by reviews --- nova/compute/api.py | 2 +- nova/virt/xenapi/vmops.py | 15 +++++++-------- plugins/xenserver/xenapi/etc/xapi.d/plugins/agent | 16 ---------------- 3 files changed, 8 insertions(+), 25 deletions(-) diff --git a/nova/compute/api.py b/nova/compute/api.py index a20dc59cb..10d7b67cf 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -297,7 +297,7 @@ class API(base.Base): host = instance['host'] if host: - self._cast_compute_message('snapshot_instance', context, + self._cast_compute_message('terminate_instance', context, instance_id, host) else: self.db.instance_destroy(context, instance_id) diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 78f35e9a7..206970c35 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -139,17 +139,19 @@ class VMOps(object): return instance_or_vm else: # Must be the instance name - instance = instance_or_vm + instance_name = instance_or_vm except (AttributeError, KeyError): # Note the the KeyError will only happen with fakes.py # Not a string; must be an ID or a vm instance if isinstance(instance_or_vm, (int, long)): - instance = instance_or_vm + ctx = context.get_admin_context() + instance_obj = db.instance_get_by_id(ctx, instance_or_vm) + instance_name = instance_obj.name else: - instance = instance_or_vm.name - vm = VMHelper.lookup(self._session, instance) + instance_name = instance_or_vm.name + vm = VMHelper.lookup(self._session, instance_name) if vm is None: - raise Exception(_('Instance not present %s') % instance) + raise Exception(_('Instance not present %s') % instance_name) return vm def snapshot(self, instance, name): @@ -378,9 +380,6 @@ class VMOps(object): rec = self._session.get_xenapi().VM.get_record(vm) args = {'dom_id': rec['domid'], 'path': path} args.update(addl_args) - # If the 'testing_mode' attribute is set, add that to the args. - if getattr(self, 'testing_mode', False): - args['testing_mode'] = 'true' try: task = self._session.async_call_plugin(plugin, method, args) ret = self._session.wait_for_task(instance_id, task) diff --git a/plugins/xenserver/xenapi/etc/xapi.d/plugins/agent b/plugins/xenserver/xenapi/etc/xapi.d/plugins/agent index 70726bf6f..82dd5466e 100755 --- a/plugins/xenserver/xenapi/etc/xapi.d/plugins/agent +++ b/plugins/xenserver/xenapi/etc/xapi.d/plugins/agent @@ -39,8 +39,6 @@ configure_logging("xenstore") import xenstore AGENT_TIMEOUT = 30 -# Used for simulating an external agent for testing -PRETEND_SECRET = 11111 def jsonify(fnc): @@ -158,13 +156,6 @@ def key_init(self, arg_dict): pub = int(arg_dict["pub"]) arg_dict["value"] = json.dumps({"name": "keyinit", "value": pub}) request_id = arg_dict["id"] - if arg_dict.get("testing_mode"): - # Pretend! - pretend = SimpleDH(secret=PRETEND_SECRET) - shared = pretend.compute_shared(pub) - # Simulate the agent's response - ret = {"returncode": "D0", "message": pretend.get_public(), "shared": shared} - return ret arg_dict["path"] = "data/host/%s" % request_id xenstore.write_record(self, arg_dict) try: @@ -184,13 +175,6 @@ def password(self, arg_dict): """ pub = int(arg_dict["pub"]) enc_pass = arg_dict["enc_pass"] - if arg_dict.get("testing_mode"): - # Decrypt the password, and send it back to verify - pretend = SimpleDH(secret=PRETEND_SECRET) - pretend.compute_shared(pub) - pw = pretend.decrypt(enc_pass) - ret = {"returncode": "0", "message": pw} - return ret arg_dict["value"] = json.dumps({"name": "password", "value": enc_pass}) request_id = arg_dict["id"] arg_dict["path"] = "data/host/%s" % request_id -- cgit From d91a06b4fea7e45fd2e9abe35803cd9deb5d8e92 Mon Sep 17 00:00:00 2001 From: Ed Leafe Date: Tue, 11 Jan 2011 12:17:39 -0600 Subject: Removed unneeded SimpleDH code from agent plugin. Improved handling of plugin call failures. --- nova/virt/xenapi/vmops.py | 14 +++- plugins/xenserver/xenapi/etc/xapi.d/plugins/agent | 95 ----------------------- 2 files changed, 12 insertions(+), 97 deletions(-) diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 206970c35..c10943aa1 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -220,6 +220,9 @@ class VMOps(object): dh = SimpleDH() args = {'id': transaction_id, 'pub': str(dh.get_public())} resp = self._make_agent_call('key_init', instance, '', args) + if resp is None: + # No response from the agent + return resp_dict = json.loads(resp) # Successful return code from key_init is 'D0' if resp_dict['returncode'] != 'D0': @@ -232,6 +235,9 @@ class VMOps(object): # Send the encrypted password args['enc_pass'] = enc_pass resp = self._make_agent_call('password', instance, '', args) + if resp is None: + # No response from the agent + return resp_dict = json.loads(resp) # Successful return code from password is '0' if resp_dict['returncode'] != '0': @@ -384,12 +390,16 @@ class VMOps(object): task = self._session.async_call_plugin(plugin, method, args) ret = self._session.wait_for_task(instance_id, task) except self.XenAPI.Failure, e: + ret = None err_trace = e.details[-1] err_msg = err_trace.splitlines()[-1] + strargs = str(args) if 'TIMEOUT:' in err_msg: - raise exception.TimeoutException(err_msg) + LOG.error(_('TIMEOUT: The call to %(method)s timed out. ' + 'VM id=%(instance_id)s; args=%(strargs)s') % locals()) else: - raise RuntimeError("%s" % e.details[-1]) + LOG.error(_('The call to %(method)s returned an error: %(e)s. ' + 'VM id=%(instance_id)s; args=%(strargs)s') % locals()) return ret def add_to_xenstore(self, vm, path, key, value): diff --git a/plugins/xenserver/xenapi/etc/xapi.d/plugins/agent b/plugins/xenserver/xenapi/etc/xapi.d/plugins/agent index 82dd5466e..12c3a19c8 100755 --- a/plugins/xenserver/xenapi/etc/xapi.d/plugins/agent +++ b/plugins/xenserver/xenapi/etc/xapi.d/plugins/agent @@ -51,101 +51,6 @@ class TimeoutError(StandardError): pass -class SimpleDH(object): - """This class wraps all the functionality needed to implement - basic Diffie-Hellman-Merkle key exchange in Python. It features - intelligent defaults for the prime and base numbers needed for the - calculation, while allowing you to supply your own. It requires that - the openssl binary be installed on the system on which this is run, - as it uses that to handle the encryption and decryption. If openssl - is not available, a RuntimeError will be raised. - - Please note that nova already uses the M2Crypto library for most - cryptographic functions, and that it includes a Diffie-Hellman - implementation. However, that is a much more complex implementation, - and is not compatible with the DH algorithm that the agent uses. Hence - the need for this 'simple' version. - """ - def __init__(self, prime=None, base=None, secret=None): - """You can specify the values for prime and base if you wish; - otherwise, reasonable default values will be used. - """ - if prime is None: - self._prime = 162259276829213363391578010288127 - else: - self._prime = prime - if base is None: - self._base = 5 - else: - self._base = base - if secret is None: - self._secret = random.randint(5000, 15000) - else: - self._secret = secret - self._shared = self._public = None - - def get_public(self): - """Return the public key""" - self._public = (self._base ** self._secret) % self._prime - return self._public - - def compute_shared(self, other): - """Given the other end's public key, compute the - shared secret. - """ - self._shared = (other ** self._secret) % self._prime - return self._shared - - def _run_ssl(self, text, which): - """The encryption/decryption methods require running the openssl - installed on the system. This method abstracts out the common - code required. - """ - base_cmd = ("cat %(tmpfile)s | openssl enc -aes-128-cbc " - "-a -pass pass:%(shared)s -nosalt %(dec_flag)s") - if which.lower()[0] == "d": - dec_flag = " -d" - else: - dec_flag = "" - # Note: instead of using 'cat' and a tempfile, it is also - # possible to just 'echo' the value. However, we can not assume - # that the value is 'safe'; i.e., it may contain semi-colons, - # octothorpes, or other characters that would not be allowed - # in an 'echo' construct. - fd, tmpfile = tempfile.mkstemp() - os.close(fd) - file(tmpfile, "w").write(text) - shared = self._shared - cmd = base_cmd % locals() - try: - return _run_command(cmd) - except PluginError, e: - raise RuntimeError("OpenSSL error: %s" % e) - - def encrypt(self, text): - """Uses the shared key to encrypt the given text.""" - return self._run_ssl(text, "enc") - - def decrypt(self, text): - """Uses the shared key to decrypt the given text.""" - return self._run_ssl(text, "dec") - - -def _run_command(cmd): - """Abstracts out the basics of issuing system commands. If the command - returns anything in stderr, a PluginError is raised with that information. - Otherwise, the output from stdout is returned. - """ - pipe = subprocess.PIPE - proc = subprocess.Popen([cmd], shell=True, stdin=pipe, stdout=pipe, - stderr=pipe, close_fds=True) - proc.wait() - err = proc.stderr.read() - if err: - raise PluginError(err) - return proc.stdout.read() - - @jsonify def key_init(self, arg_dict): """Handles the Diffie-Hellman key exchange with the agent to -- cgit From 7af8f5ac5fc02abe79dec3cf3651b6f0a9deb78c Mon Sep 17 00:00:00 2001 From: Ed Leafe Date: Thu, 13 Jan 2011 10:51:31 -0600 Subject: Minor code cleanups --- nova/compute/manager.py | 2 +- nova/tests/test_xenapi.py | 4 ---- nova/virt/xenapi/vmops.py | 1 - 3 files changed, 1 insertion(+), 6 deletions(-) diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 267beca45..613ee45f6 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -125,7 +125,7 @@ class ComputeManager(manager.Manager): # FIXME(ja): include other fields from state? instance_ref = self.db.instance_get(context, instance_id) try: - info = self.driver.get_info(instance_ref) + info = self.driver.get_info(instance_ref['name']) state = info['state'] except exception.NotFound: state = power_state.NOSTATE diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py index a03d616ad..93afe9ce1 100644 --- a/nova/tests/test_xenapi.py +++ b/nova/tests/test_xenapi.py @@ -265,10 +265,6 @@ class XenAPIVMTestCase(test.TestCase): return instance - - - - class XenAPIDiffieHellmanTestCase(test.TestCase): """ Unit tests for Diffie-Hellman code diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 6c6d25709..6e359ef82 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -22,7 +22,6 @@ Management class for VM-related functions (spawn, reboot, etc). import json import M2Crypto import os -import random import subprocess import tempfile import uuid -- cgit From 22b21cde84f200f6fd45ba5f2cfcb6a54e595f1b Mon Sep 17 00:00:00 2001 From: Ed Leafe Date: Thu, 13 Jan 2011 10:52:28 -0600 Subject: Minor code cleanups --- nova/tests/test_xenapi.py | 2 +- nova/virt/xenapi/vmops.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py index 93afe9ce1..261ee0fde 100644 --- a/nova/tests/test_xenapi.py +++ b/nova/tests/test_xenapi.py @@ -286,6 +286,6 @@ class XenAPIDiffieHellmanTestCase(test.TestCase): enc = self.alice.encrypt(msg) dec = self.bob.decrypt(enc) self.assertEquals(dec, msg) - + def tearDown(self): super(XenAPIDiffieHellmanTestCase, self).tearDown() diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 6e359ef82..6c6d25709 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -22,6 +22,7 @@ Management class for VM-related functions (spawn, reboot, etc). import json import M2Crypto import os +import random import subprocess import tempfile import uuid -- cgit From 01a1ad3d2cdf61c73ca3ab7aa14e82f0e4450103 Mon Sep 17 00:00:00 2001 From: Ed Leafe Date: Thu, 13 Jan 2011 10:53:13 -0600 Subject: Minor code cleanups --- nova/virt/xenapi/vmops.py | 1 - 1 file changed, 1 deletion(-) diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 6c6d25709..6e359ef82 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -22,7 +22,6 @@ Management class for VM-related functions (spawn, reboot, etc). import json import M2Crypto import os -import random import subprocess import tempfile import uuid -- cgit