Merge ~chad.smith/cloud-init:bug/status-fix-done-status into cloud-init:master

Proposed by Chad Smith
Status: Merged
Approved by: Chad Smith
Approved revision: c29199c7d8b0b525f31f1d0eda6f9647e481eeef
Merge reported by: Chad Smith
Merged at revision: 86d2fc7f515f70a5117f00baf701a0bed6310b3e
Proposed branch: ~chad.smith/cloud-init:bug/status-fix-done-status
Merge into: cloud-init:master
Diff against target: 140 lines (+32/-8)
2 files modified
cloudinit/cmd/status.py (+4/-1)
cloudinit/cmd/tests/test_status.py (+28/-7)
Reviewer Review Type Date Requested Status
Ryan Harper Approve
Server Team CI bot continuous-integration Approve
Review via email: mp+337306@code.launchpad.net

Commit message

cli: fix cloud-init status to report running when before result.json

Fix various corner cases for cloud-init status subcommand. Report
'runnning' under the following conditions:
 - No /run/cloud-init/result.json file exists
 - Any stage in status.json is unfinished
 - status.json reports a non-null stage it is in progress on

LP: #1747965

Description of the change

see commit message

to test:
# provide slow running cloud-config

cat >slow.yaml <EOF
#cloud-config
runcmd:
 - sleep 30
EOF

make deb # in this branch
lxc launch ubuntu-daily:bionic myb1
lxc file push cloud-init*deb myb1/.
lxc exec myb1 'dpkg -i /cloud-init*deb'
lxc config set myb1 user.user-data - < slow.yaml
lxc exec myb1 'cloud-init clean --logs --reboot'
lxc exec myb1 bash
cloud-init --wait # should print dots for < 30 seconds and exit w/ done

To post a comment you must log in.
Revision history for this message
Server Team CI bot (server-team-bot) wrote :

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

review: Approve (continuous-integration)
Revision history for this message
Ryan Harper (raharper) wrote :

I repeated my test case which is similar to the above test, but without a long running runcmd, just booting a lxd container and this branch works as expected.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/cloudinit/cmd/status.py b/cloudinit/cmd/status.py
2index d7aaee9..ea79a85 100644
3--- a/cloudinit/cmd/status.py
4+++ b/cloudinit/cmd/status.py
5@@ -105,12 +105,12 @@ def _get_status_details(paths):
6
7 Values are obtained from parsing paths.run_dir/status.json.
8 """
9-
10 status = STATUS_ENABLED_NOT_RUN
11 status_detail = ''
12 status_v1 = {}
13
14 status_file = os.path.join(paths.run_dir, 'status.json')
15+ result_file = os.path.join(paths.run_dir, 'result.json')
16
17 (is_disabled, reason) = _is_cloudinit_disabled(
18 CLOUDINIT_DISABLED_FILE, paths)
19@@ -118,12 +118,15 @@ def _get_status_details(paths):
20 status = STATUS_DISABLED
21 status_detail = reason
22 if os.path.exists(status_file):
23+ if not os.path.exists(result_file):
24+ status = STATUS_RUNNING
25 status_v1 = load_json(load_file(status_file)).get('v1', {})
26 errors = []
27 latest_event = 0
28 for key, value in sorted(status_v1.items()):
29 if key == 'stage':
30 if value:
31+ status = STATUS_RUNNING
32 status_detail = 'Running in stage: {0}'.format(value)
33 elif key == 'datasource':
34 status_detail = value
35diff --git a/cloudinit/cmd/tests/test_status.py b/cloudinit/cmd/tests/test_status.py
36index a7c0a91..4a5a8c0 100644
37--- a/cloudinit/cmd/tests/test_status.py
38+++ b/cloudinit/cmd/tests/test_status.py
39@@ -7,7 +7,7 @@ from textwrap import dedent
40
41 from cloudinit.atomic_helper import write_json
42 from cloudinit.cmd import status
43-from cloudinit.util import write_file
44+from cloudinit.util import ensure_file
45 from cloudinit.tests.helpers import CiTestCase, wrap_and_call, mock
46
47 mypaths = namedtuple('MyPaths', 'run_dir')
48@@ -36,7 +36,7 @@ class TestStatus(CiTestCase):
49
50 def test__is_cloudinit_disabled_false_on_sysvinit(self):
51 '''When not in an environment using systemd, return False.'''
52- write_file(self.disable_file, '') # Create the ignored disable file
53+ ensure_file(self.disable_file) # Create the ignored disable file
54 (is_disabled, reason) = wrap_and_call(
55 'cloudinit.cmd.status',
56 {'uses_systemd': False},
57@@ -47,7 +47,7 @@ class TestStatus(CiTestCase):
58
59 def test__is_cloudinit_disabled_true_on_disable_file(self):
60 '''When using systemd and disable_file is present return disabled.'''
61- write_file(self.disable_file, '') # Create observed disable file
62+ ensure_file(self.disable_file) # Create observed disable file
63 (is_disabled, reason) = wrap_and_call(
64 'cloudinit.cmd.status',
65 {'uses_systemd': True},
66@@ -58,7 +58,7 @@ class TestStatus(CiTestCase):
67
68 def test__is_cloudinit_disabled_false_on_kernel_cmdline_enable(self):
69 '''Not disabled when using systemd and enabled via commandline.'''
70- write_file(self.disable_file, '') # Create ignored disable file
71+ ensure_file(self.disable_file) # Create ignored disable file
72 (is_disabled, reason) = wrap_and_call(
73 'cloudinit.cmd.status',
74 {'uses_systemd': True,
75@@ -96,7 +96,7 @@ class TestStatus(CiTestCase):
76 def test__is_cloudinit_disabled_false_when_enabled_in_systemd(self):
77 '''Report enabled when systemd generator creates the enabled file.'''
78 enabled_file = os.path.join(self.paths.run_dir, 'enabled')
79- write_file(enabled_file, '')
80+ ensure_file(enabled_file)
81 (is_disabled, reason) = wrap_and_call(
82 'cloudinit.cmd.status',
83 {'uses_systemd': True,
84@@ -149,8 +149,25 @@ class TestStatus(CiTestCase):
85 ''')
86 self.assertEqual(expected, m_stdout.getvalue())
87
88+ def test_status_returns_running_on_no_results_json(self):
89+ '''Report running when status.json exists but result.json does not.'''
90+ result_file = self.tmp_path('result.json', self.new_root)
91+ write_json(self.status_file, {})
92+ self.assertFalse(
93+ os.path.exists(result_file), 'Unexpected result.json found')
94+ cmdargs = myargs(long=False, wait=False)
95+ with mock.patch('sys.stdout', new_callable=StringIO) as m_stdout:
96+ retcode = wrap_and_call(
97+ 'cloudinit.cmd.status',
98+ {'_is_cloudinit_disabled': (False, ''),
99+ 'Init': {'side_effect': self.init_class}},
100+ status.handle_status_args, 'ignored', cmdargs)
101+ self.assertEqual(0, retcode)
102+ self.assertEqual('status: running\n', m_stdout.getvalue())
103+
104 def test_status_returns_running(self):
105 '''Report running when status exists with an unfinished stage.'''
106+ ensure_file(self.tmp_path('result.json', self.new_root))
107 write_json(self.status_file,
108 {'v1': {'init': {'start': 1, 'finished': None}}})
109 cmdargs = myargs(long=False, wait=False)
110@@ -164,10 +181,11 @@ class TestStatus(CiTestCase):
111 self.assertEqual('status: running\n', m_stdout.getvalue())
112
113 def test_status_returns_done(self):
114- '''Reports done when stage is None and all stages are finished.'''
115+ '''Report done results.json exists no stages are unfinished.'''
116+ ensure_file(self.tmp_path('result.json', self.new_root))
117 write_json(
118 self.status_file,
119- {'v1': {'stage': None,
120+ {'v1': {'stage': None, # No current stage running
121 'datasource': (
122 'DataSourceNoCloud [seed=/var/.../seed/nocloud-net]'
123 '[dsmode=net]'),
124@@ -187,6 +205,7 @@ class TestStatus(CiTestCase):
125
126 def test_status_returns_done_long(self):
127 '''Long format of done status includes datasource info.'''
128+ ensure_file(self.tmp_path('result.json', self.new_root))
129 write_json(
130 self.status_file,
131 {'v1': {'stage': None,
132@@ -303,6 +322,8 @@ class TestStatus(CiTestCase):
133 write_json(self.status_file, running_json)
134 elif self.sleep_calls == 3:
135 write_json(self.status_file, done_json)
136+ result_file = self.tmp_path('result.json', self.new_root)
137+ ensure_file(result_file)
138
139 cmdargs = myargs(long=False, wait=True)
140 with mock.patch('sys.stdout', new_callable=StringIO) as m_stdout:

Subscribers

People subscribed via source and target branches