Inject guest conf files to configurable location

Introduce a configuration option for a directory on the guest where the
taskmanager should inject configuration files.

During instance creation inject the guest_info and trove-guestagent
conf files to the 'injected_config_location'. The default location is
/etc/trove/conf.d.

Also:
 - Change the default value for 'guest_config' to remove the risk of
   overwriting a guest image's conf file with the sample.
 - Add trove-guestagent.conf injection to heat template.
 - Add new 'guest_info' option that defaults to "guest_info.conf".

Depends-On: I1dffd373da722af55bdea41fead8456bb60c82b2

Co-Authored-By: Denis Makogon <dmakogon@mirantis.com>
Co-Authored-By: Duk Loi <duk@tesora.com>

DocImpact: This change introduces a new option with a default that
affects existing guest images. The guestagent init script for any
existing guest image will need to be modified to read the conf
files from /etc/trove/conf.d.  For backwards compatibility set the
injected_config_location to /etc/trove and guest_info to
/etc/guest_info.

Closes-Bug: 1309030
Change-Id: I1057f4a1b6232332ed4b520dbc7b3bbb04145f73
This commit is contained in:
Greg Lucas 2014-10-08 16:26:45 +03:00 committed by Duk Loi
parent b1fb34bfb8
commit a4e054e3ac
9 changed files with 113 additions and 47 deletions

View File

@ -1,4 +1,4 @@
These conf files are read and used by the guest to provide extra These conf files are read and used by the guest to provide extra
information to the guest. The first example of this is the information to the guest. The first example of this is the
guest_info which will have the uuid of the instance so that guest_info.conf which will have the uuid of the instance so that
the guest can report back things to the infra. the guest can report back things to the infra.

View File

@ -1 +0,0 @@
# Arbitrary information that the guest needs to work

View File

@ -0,0 +1 @@
# Guest-specific information injected by the taskmanager

View File

@ -194,7 +194,11 @@ pydev_debug = disabled
#pydev_path = <path> #pydev_path = <path>
# ================= Guestagent related ======================== # ================= Guestagent related ========================
#guest_config = $pybasedir/etc/trove/trove-guestagent.conf.sample #guest_config = /etc/trove/trove-guestagent.conf
# Use 'guest_info = /etc/guest_info' for pre-Kilo compatibility
#guest_info = guest_info.conf
# Use 'injected_config_location = /etc/trove' for pre-Kilo compatibility
#injected_config_location = /etc/trove/conf.d
#cloudinit_location = /etc/trove/cloudinit #cloudinit_location = /etc/trove/cloudinit
# ================= Security groups related ======================== # ================= Security groups related ========================

View File

@ -1,4 +1,4 @@
# Copyright 2011 OpenStack Foundation # copyright 2011 OpenStack Foundation
# Copyright 2014 Rackspace Hosting # Copyright 2014 Rackspace Hosting
# All Rights Reserved. # All Rights Reserved.
# #
@ -342,9 +342,18 @@ common_opts = [
'expression.'), 'expression.'),
cfg.StrOpt('cloudinit_location', default='/etc/trove/cloudinit', cfg.StrOpt('cloudinit_location', default='/etc/trove/cloudinit',
help='Path to folder with cloudinit scripts.'), help='Path to folder with cloudinit scripts.'),
cfg.StrOpt('injected_config_location', default='/etc/trove/conf.d',
help='Path to folder on the Guest where config files will be '
'injected during instance creation.'),
cfg.StrOpt('guest_config', cfg.StrOpt('guest_config',
default='$pybasedir/etc/trove/trove-guestagent.conf.sample', default='/etc/trove/trove-guestagent.conf',
help='Path to the Guest Agent config file.'), help='Path to the Guest Agent config file to be injected '
'during instance creation.'),
cfg.StrOpt('guest_info',
default='guest_info.conf',
help='The guest info filename found in the injected config '
'location. If a full path is specified then it will '
'be used as the path to the guest info file'),
cfg.DictOpt('datastore_registry_ext', default=dict(), cfg.DictOpt('datastore_registry_ext', default=dict(),
help='Extension for default datastore managers. ' help='Extension for default datastore managers. '
'Allows the use of custom managers for each of ' 'Allows the use of custom managers for each of '

View File

@ -217,6 +217,32 @@ class ClusterTasks(Cluster):
class FreshInstanceTasks(FreshInstance, NotifyMixin, ConfigurationMixin): class FreshInstanceTasks(FreshInstance, NotifyMixin, ConfigurationMixin):
def _get_injected_files(self, datastore_manager):
injected_config_location = CONF.get('injected_config_location')
guest_info = CONF.get('guest_info')
if ('/' in guest_info):
# Set guest_info_file to exactly guest_info from the conf file.
# This should be /etc/guest_info for pre-Kilo compatibility.
guest_info_file = guest_info
else:
guest_info_file = os.path.join(injected_config_location,
guest_info)
files = {guest_info_file: (
"[DEFAULT]\n"
"guest_id=%s\n"
"datastore_manager=%s\n"
"tenant_id=%s\n"
% (self.id, datastore_manager, self.tenant_id))}
if os.path.isfile(CONF.get('guest_config')):
with open(CONF.get('guest_config'), "r") as f:
files[os.path.join(injected_config_location,
"trove-guestagent.conf")] = f.read()
return files
def create_instance(self, flavor, image_id, databases, users, def create_instance(self, flavor, image_id, databases, users,
datastore_manager, packages, volume_size, datastore_manager, packages, volume_size,
backup_id, availability_zone, root_password, nics, backup_id, availability_zone, root_password, nics,
@ -242,6 +268,8 @@ class FreshInstanceTasks(FreshInstance, NotifyMixin, ConfigurationMixin):
LOG.debug("Successfully created security group for " LOG.debug("Successfully created security group for "
"instance: %s" % self.id) "instance: %s" % self.id)
files = self._get_injected_files(datastore_manager)
if use_heat: if use_heat:
volume_info = self._create_server_volume_heat( volume_info = self._create_server_volume_heat(
flavor, flavor,
@ -249,7 +277,8 @@ class FreshInstanceTasks(FreshInstance, NotifyMixin, ConfigurationMixin):
datastore_manager, datastore_manager,
volume_size, volume_size,
availability_zone, availability_zone,
nics) nics,
files)
elif use_nova_server_volume: elif use_nova_server_volume:
volume_info = self._create_server_volume( volume_info = self._create_server_volume(
flavor['id'], flavor['id'],
@ -258,7 +287,8 @@ class FreshInstanceTasks(FreshInstance, NotifyMixin, ConfigurationMixin):
datastore_manager, datastore_manager,
volume_size, volume_size,
availability_zone, availability_zone,
nics) nics,
files)
else: else:
volume_info = self._create_server_volume_individually( volume_info = self._create_server_volume_individually(
flavor['id'], flavor['id'],
@ -267,7 +297,8 @@ class FreshInstanceTasks(FreshInstance, NotifyMixin, ConfigurationMixin):
datastore_manager, datastore_manager,
volume_size, volume_size,
availability_zone, availability_zone,
nics) nics,
files)
config = self._render_config(flavor) config = self._render_config(flavor)
config_overrides = self._render_override_config(flavor, config_overrides = self._render_override_config(flavor,
@ -454,11 +485,10 @@ class FreshInstanceTasks(FreshInstance, NotifyMixin, ConfigurationMixin):
def _create_server_volume(self, flavor_id, image_id, security_groups, def _create_server_volume(self, flavor_id, image_id, security_groups,
datastore_manager, volume_size, datastore_manager, volume_size,
availability_zone, nics): availability_zone, nics, files):
LOG.debug("Begin _create_server_volume for id: %s" % self.id) LOG.debug("Begin _create_server_volume for id: %s" % self.id)
try: try:
files, userdata = self._prepare_file_and_userdata( userdata = self._prepare_userdata(datastore_manager)
datastore_manager)
name = self.hostname or self.name name = self.hostname or self.name
volume_desc = ("datastore volume for %s" % self.id) volume_desc = ("datastore volume for %s" % self.id)
volume_name = ("datastore-%s" % self.id) volume_name = ("datastore-%s" % self.id)
@ -508,10 +538,9 @@ class FreshInstanceTasks(FreshInstance, NotifyMixin, ConfigurationMixin):
return final return final
def _create_server_volume_heat(self, flavor, image_id, def _create_server_volume_heat(self, flavor, image_id,
datastore_manager, datastore_manager, volume_size,
volume_size, availability_zone, availability_zone, nics, files):
nics): LOG.debug("Begin _create_server_volume_heat for id: %s" % self.id)
LOG.debug("begin _create_server_volume_heat for id: %s" % self.id)
try: try:
client = create_heat_client(self.context) client = create_heat_client(self.context)
tcp_rules_mapping_list = self._build_sg_rules_mapping(CONF.get( tcp_rules_mapping_list = self._build_sg_rules_mapping(CONF.get(
@ -526,7 +555,8 @@ class FreshInstanceTasks(FreshInstance, NotifyMixin, ConfigurationMixin):
ifaces=ifaces, ports=ports, ifaces=ifaces, ports=ports,
tcp_rules=tcp_rules_mapping_list, tcp_rules=tcp_rules_mapping_list,
udp_rules=udp_ports_mapping_list, udp_rules=udp_ports_mapping_list,
datastore_manager=datastore_manager) datastore_manager=datastore_manager,
files=files)
try: try:
heat_template = heat_template_unicode.encode('utf-8') heat_template = heat_template_unicode.encode('utf-8')
except UnicodeEncodeError: except UnicodeEncodeError:
@ -604,8 +634,8 @@ class FreshInstanceTasks(FreshInstance, NotifyMixin, ConfigurationMixin):
def _create_server_volume_individually(self, flavor_id, image_id, def _create_server_volume_individually(self, flavor_id, image_id,
security_groups, datastore_manager, security_groups, datastore_manager,
volume_size, volume_size, availability_zone,
availability_zone, nics): nics, files):
LOG.debug("Begin _create_server_volume_individually for id: %s" % LOG.debug("Begin _create_server_volume_individually for id: %s" %
self.id) self.id)
server = None server = None
@ -616,7 +646,7 @@ class FreshInstanceTasks(FreshInstance, NotifyMixin, ConfigurationMixin):
server = self._create_server(flavor_id, image_id, security_groups, server = self._create_server(flavor_id, image_id, security_groups,
datastore_manager, datastore_manager,
block_device_mapping, block_device_mapping,
availability_zone, nics) availability_zone, nics, files)
server_id = server.id server_id = server.id
# Save server ID. # Save server ID.
self.update_db(compute_instance_id=server_id) self.update_db(compute_instance_id=server_id)
@ -707,28 +737,19 @@ class FreshInstanceTasks(FreshInstance, NotifyMixin, ConfigurationMixin):
'volumes': created_volumes} 'volumes': created_volumes}
return volume_info return volume_info
def _prepare_file_and_userdata(self, datastore_manager): def _prepare_userdata(self, datastore_manager):
files = {"/etc/guest_info": ("[DEFAULT]\nguest_id=%s\n"
"datastore_manager=%s\n"
"tenant_id=%s\n" %
(self.id, datastore_manager,
self.tenant_id))}
if os.path.isfile(CONF.get('guest_config')):
with open(CONF.get('guest_config'), "r") as f:
files["/etc/trove-guestagent.conf"] = f.read()
userdata = None userdata = None
cloudinit = os.path.join(CONF.get('cloudinit_location'), cloudinit = os.path.join(CONF.get('cloudinit_location'),
"%s.cloudinit" % datastore_manager) "%s.cloudinit" % datastore_manager)
if os.path.isfile(cloudinit): if os.path.isfile(cloudinit):
with open(cloudinit, "r") as f: with open(cloudinit, "r") as f:
userdata = f.read() userdata = f.read()
return files, userdata return userdata
def _create_server(self, flavor_id, image_id, security_groups, def _create_server(self, flavor_id, image_id, security_groups,
datastore_manager, block_device_mapping, datastore_manager, block_device_mapping,
availability_zone, nics): availability_zone, nics, files={}):
files, userdata = self._prepare_file_and_userdata( userdata = self._prepare_userdata(datastore_manager)
datastore_manager)
name = self.hostname or self.name name = self.hostname or self.name
bdmap = block_device_mapping bdmap = block_device_mapping
config_drive = CONF.use_nova_server_config_drive config_drive = CONF.use_nova_server_config_drive

View File

@ -34,16 +34,14 @@ Resources:
AWS::CloudFormation::Init: AWS::CloudFormation::Init:
config: config:
files: files:
/etc/guest_info: {% for file, content in files.iteritems() %}
content: {{ file }}:
Fn::Join: content: |
- '' {{ content | indent(16) }}
- ["[DEFAULT]\nguest_id=", {Ref: InstanceId},
"\ndatastore_manager=", {Ref: DatastoreManager},
"\ntenant_id=", {Ref: TenantId}]
mode: '000644' mode: '000644'
owner: root owner: root
group: root group: root
{% endfor %}
Properties: Properties:
ImageId: {Ref: ImageId} ImageId: {Ref: ImageId}
InstanceType: {Ref: Flavor} InstanceType: {Ref: Flavor}

View File

@ -168,7 +168,8 @@ class HeatTemplateLoadTest(testtools.TestCase):
volume_support=True, volume_support=True,
ifaces=[], ports=[], ifaces=[], ports=[],
tcp_rules=tcp_rules, tcp_rules=tcp_rules,
udp_rules=[]) udp_rules=[],
files={})
self.assertIsNotNone(output) self.assertIsNotNone(output)
self.assertIn('FromPort: "3306"', output) self.assertIn('FromPort: "3306"', output)
self.assertIn('ToPort: "3309"', output) self.assertIn('ToPort: "3309"', output)
@ -182,7 +183,8 @@ class HeatTemplateLoadTest(testtools.TestCase):
volume_support=True, volume_support=True,
ifaces=[], ports=[], ifaces=[], ports=[],
tcp_rules=[], tcp_rules=[],
udp_rules=[]) udp_rules=[],
files={})
self.assertIsNotNone(output) self.assertIsNotNone(output)
self.assertNotIn('- IpProtocol: "tcp"', output) self.assertNotIn('- IpProtocol: "tcp"', output)
self.assertNotIn('- IpProtocol: "udp"', output) self.assertNotIn('- IpProtocol: "udp"', output)

View File

@ -226,15 +226,47 @@ class FreshInstanceTasksTest(testtools.TestCase):
def fake_conf_getter(*args, **kwargs): def fake_conf_getter(*args, **kwargs):
if args[0] == 'guest_config': if args[0] == 'guest_config':
return self.guestconfig return self.guestconfig
if args[0] == 'guest_info':
return 'guest_info.conf'
if args[0] == 'injected_config_location':
return '/etc/trove/conf.d'
else: else:
return '' return ''
mock_conf.get.side_effect = fake_conf_getter mock_conf.get.side_effect = fake_conf_getter
# execute # execute
server = self.freshinstancetasks._create_server( files = self.freshinstancetasks._get_injected_files("test")
None, None, None, "test", None, None, None)
# verify # verify
self.assertTrue('/etc/trove-guestagent.conf' in server.files) self.assertTrue(
self.assertEqual(server.files['/etc/trove-guestagent.conf'], '/etc/trove/conf.d/guest_info.conf' in files)
self.assertTrue(
'/etc/trove/conf.d/trove-guestagent.conf' in files)
self.assertEqual(
files['/etc/trove/conf.d/trove-guestagent.conf'],
self.guestconfig_content)
@patch('trove.taskmanager.models.CONF')
def test_create_instance_guestconfig_compat(self, mock_conf):
def fake_conf_getter(*args, **kwargs):
if args[0] == 'guest_config':
return self.guestconfig
if args[0] == 'guest_info':
return '/etc/guest_info'
if args[0] == 'injected_config_location':
return '/etc'
else:
return ''
mock_conf.get.side_effect = fake_conf_getter
# execute
files = self.freshinstancetasks._get_injected_files("test")
# verify
self.assertTrue(
'/etc/guest_info' in files)
self.assertTrue(
'/etc/trove-guestagent.conf' in files)
self.assertEqual(
files['/etc/trove-guestagent.conf'],
self.guestconfig_content) self.guestconfig_content)
@patch('trove.taskmanager.models.CONF') @patch('trove.taskmanager.models.CONF')