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:
commit
29e2d55e9d
@ -1071,6 +1071,70 @@ class ImageCommands(object):
|
||||
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 for exposing the flags defined by flag_file(s)."""
|
||||
|
||||
@ -1083,6 +1147,7 @@ class ConfigCommands(object):
|
||||
|
||||
CATEGORIES = [
|
||||
('account', AccountCommands),
|
||||
('agent', AgentBuildCommands),
|
||||
('config', ConfigCommands),
|
||||
('db', DbCommands),
|
||||
('fixed', FixedIpCommands),
|
||||
|
@ -178,6 +178,9 @@ class API(base.Base):
|
||||
os_type = None
|
||||
if 'properties' in image and 'os_type' in image['properties']:
|
||||
os_type = image['properties']['os_type']
|
||||
architecture = None
|
||||
if 'properties' in image and 'arch' in image['properties']:
|
||||
architecture = image['properties']['arch']
|
||||
vm_mode = None
|
||||
if 'properties' in image and 'vm_mode' in image['properties']:
|
||||
vm_mode = image['properties']['vm_mode']
|
||||
@ -243,6 +246,7 @@ class API(base.Base):
|
||||
'metadata': metadata,
|
||||
'availability_zone': availability_zone,
|
||||
'os_type': os_type,
|
||||
'architecture': architecture,
|
||||
'vm_mode': vm_mode}
|
||||
|
||||
return (num_instances, base_options, security_groups)
|
||||
|
@ -560,6 +560,24 @@ class ComputeManager(manager.SchedulerDependentManager):
|
||||
LOG.audit(msg)
|
||||
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
|
||||
@checks_instance_lock
|
||||
def rescue_instance(self, context, instance_id):
|
||||
|
@ -1287,3 +1287,32 @@ def instance_metadata_delete(context, instance_id, key):
|
||||
def instance_metadata_update_or_create(context, instance_id, metadata):
|
||||
"""Create or update instance 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)
|
||||
|
@ -2792,3 +2792,54 @@ def instance_metadata_update_or_create(context, instance_id, metadata):
|
||||
meta_ref.save(session=session)
|
||||
|
||||
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)
|
||||
|
@ -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)
|
@ -232,6 +232,7 @@ class Instance(BASE, NovaBase):
|
||||
locked = Column(Boolean)
|
||||
|
||||
os_type = Column(String(255))
|
||||
architecture = Column(String(255))
|
||||
vm_mode = Column(String(255))
|
||||
uuid = Column(String(36))
|
||||
|
||||
@ -713,6 +714,18 @@ class Zone(BASE, NovaBase):
|
||||
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():
|
||||
"""Register Models and create metadata.
|
||||
|
||||
@ -726,7 +739,7 @@ def register_models():
|
||||
Network, SecurityGroup, SecurityGroupIngressRule,
|
||||
SecurityGroupInstanceAssociation, AuthToken, User,
|
||||
Project, Certificate, ConsolePool, Console, Zone,
|
||||
InstanceMetadata, Migration)
|
||||
AgentBuild, InstanceMetadata, Migration)
|
||||
engine = create_engine(FLAGS.sql_connection, echo=False)
|
||||
for model in models:
|
||||
model.metadata.create_all(engine)
|
||||
|
@ -281,6 +281,14 @@ class ComputeTestCase(test.TestCase):
|
||||
"File Contents")
|
||||
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):
|
||||
"""Ensure instance can be snapshotted"""
|
||||
instance_id = self._create_instance()
|
||||
|
@ -37,9 +37,8 @@ from nova import exception
|
||||
from nova.virt import xenapi_conn
|
||||
from nova.virt.xenapi import fake as xenapi_fake
|
||||
from nova.virt.xenapi import volume_utils
|
||||
from nova.virt.xenapi import vmops
|
||||
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.xenapi import stubs
|
||||
from nova.tests.glance import stubs as glance_stubs
|
||||
@ -85,7 +84,8 @@ class XenAPIVolumeTestCase(test.TestCase):
|
||||
'ramdisk_id': 3,
|
||||
'instance_type_id': '3', # m1.large
|
||||
'mac_address': 'aa:bb:cc:dd:ee:ff',
|
||||
'os_type': 'linux'}
|
||||
'os_type': 'linux',
|
||||
'architecture': 'x86-64'}
|
||||
|
||||
def _create_volume(self, size='0'):
|
||||
"""Create a volume object."""
|
||||
@ -192,7 +192,7 @@ class XenAPIVMTestCase(test.TestCase):
|
||||
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, 'reset_network', reset_network)
|
||||
self.stubs.Set(vmops.VMOps, 'reset_network', reset_network)
|
||||
stubs.stub_out_vm_methods(self.stubs)
|
||||
glance_stubs.stubout_glance_client(self.stubs)
|
||||
fake_utils.stub_out_utils_execute(self.stubs)
|
||||
@ -212,7 +212,8 @@ class XenAPIVMTestCase(test.TestCase):
|
||||
'ramdisk_id': 3,
|
||||
'instance_type_id': '3', # m1.large
|
||||
'mac_address': 'aa:bb:cc:dd:ee:ff',
|
||||
'os_type': 'linux'}
|
||||
'os_type': 'linux',
|
||||
'architecture': 'x86-64'}
|
||||
instance = db.instance_create(self.context, values)
|
||||
self.conn.spawn(instance)
|
||||
|
||||
@ -370,7 +371,8 @@ class XenAPIVMTestCase(test.TestCase):
|
||||
|
||||
def _test_spawn(self, image_ref, kernel_id, ramdisk_id,
|
||||
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)
|
||||
values = {'id': instance_id,
|
||||
'project_id': self.project.id,
|
||||
@ -380,11 +382,14 @@ class XenAPIVMTestCase(test.TestCase):
|
||||
'ramdisk_id': ramdisk_id,
|
||||
'instance_type_id': instance_type_id,
|
||||
'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)
|
||||
self.conn.spawn(instance)
|
||||
self.create_vm_record(self.conn, os_type, instance_id)
|
||||
self.check_vm_record(self.conn, check_injection)
|
||||
self.assertTrue(instance.os_type)
|
||||
self.assertTrue(instance.architecture)
|
||||
|
||||
def test_spawn_not_enough_memory(self):
|
||||
FLAGS.xenapi_image_service = 'glance'
|
||||
@ -409,7 +414,7 @@ class XenAPIVMTestCase(test.TestCase):
|
||||
def test_spawn_vhd_glance_linux(self):
|
||||
FLAGS.xenapi_image_service = 'glance'
|
||||
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()
|
||||
|
||||
def test_spawn_vhd_glance_swapdisk(self):
|
||||
@ -438,7 +443,7 @@ class XenAPIVMTestCase(test.TestCase):
|
||||
def test_spawn_vhd_glance_windows(self):
|
||||
FLAGS.xenapi_image_service = 'glance'
|
||||
self._test_spawn(glance_stubs.FakeGlance.IMAGE_VHD, None, None,
|
||||
os_type="windows")
|
||||
os_type="windows", architecture="i386")
|
||||
self.check_vm_params_for_windows()
|
||||
|
||||
def test_spawn_glance(self):
|
||||
@ -589,7 +594,8 @@ class XenAPIVMTestCase(test.TestCase):
|
||||
'ramdisk_id': 3,
|
||||
'instance_type_id': '3', # m1.large
|
||||
'mac_address': 'aa:bb:cc:dd:ee:ff',
|
||||
'os_type': 'linux'}
|
||||
'os_type': 'linux',
|
||||
'architecture': 'x86-64'}
|
||||
instance = db.instance_create(self.context, values)
|
||||
self.conn.spawn(instance)
|
||||
return instance
|
||||
@ -599,8 +605,8 @@ class XenAPIDiffieHellmanTestCase(test.TestCase):
|
||||
"""Unit tests for Diffie-Hellman code."""
|
||||
def setUp(self):
|
||||
super(XenAPIDiffieHellmanTestCase, self).setUp()
|
||||
self.alice = SimpleDH()
|
||||
self.bob = SimpleDH()
|
||||
self.alice = vmops.SimpleDH()
|
||||
self.bob = vmops.SimpleDH()
|
||||
|
||||
def test_shared(self):
|
||||
alice_pub = self.alice.get_public()
|
||||
@ -664,7 +670,8 @@ class XenAPIMigrateInstance(test.TestCase):
|
||||
'local_gb': 5,
|
||||
'instance_type_id': '3', # m1.large
|
||||
'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)
|
||||
stubs.stub_out_migration_methods(self.stubs)
|
||||
@ -703,6 +710,7 @@ class XenAPIDetermineDiskImageTestCase(test.TestCase):
|
||||
self.fake_instance = FakeInstance()
|
||||
self.fake_instance.id = 42
|
||||
self.fake_instance.os_type = 'linux'
|
||||
self.fake_instance.architecture = 'x86-64'
|
||||
|
||||
def assert_disk_type(self, disk_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)
|
||||
|
||||
|
||||
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):
|
||||
"""Fake XenApi for testing HostState."""
|
||||
|
||||
|
@ -234,6 +234,10 @@ class ComputeDriver(object):
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def agent_update(self, instance, url, md5hash):
|
||||
"""Update agent on the VM instance."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def inject_network_info(self, instance):
|
||||
"""inject network info for specified instance"""
|
||||
raise NotImplementedError()
|
||||
|
@ -225,6 +225,21 @@ class FakeConnection(driver.ComputeDriver):
|
||||
"""
|
||||
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):
|
||||
"""
|
||||
Rescue the specified instance.
|
||||
|
@ -47,6 +47,21 @@ LOG = logging.getLogger("nova.virt.xenapi.vmops")
|
||||
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):
|
||||
"""
|
||||
Management class for VM-related tasks
|
||||
@ -218,6 +233,34 @@ class VMOps(object):
|
||||
LOG.info(_('Spawning VM %(instance_name)s created %(vm_ref)s.')
|
||||
% 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():
|
||||
injected_files = instance.injected_files
|
||||
if injected_files:
|
||||
@ -252,6 +295,7 @@ class VMOps(object):
|
||||
if state == power_state.RUNNING:
|
||||
LOG.debug(_('Instance %s: booted'), instance_name)
|
||||
timer.stop()
|
||||
_check_agent_version()
|
||||
_inject_files()
|
||||
_set_admin_password()
|
||||
return True
|
||||
@ -458,6 +502,34 @@ class VMOps(object):
|
||||
task = self._session.call_xenapi('Async.VM.clean_reboot', vm_ref)
|
||||
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):
|
||||
"""Set the root/admin password on the VM instance.
|
||||
|
||||
|
@ -53,6 +53,19 @@ class TimeoutError(StandardError):
|
||||
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):
|
||||
"""Handles the Diffie-Hellman key exchange with the agent to
|
||||
establish the shared secret key used to encrypt/decrypt sensitive
|
||||
@ -144,6 +157,23 @@ def inject_file(self, arg_dict):
|
||||
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):
|
||||
"""Check that the agent has a particular method by checking its
|
||||
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__":
|
||||
XenAPIPlugin.dispatch(
|
||||
{"key_init": key_init,
|
||||
{"version": version,
|
||||
"key_init": key_init,
|
||||
"password": password,
|
||||
"resetnetwork": resetnetwork,
|
||||
"inject_file": inject_file})
|
||||
"inject_file": inject_file,
|
||||
"agentupdate": agent_update})
|
||||
|
Loading…
Reference in New Issue
Block a user