Merge ~newell-jensen/maas:lp1713556 into maas:master
- Git
- lp:~newell-jensen/maas
- lp1713556
- Merge into master
Status: | Merged |
---|---|
Approved by: | Newell Jensen |
Approved revision: | b218c144a610e7ca707c57f3a6f93e2e2969a43a |
Merge reported by: | MAAS Lander |
Merged at revision: | not available |
Proposed branch: | ~newell-jensen/maas:lp1713556 |
Merge into: | maas:master |
Diff against target: |
297 lines (+164/-4) 4 files modified
src/maasserver/api/pods.py (+5/-0) src/maasserver/api/tests/test_pods.py (+107/-1) src/maasserver/forms/pods.py (+44/-1) src/provisioningserver/drivers/pod/virsh.py (+8/-2) |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
MAAS Lander | Needs Fixing | ||
Mike Pontillo (community) | Approve | ||
Andres Rodriguez (community) | Approve | ||
Review via email: mp+343053@code.launchpad.net |
Commit message
LP: #1713556 -- Add ability to set console_logging boolean for a pod in the API for whether or not console logging should be enabled.
Description of the change
Andres Rodriguez (andreserl) wrote : | # |
questions inline.
Newell Jensen (newell-jensen) wrote : | # |
responses inline.
MAAS Lander (maas-lander) wrote : | # |
UNIT TESTS
-b lp1713556 lp:~newell-jensen/maas/+git/maas into -b master lp:~maas-committers/maas
STATUS: SUCCESS
COMMIT: 9a9e717e2e2e5b8
Andres Rodriguez (andreserl) : | # |
Newell Jensen (newell-jensen) wrote : | # |
replies inline.
Newell Jensen (newell-jensen) : | # |
Andres Rodriguez (andreserl) wrote : | # |
comment inline.
Mike Pontillo (mpontillo) wrote : | # |
I have some comments/questions inline below.
Also, I feel that (assuming the same strategy is used) unit testing for this change needs to be expanded. Ideally, it should explicitly cover the following scenarios:
(1) Updating the pod while specifying console_
(2) Updating the pod while specifying console_log=False - ensure that the tag is unset
(3) Updating the pod while not specifying console_logging at all - ensure that the tag remains set if already set
(4) Updating the pod while not specifying console_logging at all - ensure that the tag remains unset unset if already unset
So my main concern with this branch is the fact that I don't see comprehensive testing of the above scenarios. That's the main thing that makes this a "Needs Fixing" (but see my inline comments below, too).
If you implement this as a separate operation rather than changing the exiting update mechanism, you wouldn't have to worry about (3) and (4).
On the other hand, I do like how updating this value is "seamless" and doesn't require looking around for a separate operation. I think that's a better user experience that way. But what would make it even better is if this logic was instead implemented in the form. If you do it that way, it will be trivial to update the UI to allow setting this parameter as well.
Lastly, there's a better way to get a boolean value from the request when using the API; I would do it more like this:
By doing it this way, you would handle more variations than just the case-insensitive "true" and "false" strings.
Newell Jensen (newell-jensen) wrote : | # |
My original branch had it in the form with a data migration. After talking with you guys on IRC about the best route forward, will move the pod console logging tag creation to the form so that it can potentially be used in the UI.
Mike Pontillo (mpontillo) wrote : | # |
Not sure if this is actually ready for another review yet, but I caught a couple things below.
MAAS Lander (maas-lander) wrote : | # |
UNIT TESTS
-b lp1713556 lp:~newell-jensen/maas/+git/maas into -b master lp:~maas-committers/maas
STATUS: SUCCESS
COMMIT: 2cba8df0c7c0843
Andres Rodriguez (andreserl) wrote : | # |
lgtm! Please seek Mike's approval before landing.
Mike Pontillo (mpontillo) wrote : | # |
Can you add a test that ensures that the following sequence of steps works as expected:
(1) Update a pod with console_
(2) Verify that the tag exists
(3) Update something unrelated on the pod, such as the name
(4) Verify that the tag still exists
Mike Pontillo (mpontillo) wrote : | # |
Found a few more issues; see inline below.
MAAS Lander (maas-lander) wrote : | # |
UNIT TESTS
-b lp1713556 lp:~newell-jensen/maas/+git/maas into -b master lp:~maas-committers/maas
STATUS: SUCCESS
COMMIT: 2aae15b79c2b7c4
Mike Pontillo (mpontillo) wrote : | # |
See inline.
MAAS Lander (maas-lander) wrote : | # |
UNIT TESTS
-b lp1713556 lp:~newell-jensen/maas/+git/maas into -b master lp:~maas-committers/maas
STATUS: SUCCESS
COMMIT: 8d6df467b290513
Mike Pontillo (mpontillo) wrote : | # |
+1, thanks for the fixes!
MAAS Lander (maas-lander) wrote : | # |
LANDING
-b lp1713556 lp:~newell-jensen/maas/+git/maas into -b master lp:~maas-committers/maas
STATUS: FAILED BUILD
LOG: http://
MAAS Lander (maas-lander) wrote : | # |
UNIT TESTS
-b lp1713556 lp:~newell-jensen/maas/+git/maas into -b master lp:~maas-committers/maas
STATUS: FAILED
LOG: http://
COMMIT: a8adb394a9d535e
Mike Pontillo (mpontillo) wrote : | # |
FYI, I tested this branch last week when I was gathering information for another bug report. It works great!
- b218c14... by Newell Jensen
-
Fix lint.
Preview Diff
1 | diff --git a/src/maasserver/api/pods.py b/src/maasserver/api/pods.py |
2 | index 0c6b28c..42036a6 100644 |
3 | --- a/src/maasserver/api/pods.py |
4 | +++ b/src/maasserver/api/pods.py |
5 | @@ -153,6 +153,11 @@ class PodHandler(OperationsHandler): |
6 | :type default_macvlan_mode: unicode |
7 | :param tags: A tag or tags (separated by comma) for the pod. |
8 | :type tags: unicode |
9 | + :param console_log: If True, created VMs for this pod will have |
10 | + their console output logged. To do this, a tag with the name |
11 | + 'pod-console-logging' is created. If False, it checks to see if |
12 | + this tag already exists and deletes it if it does. |
13 | + :type console_log: boolean |
14 | |
15 | Note: 'type' cannot be updated on a Pod. The Pod must be deleted and |
16 | re-added to change the type. |
17 | diff --git a/src/maasserver/api/tests/test_pods.py b/src/maasserver/api/tests/test_pods.py |
18 | index 5cc1da7..4600e3f 100644 |
19 | --- a/src/maasserver/api/tests/test_pods.py |
20 | +++ b/src/maasserver/api/tests/test_pods.py |
21 | @@ -13,6 +13,7 @@ from maasserver.enum import NODE_CREATION_TYPE |
22 | from maasserver.forms import pods |
23 | from maasserver.models.bmc import Pod |
24 | from maasserver.models.node import Machine |
25 | +from maasserver.models.tag import Tag |
26 | from maasserver.testing.api import APITestCase |
27 | from maasserver.testing.factory import factory |
28 | from maasserver.utils.converters import json_load_bytes |
29 | @@ -276,7 +277,11 @@ class TestPodAPI(APITestCase.ForUser, PodMixin): |
30 | self.become_admin() |
31 | pod = factory.make_Pod(pod_type='virsh') |
32 | new_name = factory.make_name('pod') |
33 | - new_tags = [factory.make_name('tag'), factory.make_name('tag')] |
34 | + new_tags = [ |
35 | + factory.make_name('tag'), |
36 | + factory.make_name('tag'), |
37 | + 'pod-console-logging', |
38 | + ] |
39 | new_pool = factory.make_ResourcePool() |
40 | new_zone = factory.make_Zone() |
41 | new_power_parameters = { |
42 | @@ -291,10 +296,12 @@ class TestPodAPI(APITestCase.ForUser, PodMixin): |
43 | 'power_pass': new_power_parameters['power_pass'], |
44 | 'zone': new_zone.name, |
45 | 'pool': new_pool.name, |
46 | + 'console_logging': 'True', |
47 | }) |
48 | self.assertEqual( |
49 | http.client.OK, response.status_code, response.content) |
50 | pod.refresh_from_db() |
51 | + self.assertIsNotNone(Tag.objects.get(name="pod-console-logging")) |
52 | self.assertEqual(new_name, pod.name) |
53 | self.assertEqual(new_pool, pod.pool) |
54 | self.assertItemsEqual(new_tags, pod.tags) |
55 | @@ -313,10 +320,13 @@ class TestPodAPI(APITestCase.ForUser, PodMixin): |
56 | 'power_address': pod_info['power_address'], |
57 | 'power_pass': pod_info['power_pass'], |
58 | 'zone': pod_info['zone'], |
59 | + 'console_logging': 'False', |
60 | }) |
61 | self.assertEqual( |
62 | http.client.OK, response.status_code, response.content) |
63 | parsed_output = json_load_bytes(response.content) |
64 | + self.assertRaises( |
65 | + Tag.DoesNotExist, Tag.objects.get, name="pod-console-logging") |
66 | self.assertEqual(new_name, parsed_output['name']) |
67 | self.assertEqual(discovered_pod.cores, parsed_output['total']['cores']) |
68 | |
69 | @@ -337,6 +347,8 @@ class TestPodAPI(APITestCase.ForUser, PodMixin): |
70 | self.assertEqual( |
71 | http.client.OK, response.status_code, response.content) |
72 | pod.refresh_from_db() |
73 | + self.assertRaises( |
74 | + Tag.DoesNotExist, Tag.objects.get, name="pod-console-logging") |
75 | self.assertEqual(new_name, pod.name) |
76 | self.assertEqual(power_parameters, pod.power_parameters) |
77 | |
78 | @@ -354,6 +366,100 @@ class TestPodAPI(APITestCase.ForUser, PodMixin): |
79 | pod.refresh_from_db() |
80 | self.assertEqual(pod.default_macvlan_mode, default_macvlan_mode) |
81 | |
82 | + def test_PUT_update_deletes_pod_console_logging_tag_if_not_in_use(self): |
83 | + self.become_admin() |
84 | + factory.make_Tag(name='pod-console-logging') |
85 | + pod1_info = self.make_pod_info() |
86 | + factory.make_Pod(pod_type=pod1_info['type']) |
87 | + pod2_info = self.make_pod_info() |
88 | + pod2 = factory.make_Pod( |
89 | + pod_type=pod2_info['type'], |
90 | + tags=['pod-console-logging']) |
91 | + self.fake_pod_discovery() |
92 | + response = self.client.put(get_pod_uri(pod2), { |
93 | + 'console_logging': 'False', |
94 | + }) |
95 | + self.assertEqual( |
96 | + http.client.OK, response.status_code, response.content) |
97 | + self.assertRaises( |
98 | + Tag.DoesNotExist, Tag.objects.get, name='pod-console-logging') |
99 | + |
100 | + def test_PUT_update_wont_delete_pod_console_logging_tag_if_in_use(self): |
101 | + self.become_admin() |
102 | + factory.make_Tag(name='pod-console-logging') |
103 | + pod1_info = self.make_pod_info() |
104 | + factory.make_Pod( |
105 | + pod_type=pod1_info['type'], |
106 | + tags=['pod-console-logging']) |
107 | + pod2_info = self.make_pod_info() |
108 | + pod2 = factory.make_Pod( |
109 | + pod_type=pod2_info['type'], |
110 | + tags=['pod-console-logging']) |
111 | + self.fake_pod_discovery() |
112 | + response = self.client.put(get_pod_uri(pod2), { |
113 | + 'console_logging': 'False', |
114 | + }) |
115 | + self.assertEqual( |
116 | + http.client.OK, response.status_code, response.content) |
117 | + self.assertIsNotNone(Tag.objects.get(name='pod-console-logging')) |
118 | + |
119 | + def test_PUT_update_creates_pod_console_logging_tag(self): |
120 | + self.become_admin() |
121 | + self.assertRaises( |
122 | + Tag.DoesNotExist, Tag.objects.get, name='pod-console-logging') |
123 | + pod_info = self.make_pod_info() |
124 | + pod = factory.make_Pod(pod_type=pod_info['type']) |
125 | + self.fake_pod_discovery() |
126 | + response = self.client.put(get_pod_uri(pod), { |
127 | + 'console_logging': 'True', |
128 | + }) |
129 | + self.assertEqual( |
130 | + http.client.OK, response.status_code, response.content) |
131 | + self.assertIsNotNone(Tag.objects.get(name='pod-console-logging')) |
132 | + response = self.client.put(get_pod_uri(pod), { |
133 | + 'console_logging': 'True', |
134 | + }) |
135 | + self.assertEqual( |
136 | + http.client.OK, response.status_code, response.content) |
137 | + self.assertIsNotNone(Tag.objects.get(name='pod-console-logging')) |
138 | + response = self.client.put(get_pod_uri(pod), { |
139 | + 'name': factory.make_name('name'), |
140 | + }) |
141 | + self.assertEqual( |
142 | + http.client.OK, response.status_code, response.content) |
143 | + pod.refresh_from_db() |
144 | + self.assertIn('pod-console-logging', pod.tags) |
145 | + self.assertIsNotNone(Tag.objects.get(name='pod-console-logging')) |
146 | + |
147 | + def test_PUT_update_deletes_pod_console_logging_tag(self): |
148 | + self.become_admin() |
149 | + factory.make_Tag(name='pod-console-logging') |
150 | + pod_info = self.make_pod_info() |
151 | + pod = factory.make_Pod( |
152 | + pod_type=pod_info['type'], tags=['pod-console-logging']) |
153 | + self.fake_pod_discovery() |
154 | + response = self.client.put(get_pod_uri(pod), { |
155 | + 'console_logging': 'False', |
156 | + }) |
157 | + self.assertEqual( |
158 | + http.client.OK, response.status_code, response.content) |
159 | + self.assertRaises( |
160 | + Tag.DoesNotExist, Tag.objects.get, name='pod-console-logging') |
161 | + response = self.client.put(get_pod_uri(pod), { |
162 | + 'console_logging': 'False', |
163 | + }) |
164 | + self.assertEqual( |
165 | + http.client.OK, response.status_code, response.content) |
166 | + response = self.client.put(get_pod_uri(pod), { |
167 | + 'name': factory.make_name('name'), |
168 | + }) |
169 | + self.assertEqual( |
170 | + http.client.OK, response.status_code, response.content) |
171 | + pod.refresh_from_db() |
172 | + self.assertNotIn('pod-console-logging', pod.tags) |
173 | + self.assertRaises( |
174 | + Tag.DoesNotExist, Tag.objects.get, name='pod-console-logging') |
175 | + |
176 | def test_refresh_requires_admin(self): |
177 | pod = factory.make_Pod() |
178 | response = self.client.post(get_pod_uri(pod), { |
179 | diff --git a/src/maasserver/forms/pods.py b/src/maasserver/forms/pods.py |
180 | index 77f9f3e..4a43f01 100644 |
181 | --- a/src/maasserver/forms/pods.py |
182 | +++ b/src/maasserver/forms/pods.py |
183 | @@ -46,6 +46,7 @@ from maasserver.models import ( |
184 | PodStoragePool, |
185 | RackController, |
186 | ResourcePool, |
187 | + Tag, |
188 | Zone, |
189 | ) |
190 | from maasserver.node_constraint_filter_forms import ( |
191 | @@ -148,6 +149,23 @@ class PodForm(MAASModelForm): |
192 | if data is None: |
193 | data = {} |
194 | type_value = data.get('type', self.initial.get('type')) |
195 | + console_log = data.get('console_logging', '').lower() |
196 | + if console_log in ['true', 'false']: |
197 | + console_log = True if console_log == 'true' else False |
198 | + if self.is_new: |
199 | + self.update_console_log = True |
200 | + elif 'pod-console-logging' in instance.tags and console_log: |
201 | + self.update_console_log = False |
202 | + elif 'pod-console-logging' in instance.tags and not console_log: |
203 | + self.update_console_log = True |
204 | + else: |
205 | + self.update_console_log = True |
206 | + else: |
207 | + self.update_console_log = False |
208 | + |
209 | + if self.update_console_log: |
210 | + self.console_log = console_log |
211 | + |
212 | self.drivers_orig = driver_parameters.get_all_power_types() |
213 | self.drivers = { |
214 | driver['name']: driver |
215 | @@ -243,6 +261,31 @@ class PodForm(MAASModelForm): |
216 | self.instance = super(PodForm, self).save(commit=False) |
217 | self.instance.power_type = power_type |
218 | self.instance.power_parameters = power_parameters |
219 | + # If console_log is set, create a tag for the kernel parameters |
220 | + # if it does not already exist. Delete otherwise. |
221 | + if self.update_console_log: |
222 | + if self.console_log: |
223 | + tag, _ = Tag.objects.get_or_create( |
224 | + name="pod-console-logging", |
225 | + kernel_opts="console=tty1 console=ttyS0") |
226 | + # Add this tag to the pod. |
227 | + self.instance.add_tag(tag.name) |
228 | + else: |
229 | + try: |
230 | + tag = Tag.objects.get(name="pod-console-logging") |
231 | + # Remove this tag from the pod. |
232 | + self.instance.remove_tag(tag.name) |
233 | + # Delete the tag if there are no longer any other |
234 | + # pods using it. |
235 | + pods = Pod.objects.filter( |
236 | + tags__contains=['pod-console-logging']).exclude( |
237 | + id=self.instance.id) |
238 | + if not pods: |
239 | + tag.delete() |
240 | + except Tag.DoesNotExist: |
241 | + # There was no tag so just continue. |
242 | + pass |
243 | + |
244 | return self.instance |
245 | |
246 | power_type = self.cleaned_data['type'] |
247 | @@ -258,7 +301,7 @@ class PodForm(MAASModelForm): |
248 | d = deferToDatabase( |
249 | transactional(check_for_duplicate), |
250 | power_type, power_parameters) |
251 | - d.addCallback(update_obj) |
252 | + d.addCallback(partial(deferToDatabase, transactional(update_obj))) |
253 | d.addCallback(lambda _: self.discover_and_sync_pod()) |
254 | return d |
255 | else: |
256 | diff --git a/src/provisioningserver/drivers/pod/virsh.py b/src/provisioningserver/drivers/pod/virsh.py |
257 | index 2a84bbb..69629d7 100644 |
258 | --- a/src/provisioningserver/drivers/pod/virsh.py |
259 | +++ b/src/provisioningserver/drivers/pod/virsh.py |
260 | @@ -118,6 +118,7 @@ DOM_TEMPLATE_AMD64 = dedent("""\ |
261 | bus='0x00' slot='0x05' function='0x0'/> |
262 | </controller> |
263 | <serial type='pty'> |
264 | + <log file="/var/log/libvirt/qemu/{name}-serial0.log" append="off" /> |
265 | <target port='0'/> |
266 | </serial> |
267 | <console type='pty'> |
268 | @@ -162,6 +163,7 @@ DOM_TEMPLATE_ARM64 = dedent("""\ |
269 | <emulator>{emulator}</emulator> |
270 | <controller type='pci' index='0' model='pcie-root'/> |
271 | <serial type='pty'> |
272 | + <log file="/var/log/libvirt/qemu/{name}-serial0.log" append="off" /> |
273 | <target port='0'/> |
274 | </serial> |
275 | <console type='pty'> |
276 | @@ -192,6 +194,7 @@ DOM_TEMPLATE_PPC64 = dedent("""\ |
277 | <emulator>{emulator}</emulator> |
278 | <controller type='pci' index='0' model='pci-root'/> |
279 | <serial type='pty'> |
280 | + <log file="/var/log/libvirt/qemu/{name}-serial0.log" append="off" /> |
281 | <target port='0'/> |
282 | </serial> |
283 | <console type='pty'> |
284 | @@ -218,8 +221,11 @@ DOM_TEMPLATE_S390X = dedent(""" |
285 | <pae/> |
286 | </features> |
287 | <devices> |
288 | - <console type='pty' tty='/dev/pts/3'> |
289 | - <source path='/dev/pts/3'/> |
290 | + <serial type='pty'> |
291 | + <log file="/var/log/libvirt/qemu/{name}-serial0.log" append="off" /> |
292 | + <target port='0'/> |
293 | + </serial> |
294 | + <console type='pty'> |
295 | <target type='sclp' port='0'/> |
296 | <alias name='console0'/> |
297 | </console> |
UNIT TESTS
-b lp1713556 lp:~newell-jensen/maas/+git/maas into -b master lp:~maas-committers/maas
STATUS: SUCCESS 2bd10f3eff7a9fa 3a8d6a5412
COMMIT: f97362ced1eb295