~curtin-dev/curtin/artful

« back to all changes in this revision

Viewing changes to tests/unittests/test_block_iscsi.py

  • Committer: Scott Moser
  • Date: 2017-10-06 02:22:20 UTC
  • mfrom: (1.1.51)
  • Revision ID: smoser@ubuntu.com-20171006022220-1wba1lrof12m9pbx
* New upstream snapshot.
  - vmtest: fix artful networking (LP: #1714028, LP: #1718216, LP: #1706744)
  - docs: Trivial doc fix for enabling proposed.
  - setup.py: fix to allow installation into a virtualenv (LP: #1703755)
  - doc: update documentation on curtin-hooks and non-ubuntu installation.
  - reporter: Add journald reporter to send events to journald
  - vmtests: add option to tar disk images after test run
  - install: ensure iscsi service is running to handle shutdown properly
  - mdadm: handle write failures to sysfs entries when stopping mdadm
    (LP: #1708052)
  - vmtest: catch exceptions in curtin-log-print
  - iscsi: use curtin storage config to disconnect iscsi targets
    (LP: #1713537)
  - vmtests: bump skip_by_date values out to give cloud-init SRU more time
  - vmtest: get info about collected symlinks and then delete them.
  - Update network cloud-init related skiptest dates, SRU still pending

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
import mock
 
2
import os
2
3
 
3
4
from curtin.block import iscsi
 
5
from curtin import util
4
6
from .helpers import CiTestCase
5
7
 
6
8
 
557
559
        with self.assertRaises(ValueError):
558
560
            iscsi.volpath_is_iscsi(None)
559
561
 
 
562
 
 
563
class TestBlockIscsiDiskFromConfig(CiTestCase):
 
564
    # Test iscsi parsing of storage config for iscsi configure disks
 
565
 
 
566
    def setUp(self):
 
567
        super(TestBlockIscsiDiskFromConfig, self).setUp()
 
568
        self.add_patch('curtin.block.iscsi.util.subp', 'mock_subp')
 
569
 
 
570
    def test_parse_iscsi_disk_from_config(self):
 
571
        """Test parsing iscsi volume path creates the same iscsi disk"""
 
572
        target = 'curtin-659d5f45-4f23-46cb-b826-f2937b896e09'
 
573
        iscsi_path = 'iscsi:10.245.168.20::20112:1:' + target
 
574
        cfg = {
 
575
            'storage': {
 
576
                'config': [{'type': 'disk',
 
577
                            'id': 'iscsidev1',
 
578
                            'path': iscsi_path,
 
579
                            'name': 'iscsi_disk1',
 
580
                            'ptable': 'msdos',
 
581
                            'wipe': 'superblock'}]
 
582
                }
 
583
        }
 
584
        expected_iscsi_disk = iscsi.IscsiDisk(iscsi_path)
 
585
        iscsi_disk = iscsi.get_iscsi_disks_from_config(cfg).pop()
 
586
        # utilize IscsiDisk str method for equality check
 
587
        self.assertEqual(str(expected_iscsi_disk), str(iscsi_disk))
 
588
 
 
589
    def test_parse_iscsi_disk_from_config_no_iscsi(self):
 
590
        """Test parsing storage config with no iscsi disks included"""
 
591
        cfg = {
 
592
            'storage': {
 
593
                'config': [{'type': 'disk',
 
594
                            'id': 'ssd1',
 
595
                            'path': 'dev/slash/foo1',
 
596
                            'name': 'the-fast-one',
 
597
                            'ptable': 'gpt',
 
598
                            'wipe': 'superblock'}]
 
599
                }
 
600
        }
 
601
        expected_iscsi_disks = []
 
602
        iscsi_disks = iscsi.get_iscsi_disks_from_config(cfg)
 
603
        self.assertEqual(expected_iscsi_disks, iscsi_disks)
 
604
 
 
605
    def test_parse_iscsi_disk_from_config_invalid_iscsi(self):
 
606
        """Test parsing storage config with no iscsi disks included"""
 
607
        cfg = {
 
608
            'storage': {
 
609
                'config': [{'type': 'disk',
 
610
                            'id': 'iscsidev2',
 
611
                            'path': 'iscsi:garbage',
 
612
                            'name': 'noob-city',
 
613
                            'ptable': 'msdos',
 
614
                            'wipe': 'superblock'}]
 
615
                }
 
616
        }
 
617
        with self.assertRaises(ValueError):
 
618
            iscsi.get_iscsi_disks_from_config(cfg)
 
619
 
 
620
    def test_parse_iscsi_disk_from_config_empty(self):
 
621
        """Test parse_iscsi_disks handles empty/invalid config"""
 
622
        expected_iscsi_disks = []
 
623
        iscsi_disks = iscsi.get_iscsi_disks_from_config({})
 
624
        self.assertEqual(expected_iscsi_disks, iscsi_disks)
 
625
 
 
626
        cfg = {'storage': {'config': []}}
 
627
        iscsi_disks = iscsi.get_iscsi_disks_from_config(cfg)
 
628
        self.assertEqual(expected_iscsi_disks, iscsi_disks)
 
629
 
 
630
    def test_parse_iscsi_disk_from_config_none(self):
 
631
        """Test parse_iscsi_disks handles no config"""
 
632
        expected_iscsi_disks = []
 
633
        iscsi_disks = iscsi.get_iscsi_disks_from_config({})
 
634
        self.assertEqual(expected_iscsi_disks, iscsi_disks)
 
635
 
 
636
        cfg = None
 
637
        iscsi_disks = iscsi.get_iscsi_disks_from_config(cfg)
 
638
        self.assertEqual(expected_iscsi_disks, iscsi_disks)
 
639
 
 
640
 
 
641
class TestBlockIscsiDisconnect(CiTestCase):
 
642
    # test that when disconnecting iscsi targets we
 
643
    # check that the target has an active session before
 
644
    # issuing a disconnect command
 
645
 
 
646
    def setUp(self):
 
647
        super(TestBlockIscsiDisconnect, self).setUp()
 
648
        self.add_patch('curtin.block.iscsi.util.subp', 'mock_subp')
 
649
        self.add_patch('curtin.block.iscsi.iscsiadm_sessions',
 
650
                       'mock_iscsi_sessions')
 
651
        # fake target_root + iscsi nodes dir
 
652
        self.target_path = self.tmp_dir()
 
653
        self.iscsi_nodes = os.path.join(self.target_path, 'etc/iscsi/nodes')
 
654
        util.ensure_dir(self.iscsi_nodes)
 
655
 
 
656
    def _fmt_disconnect(self, target, portal):
 
657
        return ['iscsiadm', '--mode=node', '--targetname=%s' % target,
 
658
                '--portal=%s' % portal, '--logout']
 
659
 
 
660
    def _setup_nodes(self, sessions, connection):
 
661
        # setup iscsi_nodes dir (<fakeroot>/etc/iscsi/nodes) with content
 
662
        for s in sessions:
 
663
            sdir = os.path.join(self.iscsi_nodes, s)
 
664
            connpath = os.path.join(sdir, connection)
 
665
            util.ensure_dir(sdir)
 
666
            util.write_file(connpath, content="")
 
667
 
 
668
    def test_disconnect_target_disk(self):
 
669
        """Test iscsi disconnecting multiple sessions, all present"""
 
670
 
 
671
        sessions = [
 
672
            'curtin-53ab23ff-a887-449a-80a8-288151208091',
 
673
            'curtin-94b62de1-c579-42c0-879e-8a28178e64c5',
 
674
            'curtin-556aeecd-a227-41b7-83d7-2bb471c574b4',
 
675
            'curtin-fd0f644b-7858-420f-9997-3ea2aefe87b9'
 
676
        ]
 
677
        connection = '10.245.168.20,16395,1'
 
678
        self._setup_nodes(sessions, connection)
 
679
 
 
680
        self.mock_iscsi_sessions.return_value = "\n".join(sessions)
 
681
 
 
682
        iscsi.disconnect_target_disks(self.target_path)
 
683
 
 
684
        expected_calls = []
 
685
        for session in sessions:
 
686
            (host, port, _) = connection.split(',')
 
687
            disconnect = self._fmt_disconnect(session, "%s:%s" % (host, port))
 
688
            calls = [
 
689
                mock.call(['sync']),
 
690
                mock.call(disconnect, capture=True, log_captured=True),
 
691
                mock.call(['udevadm', 'settle']),
 
692
            ]
 
693
            expected_calls.extend(calls)
 
694
 
 
695
        self.mock_subp.assert_has_calls(expected_calls, any_order=True)
 
696
 
 
697
    def test_disconnect_target_disk_skip_disconnected(self):
 
698
        """Test iscsi does not attempt to disconnect already closed sessions"""
 
699
        sessions = [
 
700
            'curtin-53ab23ff-a887-449a-80a8-288151208091',
 
701
            'curtin-94b62de1-c579-42c0-879e-8a28178e64c5',
 
702
            'curtin-556aeecd-a227-41b7-83d7-2bb471c574b4',
 
703
            'curtin-fd0f644b-7858-420f-9997-3ea2aefe87b9'
 
704
        ]
 
705
        connection = '10.245.168.20,16395,1'
 
706
        self._setup_nodes(sessions, connection)
 
707
        # Test with all sessions are already disconnected
 
708
        self.mock_iscsi_sessions.return_value = ""
 
709
 
 
710
        iscsi.disconnect_target_disks(self.target_path)
 
711
 
 
712
        self.mock_subp.assert_has_calls([], any_order=True)
 
713
 
 
714
    @mock.patch('curtin.block.iscsi.iscsiadm_logout')
 
715
    def test_disconnect_target_disk_raises_runtime_error(self, mock_logout):
 
716
        """Test iscsi raises RuntimeError if we fail to logout"""
 
717
        sessions = [
 
718
            'curtin-53ab23ff-a887-449a-80a8-288151208091',
 
719
        ]
 
720
        connection = '10.245.168.20,16395,1'
 
721
        self._setup_nodes(sessions, connection)
 
722
        self.mock_iscsi_sessions.return_value = "\n".join(sessions)
 
723
        mock_logout.side_effect = util.ProcessExecutionError()
 
724
 
 
725
        with self.assertRaises(RuntimeError):
 
726
            iscsi.disconnect_target_disks(self.target_path)
 
727
 
 
728
        expected_calls = []
 
729
        for session in sessions:
 
730
            (host, port, _) = connection.split(',')
 
731
            disconnect = self._fmt_disconnect(session, "%s:%s" % (host, port))
 
732
            calls = [
 
733
                mock.call(['sync']),
 
734
                mock.call(disconnect, capture=True, log_captured=True),
 
735
                mock.call(['udevadm', 'settle']),
 
736
            ]
 
737
            expected_calls.extend(calls)
 
738
 
 
739
        self.mock_subp.assert_has_calls([], any_order=True)
 
740
 
560
741
# vi: ts=4 expandtab syntax=python