Merge ~newell-jensen/maas:lp1713556 into maas:master

Proposed by Newell Jensen
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)
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.

To post a comment you must log in.
Revision history for this message
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: f97362ced1eb2952bd10f3eff7a9fa3a8d6a5412

review: Approve
Revision history for this message
Andres Rodriguez (andreserl) wrote :

questions inline.

review: Needs Information
Revision history for this message
Newell Jensen (newell-jensen) wrote :

responses inline.

Revision history for this message
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: 9a9e717e2e2e5b8020de2821c31879af0dcaa0e2

review: Approve
Revision history for this message
Andres Rodriguez (andreserl) :
review: Needs Information
Revision history for this message
Newell Jensen (newell-jensen) wrote :

replies inline.

Revision history for this message
Newell Jensen (newell-jensen) :
Revision history for this message
Andres Rodriguez (andreserl) wrote :

comment inline.

Revision history for this message
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_logging=True - ensure that the tag gets set
(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:

        console_logging = get_optional_param(
            request.POST, 'console_logging', default=False, validator=StringBool)

By doing it this way, you would handle more variations than just the case-insensitive "true" and "false" strings.

review: Needs Fixing
Revision history for this message
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.

Revision history for this message
Mike Pontillo (mpontillo) wrote :

Not sure if this is actually ready for another review yet, but I caught a couple things below.

review: Needs Fixing
Revision history for this message
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: 2cba8df0c7c08436f6d6b04d2e0b04ab9eb8cc15

review: Approve
Revision history for this message
Andres Rodriguez (andreserl) wrote :

lgtm! Please seek Mike's approval before landing.

review: Approve
Revision history for this message
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_logging=True
(2) Verify that the tag exists
(3) Update something unrelated on the pod, such as the name
(4) Verify that the tag still exists

Revision history for this message
Mike Pontillo (mpontillo) wrote :

Found a few more issues; see inline below.

review: Needs Fixing
Revision history for this message
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: 2aae15b79c2b7c41d48a979aed47c6da30e218af

review: Approve
Revision history for this message
Mike Pontillo (mpontillo) wrote :

See inline.

review: Needs Fixing
Revision history for this message
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: 8d6df467b2905130f0aad8af6317f3a7adc9689e

review: Approve
Revision history for this message
Mike Pontillo (mpontillo) wrote :

+1, thanks for the fixes!

review: Approve
Revision history for this message
MAAS Lander (maas-lander) wrote :
Revision history for this message
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://maas-ci-jenkins.internal:8080/job/maas/job/branch-tester/4174/console
COMMIT: a8adb394a9d535eb92f1e2a040de4e92c6fc58e6

review: Needs Fixing
Revision history for this message
Mike Pontillo (mpontillo) wrote :

FYI, I tested this branch last week when I was gathering information for another bug report. It works great!

~newell-jensen/maas:lp1713556 updated
b218c14... by Newell Jensen

Fix lint.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
diff --git a/src/maasserver/api/pods.py b/src/maasserver/api/pods.py
index 0c6b28c..42036a6 100644
--- a/src/maasserver/api/pods.py
+++ b/src/maasserver/api/pods.py
@@ -153,6 +153,11 @@ class PodHandler(OperationsHandler):
153 :type default_macvlan_mode: unicode153 :type default_macvlan_mode: unicode
154 :param tags: A tag or tags (separated by comma) for the pod.154 :param tags: A tag or tags (separated by comma) for the pod.
155 :type tags: unicode155 :type tags: unicode
156 :param console_log: If True, created VMs for this pod will have
157 their console output logged. To do this, a tag with the name
158 'pod-console-logging' is created. If False, it checks to see if
159 this tag already exists and deletes it if it does.
160 :type console_log: boolean
156161
157 Note: 'type' cannot be updated on a Pod. The Pod must be deleted and162 Note: 'type' cannot be updated on a Pod. The Pod must be deleted and
158 re-added to change the type.163 re-added to change the type.
diff --git a/src/maasserver/api/tests/test_pods.py b/src/maasserver/api/tests/test_pods.py
index 5cc1da7..4600e3f 100644
--- a/src/maasserver/api/tests/test_pods.py
+++ b/src/maasserver/api/tests/test_pods.py
@@ -13,6 +13,7 @@ from maasserver.enum import NODE_CREATION_TYPE
13from maasserver.forms import pods13from maasserver.forms import pods
14from maasserver.models.bmc import Pod14from maasserver.models.bmc import Pod
15from maasserver.models.node import Machine15from maasserver.models.node import Machine
16from maasserver.models.tag import Tag
16from maasserver.testing.api import APITestCase17from maasserver.testing.api import APITestCase
17from maasserver.testing.factory import factory18from maasserver.testing.factory import factory
18from maasserver.utils.converters import json_load_bytes19from maasserver.utils.converters import json_load_bytes
@@ -276,7 +277,11 @@ class TestPodAPI(APITestCase.ForUser, PodMixin):
276 self.become_admin()277 self.become_admin()
277 pod = factory.make_Pod(pod_type='virsh')278 pod = factory.make_Pod(pod_type='virsh')
278 new_name = factory.make_name('pod')279 new_name = factory.make_name('pod')
279 new_tags = [factory.make_name('tag'), factory.make_name('tag')]280 new_tags = [
281 factory.make_name('tag'),
282 factory.make_name('tag'),
283 'pod-console-logging',
284 ]
280 new_pool = factory.make_ResourcePool()285 new_pool = factory.make_ResourcePool()
281 new_zone = factory.make_Zone()286 new_zone = factory.make_Zone()
282 new_power_parameters = {287 new_power_parameters = {
@@ -291,10 +296,12 @@ class TestPodAPI(APITestCase.ForUser, PodMixin):
291 'power_pass': new_power_parameters['power_pass'],296 'power_pass': new_power_parameters['power_pass'],
292 'zone': new_zone.name,297 'zone': new_zone.name,
293 'pool': new_pool.name,298 'pool': new_pool.name,
299 'console_logging': 'True',
294 })300 })
295 self.assertEqual(301 self.assertEqual(
296 http.client.OK, response.status_code, response.content)302 http.client.OK, response.status_code, response.content)
297 pod.refresh_from_db()303 pod.refresh_from_db()
304 self.assertIsNotNone(Tag.objects.get(name="pod-console-logging"))
298 self.assertEqual(new_name, pod.name)305 self.assertEqual(new_name, pod.name)
299 self.assertEqual(new_pool, pod.pool)306 self.assertEqual(new_pool, pod.pool)
300 self.assertItemsEqual(new_tags, pod.tags)307 self.assertItemsEqual(new_tags, pod.tags)
@@ -313,10 +320,13 @@ class TestPodAPI(APITestCase.ForUser, PodMixin):
313 'power_address': pod_info['power_address'],320 'power_address': pod_info['power_address'],
314 'power_pass': pod_info['power_pass'],321 'power_pass': pod_info['power_pass'],
315 'zone': pod_info['zone'],322 'zone': pod_info['zone'],
323 'console_logging': 'False',
316 })324 })
317 self.assertEqual(325 self.assertEqual(
318 http.client.OK, response.status_code, response.content)326 http.client.OK, response.status_code, response.content)
319 parsed_output = json_load_bytes(response.content)327 parsed_output = json_load_bytes(response.content)
328 self.assertRaises(
329 Tag.DoesNotExist, Tag.objects.get, name="pod-console-logging")
320 self.assertEqual(new_name, parsed_output['name'])330 self.assertEqual(new_name, parsed_output['name'])
321 self.assertEqual(discovered_pod.cores, parsed_output['total']['cores'])331 self.assertEqual(discovered_pod.cores, parsed_output['total']['cores'])
322332
@@ -337,6 +347,8 @@ class TestPodAPI(APITestCase.ForUser, PodMixin):
337 self.assertEqual(347 self.assertEqual(
338 http.client.OK, response.status_code, response.content)348 http.client.OK, response.status_code, response.content)
339 pod.refresh_from_db()349 pod.refresh_from_db()
350 self.assertRaises(
351 Tag.DoesNotExist, Tag.objects.get, name="pod-console-logging")
340 self.assertEqual(new_name, pod.name)352 self.assertEqual(new_name, pod.name)
341 self.assertEqual(power_parameters, pod.power_parameters)353 self.assertEqual(power_parameters, pod.power_parameters)
342354
@@ -354,6 +366,100 @@ class TestPodAPI(APITestCase.ForUser, PodMixin):
354 pod.refresh_from_db()366 pod.refresh_from_db()
355 self.assertEqual(pod.default_macvlan_mode, default_macvlan_mode)367 self.assertEqual(pod.default_macvlan_mode, default_macvlan_mode)
356368
369 def test_PUT_update_deletes_pod_console_logging_tag_if_not_in_use(self):
370 self.become_admin()
371 factory.make_Tag(name='pod-console-logging')
372 pod1_info = self.make_pod_info()
373 factory.make_Pod(pod_type=pod1_info['type'])
374 pod2_info = self.make_pod_info()
375 pod2 = factory.make_Pod(
376 pod_type=pod2_info['type'],
377 tags=['pod-console-logging'])
378 self.fake_pod_discovery()
379 response = self.client.put(get_pod_uri(pod2), {
380 'console_logging': 'False',
381 })
382 self.assertEqual(
383 http.client.OK, response.status_code, response.content)
384 self.assertRaises(
385 Tag.DoesNotExist, Tag.objects.get, name='pod-console-logging')
386
387 def test_PUT_update_wont_delete_pod_console_logging_tag_if_in_use(self):
388 self.become_admin()
389 factory.make_Tag(name='pod-console-logging')
390 pod1_info = self.make_pod_info()
391 factory.make_Pod(
392 pod_type=pod1_info['type'],
393 tags=['pod-console-logging'])
394 pod2_info = self.make_pod_info()
395 pod2 = factory.make_Pod(
396 pod_type=pod2_info['type'],
397 tags=['pod-console-logging'])
398 self.fake_pod_discovery()
399 response = self.client.put(get_pod_uri(pod2), {
400 'console_logging': 'False',
401 })
402 self.assertEqual(
403 http.client.OK, response.status_code, response.content)
404 self.assertIsNotNone(Tag.objects.get(name='pod-console-logging'))
405
406 def test_PUT_update_creates_pod_console_logging_tag(self):
407 self.become_admin()
408 self.assertRaises(
409 Tag.DoesNotExist, Tag.objects.get, name='pod-console-logging')
410 pod_info = self.make_pod_info()
411 pod = factory.make_Pod(pod_type=pod_info['type'])
412 self.fake_pod_discovery()
413 response = self.client.put(get_pod_uri(pod), {
414 'console_logging': 'True',
415 })
416 self.assertEqual(
417 http.client.OK, response.status_code, response.content)
418 self.assertIsNotNone(Tag.objects.get(name='pod-console-logging'))
419 response = self.client.put(get_pod_uri(pod), {
420 'console_logging': 'True',
421 })
422 self.assertEqual(
423 http.client.OK, response.status_code, response.content)
424 self.assertIsNotNone(Tag.objects.get(name='pod-console-logging'))
425 response = self.client.put(get_pod_uri(pod), {
426 'name': factory.make_name('name'),
427 })
428 self.assertEqual(
429 http.client.OK, response.status_code, response.content)
430 pod.refresh_from_db()
431 self.assertIn('pod-console-logging', pod.tags)
432 self.assertIsNotNone(Tag.objects.get(name='pod-console-logging'))
433
434 def test_PUT_update_deletes_pod_console_logging_tag(self):
435 self.become_admin()
436 factory.make_Tag(name='pod-console-logging')
437 pod_info = self.make_pod_info()
438 pod = factory.make_Pod(
439 pod_type=pod_info['type'], tags=['pod-console-logging'])
440 self.fake_pod_discovery()
441 response = self.client.put(get_pod_uri(pod), {
442 'console_logging': 'False',
443 })
444 self.assertEqual(
445 http.client.OK, response.status_code, response.content)
446 self.assertRaises(
447 Tag.DoesNotExist, Tag.objects.get, name='pod-console-logging')
448 response = self.client.put(get_pod_uri(pod), {
449 'console_logging': 'False',
450 })
451 self.assertEqual(
452 http.client.OK, response.status_code, response.content)
453 response = self.client.put(get_pod_uri(pod), {
454 'name': factory.make_name('name'),
455 })
456 self.assertEqual(
457 http.client.OK, response.status_code, response.content)
458 pod.refresh_from_db()
459 self.assertNotIn('pod-console-logging', pod.tags)
460 self.assertRaises(
461 Tag.DoesNotExist, Tag.objects.get, name='pod-console-logging')
462
357 def test_refresh_requires_admin(self):463 def test_refresh_requires_admin(self):
358 pod = factory.make_Pod()464 pod = factory.make_Pod()
359 response = self.client.post(get_pod_uri(pod), {465 response = self.client.post(get_pod_uri(pod), {
diff --git a/src/maasserver/forms/pods.py b/src/maasserver/forms/pods.py
index 77f9f3e..4a43f01 100644
--- a/src/maasserver/forms/pods.py
+++ b/src/maasserver/forms/pods.py
@@ -46,6 +46,7 @@ from maasserver.models import (
46 PodStoragePool,46 PodStoragePool,
47 RackController,47 RackController,
48 ResourcePool,48 ResourcePool,
49 Tag,
49 Zone,50 Zone,
50)51)
51from maasserver.node_constraint_filter_forms import (52from maasserver.node_constraint_filter_forms import (
@@ -148,6 +149,23 @@ class PodForm(MAASModelForm):
148 if data is None:149 if data is None:
149 data = {}150 data = {}
150 type_value = data.get('type', self.initial.get('type'))151 type_value = data.get('type', self.initial.get('type'))
152 console_log = data.get('console_logging', '').lower()
153 if console_log in ['true', 'false']:
154 console_log = True if console_log == 'true' else False
155 if self.is_new:
156 self.update_console_log = True
157 elif 'pod-console-logging' in instance.tags and console_log:
158 self.update_console_log = False
159 elif 'pod-console-logging' in instance.tags and not console_log:
160 self.update_console_log = True
161 else:
162 self.update_console_log = True
163 else:
164 self.update_console_log = False
165
166 if self.update_console_log:
167 self.console_log = console_log
168
151 self.drivers_orig = driver_parameters.get_all_power_types()169 self.drivers_orig = driver_parameters.get_all_power_types()
152 self.drivers = {170 self.drivers = {
153 driver['name']: driver171 driver['name']: driver
@@ -243,6 +261,31 @@ class PodForm(MAASModelForm):
243 self.instance = super(PodForm, self).save(commit=False)261 self.instance = super(PodForm, self).save(commit=False)
244 self.instance.power_type = power_type262 self.instance.power_type = power_type
245 self.instance.power_parameters = power_parameters263 self.instance.power_parameters = power_parameters
264 # If console_log is set, create a tag for the kernel parameters
265 # if it does not already exist. Delete otherwise.
266 if self.update_console_log:
267 if self.console_log:
268 tag, _ = Tag.objects.get_or_create(
269 name="pod-console-logging",
270 kernel_opts="console=tty1 console=ttyS0")
271 # Add this tag to the pod.
272 self.instance.add_tag(tag.name)
273 else:
274 try:
275 tag = Tag.objects.get(name="pod-console-logging")
276 # Remove this tag from the pod.
277 self.instance.remove_tag(tag.name)
278 # Delete the tag if there are no longer any other
279 # pods using it.
280 pods = Pod.objects.filter(
281 tags__contains=['pod-console-logging']).exclude(
282 id=self.instance.id)
283 if not pods:
284 tag.delete()
285 except Tag.DoesNotExist:
286 # There was no tag so just continue.
287 pass
288
246 return self.instance289 return self.instance
247290
248 power_type = self.cleaned_data['type']291 power_type = self.cleaned_data['type']
@@ -258,7 +301,7 @@ class PodForm(MAASModelForm):
258 d = deferToDatabase(301 d = deferToDatabase(
259 transactional(check_for_duplicate),302 transactional(check_for_duplicate),
260 power_type, power_parameters)303 power_type, power_parameters)
261 d.addCallback(update_obj)304 d.addCallback(partial(deferToDatabase, transactional(update_obj)))
262 d.addCallback(lambda _: self.discover_and_sync_pod())305 d.addCallback(lambda _: self.discover_and_sync_pod())
263 return d306 return d
264 else:307 else:
diff --git a/src/provisioningserver/drivers/pod/virsh.py b/src/provisioningserver/drivers/pod/virsh.py
index 2a84bbb..69629d7 100644
--- a/src/provisioningserver/drivers/pod/virsh.py
+++ b/src/provisioningserver/drivers/pod/virsh.py
@@ -118,6 +118,7 @@ DOM_TEMPLATE_AMD64 = dedent("""\
118 bus='0x00' slot='0x05' function='0x0'/>118 bus='0x00' slot='0x05' function='0x0'/>
119 </controller>119 </controller>
120 <serial type='pty'>120 <serial type='pty'>
121 <log file="/var/log/libvirt/qemu/{name}-serial0.log" append="off" />
121 <target port='0'/>122 <target port='0'/>
122 </serial>123 </serial>
123 <console type='pty'>124 <console type='pty'>
@@ -162,6 +163,7 @@ DOM_TEMPLATE_ARM64 = dedent("""\
162 <emulator>{emulator}</emulator>163 <emulator>{emulator}</emulator>
163 <controller type='pci' index='0' model='pcie-root'/>164 <controller type='pci' index='0' model='pcie-root'/>
164 <serial type='pty'>165 <serial type='pty'>
166 <log file="/var/log/libvirt/qemu/{name}-serial0.log" append="off" />
165 <target port='0'/>167 <target port='0'/>
166 </serial>168 </serial>
167 <console type='pty'>169 <console type='pty'>
@@ -192,6 +194,7 @@ DOM_TEMPLATE_PPC64 = dedent("""\
192 <emulator>{emulator}</emulator>194 <emulator>{emulator}</emulator>
193 <controller type='pci' index='0' model='pci-root'/>195 <controller type='pci' index='0' model='pci-root'/>
194 <serial type='pty'>196 <serial type='pty'>
197 <log file="/var/log/libvirt/qemu/{name}-serial0.log" append="off" />
195 <target port='0'/>198 <target port='0'/>
196 </serial>199 </serial>
197 <console type='pty'>200 <console type='pty'>
@@ -218,8 +221,11 @@ DOM_TEMPLATE_S390X = dedent("""
218 <pae/>221 <pae/>
219 </features>222 </features>
220 <devices>223 <devices>
221 <console type='pty' tty='/dev/pts/3'>224 <serial type='pty'>
222 <source path='/dev/pts/3'/>225 <log file="/var/log/libvirt/qemu/{name}-serial0.log" append="off" />
226 <target port='0'/>
227 </serial>
228 <console type='pty'>
223 <target type='sclp' port='0'/>229 <target type='sclp' port='0'/>
224 <alias name='console0'/>230 <alias name='console0'/>
225 </console>231 </console>

Subscribers

People subscribed via source and target branches