From a4e054e3ac68426b71a55dfedc4385966b0ef623 Mon Sep 17 00:00:00 2001 From: Greg Lucas Date: Wed, 8 Oct 2014 16:26:45 +0300 Subject: [PATCH] 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 Co-Authored-By: Duk Loi 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 --- etc/trove/conf.d/README | 2 +- etc/trove/conf.d/guest_info | 1 - etc/trove/conf.d/guest_info.conf | 1 + etc/trove/trove-taskmanager.conf.sample | 6 +- trove/common/cfg.py | 15 +++- trove/taskmanager/models.py | 75 ++++++++++++------- trove/templates/default.heat.template | 12 ++- trove/tests/unittests/common/test_template.py | 6 +- .../unittests/taskmanager/test_models.py | 42 +++++++++-- 9 files changed, 113 insertions(+), 47 deletions(-) delete mode 100644 etc/trove/conf.d/guest_info create mode 100644 etc/trove/conf.d/guest_info.conf diff --git a/etc/trove/conf.d/README b/etc/trove/conf.d/README index e72154054f..d27a4ff9c7 100644 --- a/etc/trove/conf.d/README +++ b/etc/trove/conf.d/README @@ -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. diff --git a/etc/trove/conf.d/guest_info b/etc/trove/conf.d/guest_info deleted file mode 100644 index fb5ba36a53..0000000000 --- a/etc/trove/conf.d/guest_info +++ /dev/null @@ -1 +0,0 @@ -# Arbitrary information that the guest needs to work diff --git a/etc/trove/conf.d/guest_info.conf b/etc/trove/conf.d/guest_info.conf new file mode 100644 index 0000000000..6a1f77c271 --- /dev/null +++ b/etc/trove/conf.d/guest_info.conf @@ -0,0 +1 @@ +# Guest-specific information injected by the taskmanager diff --git a/etc/trove/trove-taskmanager.conf.sample b/etc/trove/trove-taskmanager.conf.sample index 038251f276..3c29b1a5e9 100644 --- a/etc/trove/trove-taskmanager.conf.sample +++ b/etc/trove/trove-taskmanager.conf.sample @@ -194,7 +194,11 @@ pydev_debug = disabled #pydev_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 ======================== diff --git a/trove/common/cfg.py b/trove/common/cfg.py index 28fc5e1f55..b879dd0bb3 100644 --- a/trove/common/cfg.py +++ b/trove/common/cfg.py @@ -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 ' diff --git a/trove/taskmanager/models.py b/trove/taskmanager/models.py index 4e40268354..f769b8569d 100755 --- a/trove/taskmanager/models.py +++ b/trove/taskmanager/models.py @@ -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 diff --git a/trove/templates/default.heat.template b/trove/templates/default.heat.template index 41a4a539da..839e5e56d1 100644 --- a/trove/templates/default.heat.template +++ b/trove/templates/default.heat.template @@ -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} diff --git a/trove/tests/unittests/common/test_template.py b/trove/tests/unittests/common/test_template.py index 60fd338081..3066404bf8 100644 --- a/trove/tests/unittests/common/test_template.py +++ b/trove/tests/unittests/common/test_template.py @@ -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) diff --git a/trove/tests/unittests/taskmanager/test_models.py b/trove/tests/unittests/taskmanager/test_models.py index 5996a70f40..cf3157e22d 100644 --- a/trove/tests/unittests/taskmanager/test_models.py +++ b/trove/tests/unittests/taskmanager/test_models.py @@ -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):