This branch adds support to the xenapi driver for updating the guest agent on creation of a new instance. This ensures that the guest agent is running the latest code before nova starts configuring networking, setting root password or injecting files.

This commit is contained in:
Johannes Erdfelt 2011-06-21 16:43:10 +00:00 committed by Tarmac
commit 29e2d55e9d
13 changed files with 430 additions and 16 deletions

View File

@ -1071,6 +1071,70 @@ class ImageCommands(object):
self._convert_images(machine_images) self._convert_images(machine_images)
class AgentBuildCommands(object):
"""Class for managing agent builds."""
def create(self, os, architecture, version, url, md5hash,
hypervisor='xen'):
"""Creates a new agent build.
arguments: os architecture version url md5hash [hypervisor='xen']"""
ctxt = context.get_admin_context()
agent_build = db.agent_build_create(ctxt,
{'hypervisor': hypervisor,
'os': os,
'architecture': architecture,
'version': version,
'url': url,
'md5hash': md5hash})
def delete(self, os, architecture, hypervisor='xen'):
"""Deletes an existing agent build.
arguments: os architecture [hypervisor='xen']"""
ctxt = context.get_admin_context()
agent_build_ref = db.agent_build_get_by_triple(ctxt,
hypervisor, os, architecture)
db.agent_build_destroy(ctxt, agent_build_ref['id'])
def list(self, hypervisor=None):
"""Lists all agent builds.
arguments: <none>"""
fmt = "%-10s %-8s %12s %s"
ctxt = context.get_admin_context()
by_hypervisor = {}
for agent_build in db.agent_build_get_all(ctxt):
buildlist = by_hypervisor.get(agent_build.hypervisor)
if not buildlist:
buildlist = by_hypervisor[agent_build.hypervisor] = []
buildlist.append(agent_build)
for key, buildlist in by_hypervisor.iteritems():
if hypervisor and key != hypervisor:
continue
print "Hypervisor: %s" % key
print fmt % ('-' * 10, '-' * 8, '-' * 12, '-' * 32)
for agent_build in buildlist:
print fmt % (agent_build.os, agent_build.architecture,
agent_build.version, agent_build.md5hash)
print ' %s' % agent_build.url
print
def modify(self, os, architecture, version, url, md5hash,
hypervisor='xen'):
"""Update an existing agent build.
arguments: os architecture version url md5hash [hypervisor='xen']
"""
ctxt = context.get_admin_context()
agent_build_ref = db.agent_build_get_by_triple(ctxt,
hypervisor, os, architecture)
db.agent_build_update(ctxt, agent_build_ref['id'],
{'version': version,
'url': url,
'md5hash': md5hash})
class ConfigCommands(object): class ConfigCommands(object):
"""Class for exposing the flags defined by flag_file(s).""" """Class for exposing the flags defined by flag_file(s)."""
@ -1083,6 +1147,7 @@ class ConfigCommands(object):
CATEGORIES = [ CATEGORIES = [
('account', AccountCommands), ('account', AccountCommands),
('agent', AgentBuildCommands),
('config', ConfigCommands), ('config', ConfigCommands),
('db', DbCommands), ('db', DbCommands),
('fixed', FixedIpCommands), ('fixed', FixedIpCommands),

View File

@ -178,6 +178,9 @@ class API(base.Base):
os_type = None os_type = None
if 'properties' in image and 'os_type' in image['properties']: if 'properties' in image and 'os_type' in image['properties']:
os_type = image['properties']['os_type'] os_type = image['properties']['os_type']
architecture = None
if 'properties' in image and 'arch' in image['properties']:
architecture = image['properties']['arch']
vm_mode = None vm_mode = None
if 'properties' in image and 'vm_mode' in image['properties']: if 'properties' in image and 'vm_mode' in image['properties']:
vm_mode = image['properties']['vm_mode'] vm_mode = image['properties']['vm_mode']
@ -243,6 +246,7 @@ class API(base.Base):
'metadata': metadata, 'metadata': metadata,
'availability_zone': availability_zone, 'availability_zone': availability_zone,
'os_type': os_type, 'os_type': os_type,
'architecture': architecture,
'vm_mode': vm_mode} 'vm_mode': vm_mode}
return (num_instances, base_options, security_groups) return (num_instances, base_options, security_groups)

View File

@ -560,6 +560,24 @@ class ComputeManager(manager.SchedulerDependentManager):
LOG.audit(msg) LOG.audit(msg)
self.driver.inject_file(instance_ref, path, file_contents) self.driver.inject_file(instance_ref, path, file_contents)
@exception.wrap_exception
@checks_instance_lock
def agent_update(self, context, instance_id, url, md5hash):
"""Update agent running on an instance on this host."""
context = context.elevated()
instance_ref = self.db.instance_get(context, instance_id)
instance_id = instance_ref['id']
instance_state = instance_ref['state']
expected_state = power_state.RUNNING
if instance_state != expected_state:
LOG.warn(_('trying to update agent on a non-running '
'instance: %(instance_id)s (state: %(instance_state)s '
'expected: %(expected_state)s)') % locals())
nm = instance_ref['name']
msg = _('instance %(nm)s: updating agent to %(url)s') % locals()
LOG.audit(msg)
self.driver.agent_update(instance_ref, url, md5hash)
@exception.wrap_exception @exception.wrap_exception
@checks_instance_lock @checks_instance_lock
def rescue_instance(self, context, instance_id): def rescue_instance(self, context, instance_id):

View File

@ -1287,3 +1287,32 @@ def instance_metadata_delete(context, instance_id, key):
def instance_metadata_update_or_create(context, instance_id, metadata): def instance_metadata_update_or_create(context, instance_id, metadata):
"""Create or update instance metadata.""" """Create or update instance metadata."""
IMPL.instance_metadata_update_or_create(context, instance_id, metadata) IMPL.instance_metadata_update_or_create(context, instance_id, metadata)
####################
def agent_build_create(context, values):
"""Create a new agent build entry."""
return IMPL.agent_build_create(context, values)
def agent_build_get_by_triple(context, hypervisor, os, architecture):
"""Get agent build by hypervisor/OS/architecture triple."""
return IMPL.agent_build_get_by_triple(context, hypervisor, os,
architecture)
def agent_build_get_all(context):
"""Get all agent builds."""
return IMPL.agent_build_get_all(context)
def agent_build_destroy(context, agent_update_id):
"""Destroy agent build entry."""
IMPL.agent_build_destroy(context, agent_update_id)
def agent_build_update(context, agent_build_id, values):
"""Update agent build entry."""
IMPL.agent_build_update(context, agent_build_id, values)

View File

@ -2792,3 +2792,54 @@ def instance_metadata_update_or_create(context, instance_id, metadata):
meta_ref.save(session=session) meta_ref.save(session=session)
return metadata return metadata
@require_admin_context
def agent_build_create(context, values):
agent_build_ref = models.AgentBuild()
agent_build_ref.update(values)
agent_build_ref.save()
return agent_build_ref
@require_admin_context
def agent_build_get_by_triple(context, hypervisor, os, architecture,
session=None):
if not session:
session = get_session()
return session.query(models.AgentBuild).\
filter_by(hypervisor=hypervisor).\
filter_by(os=os).\
filter_by(architecture=architecture).\
filter_by(deleted=False).\
first()
@require_admin_context
def agent_build_get_all(context):
session = get_session()
return session.query(models.AgentBuild).\
filter_by(deleted=False).\
all()
@require_admin_context
def agent_build_destroy(context, agent_build_id):
session = get_session()
with session.begin():
session.query(models.AgentBuild).\
filter_by(id=agent_build_id).\
update({'deleted': 1,
'deleted_at': datetime.datetime.utcnow(),
'updated_at': literal_column('updated_at')})
@require_admin_context
def agent_build_update(context, agent_build_id, values):
session = get_session()
with session.begin():
agent_build_ref = session.query(models.AgentBuild).\
filter_by(id=agent_build_id). \
first()
agent_build_ref.update(values)
agent_build_ref.save(session=session)

View File

@ -0,0 +1,73 @@
# Copyright 2011 OpenStack LLC.
# 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.
from sqlalchemy import Boolean, Column, DateTime, Integer
from sqlalchemy import MetaData, String, Table
from nova import log as logging
meta = MetaData()
#
# New Tables
#
builds = Table('agent_builds', meta,
Column('created_at', DateTime(timezone=False)),
Column('updated_at', DateTime(timezone=False)),
Column('deleted_at', DateTime(timezone=False)),
Column('deleted', Boolean(create_constraint=True, name=None)),
Column('id', Integer(), primary_key=True, nullable=False),
Column('hypervisor',
String(length=255, convert_unicode=False, assert_unicode=None,
unicode_error=None, _warn_on_bytestring=False)),
Column('os',
String(length=255, convert_unicode=False, assert_unicode=None,
unicode_error=None, _warn_on_bytestring=False)),
Column('architecture',
String(length=255, convert_unicode=False, assert_unicode=None,
unicode_error=None, _warn_on_bytestring=False)),
Column('version',
String(length=255, convert_unicode=False, assert_unicode=None,
unicode_error=None, _warn_on_bytestring=False)),
Column('url',
String(length=255, convert_unicode=False, assert_unicode=None,
unicode_error=None, _warn_on_bytestring=False)),
Column('md5hash',
String(length=255, convert_unicode=False, assert_unicode=None,
unicode_error=None, _warn_on_bytestring=False)),
)
#
# New Column
#
architecture = Column('architecture', String(length=255))
def upgrade(migrate_engine):
# Upgrade operations go here. Don't create your own engine;
# bind migrate_engine to your metadata
meta.bind = migrate_engine
for table in (builds, ):
try:
table.create()
except Exception:
logging.info(repr(table))
instances = Table('instances', meta, autoload=True,
autoload_with=migrate_engine)
# Add columns to existing tables
instances.create_column(architecture)

View File

@ -232,6 +232,7 @@ class Instance(BASE, NovaBase):
locked = Column(Boolean) locked = Column(Boolean)
os_type = Column(String(255)) os_type = Column(String(255))
architecture = Column(String(255))
vm_mode = Column(String(255)) vm_mode = Column(String(255))
uuid = Column(String(36)) uuid = Column(String(36))
@ -713,6 +714,18 @@ class Zone(BASE, NovaBase):
password = Column(String(255)) password = Column(String(255))
class AgentBuild(BASE, NovaBase):
"""Represents an agent build."""
__tablename__ = 'agent_builds'
id = Column(Integer, primary_key=True)
hypervisor = Column(String(255))
os = Column(String(255))
architecture = Column(String(255))
version = Column(String(255))
url = Column(String(255))
md5hash = Column(String(255))
def register_models(): def register_models():
"""Register Models and create metadata. """Register Models and create metadata.
@ -726,7 +739,7 @@ def register_models():
Network, SecurityGroup, SecurityGroupIngressRule, Network, SecurityGroup, SecurityGroupIngressRule,
SecurityGroupInstanceAssociation, AuthToken, User, SecurityGroupInstanceAssociation, AuthToken, User,
Project, Certificate, ConsolePool, Console, Zone, Project, Certificate, ConsolePool, Console, Zone,
InstanceMetadata, Migration) AgentBuild, InstanceMetadata, Migration)
engine = create_engine(FLAGS.sql_connection, echo=False) engine = create_engine(FLAGS.sql_connection, echo=False)
for model in models: for model in models:
model.metadata.create_all(engine) model.metadata.create_all(engine)

View File

@ -281,6 +281,14 @@ class ComputeTestCase(test.TestCase):
"File Contents") "File Contents")
self.compute.terminate_instance(self.context, instance_id) self.compute.terminate_instance(self.context, instance_id)
def test_agent_update(self):
"""Ensure instance can have its agent updated"""
instance_id = self._create_instance()
self.compute.run_instance(self.context, instance_id)
self.compute.agent_update(self.context, instance_id,
'http://127.0.0.1/agent', '00112233445566778899aabbccddeeff')
self.compute.terminate_instance(self.context, instance_id)
def test_snapshot(self): def test_snapshot(self):
"""Ensure instance can be snapshotted""" """Ensure instance can be snapshotted"""
instance_id = self._create_instance() instance_id = self._create_instance()

View File

@ -37,9 +37,8 @@ from nova import exception
from nova.virt import xenapi_conn from nova.virt import xenapi_conn
from nova.virt.xenapi import fake as xenapi_fake from nova.virt.xenapi import fake as xenapi_fake
from nova.virt.xenapi import volume_utils from nova.virt.xenapi import volume_utils
from nova.virt.xenapi import vmops
from nova.virt.xenapi import vm_utils from nova.virt.xenapi import vm_utils
from nova.virt.xenapi.vmops import SimpleDH
from nova.virt.xenapi.vmops import VMOps
from nova.tests.db import fakes as db_fakes from nova.tests.db import fakes as db_fakes
from nova.tests.xenapi import stubs from nova.tests.xenapi import stubs
from nova.tests.glance import stubs as glance_stubs from nova.tests.glance import stubs as glance_stubs
@ -85,7 +84,8 @@ class XenAPIVolumeTestCase(test.TestCase):
'ramdisk_id': 3, 'ramdisk_id': 3,
'instance_type_id': '3', # m1.large 'instance_type_id': '3', # m1.large
'mac_address': 'aa:bb:cc:dd:ee:ff', 'mac_address': 'aa:bb:cc:dd:ee:ff',
'os_type': 'linux'} 'os_type': 'linux',
'architecture': 'x86-64'}
def _create_volume(self, size='0'): def _create_volume(self, size='0'):
"""Create a volume object.""" """Create a volume object."""
@ -192,7 +192,7 @@ class XenAPIVMTestCase(test.TestCase):
stubs.stubout_get_this_vm_uuid(self.stubs) stubs.stubout_get_this_vm_uuid(self.stubs)
stubs.stubout_stream_disk(self.stubs) stubs.stubout_stream_disk(self.stubs)
stubs.stubout_is_vdi_pv(self.stubs) stubs.stubout_is_vdi_pv(self.stubs)
self.stubs.Set(VMOps, 'reset_network', reset_network) self.stubs.Set(vmops.VMOps, 'reset_network', reset_network)
stubs.stub_out_vm_methods(self.stubs) stubs.stub_out_vm_methods(self.stubs)
glance_stubs.stubout_glance_client(self.stubs) glance_stubs.stubout_glance_client(self.stubs)
fake_utils.stub_out_utils_execute(self.stubs) fake_utils.stub_out_utils_execute(self.stubs)
@ -212,7 +212,8 @@ class XenAPIVMTestCase(test.TestCase):
'ramdisk_id': 3, 'ramdisk_id': 3,
'instance_type_id': '3', # m1.large 'instance_type_id': '3', # m1.large
'mac_address': 'aa:bb:cc:dd:ee:ff', 'mac_address': 'aa:bb:cc:dd:ee:ff',
'os_type': 'linux'} 'os_type': 'linux',
'architecture': 'x86-64'}
instance = db.instance_create(self.context, values) instance = db.instance_create(self.context, values)
self.conn.spawn(instance) self.conn.spawn(instance)
@ -370,7 +371,8 @@ class XenAPIVMTestCase(test.TestCase):
def _test_spawn(self, image_ref, kernel_id, ramdisk_id, def _test_spawn(self, image_ref, kernel_id, ramdisk_id,
instance_type_id="3", os_type="linux", instance_type_id="3", os_type="linux",
instance_id=1, check_injection=False): architecture="x86-64", instance_id=1,
check_injection=False):
stubs.stubout_loopingcall_start(self.stubs) stubs.stubout_loopingcall_start(self.stubs)
values = {'id': instance_id, values = {'id': instance_id,
'project_id': self.project.id, 'project_id': self.project.id,
@ -380,11 +382,14 @@ class XenAPIVMTestCase(test.TestCase):
'ramdisk_id': ramdisk_id, 'ramdisk_id': ramdisk_id,
'instance_type_id': instance_type_id, 'instance_type_id': instance_type_id,
'mac_address': 'aa:bb:cc:dd:ee:ff', 'mac_address': 'aa:bb:cc:dd:ee:ff',
'os_type': os_type} 'os_type': os_type,
'architecture': architecture}
instance = db.instance_create(self.context, values) instance = db.instance_create(self.context, values)
self.conn.spawn(instance) self.conn.spawn(instance)
self.create_vm_record(self.conn, os_type, instance_id) self.create_vm_record(self.conn, os_type, instance_id)
self.check_vm_record(self.conn, check_injection) self.check_vm_record(self.conn, check_injection)
self.assertTrue(instance.os_type)
self.assertTrue(instance.architecture)
def test_spawn_not_enough_memory(self): def test_spawn_not_enough_memory(self):
FLAGS.xenapi_image_service = 'glance' FLAGS.xenapi_image_service = 'glance'
@ -409,7 +414,7 @@ class XenAPIVMTestCase(test.TestCase):
def test_spawn_vhd_glance_linux(self): def test_spawn_vhd_glance_linux(self):
FLAGS.xenapi_image_service = 'glance' FLAGS.xenapi_image_service = 'glance'
self._test_spawn(glance_stubs.FakeGlance.IMAGE_VHD, None, None, self._test_spawn(glance_stubs.FakeGlance.IMAGE_VHD, None, None,
os_type="linux") os_type="linux", architecture="x86-64")
self.check_vm_params_for_linux() self.check_vm_params_for_linux()
def test_spawn_vhd_glance_swapdisk(self): def test_spawn_vhd_glance_swapdisk(self):
@ -438,7 +443,7 @@ class XenAPIVMTestCase(test.TestCase):
def test_spawn_vhd_glance_windows(self): def test_spawn_vhd_glance_windows(self):
FLAGS.xenapi_image_service = 'glance' FLAGS.xenapi_image_service = 'glance'
self._test_spawn(glance_stubs.FakeGlance.IMAGE_VHD, None, None, self._test_spawn(glance_stubs.FakeGlance.IMAGE_VHD, None, None,
os_type="windows") os_type="windows", architecture="i386")
self.check_vm_params_for_windows() self.check_vm_params_for_windows()
def test_spawn_glance(self): def test_spawn_glance(self):
@ -589,7 +594,8 @@ class XenAPIVMTestCase(test.TestCase):
'ramdisk_id': 3, 'ramdisk_id': 3,
'instance_type_id': '3', # m1.large 'instance_type_id': '3', # m1.large
'mac_address': 'aa:bb:cc:dd:ee:ff', 'mac_address': 'aa:bb:cc:dd:ee:ff',
'os_type': 'linux'} 'os_type': 'linux',
'architecture': 'x86-64'}
instance = db.instance_create(self.context, values) instance = db.instance_create(self.context, values)
self.conn.spawn(instance) self.conn.spawn(instance)
return instance return instance
@ -599,8 +605,8 @@ class XenAPIDiffieHellmanTestCase(test.TestCase):
"""Unit tests for Diffie-Hellman code.""" """Unit tests for Diffie-Hellman code."""
def setUp(self): def setUp(self):
super(XenAPIDiffieHellmanTestCase, self).setUp() super(XenAPIDiffieHellmanTestCase, self).setUp()
self.alice = SimpleDH() self.alice = vmops.SimpleDH()
self.bob = SimpleDH() self.bob = vmops.SimpleDH()
def test_shared(self): def test_shared(self):
alice_pub = self.alice.get_public() alice_pub = self.alice.get_public()
@ -664,7 +670,8 @@ class XenAPIMigrateInstance(test.TestCase):
'local_gb': 5, 'local_gb': 5,
'instance_type_id': '3', # m1.large 'instance_type_id': '3', # m1.large
'mac_address': 'aa:bb:cc:dd:ee:ff', 'mac_address': 'aa:bb:cc:dd:ee:ff',
'os_type': 'linux'} 'os_type': 'linux',
'architecture': 'x86-64'}
fake_utils.stub_out_utils_execute(self.stubs) fake_utils.stub_out_utils_execute(self.stubs)
stubs.stub_out_migration_methods(self.stubs) stubs.stub_out_migration_methods(self.stubs)
@ -703,6 +710,7 @@ class XenAPIDetermineDiskImageTestCase(test.TestCase):
self.fake_instance = FakeInstance() self.fake_instance = FakeInstance()
self.fake_instance.id = 42 self.fake_instance.id = 42
self.fake_instance.os_type = 'linux' self.fake_instance.os_type = 'linux'
self.fake_instance.architecture = 'x86-64'
def assert_disk_type(self, disk_type): def assert_disk_type(self, disk_type):
dt = vm_utils.VMHelper.determine_disk_image_type( dt = vm_utils.VMHelper.determine_disk_image_type(
@ -747,6 +755,28 @@ class XenAPIDetermineDiskImageTestCase(test.TestCase):
self.assert_disk_type(vm_utils.ImageType.DISK_VHD) self.assert_disk_type(vm_utils.ImageType.DISK_VHD)
class CompareVersionTestCase(test.TestCase):
def test_less_than(self):
"""Test that cmp_version compares a as less than b"""
self.assertTrue(vmops.cmp_version('1.2.3.4', '1.2.3.5') < 0)
def test_greater_than(self):
"""Test that cmp_version compares a as greater than b"""
self.assertTrue(vmops.cmp_version('1.2.3.5', '1.2.3.4') > 0)
def test_equal(self):
"""Test that cmp_version compares a as equal to b"""
self.assertTrue(vmops.cmp_version('1.2.3.4', '1.2.3.4') == 0)
def test_non_lexical(self):
"""Test that cmp_version compares non-lexically"""
self.assertTrue(vmops.cmp_version('1.2.3.10', '1.2.3.4') > 0)
def test_length(self):
"""Test that cmp_version compares by length as last resort"""
self.assertTrue(vmops.cmp_version('1.2.3', '1.2.3.4') < 0)
class FakeXenApi(object): class FakeXenApi(object):
"""Fake XenApi for testing HostState.""" """Fake XenApi for testing HostState."""

View File

@ -234,6 +234,10 @@ class ComputeDriver(object):
""" """
raise NotImplementedError() raise NotImplementedError()
def agent_update(self, instance, url, md5hash):
"""Update agent on the VM instance."""
raise NotImplementedError()
def inject_network_info(self, instance): def inject_network_info(self, instance):
"""inject network info for specified instance""" """inject network info for specified instance"""
raise NotImplementedError() raise NotImplementedError()

View File

@ -225,6 +225,21 @@ class FakeConnection(driver.ComputeDriver):
""" """
pass pass
def agent_update(self, instance, url, md5hash):
"""
Update agent on the specified instance.
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 URL of the agent to be fetched and updated on the
instance; the third is the md5 hash of the file for verification
purposes.
The work will be done asynchronously. This function returns a
task that allows the caller to detect when it is complete.
"""
pass
def rescue(self, instance): def rescue(self, instance):
""" """
Rescue the specified instance. Rescue the specified instance.

View File

@ -47,6 +47,21 @@ LOG = logging.getLogger("nova.virt.xenapi.vmops")
FLAGS = flags.FLAGS FLAGS = flags.FLAGS
def cmp_version(a, b):
"""Compare two version strings (eg 0.0.1.10 > 0.0.1.9)"""
a = a.split('.')
b = b.split('.')
# Compare each individual portion of both version strings
for va, vb in zip(a, b):
ret = int(va) - int(vb)
if ret:
return ret
# Fallback to comparing length last
return len(a) - len(b)
class VMOps(object): class VMOps(object):
""" """
Management class for VM-related tasks Management class for VM-related tasks
@ -218,6 +233,34 @@ class VMOps(object):
LOG.info(_('Spawning VM %(instance_name)s created %(vm_ref)s.') LOG.info(_('Spawning VM %(instance_name)s created %(vm_ref)s.')
% locals()) % locals())
ctx = context.get_admin_context()
agent_build = db.agent_build_get_by_triple(ctx, 'xen',
instance.os_type, instance.architecture)
if agent_build:
LOG.info(_('Latest agent build for %(hypervisor)s/%(os)s' + \
'/%(architecture)s is %(version)s') % agent_build)
else:
LOG.info(_('No agent build found for %(hypervisor)s/%(os)s' + \
'/%(architecture)s') % {
'hypervisor': 'xen',
'os': instance.os_type,
'architecture': instance.architecture})
def _check_agent_version():
version = self.get_agent_version(instance)
if not version:
LOG.info(_('No agent version returned by instance'))
return
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(): def _inject_files():
injected_files = instance.injected_files injected_files = instance.injected_files
if injected_files: if injected_files:
@ -252,6 +295,7 @@ class VMOps(object):
if state == power_state.RUNNING: if state == power_state.RUNNING:
LOG.debug(_('Instance %s: booted'), instance_name) LOG.debug(_('Instance %s: booted'), instance_name)
timer.stop() timer.stop()
_check_agent_version()
_inject_files() _inject_files()
_set_admin_password() _set_admin_password()
return True return True
@ -458,6 +502,34 @@ class VMOps(object):
task = self._session.call_xenapi('Async.VM.clean_reboot', vm_ref) task = self._session.call_xenapi('Async.VM.clean_reboot', vm_ref)
self._session.wait_for_task(task, instance.id) self._session.wait_for_task(task, instance.id)
def get_agent_version(self, instance):
"""Get the version of the agent running on the VM instance."""
# Send the encrypted password
transaction_id = str(uuid.uuid4())
args = {'id': transaction_id}
resp = self._make_agent_call('version', instance, '', args)
if resp is None:
# No response from the agent
return
resp_dict = json.loads(resp)
return resp_dict['message']
def agent_update(self, instance, url, md5sum):
"""Update agent on the VM instance."""
# Send the encrypted password
transaction_id = str(uuid.uuid4())
args = {'id': transaction_id, 'url': url, 'md5sum': md5sum}
resp = self._make_agent_call('agentupdate', instance, '', args)
if resp is None:
# No response from the agent
return
resp_dict = json.loads(resp)
if resp_dict['returncode'] != '0':
raise RuntimeError(resp_dict['message'])
return resp_dict['message']
def set_admin_password(self, instance, new_pass): def set_admin_password(self, instance, new_pass):
"""Set the root/admin password on the VM instance. """Set the root/admin password on the VM instance.

View File

@ -53,6 +53,19 @@ class TimeoutError(StandardError):
pass pass
def version(self, arg_dict):
"""Get version of agent."""
arg_dict["value"] = json.dumps({"name": "version", "value": ""})
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(e)
return resp
def key_init(self, arg_dict): def key_init(self, arg_dict):
"""Handles the Diffie-Hellman key exchange with the agent to """Handles the Diffie-Hellman key exchange with the agent to
establish the shared secret key used to encrypt/decrypt sensitive establish the shared secret key used to encrypt/decrypt sensitive
@ -144,6 +157,23 @@ def inject_file(self, arg_dict):
return resp return resp
def agent_update(self, arg_dict):
"""Expects an URL and md5sum of the contents, then directs the agent to
update itself."""
request_id = arg_dict["id"]
url = arg_dict["url"]
md5sum = arg_dict["md5sum"]
arg_dict["value"] = json.dumps({"name": "agentupdate",
"value": {"url": url, "md5sum": md5sum}})
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(e)
return resp
def _agent_has_method(self, method): def _agent_has_method(self, method):
"""Check that the agent has a particular method by checking its """Check that the agent has a particular method by checking its
features. Cache the features so we don't have to query the agent features. Cache the features so we don't have to query the agent
@ -201,7 +231,9 @@ def _wait_for_agent(self, request_id, arg_dict):
if __name__ == "__main__": if __name__ == "__main__":
XenAPIPlugin.dispatch( XenAPIPlugin.dispatch(
{"key_init": key_init, {"version": version,
"key_init": key_init,
"password": password, "password": password,
"resetnetwork": resetnetwork, "resetnetwork": resetnetwork,
"inject_file": inject_file}) "inject_file": inject_file,
"agentupdate": agent_update})