Merge lp:~blake-rouse/maas/commissioning-get-disk-info into lp:~maas-committers/maas/trunk

Proposed by Blake Rouse
Status: Merged
Approved by: Blake Rouse
Approved revision: no longer in the source branch.
Merged at revision: 3443
Proposed branch: lp:~blake-rouse/maas/commissioning-get-disk-info
Merge into: lp:~maas-committers/maas/trunk
Prerequisite: lp:~blake-rouse/maas/physical-block-device-model
Diff against target: 532 lines (+467/-0)
2 files modified
src/metadataserver/models/commissioningscript.py (+121/-0)
src/metadataserver/models/tests/test_noderesults.py (+346/-0)
To merge this branch: bzr merge lp:~blake-rouse/maas/commissioning-get-disk-info
Reviewer Review Type Date Requested Status
Raphaël Badin (community) Approve
Review via email: mp+245585@code.launchpad.net

Commit message

Populates the PhysicalBlockDevice model for a node when commissioning. Uses a combination of lsblk, udevadm, and blockdev to gather the information from the node during commissioning.

To post a comment you must log in.
Revision history for this message
Raphaël Badin (rvb) wrote :

This looks generally good. I've got a bunch of comments but more importantly, I'm wondering about the failure modes for this. See below.

This uses a lot of commands and a fair share of parsing. All of which could go wrong at some point and break the commissioning. I think it's worth taking a step back and thinking about the failure modes here:
- which part of the code are likely to fail (command failure, parsing failure, etc)? Can we make this more robust against non-fatal error?
- what's the failure mode? In other words, what is going to happen when this fails, what will the consequences be, how easy will it be to the user to get information about what's wrong?

What do you think?

review: Needs Information
Revision history for this message
Blake Rouse (blake-rouse) wrote :

I think at the moment we should leave it how it is. I understand your concern, but the issue is that all the calls that are made are required and not optional. Currently if this script fails then all of commissioning fails, which would allow the user to view the output where the exception would be outputted. The only way I can think around this is to remove disks as commands for that disk fail, which might be even more confusing for the user.

Revision history for this message
Raphaël Badin (rvb) wrote :

> Currently if this script fails then all of commissioning fails, which would allow the user to
> view the output where the exception would be outputted. The only way I can think around this is
> to remove disks as commands for that disk fail, which might be even more confusing for the user.

I'm a bit concerned about releasing code that might prevent the nodes from being commissioned at all if the parsing fails (given that the commands we run and the parsing we do are non-trivial). Now, I'm fine with releasing this as is as long as we preform extensive QA on this and test it on all the machines that we have: the lab, the garage MAAS, Jason's lab, OIL, etc. Can you please do this?

fwiw, I really think this ugly code could be best done with a regular expression but I'm not going to block this branch for it :)

+ info_line = info_line.strip()
+ if info_line == "":
+ continue
+ _, info = info_line.split(" ", 1)
+ if "=" not in info:
+ continue
+ k, v = info.split("=", 1)

review: Approve
Revision history for this message
MAAS Lander (maas-lander) wrote :
Download full text (20.4 KiB)

The attempt to merge lp:~blake-rouse/maas/commissioning-get-disk-info into lp:maas failed. Below is the output from the failed tests.

Ign http://security.ubuntu.com trusty-security InRelease
Get:1 http://security.ubuntu.com trusty-security Release.gpg [933 B]
Ign http://nova.clouds.archive.ubuntu.com trusty InRelease
Get:2 http://security.ubuntu.com trusty-security Release [62.0 kB]
Ign http://nova.clouds.archive.ubuntu.com trusty-updates InRelease
Hit http://nova.clouds.archive.ubuntu.com trusty Release.gpg
Get:3 http://nova.clouds.archive.ubuntu.com trusty-updates Release.gpg [933 B]
Hit http://nova.clouds.archive.ubuntu.com trusty Release
Get:4 http://nova.clouds.archive.ubuntu.com trusty-updates Release [62.0 kB]
Get:5 http://security.ubuntu.com trusty-security/main Sources [59.7 kB]
Hit http://nova.clouds.archive.ubuntu.com trusty/main Sources
Get:6 http://security.ubuntu.com trusty-security/universe Sources [17.4 kB]
Get:7 http://security.ubuntu.com trusty-security/main amd64 Packages [187 kB]
Hit http://nova.clouds.archive.ubuntu.com trusty/universe Sources
Hit http://nova.clouds.archive.ubuntu.com trusty/main amd64 Packages
Hit http://nova.clouds.archive.ubuntu.com trusty/universe amd64 Packages
Get:8 http://security.ubuntu.com trusty-security/universe amd64 Packages [82.2 kB]
Hit http://nova.clouds.archive.ubuntu.com trusty/main Translation-en
Hit http://nova.clouds.archive.ubuntu.com trusty/universe Translation-en
Get:9 http://nova.clouds.archive.ubuntu.com trusty-updates/main Sources [152 kB]
Hit http://security.ubuntu.com trusty-security/main Translation-en
Hit http://security.ubuntu.com trusty-security/universe Translation-en
Get:10 http://nova.clouds.archive.ubuntu.com trusty-updates/universe Sources [96.5 kB]
Get:11 http://nova.clouds.archive.ubuntu.com trusty-updates/main amd64 Packages [392 kB]
Get:12 http://nova.clouds.archive.ubuntu.com trusty-updates/universe amd64 Packages [236 kB]
Hit http://nova.clouds.archive.ubuntu.com trusty-updates/main Translation-en
Hit http://nova.clouds.archive.ubuntu.com trusty-updates/universe Translation-en
Ign http://nova.clouds.archive.ubuntu.com trusty/main Translation-en_US
Ign http://nova.clouds.archive.ubuntu.com trusty/universe Translation-en_US
Fetched 1,348 kB in 3s (441 kB/s)
Reading package lists...
sudo DEBIAN_FRONTEND=noninteractive apt-get -y \
     --no-install-recommends install apache2 authbind bind9 bind9utils build-essential bzr-builddeb curl daemontools debhelper dh-apport distro-info dnsutils firefox freeipmi-tools gjs ipython isc-dhcp-common libjs-raphael libjs-yui3-full libjs-yui3-min libpq-dev make pep8 postgresql pyflakes python-amqplib python-bzrlib python-celery python-convoy python-crochet python-cssselect python-curtin python-dev python-distro-info python-django python-django-piston python-django-south python-djorm-ext-pgarray python-docutils python-extras python-fixtures python-flake8 python-formencode python-hivex python-httplib2 python-jinja2 python-jsonschema python-lockfile python-lxml python-mimeparse python-mock python-netaddr python-netifaces python-nose python-oauth python-oops python-oops-amqp python-oops-datedir-repo python-oops-twisted python-oops-wsg...

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'src/metadataserver/models/commissioningscript.py'
--- src/metadataserver/models/commissioningscript.py 2014-12-05 21:07:48 +0000
+++ src/metadataserver/models/commissioningscript.py 2015-01-09 14:32:14 +0000
@@ -43,6 +43,7 @@
43 )43 )
44from lxml import etree44from lxml import etree
45from maasserver.fields import MAC45from maasserver.fields import MAC
46from maasserver.models.physicalblockdevice import PhysicalBlockDevice
46from maasserver.models.tag import Tag47from maasserver.models.tag import Tag
47from metadataserver import DefaultMeta48from metadataserver import DefaultMeta
48from metadataserver.enum import RESULT_TYPE49from metadataserver.enum import RESULT_TYPE
@@ -354,6 +355,122 @@
354 'find /sys -name modalias -print0 | xargs -0 cat | sort -u'355 'find /sys -name modalias -print0 | xargs -0 cat | sort -u'
355356
356357
358def gather_physical_block_devices(print_output=True):
359 """Gathers information about a nodes physical block devices.
360
361 The following commands are ran in order to gather the required information.
362
363 lsblk Gathers the initial block devices not including slaves or
364 holders. Gets the name, read-only, removable, model, and
365 if rotary.
366
367 udevadm Grabs the device path, serial number, and if connected over
368 SATA.
369
370 blockdev Grabs the block size and size of the disk in bytes.
371
372 :param print_output: False will return the output instead of
373 printing. (Used only for testing.)
374 """
375 import shlex
376 from subprocess import check_output
377
378 # Grab the block devices from lsblk.
379 blockdevs = []
380 block_list = check_output(
381 ("lsblk", "-d", "-P", "-o", "NAME,RO,RM,MODEL,ROTA"))
382 for blockdev in block_list.splitlines():
383 tokens = shlex.split(blockdev)
384 current_block = {}
385 for token in tokens:
386 k, v = token.split("=", 1)
387 current_block[k] = v.strip()
388 blockdevs.append(current_block)
389
390 # Grab the device path, serial number, and sata connection.
391 UDEV_MAPPINGS = {
392 "DEVNAME": "PATH",
393 "ID_SERIAL_SHORT": "SERIAL",
394 "ID_ATA_SATA": "SATA",
395 }
396 del_blocks = []
397 for block_info in blockdevs:
398 # Some RAID devices return the name of the device seperated with "!",
399 # but udevadm expects it to be a "/".
400 block_name = block_info["NAME"].replace("!", "/")
401 udev_info = check_output(
402 ("udevadm", "info", "-q", "all", "-n", block_name))
403 for info_line in udev_info.splitlines():
404 info_line = info_line.strip()
405 if info_line == "":
406 continue
407 _, info = info_line.split(" ", 1)
408 if "=" not in info:
409 continue
410 k, v = info.split("=", 1)
411 if k in UDEV_MAPPINGS:
412 block_info[UDEV_MAPPINGS[k]] = v.strip()
413 if k == "ID_CDROM" and v == "1":
414 # Remove any type of CDROM from the blockdevs, as we
415 # cannot use this device for installation.
416 del_blocks.append(block_name)
417
418 # Remove any devices that need to be removed.
419 blockdevs = [
420 block_info
421 for block_info in blockdevs
422 if block_info["NAME"] not in del_blocks
423 ]
424
425 # Grab the size of the device and block size.
426 for block_info in blockdevs:
427 block_path = block_info["PATH"]
428 device_size = check_output(
429 ("blockdev", "--getsize64", block_path))
430 device_block_size = check_output(
431 ("blockdev", "--getbsz", block_path))
432 block_info["SIZE"] = device_size.strip()
433 block_info["BLOCK_SIZE"] = device_block_size.strip()
434
435 # Output block device information in json
436 json_output = json.dumps(blockdevs, indent=True)
437 if print_output:
438 print(json_output)
439 else:
440 return json_output
441
442
443def update_node_physical_block_devices(node, output, exit_status):
444 """Process the results of `gather_physical_block_devices`.
445
446 This updates the physical block devices that are attached to a node.
447
448 If `exit_status` is non-zero, this function returns without doing
449 anything.
450 """
451 assert isinstance(output, bytes)
452 if exit_status != 0:
453 return
454 blockdevs = json.loads(output)
455 PhysicalBlockDevice.objects.filter(node=node).delete()
456 for block_info in blockdevs:
457 # Skip the read-only devices. We keep them in the output for
458 # the user to view but they do not get an entry in the database.
459 if block_info["RO"] == "1":
460 continue
461 model = block_info.get("MODEL", "")
462 serial = block_info.get("SERIAL", "")
463 PhysicalBlockDevice.objects.create(
464 node=node,
465 name=block_info["NAME"],
466 path=block_info["PATH"],
467 size=long(block_info["SIZE"]),
468 block_size=int(block_info["BLOCK_SIZE"]),
469 model=model,
470 serial=serial,
471 )
472
473
357def null_hook(node, output, exit_status):474def null_hook(node, output, exit_status):
358 """Intentionally do nothing.475 """Intentionally do nothing.
359476
@@ -401,6 +518,10 @@
401 'content': make_function_call_script(dhcp_explore),518 'content': make_function_call_script(dhcp_explore),
402 'hook': null_hook,519 'hook': null_hook,
403 },520 },
521 '00-maas-06-block-devices.out': {
522 'content': make_function_call_script(gather_physical_block_devices),
523 'hook': update_node_physical_block_devices,
524 },
404 '99-maas-01-wait-for-lldpd.out': {525 '99-maas-01-wait-for-lldpd.out': {
405 'content': make_function_call_script(526 'content': make_function_call_script(
406 lldpd_wait, "/var/run/lldpd.socket", time_delay=60),527 lldpd_wait, "/var/run/lldpd.socket", time_delay=60),
407528
=== modified file 'src/metadataserver/models/tests/test_noderesults.py'
--- src/metadataserver/models/tests/test_noderesults.py 2014-12-05 21:07:48 +0000
+++ src/metadataserver/models/tests/test_noderesults.py 2015-01-09 14:32:14 +0000
@@ -17,11 +17,13 @@
17import doctest17import doctest
18from inspect import getsource18from inspect import getsource
19from io import BytesIO19from io import BytesIO
20import json
20from math import (21from math import (
21 ceil,22 ceil,
22 floor,23 floor,
23 )24 )
24import os.path25import os.path
26import random
25from random import randint27from random import randint
26import subprocess28import subprocess
27from subprocess import (29from subprocess import (
@@ -35,6 +37,7 @@
3537
36from fixtures import FakeLogger38from fixtures import FakeLogger
37from maasserver.fields import MAC39from maasserver.fields import MAC
40from maasserver.models.physicalblockdevice import PhysicalBlockDevice
38from maasserver.models.tag import Tag41from maasserver.models.tag import Tag
39from maasserver.testing.factory import factory42from maasserver.testing.factory import factory
40from maasserver.testing.orm import reload_object43from maasserver.testing.orm import reload_object
@@ -56,6 +59,7 @@
56from metadataserver.models.commissioningscript import (59from metadataserver.models.commissioningscript import (
57 ARCHIVE_PREFIX,60 ARCHIVE_PREFIX,
58 extract_router_mac_addresses,61 extract_router_mac_addresses,
62 gather_physical_block_devices,
59 inject_lldp_result,63 inject_lldp_result,
60 inject_lshw_result,64 inject_lshw_result,
61 inject_result,65 inject_result,
@@ -65,6 +69,7 @@
65 set_node_routers,69 set_node_routers,
66 set_virtual_tag,70 set_virtual_tag,
67 update_hardware_details,71 update_hardware_details,
72 update_node_physical_block_devices,
68 )73 )
69from metadataserver.models.noderesult import NodeResult74from metadataserver.models.noderesult import NodeResult
70from mock import (75from mock import (
@@ -676,3 +681,344 @@
676 logger = self.useFixture(FakeLogger(name='commissioningscript'))681 logger = self.useFixture(FakeLogger(name='commissioningscript'))
677 update_hardware_details(factory.make_Node(), b"garbage", exit_status=1)682 update_hardware_details(factory.make_Node(), b"garbage", exit_status=1)
678 self.assertEqual("", logger.output)683 self.assertEqual("", logger.output)
684
685
686class TestGatherPhysicalBlockDevices(MAASServerTestCase):
687
688 def make_lsblk_output(
689 self, name=None, read_only=False, removable=False,
690 model=None, rotary=True):
691 if name is None:
692 name = factory.make_name('name')
693 if model is None:
694 model = factory.make_name('model')
695 read_only = "1" if read_only else "0"
696 removable = "1" if removable else "0"
697 rotary = "1" if rotary else "0"
698 return 'NAME="%s" RO="%s" RM="%s" MODEL="%s" ROTA="%s"' % (
699 name, read_only, removable, model, rotary)
700
701 def make_udevadm_output(self, name, serial=None, sata=True, cdrom=False):
702 if serial is None:
703 serial = factory.make_name('serial')
704 sata = "1" if sata else "0"
705 output = dedent("""\
706 P: /devices/pci0000:00/ata3/host2/target2:0:0/2:0:0:0/block/{name}
707 N: {name}
708 E: DEVNAME=/dev/{name}
709 E: DEVTYPE=disk
710 E: ID_ATA_SATA={sata}
711 E: ID_SERIAL_SHORT={serial}
712 """).format(name=name, serial=serial, sata=sata)
713 if cdrom:
714 output += "E: ID_CDROM=1"
715 return output
716
717 def call_gather_physical_block_devices(self):
718 return json.loads(gather_physical_block_devices(print_output=False))
719
720 def test__calls_lsblk(self):
721 check_output = self.patch(subprocess, "check_output")
722 check_output.return_value = ""
723 self.call_gather_physical_block_devices()
724 self.assertThat(check_output, MockCalledOnceWith(
725 ("lsblk", "-d", "-P", "-o", "NAME,RO,RM,MODEL,ROTA")))
726
727 def test__returns_empty_list_when_no_disks(self):
728 check_output = self.patch(subprocess, "check_output")
729 check_output.return_value = ""
730 self.assertEquals([], self.call_gather_physical_block_devices())
731
732 def test__calls_lsblk_then_udevadm(self):
733 name = factory.make_name('name')
734 check_output = self.patch(subprocess, "check_output")
735 check_output.side_effect = [
736 self.make_lsblk_output(
737 name=name),
738 self.make_udevadm_output(
739 name, cdrom=True),
740 ]
741 self.call_gather_physical_block_devices()
742 self.assertThat(check_output, MockCallsMatch(
743 call(("lsblk", "-d", "-P", "-o", "NAME,RO,RM,MODEL,ROTA")),
744 call(("udevadm", "info", "-q", "all", "-n", name))))
745
746 def test__returns_empty_list_when_cdrom_only(self):
747 name = factory.make_name('name')
748 check_output = self.patch(subprocess, "check_output")
749 check_output.side_effect = [
750 self.make_lsblk_output(
751 name=name),
752 self.make_udevadm_output(
753 name, cdrom=True),
754 ]
755 self.assertEquals([], self.call_gather_physical_block_devices())
756
757 def test__calls_lsblk_udevadm_then_blockdev(self):
758 name = factory.make_name('name')
759 model = factory.make_name('model')
760 serial = factory.make_name('serial')
761 size = random.randint(1000 * 1000, 1000 * 1000 * 1000)
762 block_size = random.choice([512, 1024, 4096])
763 check_output = self.patch(subprocess, "check_output")
764 check_output.side_effect = [
765 self.make_lsblk_output(name=name, model=model),
766 self.make_udevadm_output(name, serial=serial),
767 '%s' % size,
768 '%s' % block_size,
769 ]
770 self.call_gather_physical_block_devices()
771 self.assertThat(check_output, MockCallsMatch(
772 call(("lsblk", "-d", "-P", "-o", "NAME,RO,RM,MODEL,ROTA")),
773 call(("udevadm", "info", "-q", "all", "-n", name)),
774 call(("blockdev", "--getsize64", "/dev/%s" % name)),
775 call(("blockdev", "--getbsz", "/dev/%s" % name))))
776
777 def test__returns_block_device(self):
778 name = factory.make_name('name')
779 model = factory.make_name('model')
780 serial = factory.make_name('serial')
781 size = random.randint(1000 * 1000, 1000 * 1000 * 1000)
782 block_size = random.choice([512, 1024, 4096])
783 check_output = self.patch(subprocess, "check_output")
784 check_output.side_effect = [
785 self.make_lsblk_output(name=name, model=model),
786 self.make_udevadm_output(name, serial=serial),
787 '%s' % size,
788 '%s' % block_size,
789 ]
790 self.assertEquals([{
791 "NAME": name,
792 "PATH": "/dev/%s" % name,
793 "RO": "0",
794 "RM": "0",
795 "MODEL": model,
796 "ROTA": "1",
797 "SATA": "1",
798 "SERIAL": serial,
799 "SIZE": "%s" % size,
800 "BLOCK_SIZE": "%s" % block_size,
801 }], self.call_gather_physical_block_devices())
802
803 def test__returns_block_device_readonly(self):
804 name = factory.make_name('name')
805 model = factory.make_name('model')
806 serial = factory.make_name('serial')
807 size = random.randint(1000 * 1000, 1000 * 1000 * 1000)
808 block_size = random.choice([512, 1024, 4096])
809 check_output = self.patch(subprocess, "check_output")
810 check_output.side_effect = [
811 self.make_lsblk_output(name=name, model=model, read_only=True),
812 self.make_udevadm_output(name, serial=serial),
813 '%s' % size,
814 '%s' % block_size,
815 ]
816 self.assertEquals([{
817 "NAME": name,
818 "PATH": "/dev/%s" % name,
819 "RO": "1",
820 "RM": "0",
821 "MODEL": model,
822 "ROTA": "1",
823 "SATA": "1",
824 "SERIAL": serial,
825 "SIZE": "%s" % size,
826 "BLOCK_SIZE": "%s" % block_size,
827 }], self.call_gather_physical_block_devices())
828
829 def test__returns_block_device_ssd(self):
830 name = factory.make_name('name')
831 model = factory.make_name('model')
832 serial = factory.make_name('serial')
833 size = random.randint(1000 * 1000, 1000 * 1000 * 1000)
834 block_size = random.choice([512, 1024, 4096])
835 check_output = self.patch(subprocess, "check_output")
836 check_output.side_effect = [
837 self.make_lsblk_output(name=name, model=model, rotary=False),
838 self.make_udevadm_output(name, serial=serial),
839 '%s' % size,
840 '%s' % block_size,
841 ]
842 self.assertEquals([{
843 "NAME": name,
844 "PATH": "/dev/%s" % name,
845 "RO": "0",
846 "RM": "0",
847 "MODEL": model,
848 "ROTA": "0",
849 "SATA": "1",
850 "SERIAL": serial,
851 "SIZE": "%s" % size,
852 "BLOCK_SIZE": "%s" % block_size,
853 }], self.call_gather_physical_block_devices())
854
855 def test__returns_block_device_not_sata(self):
856 name = factory.make_name('name')
857 model = factory.make_name('model')
858 serial = factory.make_name('serial')
859 size = random.randint(1000 * 1000, 1000 * 1000 * 1000)
860 block_size = random.choice([512, 1024, 4096])
861 check_output = self.patch(subprocess, "check_output")
862 check_output.side_effect = [
863 self.make_lsblk_output(name=name, model=model),
864 self.make_udevadm_output(name, serial=serial, sata=False),
865 '%s' % size,
866 '%s' % block_size,
867 ]
868 self.assertEquals([{
869 "NAME": name,
870 "PATH": "/dev/%s" % name,
871 "RO": "0",
872 "RM": "0",
873 "MODEL": model,
874 "ROTA": "1",
875 "SATA": "0",
876 "SERIAL": serial,
877 "SIZE": "%s" % size,
878 "BLOCK_SIZE": "%s" % block_size,
879 }], self.call_gather_physical_block_devices())
880
881 def test__returns_block_device_removable(self):
882 name = factory.make_name('name')
883 model = factory.make_name('model')
884 serial = factory.make_name('serial')
885 size = random.randint(1000 * 1000, 1000 * 1000 * 1000)
886 block_size = random.choice([512, 1024, 4096])
887 check_output = self.patch(subprocess, "check_output")
888 check_output.side_effect = [
889 self.make_lsblk_output(name=name, model=model, removable=True),
890 self.make_udevadm_output(name, serial=serial),
891 '%s' % size,
892 '%s' % block_size,
893 ]
894 self.assertEquals([{
895 "NAME": name,
896 "PATH": "/dev/%s" % name,
897 "RO": "0",
898 "RM": "1",
899 "MODEL": model,
900 "ROTA": "1",
901 "SATA": "1",
902 "SERIAL": serial,
903 "SIZE": "%s" % size,
904 "BLOCK_SIZE": "%s" % block_size,
905 }], self.call_gather_physical_block_devices())
906
907 def test__returns_multiple_block_devices_in_order(self):
908 names = [factory.make_name('name') for _ in range(3)]
909 lsblk = [
910 self.make_lsblk_output(name=name)
911 for name in names
912 ]
913 call_outputs = []
914 call_outputs.append("\n".join(lsblk))
915 for name in names:
916 call_outputs.append(self.make_udevadm_output(name))
917 for name in names:
918 call_outputs.append(
919 "%s" % random.randint(1000 * 1000, 1000 * 1000 * 1000))
920 call_outputs.append(
921 "%s" % random.choice([512, 1024, 4096]))
922 check_output = self.patch(subprocess, "check_output")
923 check_output.side_effect = call_outputs
924 device_names = [
925 block_info['NAME']
926 for block_info in self.call_gather_physical_block_devices()
927 ]
928 self.assertEquals(names, device_names)
929
930
931class TestUpdateNodePhysicalBlockDevices(MAASServerTestCase):
932
933 def make_block_device(
934 self, name=None, path=None, size=None, block_size=None,
935 model=None, serial=None):
936 if name is None:
937 name = factory.make_name('name')
938 if path is None:
939 path = '/dev/%s' % name
940 if size is None:
941 size = random.randint(1000 * 1000, 1000 * 1000 * 1000)
942 if block_size is None:
943 block_size = random.choice([512, 1024, 4096])
944 if model is None:
945 model = factory.make_name('model')
946 if serial is None:
947 serial = factory.make_name('serial')
948 return {
949 "NAME": name,
950 "PATH": path,
951 "SIZE": '%s' % size,
952 "BLOCK_SIZE": '%s' % block_size,
953 "MODEL": model,
954 "SERIAL": serial,
955 "RO": "0",
956 "RM": "0",
957 "ROTA": "1",
958 }
959
960 def test__does_nothing_when_exit_status_is_not_zero(self):
961 node = factory.make_Node()
962 block_device = factory.make_PhysicalBlockDevice(node=node)
963 update_node_physical_block_devices(node, b"garbage", exit_status=1)
964 self.assertIsNotNone(reload_object(block_device))
965
966 def test__clears_previous_physical_block_devices(self):
967 node = factory.make_Node()
968 block_device = factory.make_PhysicalBlockDevice(node=node)
969 update_node_physical_block_devices(node, b"[]", 0)
970 self.assertIsNone(reload_object(block_device))
971
972 def test__creates_physical_block_devices(self):
973 devices = [self.make_block_device() for _ in range(3)]
974 device_names = [device['NAME'] for device in devices]
975 node = factory.make_Node()
976 json_output = json.dumps(devices).encode('utf-8')
977 update_node_physical_block_devices(node, json_output, 0)
978 created_names = [
979 device.name
980 for device in PhysicalBlockDevice.objects.filter(node=node)
981 ]
982 self.assertItemsEqual(device_names, created_names)
983
984 def test__creates_physical_block_devices_in_order(self):
985 devices = [self.make_block_device() for _ in range(3)]
986 device_names = [device['NAME'] for device in devices]
987 node = factory.make_Node()
988 json_output = json.dumps(devices).encode('utf-8')
989 update_node_physical_block_devices(node, json_output, 0)
990 created_names = [
991 device.name
992 for device in (
993 PhysicalBlockDevice.objects.filter(node=node).order_by('id'))
994 ]
995 self.assertEquals(device_names, created_names)
996
997 def test__creates_physical_block_device(self):
998 name = factory.make_name('name')
999 path = '/dev/%s' % name
1000 size = random.randint(1000 * 1000, 1000 * 1000 * 1000)
1001 block_size = random.choice([512, 1024, 4096])
1002 model = factory.make_name('model')
1003 serial = factory.make_name('serial')
1004 device = self.make_block_device(
1005 name=name, path=path, size=size, block_size=block_size,
1006 model=model, serial=serial)
1007 node = factory.make_Node()
1008 json_output = json.dumps([device]).encode('utf-8')
1009 update_node_physical_block_devices(node, json_output, 0)
1010 self.assertThat(
1011 PhysicalBlockDevice.objects.filter(node=node).first(),
1012 MatchesStructure.byEquality(
1013 name=name, path=path, size=size, block_size=block_size,
1014 model=model, serial=serial))
1015
1016 def test__creates_physical_block_device_only_for_node(self):
1017 device = self.make_block_device()
1018 node = factory.make_Node()
1019 other_node = factory.make_Node()
1020 json_output = json.dumps([device]).encode('utf-8')
1021 update_node_physical_block_devices(node, json_output, 0)
1022 self.assertEquals(
1023 0, PhysicalBlockDevice.objects.filter(node=other_node).count(),
1024 "Created physical block device for the incorrect node.")