Merge ~raharper/cloud-init:default-lang-c-utf8 into cloud-init:master
- Git
- lp:~raharper/cloud-init
- default-lang-c-utf8
- Merge into master
Status: | Merged |
---|---|
Approved by: | Scott Moser |
Approved revision: | 29ab5e4724b755c1fe2346cc1e9a48bdcc216386 |
Merged at revision: | 7e76c57b590c7c2c13f7b1a2a8b5b7d4f2d18396 |
Proposed branch: | ~raharper/cloud-init:default-lang-c-utf8 |
Merge into: | cloud-init:master |
Diff against target: |
435 lines (+195/-52) 7 files modified
cloudinit/distros/__init__.py (+3/-0) cloudinit/distros/debian.py (+71/-23) cloudinit/sources/__init__.py (+8/-1) tests/unittests/test_distros/test_debian.py (+42/-24) tests/unittests/test_distros/test_generic.py (+16/-0) tests/unittests/test_handler/test_handler_debug.py (+7/-4) tests/unittests/test_handler/test_handler_locale.py (+48/-0) |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Scott Moser | Approve | ||
Server Team CI bot | continuous-integration | Needs Fixing | |
Chad Smith | Approve | ||
Review via email: mp+329152@code.launchpad.net |
Commit message
Description of the change
distro: allow distro to specify a default locale
Currently the cloud-init default locale (en_US.UTF-8) is set by
the base datasource class. This patch allows a distro to overide
the fallback value with one that's available in the distro. For
Debian and Ubuntu, the C.UTF-8 lang is available and built-in to
cloud-images. Adjust apply_locale logic to skip locale-regen if
the specified LANG value is C.UTF-8, it does not require regeneration.
Further add unittests to exercise the default paths for Ubuntu and
non-ubuntu paths to validate they get the LANG expected.
Ryan Harper (raharper) wrote : | # |
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:8fe47a4abc1
https:/
Executed test runs:
SUCCESS: Checkout
SUCCESS: Unit & Style Tests
SUCCESS: Ubuntu LTS: Build
SUCCESS: Ubuntu LTS: Integration
SUCCESS: MAAS Compatability Testing
IN_PROGRESS: Declarative: Post Actions
Click here to trigger a rebuild:
https:/
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:aaec147b763
https:/
Executed test runs:
SUCCESS: Checkout
SUCCESS: Unit & Style Tests
SUCCESS: Ubuntu LTS: Build
SUCCESS: Ubuntu LTS: Integration
SUCCESS: MAAS Compatability Testing
IN_PROGRESS: Declarative: Post Actions
Click here to trigger a rebuild:
https:/
Chad Smith (chad.smith) wrote : | # |
Looks good, just some inline nits and questions
Ryan Harper (raharper) : | # |
Scott Moser (smoser) wrote : | # |
The distro can already override the default locale by simply providing /etc/cloud/
The bigger issue with this is that it changes the default locale for any instance with cloud-initnit from en_US.UTF-8 to C.UTF_8. That is a behavior change that we'd then have to revert in Ubuntu stable releases. Admittedly, I'm not sure of the real-world affect of that change other than having 'locale' mention it, but I think that change was generally not intended or thought out.
The one other comment I have is that it'd be best if we chose to not regenerate any locale that was already generated. Currently what we do is simply not generate (or regenerate) the locale if it is configured to be the system default locale.
Ryan Harper (raharper) wrote : | # |
On Fri, Aug 18, 2017 at 3:57 PM, Scott Moser <email address hidden> wrote:
> Review: Needs Information
>
> The distro can already override the default locale by simply providing
> /etc/cloud/
> that there is a reason to have the added complexity of looking this up in
> the distro object. Arguably we should support configuring locale under
> "system_info" as it is OS configuration similar to "default_user".
>
In general, I think default locale is something only the distro can
provide; I don;t think it's something that should be in the datasource (the
default value)
A distro can tell cloud-init what the current settings are:
(read LANG from env)
See what's available:
locale -a
And possible generate new locales
(locale-gen)
If anything, it would be better to remove the "Default" value from
Datasource base-class and leave it to the distro (to determine default).
>
> The bigger issue with this is that it changes the default locale for any
> instance with cloud-initnit from en_US.UTF-8 to C.UTF_8. That is a
> behavior change that we'd then have to revert in Ubuntu stable releases.
> Admittedly, I'm not sure of the real-world affect of that change other than
> having 'locale' mention it, but I think that change was generally not
> intended or thought out.
>
Yes, that's true; I don't think we can change this in previous stable
releases.
>
>
> The one other comment I have is that it'd be best if we chose to not
> regenerate any locale that was already generated. Currently what we do is
> simply not generate (or regenerate) the locale if it is configured to be
> the system default locale.
>
Yes, I was hoping to avoid more exec'ing but with some thought, I probably
makes sense to
1) determine the current LANG setting
2) check if there's a system configured LANG setting (/etc/default/
3) check what's available (locale -a)
compare that to what's being requested
And only locale-gen if the requested locale isn't already available (1, 2)
If the requested locale is available (whether we regen or not) we should
update
the system_conf file with the requested locale.
Does that make sense?
>
> --
> https:/
> cloud-init/
> You are the owner of ~raharper/
>
Scott Moser (smoser) wrote : | # |
> On Fri, Aug 18, 2017 at 3:57 PM, Scott Moser <email address hidden> wrote:
>
> > Review: Needs Information
> >
> > The distro can already override the default locale by simply providing
> > /etc/cloud/
> > that there is a reason to have the added complexity of looking this up in
> > the distro object. Arguably we should support configuring locale under
> > "system_info" as it is OS configuration similar to "default_user".
> >
>
> In general, I think default locale is something only the distro can
> provide; I don;t think it's something that should be in the datasource (the
> default value)
I agree that the datasource seems an odd place for this setting.
One example of it not being all that odd, though is if it came from the
metadata service. Ie, it isn't that far fetched at all to consider
a China based cloud-provider to want to default to having a Chinese locale.
We're not doing anything dynamic with the locale currently, though.
So for now I have to agree.
> A distro can tell cloud-init what the current settings are:
>
> (read LANG from env)
Well, not really. LANG is just the LANG of the user running cloud-init.
Most of the time that would be the LANG that is set in the init system.
That definitely could contain 'LANG', but to my knowledge usually doesnt.
From my desktop:
$ cat /etc/default/locale
LANG=
$ sudo cat /proc/1/environ | tr '\0' '\n'
HOME=/
init=/sbin/init
recovery=
TERM=linux
drop_caps=
BOOT_
PATH=
PWD=/
rootmnt=/root
Thats the environment that cloud-init would inherit when running in normal
boot, it does not have a LANG set.
> If anything, it would be better to remove the "Default" value from
> Datasource base-class and leave it to the distro (to determine default).
Yes, you've convinced me of this. But your 'get_locale' method that
you've added to distro should read /etc/default/locale on Debian/Ubuntu.
That way we read the "correct" place for such a definition and the
image-builder does not have to repeat themselves or patch cloud-init
to get that change done.
Basically, get_locale should do
if //etc/default/
return that
else
return 'en_US.UTF-8'
That would make cloud-init respect the wishes of the image builder
and also allow the cloud-images team to make the change if they so
choose.
The only issue I see with this is that they wont' necessarily think that
through, and a bug will be opened against cloud-init for behavioral change...
> > The bigger issue with this is that it changes the default locale for any
> > instance with cloud-initnit from en_US.UTF-8 to C.UTF_8. That is a
> > behavior change that we'd then have to revert in Ubuntu stable releases.
> > Admittedly, I'm not sure of the real-world affect of that change other than
> > having 'locale' mention it, but I think that change was generally not
> > intended or thought out.
> >
> Yes, that's true; I don't think we can change this in previous stable
> releases.
I'm willing to push that burden off onto to the cloud-images team if they
agree.
> ...
Scott Moser (smoser) wrote : | # |
I moved to work-in-progress.
Please feel free to respond and/or disagree with above and then move it back to 'needs review'.
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:cf1c4106545
https:/
Executed test runs:
SUCCESS: Checkout
SUCCESS: Unit & Style Tests
SUCCESS: Ubuntu LTS: Build
SUCCESS: Ubuntu LTS: Integration
SUCCESS: MAAS Compatability Testing
IN_PROGRESS: Declarative: Post Actions
Click here to trigger a rebuild:
https:/
Chad Smith (chad.smith) wrote : | # |
Changeset looks good to me assuming (as you said) we can rely on image creators to properly write /etc/default/locale +1.
Scott Moser (smoser) wrote : | # |
some comments in line.
Ryan Harper (raharper) wrote : | # |
Thanks for review, will fix up.
Server Team CI bot (server-team-bot) wrote : | # |
FAILED: Continuous integration, rev:29ab5e4724b
https:/
Executed test runs:
SUCCESS: Checkout
SUCCESS: Unit & Style Tests
FAILED: Ubuntu LTS: Build
Click here to trigger a rebuild:
https:/
Scott Moser (smoser) wrote : | # |
your tests failed due to some bad test in master.
i fixed that with 300e4516f78dbb0
and then fixed your copy of that bad test too.
http://
I'm 'approving' and merging with my change above.
Preview Diff
1 | diff --git a/cloudinit/distros/__init__.py b/cloudinit/distros/__init__.py |
2 | index 807b3ea..b714b9a 100755 |
3 | --- a/cloudinit/distros/__init__.py |
4 | +++ b/cloudinit/distros/__init__.py |
5 | @@ -188,6 +188,9 @@ class Distro(object): |
6 | def _get_localhost_ip(self): |
7 | return "127.0.0.1" |
8 | |
9 | + def get_locale(self): |
10 | + raise NotImplementedError() |
11 | + |
12 | @abc.abstractmethod |
13 | def _read_hostname(self, filename, default=None): |
14 | raise NotImplementedError() |
15 | diff --git a/cloudinit/distros/debian.py b/cloudinit/distros/debian.py |
16 | index abfb81f..33cc0bf 100644 |
17 | --- a/cloudinit/distros/debian.py |
18 | +++ b/cloudinit/distros/debian.py |
19 | @@ -61,11 +61,49 @@ class Distro(distros.Distro): |
20 | # should only happen say once per instance...) |
21 | self._runner = helpers.Runners(paths) |
22 | self.osfamily = 'debian' |
23 | + self.default_locale = 'en_US.UTF-8' |
24 | + self.system_locale = None |
25 | |
26 | - def apply_locale(self, locale, out_fn=None): |
27 | + def get_locale(self): |
28 | + """Return the default locale if set, else use default locale""" |
29 | + |
30 | + # read system locale value |
31 | + if not self.system_locale: |
32 | + self.system_locale = read_system_locale() |
33 | + |
34 | + # Return system_locale setting if valid, else use default locale |
35 | + return (self.system_locale if self.system_locale else |
36 | + self.default_locale) |
37 | + |
38 | + def apply_locale(self, locale, out_fn=None, keyname='LANG'): |
39 | + """Apply specified locale to system, regenerate if specified locale |
40 | + differs from system default.""" |
41 | if not out_fn: |
42 | out_fn = LOCALE_CONF_FN |
43 | - apply_locale(locale, out_fn) |
44 | + |
45 | + if not locale: |
46 | + raise ValueError('Failed to provide locale value.') |
47 | + |
48 | + # Only call locale regeneration if needed |
49 | + # Update system locale config with specified locale if needed |
50 | + distro_locale = self.get_locale() |
51 | + conf_fn_exists = os.path.exists(out_fn) |
52 | + sys_locale_unset = False if self.system_locale else True |
53 | + need_regen = (locale.lower() != distro_locale.lower() or |
54 | + not conf_fn_exists or sys_locale_unset) |
55 | + need_conf = not conf_fn_exists or need_regen or sys_locale_unset |
56 | + |
57 | + if need_regen: |
58 | + regenerate_locale(locale, out_fn, keyname=keyname) |
59 | + else: |
60 | + LOG.debug( |
61 | + "System has '%s=%s' requested '%s', skipping regeneration.", |
62 | + keyname, self.system_locale, locale) |
63 | + |
64 | + if need_conf: |
65 | + update_locale_conf(locale, out_fn, keyname=keyname) |
66 | + # once we've updated the system config, invalidate cache |
67 | + self.system_locale = None |
68 | |
69 | def install_packages(self, pkglist): |
70 | self.update_package_sources() |
71 | @@ -218,37 +256,47 @@ def _maybe_remove_legacy_eth0(path="/etc/network/interfaces.d/eth0.cfg"): |
72 | LOG.warning(msg) |
73 | |
74 | |
75 | -def apply_locale(locale, sys_path=LOCALE_CONF_FN, keyname='LANG'): |
76 | - """Apply the locale. |
77 | - |
78 | - Run locale-gen for the provided locale and set the default |
79 | - system variable `keyname` appropriately in the provided `sys_path`. |
80 | - |
81 | - If sys_path indicates that `keyname` is already set to `locale` |
82 | - then no changes will be made and locale-gen not called. |
83 | - This allows images built with a locale already generated to not re-run |
84 | - locale-gen which can be very heavy. |
85 | - """ |
86 | - if not locale: |
87 | - raise ValueError('Failed to provide locale value.') |
88 | - |
89 | +def read_system_locale(sys_path=LOCALE_CONF_FN, keyname='LANG'): |
90 | + """Read system default locale setting, if present""" |
91 | + sys_val = "" |
92 | if not sys_path: |
93 | raise ValueError('Invalid path: %s' % sys_path) |
94 | |
95 | if os.path.exists(sys_path): |
96 | locale_content = util.load_file(sys_path) |
97 | - # if LANG isn't present, regen |
98 | sys_defaults = util.load_shell_content(locale_content) |
99 | sys_val = sys_defaults.get(keyname, "") |
100 | - if sys_val.lower() == locale.lower(): |
101 | - LOG.debug( |
102 | - "System has '%s=%s' requested '%s', skipping regeneration.", |
103 | - keyname, sys_val, locale) |
104 | - return |
105 | |
106 | - util.subp(['locale-gen', locale], capture=False) |
107 | + return sys_val |
108 | + |
109 | + |
110 | +def update_locale_conf(locale, sys_path, keyname='LANG'): |
111 | + """Update system locale config""" |
112 | + LOG.debug('Updating %s with locale setting %s=%s', |
113 | + sys_path, keyname, locale) |
114 | util.subp( |
115 | ['update-locale', '--locale-file=' + sys_path, |
116 | '%s=%s' % (keyname, locale)], capture=False) |
117 | |
118 | + |
119 | +def regenerate_locale(locale, sys_path, keyname='LANG'): |
120 | + """ |
121 | + Run locale-gen for the provided locale and set the default |
122 | + system variable `keyname` appropriately in the provided `sys_path`. |
123 | + |
124 | + """ |
125 | + # special case for locales which do not require regen |
126 | + # % locale -a |
127 | + # C |
128 | + # C.UTF-8 |
129 | + # POSIX |
130 | + if locale.lower() in ['c', 'c.utf-8', 'posix']: |
131 | + LOG.debug('%s=%s does not require rengeneration', keyname, locale) |
132 | + return |
133 | + |
134 | + # finally, trigger regeneration |
135 | + LOG.debug('Generating locales for %s', locale) |
136 | + util.subp(['locale-gen', locale], capture=False) |
137 | + |
138 | + |
139 | # vi: ts=4 expandtab |
140 | diff --git a/cloudinit/sources/__init__.py b/cloudinit/sources/__init__.py |
141 | index 952caf3..9a43fbe 100644 |
142 | --- a/cloudinit/sources/__init__.py |
143 | +++ b/cloudinit/sources/__init__.py |
144 | @@ -44,6 +44,7 @@ class DataSourceNotFoundException(Exception): |
145 | class DataSource(object): |
146 | |
147 | dsmode = DSMODE_NETWORK |
148 | + default_locale = 'en_US.UTF-8' |
149 | |
150 | def __init__(self, sys_cfg, distro, paths, ud_proc=None): |
151 | self.sys_cfg = sys_cfg |
152 | @@ -150,7 +151,13 @@ class DataSource(object): |
153 | return None |
154 | |
155 | def get_locale(self): |
156 | - return 'en_US.UTF-8' |
157 | + """Default locale is en_US.UTF-8, but allow distros to override""" |
158 | + locale = self.default_locale |
159 | + try: |
160 | + locale = self.distro.get_locale() |
161 | + except NotImplementedError: |
162 | + pass |
163 | + return locale |
164 | |
165 | @property |
166 | def availability_zone(self): |
167 | diff --git a/tests/unittests/test_distros/test_debian.py b/tests/unittests/test_distros/test_debian.py |
168 | index 2330ad5..72d3aad 100644 |
169 | --- a/tests/unittests/test_distros/test_debian.py |
170 | +++ b/tests/unittests/test_distros/test_debian.py |
171 | @@ -1,67 +1,85 @@ |
172 | # This file is part of cloud-init. See LICENSE file for license information. |
173 | |
174 | -from ..helpers import (CiTestCase, mock) |
175 | - |
176 | -from cloudinit.distros.debian import apply_locale |
177 | +from cloudinit import distros |
178 | from cloudinit import util |
179 | +from ..helpers import (FilesystemMockingTestCase, mock) |
180 | |
181 | |
182 | @mock.patch("cloudinit.distros.debian.util.subp") |
183 | -class TestDebianApplyLocale(CiTestCase): |
184 | +class TestDebianApplyLocale(FilesystemMockingTestCase): |
185 | + |
186 | + def setUp(self): |
187 | + super(TestDebianApplyLocale, self).setUp() |
188 | + self.new_root = self.tmp_dir() |
189 | + self.patchOS(self.new_root) |
190 | + self.patchUtils(self.new_root) |
191 | + self.spath = self.tmp_path('etc/default/locale', self.new_root) |
192 | + cls = distros.fetch("debian") |
193 | + self.distro = cls("debian", {}, None) |
194 | + |
195 | def test_no_rerun(self, m_subp): |
196 | """If system has defined locale, no re-run is expected.""" |
197 | - spath = self.tmp_path("default-locale") |
198 | m_subp.return_value = (None, None) |
199 | locale = 'en_US.UTF-8' |
200 | - util.write_file(spath, 'LANG=%s\n' % locale, omode="w") |
201 | - apply_locale(locale, sys_path=spath) |
202 | + util.write_file(self.spath, 'LANG=%s\n' % locale, omode="w") |
203 | + self.distro.apply_locale(locale, out_fn=self.spath) |
204 | m_subp.assert_not_called() |
205 | |
206 | + def test_no_regen_on_c_utf8(self, m_subp): |
207 | + """If locale is set to C.UTF8, do not attempt to call locale-gen""" |
208 | + m_subp.return_value = (None, None) |
209 | + locale = 'C.UTF-8' |
210 | + util.write_file(self.spath, 'LANG=%s\n' % 'en_US.UTF-8', omode="w") |
211 | + self.distro.apply_locale(locale, out_fn=self.spath) |
212 | + self.assertEqual( |
213 | + [['update-locale', '--locale-file=' + self.spath, |
214 | + 'LANG=%s' % locale]], |
215 | + [p[0][0] for p in m_subp.call_args_list]) |
216 | + |
217 | def test_rerun_if_different(self, m_subp): |
218 | """If system has different locale, locale-gen should be called.""" |
219 | - spath = self.tmp_path("default-locale") |
220 | m_subp.return_value = (None, None) |
221 | locale = 'en_US.UTF-8' |
222 | - util.write_file(spath, 'LANG=fr_FR.UTF-8', omode="w") |
223 | - apply_locale(locale, sys_path=spath) |
224 | + util.write_file(self.spath, 'LANG=fr_FR.UTF-8', omode="w") |
225 | + self.distro.apply_locale(locale, out_fn=self.spath) |
226 | self.assertEqual( |
227 | [['locale-gen', locale], |
228 | - ['update-locale', '--locale-file=' + spath, 'LANG=%s' % locale]], |
229 | + ['update-locale', '--locale-file=' + self.spath, |
230 | + 'LANG=%s' % locale]], |
231 | [p[0][0] for p in m_subp.call_args_list]) |
232 | |
233 | def test_rerun_if_no_file(self, m_subp): |
234 | """If system has no locale file, locale-gen should be called.""" |
235 | - spath = self.tmp_path("default-locale") |
236 | m_subp.return_value = (None, None) |
237 | locale = 'en_US.UTF-8' |
238 | - apply_locale(locale, sys_path=spath) |
239 | + self.distro.apply_locale(locale, out_fn=self.spath) |
240 | self.assertEqual( |
241 | [['locale-gen', locale], |
242 | - ['update-locale', '--locale-file=' + spath, 'LANG=%s' % locale]], |
243 | + ['update-locale', '--locale-file=' + self.spath, |
244 | + 'LANG=%s' % locale]], |
245 | [p[0][0] for p in m_subp.call_args_list]) |
246 | |
247 | def test_rerun_on_unset_system_locale(self, m_subp): |
248 | """If system has unset locale, locale-gen should be called.""" |
249 | m_subp.return_value = (None, None) |
250 | - spath = self.tmp_path("default-locale") |
251 | locale = 'en_US.UTF-8' |
252 | - util.write_file(spath, 'LANG=', omode="w") |
253 | - apply_locale(locale, sys_path=spath) |
254 | + util.write_file(self.spath, 'LANG=', omode="w") |
255 | + self.distro.apply_locale(locale, out_fn=self.spath) |
256 | self.assertEqual( |
257 | [['locale-gen', locale], |
258 | - ['update-locale', '--locale-file=' + spath, 'LANG=%s' % locale]], |
259 | + ['update-locale', '--locale-file=' + self.spath, |
260 | + 'LANG=%s' % locale]], |
261 | [p[0][0] for p in m_subp.call_args_list]) |
262 | |
263 | def test_rerun_on_mismatched_keys(self, m_subp): |
264 | """If key is LC_ALL and system has only LANG, rerun is expected.""" |
265 | m_subp.return_value = (None, None) |
266 | - spath = self.tmp_path("default-locale") |
267 | locale = 'en_US.UTF-8' |
268 | - util.write_file(spath, 'LANG=', omode="w") |
269 | - apply_locale(locale, sys_path=spath, keyname='LC_ALL') |
270 | + util.write_file(self.spath, 'LANG=', omode="w") |
271 | + self.distro.apply_locale(locale, out_fn=self.spath, keyname='LC_ALL') |
272 | self.assertEqual( |
273 | [['locale-gen', locale], |
274 | - ['update-locale', '--locale-file=' + spath, |
275 | + ['update-locale', '--locale-file=' + self.spath, |
276 | 'LC_ALL=%s' % locale]], |
277 | [p[0][0] for p in m_subp.call_args_list]) |
278 | |
279 | @@ -69,14 +87,14 @@ class TestDebianApplyLocale(CiTestCase): |
280 | """locale as None or "" is invalid and should raise ValueError.""" |
281 | |
282 | with self.assertRaises(ValueError) as ctext_m: |
283 | - apply_locale(None) |
284 | + self.distro.apply_locale(None) |
285 | m_subp.assert_not_called() |
286 | |
287 | self.assertEqual( |
288 | 'Failed to provide locale value.', str(ctext_m.exception)) |
289 | |
290 | with self.assertRaises(ValueError) as ctext_m: |
291 | - apply_locale("") |
292 | + self.distro.apply_locale("") |
293 | m_subp.assert_not_called() |
294 | self.assertEqual( |
295 | 'Failed to provide locale value.', str(ctext_m.exception)) |
296 | diff --git a/tests/unittests/test_distros/test_generic.py b/tests/unittests/test_distros/test_generic.py |
297 | index c9be277..b355a19 100644 |
298 | --- a/tests/unittests/test_distros/test_generic.py |
299 | +++ b/tests/unittests/test_distros/test_generic.py |
300 | @@ -228,5 +228,21 @@ class TestGenericDistro(helpers.FilesystemMockingTestCase): |
301 | os.symlink('/', '/run/systemd/system') |
302 | self.assertFalse(d.uses_systemd()) |
303 | |
304 | + @mock.patch('cloudinit.distros.debian.read_system_locale') |
305 | + def test_get_locale_ubuntu(self, m_locale): |
306 | + """Test ubuntu distro returns locale set to C.UTF-8""" |
307 | + m_locale.return_value = 'C.UTF-8' |
308 | + cls = distros.fetch("ubuntu") |
309 | + d = cls("ubuntu", {}, None) |
310 | + locale = d.get_locale() |
311 | + self.assertEqual('C.UTF-8', locale) |
312 | + |
313 | + def test_get_locale_rhel(self): |
314 | + """Test rhel distro returns NotImplementedError exception""" |
315 | + cls = distros.fetch("rhel") |
316 | + d = cls("rhel", {}, None) |
317 | + with self.assertRaises(NotImplementedError): |
318 | + d.get_locale() |
319 | + |
320 | |
321 | # vi: ts=4 expandtab |
322 | diff --git a/tests/unittests/test_handler/test_handler_debug.py b/tests/unittests/test_handler/test_handler_debug.py |
323 | index 929f786..1873c3e 100644 |
324 | --- a/tests/unittests/test_handler/test_handler_debug.py |
325 | +++ b/tests/unittests/test_handler/test_handler_debug.py |
326 | @@ -11,7 +11,7 @@ from cloudinit import util |
327 | |
328 | from cloudinit.sources import DataSourceNone |
329 | |
330 | -from .. import helpers as t_help |
331 | +from ..helpers import (FilesystemMockingTestCase, mock) |
332 | |
333 | import logging |
334 | import shutil |
335 | @@ -20,7 +20,8 @@ import tempfile |
336 | LOG = logging.getLogger(__name__) |
337 | |
338 | |
339 | -class TestDebug(t_help.FilesystemMockingTestCase): |
340 | +@mock.patch('cloudinit.distros.debian.read_system_locale') |
341 | +class TestDebug(FilesystemMockingTestCase): |
342 | def setUp(self): |
343 | super(TestDebug, self).setUp() |
344 | self.new_root = tempfile.mkdtemp() |
345 | @@ -36,7 +37,8 @@ class TestDebug(t_help.FilesystemMockingTestCase): |
346 | ds.metadata.update(metadata) |
347 | return cloud.Cloud(ds, paths, {}, d, None) |
348 | |
349 | - def test_debug_write(self): |
350 | + def test_debug_write(self, m_locale): |
351 | + m_locale.return_value = 'en_US.UTF-8' |
352 | cfg = { |
353 | 'abc': '123', |
354 | 'c': u'\u20a0', |
355 | @@ -54,7 +56,8 @@ class TestDebug(t_help.FilesystemMockingTestCase): |
356 | for k in cfg.keys(): |
357 | self.assertIn(k, contents) |
358 | |
359 | - def test_debug_no_write(self): |
360 | + def test_debug_no_write(self, m_locale): |
361 | + m_locale.return_value = 'en_US.UTF-8' |
362 | cfg = { |
363 | 'abc': '123', |
364 | 'debug': { |
365 | diff --git a/tests/unittests/test_handler/test_handler_locale.py b/tests/unittests/test_handler/test_handler_locale.py |
366 | index aaf6c76..4e7ce64 100644 |
367 | --- a/tests/unittests/test_handler/test_handler_locale.py |
368 | +++ b/tests/unittests/test_handler/test_handler_locale.py |
369 | @@ -20,6 +20,8 @@ from configobj import ConfigObj |
370 | from six import BytesIO |
371 | |
372 | import logging |
373 | +import mock |
374 | +import os |
375 | import shutil |
376 | import tempfile |
377 | |
378 | @@ -27,6 +29,9 @@ LOG = logging.getLogger(__name__) |
379 | |
380 | |
381 | class TestLocale(t_help.FilesystemMockingTestCase): |
382 | + |
383 | + with_logs = True |
384 | + |
385 | def setUp(self): |
386 | super(TestLocale, self).setUp() |
387 | self.new_root = tempfile.mkdtemp() |
388 | @@ -60,4 +65,47 @@ class TestLocale(t_help.FilesystemMockingTestCase): |
389 | else: |
390 | self.assertEqual({'RC_LANG': cfg['locale']}, dict(n_cfg)) |
391 | |
392 | + def test_set_locale_sles_default(self): |
393 | + cfg = {} |
394 | + cc = self._get_cloud('sles') |
395 | + cc_locale.handle('cc_locale', cfg, cc, LOG, []) |
396 | + |
397 | + if cc.distro.uses_systemd: |
398 | + locale_conf = cc.distro.systemd_locale_conf_fn |
399 | + keyname = 'LANG' |
400 | + else: |
401 | + locale_conf = cc.distro.locale_conf_fn |
402 | + keyname = 'RC_LANG' |
403 | + |
404 | + contents = util.load_file(locale_conf, decode=False) |
405 | + n_cfg = ConfigObj(BytesIO(contents)) |
406 | + self.assertEqual({keyname: 'en_US.UTF-8'}, dict(n_cfg)) |
407 | + |
408 | + def test_locale_update_config_if_different_than_default(self): |
409 | + """Test cc_locale writes updates conf if different than default""" |
410 | + locale_conf = os.path.join(self.new_root, "etc/default/locale") |
411 | + util.write_file(locale_conf, 'LANG="en_US.UTF-8"\n') |
412 | + cfg = {'locale': 'C.UTF-8'} |
413 | + cc = self._get_cloud('ubuntu') |
414 | + with mock.patch('cloudinit.distros.debian.util.subp') as m_subp: |
415 | + with mock.patch('cloudinit.distros.debian.LOCALE_CONF_FN', |
416 | + locale_conf): |
417 | + cc_locale.handle('cc_locale', cfg, cc, LOG, []) |
418 | + m_subp.assert_called_with(['update-locale', |
419 | + '--locale-file=%s' % locale_conf, |
420 | + 'LANG=C.UTF-8'], capture=False) |
421 | + |
422 | + def test_locale_rhel_defaults_en_us_utf8(self): |
423 | + """Test cc_locale gets en_US.UTF-8 from distro get_locale fallback""" |
424 | + cfg = {} |
425 | + cc = self._get_cloud('rhel') |
426 | + update_sysconfig = 'cloudinit.distros.rhel_util.update_sysconfig_file' |
427 | + with mock.patch.object(cc.distro, 'uses_systemd') as m_use_sd: |
428 | + m_use_sd.return_value = True |
429 | + with mock.patch(update_sysconfig) as m_update_syscfg: |
430 | + cc_locale.handle('cc_locale', cfg, cc, LOG, []) |
431 | + m_update_syscfg.assert_called_with('/etc/locale.conf', |
432 | + {'LANG': 'en_US.UTF-8'}) |
433 | + |
434 | + |
435 | # vi: ts=4 expandtab |
Current Ubuntu (and Debian) images already include the C.UTF-8 locale. Updating the default in cloud-init (for Ubuntu and Debian) means we can realize a non-zero speed up during boot.
Building cloud-init from this branch, injecting it into an artful image and comparing time spent in locale-gen vs. existing artful image with older cloud-init (which will locale-gen en_US.UTF-8) there's a non-zero improvement
# existing artful image log/cloud- init.log - | PYTHONPATH=`pwd` python3 -m cloudinit.analyze blame -i - | grep locale config/ config- locale)
% lxc file pull a3/var/
01.07200s (modules-
# modified artful image (updated cloud-init, LANG=C.UTF-8 in /etc/default/ locale) log/cloud- init.log - | PYTHONPATH=`pwd` python3 -m cloudinit.analyze blame -i - | grep locale config/ config- locale)
% lxc file pull a1/var/
00.00200s (modules-