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
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.

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>
# ================= 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
# ================= Security groups related ========================

View File

@ -1,4 +1,4 @@
# Copyright 2011 OpenStack Foundation
# copyright 2011 OpenStack Foundation
# Copyright 2014 Rackspace Hosting
# All Rights Reserved.
#
@ -342,9 +342,18 @@ common_opts = [
'expression.'),
cfg.StrOpt('cloudinit_location', default='/etc/trove/cloudinit',
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',
default='$pybasedir/etc/trove/trove-guestagent.conf.sample',
help='Path to the Guest Agent config file.'),
default='/etc/trove/trove-guestagent.conf',
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(),
help='Extension for default datastore managers. '
'Allows the use of custom managers for each of '

View File

@ -217,6 +217,32 @@ class ClusterTasks(Cluster):
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,
datastore_manager, packages, volume_size,
backup_id, availability_zone, root_password, nics,
@ -242,6 +268,8 @@ class FreshInstanceTasks(FreshInstance, NotifyMixin, ConfigurationMixin):
LOG.debug("Successfully created security group for "
"instance: %s" % self.id)
files = self._get_injected_files(datastore_manager)
if use_heat:
volume_info = self._create_server_volume_heat(
flavor,
@ -249,7 +277,8 @@ class FreshInstanceTasks(FreshInstance, NotifyMixin, ConfigurationMixin):
datastore_manager,
volume_size,
availability_zone,
nics)
nics,
files)
elif use_nova_server_volume:
volume_info = self._create_server_volume(
flavor['id'],
@ -258,7 +287,8 @@ class FreshInstanceTasks(FreshInstance, NotifyMixin, ConfigurationMixin):
datastore_manager,
volume_size,
availability_zone,
nics)
nics,
files)
else:
volume_info = self._create_server_volume_individually(
flavor['id'],
@ -267,7 +297,8 @@ class FreshInstanceTasks(FreshInstance, NotifyMixin, ConfigurationMixin):
datastore_manager,
volume_size,
availability_zone,
nics)
nics,
files)
config = self._render_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,
datastore_manager, volume_size,
availability_zone, nics):
availability_zone, nics, files):
LOG.debug("Begin _create_server_volume for id: %s" % self.id)
try:
files, userdata = self._prepare_file_and_userdata(
datastore_manager)
userdata = self._prepare_userdata(datastore_manager)
name = self.hostname or self.name
volume_desc = ("datastore volume for %s" % self.id)
volume_name = ("datastore-%s" % self.id)
@ -508,10 +538,9 @@ class FreshInstanceTasks(FreshInstance, NotifyMixin, ConfigurationMixin):
return final
def _create_server_volume_heat(self, flavor, image_id,
datastore_manager,
volume_size, availability_zone,
nics):
LOG.debug("begin _create_server_volume_heat for id: %s" % self.id)
datastore_manager, volume_size,
availability_zone, nics, files):
LOG.debug("Begin _create_server_volume_heat for id: %s" % self.id)
try:
client = create_heat_client(self.context)
tcp_rules_mapping_list = self._build_sg_rules_mapping(CONF.get(
@ -526,7 +555,8 @@ class FreshInstanceTasks(FreshInstance, NotifyMixin, ConfigurationMixin):
ifaces=ifaces, ports=ports,
tcp_rules=tcp_rules_mapping_list,
udp_rules=udp_ports_mapping_list,
datastore_manager=datastore_manager)
datastore_manager=datastore_manager,
files=files)
try:
heat_template = heat_template_unicode.encode('utf-8')
except UnicodeEncodeError:
@ -604,8 +634,8 @@ class FreshInstanceTasks(FreshInstance, NotifyMixin, ConfigurationMixin):
def _create_server_volume_individually(self, flavor_id, image_id,
security_groups, datastore_manager,
volume_size,
availability_zone, nics):
volume_size, availability_zone,
nics, files):
LOG.debug("Begin _create_server_volume_individually for id: %s" %
self.id)
server = None
@ -616,7 +646,7 @@ class FreshInstanceTasks(FreshInstance, NotifyMixin, ConfigurationMixin):
server = self._create_server(flavor_id, image_id, security_groups,
datastore_manager,
block_device_mapping,
availability_zone, nics)
availability_zone, nics, files)
server_id = server.id
# Save server ID.
self.update_db(compute_instance_id=server_id)
@ -707,28 +737,19 @@ class FreshInstanceTasks(FreshInstance, NotifyMixin, ConfigurationMixin):
'volumes': created_volumes}
return volume_info
def _prepare_file_and_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()
def _prepare_userdata(self, datastore_manager):
userdata = None
cloudinit = os.path.join(CONF.get('cloudinit_location'),
"%s.cloudinit" % datastore_manager)
if os.path.isfile(cloudinit):
with open(cloudinit, "r") as f:
userdata = f.read()
return files, userdata
return userdata
def _create_server(self, flavor_id, image_id, security_groups,
datastore_manager, block_device_mapping,
availability_zone, nics):
files, userdata = self._prepare_file_and_userdata(
datastore_manager)
availability_zone, nics, files={}):
userdata = self._prepare_userdata(datastore_manager)
name = self.hostname or self.name
bdmap = block_device_mapping
config_drive = CONF.use_nova_server_config_drive

View File

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

View File

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

View File

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