Skip to content

Commit

Permalink
Make XenAPI agent configuration synchronous
Browse files Browse the repository at this point in the history
Fixes bug 897887

Much of the XenAPI agent configuration happens asynchronously from the
spawn process. This allows the instance to go ACTIVE state earlier and
leaves the possibility of a race with delete open. This makes the
steps synchronous, ensuring the process only goes to ACTIVE after the
configuration is done.

Change-Id: I294b595128979e153c797d9e610b66bc76f47666
  • Loading branch information
Johannes Erdfelt committed Dec 15, 2011
1 parent c421328 commit d58c2d2
Show file tree
Hide file tree
Showing 2 changed files with 66 additions and 76 deletions.
5 changes: 3 additions & 2 deletions nova/tests/test_xenapi.py
Expand Up @@ -188,7 +188,7 @@ def tearDown(self):
self.stubs.UnsetAll()


def reset_network(*args):
def configure_instance(*args):
pass


Expand All @@ -214,7 +214,8 @@ def setUp(self):
stubs.stubout_get_this_vm_uuid(self.stubs)
stubs.stubout_stream_disk(self.stubs)
stubs.stubout_is_vdi_pv(self.stubs)
self.stubs.Set(vmops.VMOps, 'reset_network', reset_network)
self.stubs.Set(vmops.VMOps, '_configure_instance',
configure_instance)
self.stubs.Set(vmops.VMOps, '_find_rescue_vbd_ref',
_find_rescue_vbd_ref)
stubs.stub_out_vm_methods(self.stubs)
Expand Down
137 changes: 63 additions & 74 deletions nova/virt/xenapi/vmops.py
Expand Up @@ -29,6 +29,7 @@
import time
import uuid

from eventlet import greenthread
import M2Crypto

from nova.compute import api as compute
Expand All @@ -55,6 +56,9 @@
flags.DEFINE_integer('agent_version_timeout', 300,
'number of seconds to wait for agent to be fully '
'operational')
flags.DEFINE_integer('xenapi_running_timeout', 60,
'number of seconds to wait for instance to go to '
'running state')
flags.DEFINE_string('xenapi_vif_driver',
'nova.virt.xenapi.vif.XenAPIBridgeDriver',
'The XenAPI VIF driver using XenServer Network APIs.')
Expand Down Expand Up @@ -383,6 +387,42 @@ def _attach_disks(self, instance, disk_image_type, vm_ref, first_vdi_ref,
bootable=False)
userdevice += 1

def _configure_instance(self, ctx, instance, vm_ref):
# Inject files, if necessary
injected_files = instance.injected_files
if injected_files:
# Check if this is a JSON-encoded string and convert if needed.
if isinstance(injected_files, basestring):
try:
injected_files = json.loads(injected_files)
except ValueError:
LOG.exception(
_("Invalid value for injected_files: '%s'")
% injected_files)
injected_files = []
# Inject any files, if specified
for path, contents in instance.injected_files:
LOG.debug(_("Injecting file path: '%s'") % path)
self.inject_file(instance, path, contents)

# Set admin password, if necessary
admin_password = instance.admin_pass
if admin_password:
LOG.debug(_("Setting admin password"))
self.set_admin_password(instance, admin_password)

# Reset network config
LOG.debug(_("Resetting network"))
self.reset_network(instance, vm_ref)

# Set VCPU weight
inst_type = db.instance_type_get(ctx, instance.instance_type_id)
vcpu_weight = inst_type["vcpu_weight"]
if str(vcpu_weight) != "None":
LOG.debug(_("Setting VCPU weight"))
self._session.call_xenapi("VM.add_to_VCPUs_params", vm_ref,
"weight", vcpu_weight)

def _spawn(self, instance, vm_ref):
"""Spawn a new instance."""
LOG.debug(_('Starting VM %s...'), vm_ref)
Expand All @@ -404,83 +444,32 @@ def _spawn(self, instance, vm_ref):
'os': instance.os_type,
'architecture': instance.architecture})

def _check_agent_version():
LOG.debug(_("Querying agent version"))
# Wait for boot to finish
LOG.debug(_('Instance %s: waiting for running'), instance_name)
expiration = time.time() + FLAGS.xenapi_running_timeout
while time.time() < expiration:
state = self.get_info(instance_name)['state']
if state == power_state.RUNNING:
break

greenthread.sleep(0.5)

version = self.get_agent_version(instance)
if not version:
return
LOG.debug(_('Instance %s: running'), instance_name)

# Update agent, if necessary
# This also waits until the agent starts
LOG.debug(_("Querying agent version"))
version = self.get_agent_version(instance)
if version:
LOG.info(_('Instance agent version: %s') % version)
if not agent_build:
return

if cmp_version(version, agent_build['version']) < 0:
LOG.info(_('Updating Agent to %s') % agent_build['version'])
self.agent_update(instance, agent_build['url'],
agent_build['md5hash'])

def _inject_files():
injected_files = instance.injected_files
if injected_files:
# Check if this is a JSON-encoded string and convert if needed.
if isinstance(injected_files, basestring):
try:
injected_files = json.loads(injected_files)
except ValueError:
LOG.exception(
_("Invalid value for injected_files: '%s'")
% injected_files)
injected_files = []
# Inject any files, if specified
for path, contents in instance.injected_files:
LOG.debug(_("Injecting file path: '%s'") % path)
self.inject_file(instance, path, contents)

def _set_admin_password():
admin_password = instance.admin_pass
if admin_password:
LOG.debug(_("Setting admin password"))
self.set_admin_password(instance, admin_password)

def _reset_network():
LOG.debug(_("Resetting network"))
self.reset_network(instance, vm_ref)

def _set_vcpu_weight():
inst_type = db.instance_type_get(ctx, instance.instance_type_id)
vcpu_weight = inst_type["vcpu_weight"]
if str(vcpu_weight) != "None":
LOG.debug(_("Setting VCPU weight"))
self._session.call_xenapi("VM.add_to_VCPUs_params", vm_ref,
"weight", vcpu_weight)

# NOTE(armando): Do we really need to do this in virt?
# NOTE(tr3buchet): not sure but wherever we do it, we need to call
# reset_network afterwards
timer = utils.LoopingCall(f=None)

def _wait_for_boot():
try:
state = self.get_info(instance_name)['state']
if state == power_state.RUNNING:
LOG.debug(_('Instance %s: booted'), instance_name)
timer.stop()
_check_agent_version()
_inject_files()
_set_admin_password()
_reset_network()
_set_vcpu_weight()
return True
except Exception, exc:
LOG.warn(exc)
LOG.exception(_('Instance %s: failed to boot'), instance_name)
timer.stop()
return False

timer.f = _wait_for_boot

return timer.start(interval=0.5, now=True)

if version and agent_build and \
cmp_version(version, agent_build['version']) < 0:
LOG.info(_('Updating Agent to %s') % agent_build['version'])
self.agent_update(instance, agent_build['url'],
agent_build['md5hash'])

self._configure_instance(ctx, instance, vm_ref)

def _handle_spawn_error(self, vdis, spawn_error):
# Extract resource list from spawn_error.
Expand Down

0 comments on commit d58c2d2

Please sign in to comment.