summaryrefslogtreecommitdiffstats
path: root/nova
diff options
context:
space:
mode:
authorMate Lakat <mate.lakat@citrix.com>2012-10-26 16:00:14 +0100
committerMate Lakat <mate.lakat@citrix.com>2012-10-26 17:55:47 +0100
commitf25c2d873014eb7d665e53dbb98ee80d9a424f6b (patch)
treeaf3d8857b4a9cb93922fa5aa42eb193a370392a5 /nova
parent50a0f3efb6cc4ae9c94439eea346828d35080d6c (diff)
downloadnova-f25c2d873014eb7d665e53dbb98ee80d9a424f6b.tar.gz
nova-f25c2d873014eb7d665e53dbb98ee80d9a424f6b.tar.xz
nova-f25c2d873014eb7d665e53dbb98ee80d9a424f6b.zip
xenapi: refactor: Agent class
Related to blueprint xenapi-config-drive As config drive and agent are doing similar operations, part of the config drive introduction, we would like to make the agent optional. This refactor is introducing an agent class holding together the agent operations, as a first step. The steps were: Introduce XenAPIBasedAgent Added constructor parameters to XenAPIBasedAgent get rid of parameters on get_agent_version get rid of parameters on agent_update get rid of parameters on set_admin_password get rid of parameters on inject_file get rid of parameters on resetnetwork Correct newlines add factory method for getting an agent instance For the small changes, see: https://github.com/citrix-openstack/nova/commits/agent2class Change-Id: I3928c0776ff98c830c19487eb8ff7820e41ca025
Diffstat (limited to 'nova')
-rw-r--r--nova/virt/xenapi/agent.py221
-rw-r--r--nova/virt/xenapi/vmops.py27
2 files changed, 130 insertions, 118 deletions
diff --git a/nova/virt/xenapi/agent.py b/nova/virt/xenapi/agent.py
index e3288a47b..0c17dccff 100644
--- a/nova/virt/xenapi/agent.py
+++ b/nova/virt/xenapi/agent.py
@@ -115,121 +115,128 @@ def _get_agent_version(session, instance, vm_ref):
return resp['message'].replace('\\r\\n', '')
-def get_agent_version(session, instance, vm_ref):
- """Get the version of the agent running on the VM instance."""
+class XenAPIBasedAgent(object):
+ def __init__(self, session, instance, vm_ref):
+ self.session = session
+ self.instance = instance
+ self.vm_ref = vm_ref
- LOG.debug(_('Querying agent version'), instance=instance)
+ def get_agent_version(self):
+ """Get the version of the agent running on the VM instance."""
- # The agent can be slow to start for a variety of reasons. On Windows,
- # it will generally perform a setup process on first boot that can
- # take a couple of minutes and then reboot. On Linux, the system can
- # also take a while to boot. So we need to be more patient than
- # normal as well as watch for domid changes
+ LOG.debug(_('Querying agent version'), instance=self.instance)
- expiration = time.time() + FLAGS.agent_version_timeout
- while time.time() < expiration:
- ret = _get_agent_version(session, instance, vm_ref)
- if ret:
- return ret
+ # The agent can be slow to start for a variety of reasons. On Windows,
+ # it will generally perform a setup process on first boot that can
+ # take a couple of minutes and then reboot. On Linux, the system can
+ # also take a while to boot. So we need to be more patient than
+ # normal as well as watch for domid changes
- LOG.info(_('Reached maximum time attempting to query agent version'),
- instance=instance)
+ expiration = time.time() + FLAGS.agent_version_timeout
+ while time.time() < expiration:
+ ret = _get_agent_version(self.session, self.instance, self.vm_ref)
+ if ret:
+ return ret
- return None
+ LOG.info(_('Reached maximum time attempting to query agent version'),
+ instance=self.instance)
-
-def agent_update(session, instance, vm_ref, agent_build):
- """Update agent on the VM instance."""
-
- LOG.info(_('Updating agent to %s'), agent_build['version'],
- instance=instance)
-
- # Send the encrypted password
- args = {'url': agent_build['url'], 'md5sum': agent_build['md5hash']}
- resp = _call_agent(session, instance, vm_ref, 'agentupdate', args)
- if resp['returncode'] != '0':
- LOG.error(_('Failed to update agent: %(resp)r'), locals(),
- instance=instance)
- return None
- return resp['message']
-
-
-def set_admin_password(session, instance, vm_ref, 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 a more advanced
- library (such as M2Crypto) for compatibility with the agent code.
- """
- LOG.debug(_('Setting admin password'), instance=instance)
-
- dh = SimpleDH()
-
- # Exchange keys
- args = {'pub': str(dh.get_public())}
- resp = _call_agent(session, instance, vm_ref, 'key_init', args)
-
- # Successful return code from key_init is 'D0'
- if resp['returncode'] != 'D0':
- msg = _('Failed to exchange keys: %(resp)r') % locals()
- LOG.error(msg, instance=instance)
- raise Exception(msg)
-
- # Some old versions of the Windows agent have a trailing \\r\\n
- # (ie CRLF escaped) for some reason. Strip that off.
- agent_pub = int(resp['message'].replace('\\r\\n', ''))
- dh.compute_shared(agent_pub)
-
- # Some old versions of Linux and Windows agent expect trailing \n
- # on password to work correctly.
- enc_pass = dh.encrypt(new_pass + '\n')
-
- # Send the encrypted password
- args = {'enc_pass': enc_pass}
- resp = _call_agent(session, instance, vm_ref, 'password', args)
-
- # Successful return code from password is '0'
- if resp['returncode'] != '0':
- msg = _('Failed to update password: %(resp)r') % locals()
- LOG.error(msg, instance=instance)
- raise Exception(msg)
-
- return resp['message']
-
-
-def inject_file(session, instance, vm_ref, path, contents):
- LOG.debug(_('Injecting file path: %r'), path, instance=instance)
-
- # Files/paths must be base64-encoded for transmission to agent
- b64_path = base64.b64encode(path)
- b64_contents = base64.b64encode(contents)
-
- args = {'b64_path': b64_path, 'b64_contents': b64_contents}
-
- # If the agent doesn't support file injection, a NotImplementedError
- # will be raised with the appropriate message.
- resp = _call_agent(session, instance, vm_ref, 'inject_file', args)
- if resp['returncode'] != '0':
- LOG.error(_('Failed to inject file: %(resp)r'), locals(),
- instance=instance)
- return None
-
- return resp['message']
-
-
-def resetnetwork(session, instance, vm_ref):
- LOG.debug(_('Resetting network'), instance=instance)
-
- resp = _call_agent(session, instance, vm_ref, 'resetnetwork',
- timeout=FLAGS.agent_resetnetwork_timeout)
- if resp['returncode'] != '0':
- LOG.error(_('Failed to reset network: %(resp)r'), locals(),
- instance=instance)
return None
- return resp['message']
+ def agent_update(self, agent_build):
+ """Update agent on the VM instance."""
+
+ LOG.info(_('Updating agent to %s'), agent_build['version'],
+ instance=self.instance)
+
+ # Send the encrypted password
+ args = {'url': agent_build['url'], 'md5sum': agent_build['md5hash']}
+ resp = _call_agent(
+ self.session, self.instance, self.vm_ref, 'agentupdate', args)
+ if resp['returncode'] != '0':
+ LOG.error(_('Failed to update agent: %(resp)r'), locals(),
+ instance=self.instance)
+ return None
+ return resp['message']
+
+ def set_admin_password(self, 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 a more advanced
+ library (such as M2Crypto) for compatibility with the agent code.
+ """
+ LOG.debug(_('Setting admin password'), instance=self.instance)
+
+ dh = SimpleDH()
+
+ # Exchange keys
+ args = {'pub': str(dh.get_public())}
+ resp = _call_agent(
+ self.session, self.instance, self.vm_ref, 'key_init', args)
+
+ # Successful return code from key_init is 'D0'
+ if resp['returncode'] != 'D0':
+ msg = _('Failed to exchange keys: %(resp)r') % locals()
+ LOG.error(msg, instance=self.instance)
+ raise Exception(msg)
+
+ # Some old versions of the Windows agent have a trailing \\r\\n
+ # (ie CRLF escaped) for some reason. Strip that off.
+ agent_pub = int(resp['message'].replace('\\r\\n', ''))
+ dh.compute_shared(agent_pub)
+
+ # Some old versions of Linux and Windows agent expect trailing \n
+ # on password to work correctly.
+ enc_pass = dh.encrypt(new_pass + '\n')
+
+ # Send the encrypted password
+ args = {'enc_pass': enc_pass}
+ resp = _call_agent(
+ self.session, self.instance, self.vm_ref, 'password', args)
+
+ # Successful return code from password is '0'
+ if resp['returncode'] != '0':
+ msg = _('Failed to update password: %(resp)r') % locals()
+ LOG.error(msg, instance=self.instance)
+ raise Exception(msg)
+
+ return resp['message']
+
+ def inject_file(self, path, contents):
+ LOG.debug(_('Injecting file path: %r'), path, instance=self.instance)
+
+ # Files/paths must be base64-encoded for transmission to agent
+ b64_path = base64.b64encode(path)
+ b64_contents = base64.b64encode(contents)
+
+ args = {'b64_path': b64_path, 'b64_contents': b64_contents}
+
+ # If the agent doesn't support file injection, a NotImplementedError
+ # will be raised with the appropriate message.
+ resp = _call_agent(
+ self.session, self.instance, self.vm_ref, 'inject_file', args)
+ if resp['returncode'] != '0':
+ LOG.error(_('Failed to inject file: %(resp)r'), locals(),
+ instance=self.instance)
+ return None
+
+ return resp['message']
+
+ def resetnetwork(self):
+ LOG.debug(_('Resetting network'), instance=self.instance)
+
+ resp = _call_agent(
+ self.session, self.instance, self.vm_ref, 'resetnetwork',
+ timeout=FLAGS.agent_resetnetwork_timeout)
+ if resp['returncode'] != '0':
+ LOG.error(_('Failed to reset network: %(resp)r'), locals(),
+ instance=self.instance)
+ return None
+
+ return resp['message']
def find_guest_agent(base_dir):
diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py
index 469892ae8..ac00be6d7 100644
--- a/nova/virt/xenapi/vmops.py
+++ b/nova/virt/xenapi/vmops.py
@@ -42,7 +42,7 @@ from nova.openstack.common import log as logging
from nova.openstack.common import timeutils
from nova import utils
from nova.virt import firewall
-from nova.virt.xenapi import agent
+from nova.virt.xenapi import agent as xapi_agent
from nova.virt.xenapi import pool_states
from nova.virt.xenapi import vm_utils
from nova.virt.xenapi import volume_utils
@@ -156,6 +156,9 @@ class VMOps(object):
self.vif_driver = vif_impl(xenapi_session=self._session)
self.default_root_dev = '/dev/sda'
+ def _get_agent(self, instance, vm_ref):
+ return xapi_agent.XenAPIBasedAgent(self._session, instance, vm_ref)
+
def list_instances(self):
"""List VM instances."""
# TODO(justinsb): Should we just always use the details method?
@@ -514,14 +517,15 @@ class VMOps(object):
# Update agent, if necessary
# This also waits until the agent starts
- version = agent.get_agent_version(self._session, instance, vm_ref)
+ agent = self._get_agent(instance, vm_ref)
+ version = agent.get_agent_version()
if version:
LOG.info(_('Instance agent version: %s'), version,
instance=instance)
if (version and agent_build and
cmp_version(version, agent_build['version']) < 0):
- agent.agent_update(self._session, instance, vm_ref, agent_build)
+ agent.agent_update(agent_build)
# if the guest agent is not available, configure the
# instance, but skip the admin password configuration
@@ -531,16 +535,14 @@ class VMOps(object):
if injected_files:
# Inject any files, if specified
for path, contents in injected_files:
- agent.inject_file(self._session, instance, vm_ref,
- path, contents)
+ agent.inject_file(path, contents)
# Set admin password, if necessary
if admin_password and not no_agent:
- agent.set_admin_password(self._session, instance, vm_ref,
- admin_password)
+ agent.set_admin_password(admin_password)
# Reset network config
- agent.resetnetwork(self._session, instance, vm_ref)
+ agent.resetnetwork()
# Set VCPU weight
inst_type = db.instance_type_get(ctx, instance['instance_type_id'])
@@ -834,12 +836,14 @@ class VMOps(object):
def set_admin_password(self, instance, new_pass):
"""Set the root/admin password on the VM instance."""
vm_ref = self._get_vm_opaque_ref(instance)
- agent.set_admin_password(self._session, instance, vm_ref, new_pass)
+ agent = self._get_agent(instance, vm_ref)
+ agent.set_admin_password(new_pass)
def inject_file(self, instance, path, contents):
"""Write a file to the VM instance."""
vm_ref = self._get_vm_opaque_ref(instance)
- agent.inject_file(self._session, instance, vm_ref, path, contents)
+ agent = self._get_agent(instance, vm_ref)
+ agent.inject_file(path, contents)
@staticmethod
def _sanitize_xenstore_key(key):
@@ -1384,7 +1388,8 @@ class VMOps(object):
def reset_network(self, instance):
"""Calls resetnetwork method in agent."""
vm_ref = self._get_vm_opaque_ref(instance)
- agent.resetnetwork(self._session, instance, vm_ref)
+ agent = self._get_agent(instance, vm_ref)
+ agent.resetnetwork()
def inject_hostname(self, instance, vm_ref, hostname):
"""Inject the hostname of the instance into the xenstore."""