Data files are now and should continue to be gleaned from pypowervm, which should be relied upon to keep them current (with respect to the REST API), clean, and consolidated. A recent pypowervm change set has added all the data files needed by this project, so they are removed from here. The test utilities used to load these files were also refactored (moved to a different package). This change set accomodates that refactor. Change-Id: Ic289c0fda3fe8271465f4c66ab99238d880931b3
145 lines
5.9 KiB
Python
145 lines
5.9 KiB
Python
# Copyright 2015 IBM Corp.
|
|
#
|
|
# All Rights Reserved.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
# not use this file except in compliance with the License. You may obtain
|
|
# a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
# under the License.
|
|
|
|
import mock
|
|
|
|
from nova import exception
|
|
from nova import test
|
|
from pypowervm.tests import test_fixtures as pvm_fx
|
|
from pypowervm.tests.test_utils import pvmhttp
|
|
from pypowervm.wrappers import logical_partition as pvm_lpar
|
|
|
|
from nova_powervm.virt.powervm import exception as npvmex
|
|
from nova_powervm.virt.powervm import mgmt
|
|
|
|
LPAR_HTTPRESP_FILE = "lpar.txt"
|
|
|
|
|
|
class TestMgmt(test.TestCase):
|
|
def setUp(self):
|
|
super(TestMgmt, self).setUp()
|
|
self.apt = self.useFixture(pvm_fx.AdapterFx()).adpt
|
|
|
|
lpar_http = pvmhttp.load_pvm_resp(LPAR_HTTPRESP_FILE, adapter=self.apt)
|
|
self.assertNotEqual(lpar_http, None,
|
|
"Could not load %s " % LPAR_HTTPRESP_FILE)
|
|
|
|
self.resp = lpar_http.response
|
|
|
|
def test_get_mgmt_partition(self):
|
|
self.apt.read.return_value = self.resp
|
|
mp_wrap = mgmt.get_mgmt_partition(self.apt)
|
|
self.assertIsInstance(mp_wrap, pvm_lpar.LPAR)
|
|
self.assertTrue(mp_wrap.is_mgmt_partition)
|
|
|
|
@mock.patch('glob.glob')
|
|
@mock.patch('nova.utils.execute')
|
|
@mock.patch('os.path.realpath')
|
|
def test_discover_vscsi_disk(self, mock_realpath, mock_exec, mock_glob):
|
|
scanpath = '/sys/bus/vio/devices/30000005/host*/scsi_host/host*/scan'
|
|
udid = ('275b5d5f88fa5611e48be9000098be9400'
|
|
'13fb2aa55a2d7b8d150cb1b7b6bc04d6')
|
|
devlink = ('/dev/disk/by-id/scsi-SIBM_3303_NVDISK' + udid)
|
|
mapping = mock.Mock()
|
|
mapping.client_adapter.slot_number = 5
|
|
mapping.backing_storage.udid = udid
|
|
# Realistically, first glob would return e.g. .../host0/.../host0/...
|
|
# but it doesn't matter for test purposes.
|
|
mock_glob.side_effect = [[scanpath], [devlink]]
|
|
mgmt.discover_vscsi_disk(mapping)
|
|
mock_glob.assert_has_calls(
|
|
[mock.call(scanpath), mock.call('/dev/disk/by-id/*' + udid[-32:])])
|
|
mock_exec.assert_called_with('tee', '-a', scanpath,
|
|
process_input='- - -', run_as_root=True)
|
|
mock_realpath.assert_called_with(devlink)
|
|
|
|
@mock.patch('time.sleep')
|
|
@mock.patch('glob.glob')
|
|
@mock.patch('nova.utils.execute')
|
|
def test_discover_vscsi_disk_not_one_result(self, mock_exec, mock_glob,
|
|
mock_sleep):
|
|
"""Zero or more than one disk is found by discover_vscsi_disk."""
|
|
udid = ('275b5d5f88fa5611e48be9000098be9400'
|
|
'13fb2aa55a2d7b8d150cb1b7b6bc04d6')
|
|
mapping = mock.Mock()
|
|
mapping.client_adapter.slot_number = 5
|
|
mapping.backing_storage.udid = udid
|
|
# No disks found
|
|
mock_glob.side_effect = lambda path: []
|
|
self.assertRaises(npvmex.NoDiskDiscoveryException,
|
|
mgmt.discover_vscsi_disk, mapping)
|
|
self.assertTrue(mock_sleep.call_count)
|
|
# Multiple disks found
|
|
mock_sleep.reset_mock()
|
|
mock_glob.side_effect = [['path'], ['/dev/sde', '/dev/sdf']]
|
|
self.assertRaises(npvmex.UniqueDiskDiscoveryException,
|
|
mgmt.discover_vscsi_disk, mapping)
|
|
self.assertEqual(0, mock_sleep.call_count)
|
|
|
|
@mock.patch('time.sleep')
|
|
@mock.patch('os.path.realpath')
|
|
@mock.patch('os.stat')
|
|
@mock.patch('nova.utils.execute')
|
|
def test_remove_block_dev(self, mock_exec, mock_stat, mock_realpath,
|
|
mock_sleep):
|
|
link = '/dev/link/foo'
|
|
realpath = '/dev/sde'
|
|
delpath = '/sys/block/sde/device/delete'
|
|
mock_realpath.return_value = realpath
|
|
|
|
# Good path
|
|
mock_stat.side_effect = (None, None, OSError())
|
|
mgmt.remove_block_dev(link)
|
|
mock_realpath.assert_called_with(link)
|
|
mock_stat.assert_has_calls([mock.call(realpath), mock.call(delpath),
|
|
mock.call(realpath)])
|
|
mock_exec.assert_called_with('tee', '-a', delpath, process_input='1',
|
|
run_as_root=True)
|
|
self.assertEqual(0, mock_sleep.call_count)
|
|
|
|
# Device param not found
|
|
mock_exec.reset_mock()
|
|
mock_stat.reset_mock()
|
|
mock_stat.side_effect = (OSError(), None, None)
|
|
self.assertRaises(exception.InvalidDevicePath, mgmt.remove_block_dev,
|
|
link)
|
|
# stat was called once; exec was not called
|
|
self.assertEqual(1, mock_stat.call_count)
|
|
self.assertEqual(0, mock_exec.call_count)
|
|
|
|
# Delete special file not found
|
|
mock_exec.reset_mock()
|
|
mock_stat.reset_mock()
|
|
mock_stat.side_effect = (None, OSError(), None)
|
|
self.assertRaises(exception.InvalidDevicePath, mgmt.remove_block_dev,
|
|
link)
|
|
# stat was called twice; exec was not called
|
|
self.assertEqual(2, mock_stat.call_count)
|
|
self.assertEqual(0, mock_exec.call_count)
|
|
|
|
# Deletion was attempted, but device is still there
|
|
mock_exec.reset_mock()
|
|
mock_stat.reset_mock()
|
|
mock_sleep.reset_mock()
|
|
mock_stat.side_effect = lambda path: 1
|
|
self.assertRaises(
|
|
npvmex.DeviceDeletionException, mgmt.remove_block_dev, link)
|
|
# stat was called many times; exec was called once
|
|
self.assertTrue(mock_stat.call_count > 4)
|
|
self.assertEqual(1, mock_exec.call_count)
|
|
# sleep was called many times
|
|
self.assertTrue(mock_sleep.call_count)
|