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)
|
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),
|
||||||
|
@ -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)
|
||||||
|
@ -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):
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
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)
|
||||||
|
@ -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()
|
||||||
|
@ -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."""
|
||||||
|
|
||||||
|
@ -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()
|
||||||
|
@ -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.
|
||||||
|
@ -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.
|
||||||
|
|
||||||
|
@ -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})
|
||||||
|
Loading…
Reference in New Issue
Block a user