Merge ~raharper/cloud-init:default-lang-c-utf8 into cloud-init:master

Proposed by Ryan Harper
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)
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

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.

To post a comment you must log in.
Revision history for this message
Ryan Harper (raharper) wrote :

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
% lxc file pull a3/var/log/cloud-init.log - | PYTHONPATH=`pwd` python3 -m cloudinit.analyze blame -i - | grep locale
     01.07200s (modules-config/config-locale)

# modified artful image (updated cloud-init, LANG=C.UTF-8 in /etc/default/locale)
% lxc file pull a1/var/log/cloud-init.log - | PYTHONPATH=`pwd` python3 -m cloudinit.analyze blame -i - | grep locale
     00.00200s (modules-config/config-locale)

Revision history for this message
Server Team CI bot (server-team-bot) wrote :

PASSED: Continuous integration, rev:8fe47a4abc15c59579c5a7805ff409c4ec140563
https://jenkins.ubuntu.com/server/job/cloud-init-ci/159/
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://jenkins.ubuntu.com/server/job/cloud-init-ci/159/rebuild

review: Approve (continuous-integration)
Revision history for this message
Server Team CI bot (server-team-bot) wrote :

PASSED: Continuous integration, rev:aaec147b763cefe35e9d9f246d502f27de59edd7
https://jenkins.ubuntu.com/server/job/cloud-init-ci/161/
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://jenkins.ubuntu.com/server/job/cloud-init-ci/161/rebuild

review: Approve (continuous-integration)
Revision history for this message
Chad Smith (chad.smith) wrote :

Looks good, just some inline nits and questions

Revision history for this message
Ryan Harper (raharper) :
Revision history for this message
Scott Moser (smoser) wrote :

The distro can already override the default locale by simply providing /etc/cloud/cloud.cfg.d/ entries with 'locale: C.UTF-8'. So I don't know 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".

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.

review: Needs Information
Revision history for this message
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/cloud.cfg.d/ entries with 'locale: C.UTF-8'. So I don't know
> 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/locale)
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://code.launchpad.net/~raharper/cloud-init/+git/
> cloud-init/+merge/329152
> You are the owner of ~raharper/cloud-init:default-lang-c-utf8.
>

Revision history for this message
Scott Moser (smoser) wrote :
Download full text (4.2 KiB)

> 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/cloud.cfg.d/ entries with 'locale: C.UTF-8'. So I don't know
> > 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="en_US.UTF-8"

  $ sudo cat /proc/1/environ | tr '\0' '\n'
  HOME=/
  init=/sbin/init
  recovery=
  TERM=linux
  drop_caps=
  BOOT_IMAGE=/boot/vmlinuz-4.12.0-11-generic
  PATH=/sbin:/usr/sbin:/bin:/usr/bin
  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/locale has value:
       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.

> ...

Read more...

Revision history for this message
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'.

Revision history for this message
Server Team CI bot (server-team-bot) wrote :

PASSED: Continuous integration, rev:cf1c4106545b6acceded30ae9f3f65910277335e
https://jenkins.ubuntu.com/server/job/cloud-init-ci/229/
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://jenkins.ubuntu.com/server/job/cloud-init-ci/229/rebuild

review: Approve (continuous-integration)
Revision history for this message
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.

review: Approve
Revision history for this message
Scott Moser (smoser) wrote :

some comments in line.

Revision history for this message
Ryan Harper (raharper) wrote :

Thanks for review, will fix up.

Revision history for this message
Server Team CI bot (server-team-bot) wrote :

FAILED: Continuous integration, rev:29ab5e4724b755c1fe2346cc1e9a48bdcc216386
https://jenkins.ubuntu.com/server/job/cloud-init-ci/236/
Executed test runs:
    SUCCESS: Checkout
    SUCCESS: Unit & Style Tests
    FAILED: Ubuntu LTS: Build

Click here to trigger a rebuild:
https://jenkins.ubuntu.com/server/job/cloud-init-ci/236/rebuild

review: Needs Fixing (continuous-integration)
Revision history for this message
Scott Moser (smoser) wrote :

your tests failed due to some bad test in master.
i fixed that with 300e4516f78dbb0a9533749aa84f7e366b023d04
and then fixed your copy of that bad test too.

 http://paste.ubuntu.com/25435526/

I'm 'approving' and merging with my change above.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/cloudinit/distros/__init__.py b/cloudinit/distros/__init__.py
2index 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()
15diff --git a/cloudinit/distros/debian.py b/cloudinit/distros/debian.py
16index 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
140diff --git a/cloudinit/sources/__init__.py b/cloudinit/sources/__init__.py
141index 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):
167diff --git a/tests/unittests/test_distros/test_debian.py b/tests/unittests/test_distros/test_debian.py
168index 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))
296diff --git a/tests/unittests/test_distros/test_generic.py b/tests/unittests/test_distros/test_generic.py
297index 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
322diff --git a/tests/unittests/test_handler/test_handler_debug.py b/tests/unittests/test_handler/test_handler_debug.py
323index 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': {
365diff --git a/tests/unittests/test_handler/test_handler_locale.py b/tests/unittests/test_handler/test_handler_locale.py
366index 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

Subscribers

People subscribed via source and target branches