From de574dc6a8a5ff32f140b803fb0e3edd5239892b Mon Sep 17 00:00:00 2001 From: Sean Dague Date: Mon, 16 Jan 2017 14:20:58 -0500 Subject: [PATCH] support localconf set --- devstack/dsconf.py | 59 ++++++++++++++ devstack/tests/test_localconf_set.py | 115 +++++++++++++++++++++++++++ 2 files changed, 174 insertions(+) create mode 100644 devstack/tests/test_localconf_set.py diff --git a/devstack/dsconf.py b/devstack/dsconf.py index 8d64b4e..a1f88b9 100644 --- a/devstack/dsconf.py +++ b/devstack/dsconf.py @@ -211,3 +211,62 @@ class LocalConf(object): def _do_set(writer, line): writer.write("%s = %s\n" % (name, value)) self._at_insert_point_local(name, _do_set) + + def _at_insert_point(self, group, conf, section, name, func): + temp = tempfile.NamedTemporaryFile(mode='r') + shutil.copyfile(self.fname, temp.name) + in_meta = False + in_section = False + done = False + with open(self.fname, "w+") as writer: + with open(temp.name) as reader: + for line in reader.readlines(): + if done: + writer.write(line) + continue + + if re.match(re.escape("[[%s|%s]]" % (group, conf)), line): + in_meta = True + writer.write(line) + elif re.match("\[\[.*\|.*\]\]", line): + # if we're not done yet, we + if in_meta: + if not in_section: + # if we've not found the section yet, + # write out section as well. + writer.write("[%s]\n" % section) + func(writer, None) + done = True + writer.write(line) + in_meta = False + in_section = False + elif re.match(re.escape("[%s]" % section), line): + # we found a relevant section + writer.write(line) + in_section = True + elif re.match("\[[^\[\]]+\]", line): + if in_meta and in_section: + # We've ended our section, in our meta, + # never found the key. Time to add it. + func(writer, None) + done = True + in_section = False + writer.write(line) + elif (in_meta and in_section and + re.match("\s*%s\s*\=" % re.escape(name), line)): + # we found our match point + func(writer, line) + done = True + else: + # write out whatever we find + writer.write(line) + if not done: + # we never found meta with a relevant section + writer.write("[[%s|%s]]\n" % (group, conf)) + writer.write("[%s]\n" % (section)) + func(writer, None) + + def set(self, group, conf, section, name, value): + def _do_set(writer, line): + writer.write("%s = %s\n" % (name, value)) + self._at_insert_point(group, conf, section, name, _do_set) diff --git a/devstack/tests/test_localconf_set.py b/devstack/tests/test_localconf_set.py new file mode 100644 index 0000000..23af330 --- /dev/null +++ b/devstack/tests/test_localconf_set.py @@ -0,0 +1,115 @@ +# Copyright 2017 IBM +# +# 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. + +# Implementation of ini add / remove for devstack. We don't use the +# python ConfigFile parser because that ends up rewriting the entire +# file and doesn't ensure comments remain. + +import fixtures +import os.path +import testtools + +from devstack import dsconf + + +BASIC = """ +[[local|localrc]] +a = b +c = d +f = 1 +[[post-config|$NEUTRON_CONF]] +[DEFAULT] +global_physnet_mtu=1450 +[[post-config|$NOVA_CONF]] +[upgrade_levels] +compute = auto +""" + +RESULT1 = """ +[[local|localrc]] +a = b +c = d +f = 1 +[[post-config|$NEUTRON_CONF]] +[DEFAULT] +global_physnet_mtu = 1400 +[[post-config|$NOVA_CONF]] +[upgrade_levels] +compute = auto +""" + +RESULT2 = """ +[[local|localrc]] +a = b +c = d +f = 1 +[[post-config|$NEUTRON_CONF]] +[DEFAULT] +global_physnet_mtu=1450 +[oslo_policy] +policy_file = /etc/neutron/policy.json +[[post-config|$NOVA_CONF]] +[upgrade_levels] +compute = auto +""" + +RESULT3 = """ +[[local|localrc]] +a = b +c = d +f = 1 +[[post-config|$NEUTRON_CONF]] +[DEFAULT] +global_physnet_mtu=1450 +[[post-config|$NOVA_CONF]] +[upgrade_levels] +compute = auto +[[post-config|$GLANCE_CONF]] +[DEFAULT] +graceful_shutdown_timeout = 5 +""" + + +class TestLcSet(testtools.TestCase): + + def setUp(self): + super(TestLcSet, self).setUp() + self._path = self.useFixture(fixtures.TempDir()).path + self._path += "/local.conf" + with open(self._path, "w") as f: + f.write(BASIC) + + def test_set_existing(self): + conf = dsconf.LocalConf(self._path) + conf.set("post-config", "$NEUTRON_CONF", "DEFAULT", + "global_physnet_mtu", "1400") + with open(self._path) as f: + content = f.read() + self.assertEqual(content, RESULT1) + + def test_set_new(self): + conf = dsconf.LocalConf(self._path) + conf.set("post-config", "$NEUTRON_CONF", "oslo_policy", + "policy_file", "/etc/neutron/policy.json") + with open(self._path) as f: + content = f.read() + self.assertEqual(content, RESULT2) + + def test_set_new_section(self): + conf = dsconf.LocalConf(self._path) + conf.set("post-config", "$GLANCE_CONF", "DEFAULT", + "graceful_shutdown_timeout", "5") + with open(self._path) as f: + content = f.read() + self.assertEqual(content, RESULT3)