summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJohannes Erdfelt <johannes.erdfelt@rackspace.com>2011-06-09 20:11:55 +0000
committerJohannes Erdfelt <johannes.erdfelt@rackspace.com>2011-06-09 20:11:55 +0000
commitf732831bf4f0c5581b28322d76fb13a17cd65839 (patch)
tree3c730b7cc18c3bdb09db86297398aad899115347
parent1392d885e650e06937846d536ebb11b91d0adc75 (diff)
Record architecture of image for matching to agent build later.
Add code to automatically update agent running on instance on instance creation.
-rwxr-xr-xbin/nova-manage45
-rw-r--r--nova/compute/api.py6
-rw-r--r--nova/db/api.py23
-rw-r--r--nova/db/sqlalchemy/api.py50
-rw-r--r--nova/db/sqlalchemy/migrate_repo/versions/023_add_agent_table.py78
-rw-r--r--nova/db/sqlalchemy/models.py15
-rw-r--r--nova/virt/xenapi/vmops.py68
7 files changed, 283 insertions, 2 deletions
diff --git a/bin/nova-manage b/bin/nova-manage
index 0147ae21b..c004a36c0 100755
--- a/bin/nova-manage
+++ b/bin/nova-manage
@@ -1082,6 +1082,50 @@ 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):
+ """lists all agent builds
+ arguments: <none>"""
+ # TODO(johannes.erdfelt): Make the output easier to read
+ ctxt = context.get_admin_context()
+ for agent_build in db.agent_build_get_all(ctxt):
+ print agent_build.hypervisor, agent_build.os, agent_build.architecture, agent_build.version, agent_build.url, agent_build.md5hash
+
+ 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)."""
@@ -1094,6 +1138,7 @@ class ConfigCommands(object):
CATEGORIES = [
('account', AccountCommands),
+ ('agent', AgentBuildCommands),
('config', ConfigCommands),
('db', DbCommands),
('fixed', FixedIpCommands),
diff --git a/nova/compute/api.py b/nova/compute/api.py
index b0949a729..e1eea67e6 100644
--- a/nova/compute/api.py
+++ b/nova/compute/api.py
@@ -164,6 +164,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']
if kernel_id is None:
kernel_id = image['properties'].get('kernel_id', None)
@@ -222,7 +225,8 @@ class API(base.Base):
'locked': False,
'metadata': metadata,
'availability_zone': availability_zone,
- 'os_type': os_type}
+ 'os_type': os_type,
+ 'architecture': architecture}
return (num_instances, base_options, security_groups)
diff --git a/nova/db/api.py b/nova/db/api.py
index 4e0aa60a2..30663662b 100644
--- a/nova/db/api.py
+++ b/nova/db/api.py
@@ -1247,3 +1247,26 @@ 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):
+ return IMPL.agent_build_create(context, values)
+
+
+def agent_build_get_by_triple(context, hypervisor, os, architecture):
+ return IMPL.agent_build_get_by_triple(context, hypervisor, os, architecture)
+
+
+def agent_build_get_all(context):
+ return IMPL.agent_build_get_all(context)
+
+
+def agent_build_destroy(context, agent_update_id):
+ IMPL.agent_build_destroy(context, agent_update_id)
+
+
+def agent_build_update(context, agent_build_id, values):
+ IMPL.agent_build_update(context, agent_build_id, values)
diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py
index 73870d2f3..d7a3b4c49 100644
--- a/nova/db/sqlalchemy/api.py
+++ b/nova/db/sqlalchemy/api.py
@@ -2682,3 +2682,53 @@ 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)
diff --git a/nova/db/sqlalchemy/migrate_repo/versions/023_add_agent_table.py b/nova/db/sqlalchemy/migrate_repo/versions/023_add_agent_table.py
new file mode 100644
index 000000000..33979ca79
--- /dev/null
+++ b/nova/db/sqlalchemy/migrate_repo/versions/023_add_agent_table.py
@@ -0,0 +1,78 @@
+# Copyright 2010 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)),
+ )
+
+
+# Table stub-definitions
+# Just for the ForeignKey and column creation to succeed, these are not the
+# actual definitions of instances or services.
+#
+instances = Table('instances', meta,
+ Column('id', Integer(), primary_key=True, nullable=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))
+
+ # Add columns to existing tables
+ instances.create_column(architecture)
diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py
index 239f6e96a..3aabfb58f 100644
--- a/nova/db/sqlalchemy/models.py
+++ b/nova/db/sqlalchemy/models.py
@@ -232,6 +232,7 @@ class Instance(BASE, NovaBase):
locked = Column(Boolean)
os_type = Column(String(255))
+ architecture = Column(String(255))
# TODO(vish): see Ewan's email about state improvements, probably
# should be in a driver base class or some such
@@ -672,6 +673,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.
@@ -685,7 +698,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)
diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py
index c6d2b0936..f45912867 100644
--- a/nova/virt/xenapi/vmops.py
+++ b/nova/virt/xenapi/vmops.py
@@ -47,6 +47,18 @@ LOG = logging.getLogger("nova.virt.xenapi.vmops")
FLAGS = flags.FLAGS
+def _cmp_version(a, b):
+ a = a.split('.')
+ b = b.split('.')
+
+ for va, vb in zip(a, b):
+ ret = int(va) - int(vb)
+ if ret:
+ return ret
+
+ return len(a) - len(b)
+
+
class VMOps(object):
"""
Management class for VM-related tasks
@@ -203,6 +215,33 @@ 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 %s/%s/%s is %s') % (
+ agent_build['hypervisor'], agent_build['os'],
+ agent_build['architecture'], agent_build['version']))
+ else:
+ LOG.info(_('No agent build found for %s/%s/%s') % (
+ 'xen', instance.os_type, 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'])
+ ret = self.agent_update(instance, agent_build['url'],
+ agent_build['md5hash'])
+ LOG.info('Agent Update returned: %s' % ret)
+
def _inject_files():
injected_files = instance.injected_files
if injected_files:
@@ -237,6 +276,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
@@ -443,6 +483,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.