Task for connecting and discovering instance disk

New nova_powervm.virt.powervm.tasks.storage.InstanceDiskToMgmt Task
class encompassing a) connecting an instance's boot disk to the
management partition, and b) discovering that disk and figuring out its
device path.

Change-Id: I77670ff970325781a69d96e24d5e69a23b6c195b
This commit is contained in:
Eric Fried
2015-06-24 12:02:53 -05:00
parent 5471cad97f
commit 91cf422ee2
4 changed files with 208 additions and 8 deletions

View File

@@ -0,0 +1,109 @@
# 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 test
from nova_powervm.virt.powervm import exception as npvmex
from nova_powervm.virt.powervm.tasks import storage as tf_stg
class TestStorage(test.TestCase):
@mock.patch('pypowervm.tasks.scsi_mapper.find_maps')
@mock.patch('nova_powervm.virt.powervm.mgmt.discover_vscsi_disk')
@mock.patch('nova_powervm.virt.powervm.mgmt.remove_block_dev')
def test_instance_disk_to_mgmt(self, mock_rm, mock_discover, mock_find):
mock_discover.return_value = '/dev/disk'
mock_instance = mock.Mock()
mock_instance.name = 'instance_name'
mock_stg = mock.Mock()
mock_stg.name = 'stg_name'
mock_vwrap = mock.Mock()
mock_vwrap.name = 'vios_name'
mock_vwrap.uuid = 'vios_uuid'
mock_vwrap.scsi_mappings = ['mapping1']
def verify_connect(inst):
self.assertEqual(mock_instance, inst)
return mock_stg, mock_vwrap
def verify_disconnect(vios_uuid, stg_name):
self.assertEqual('vios_uuid', vios_uuid)
self.assertEqual('stg_name', stg_name)
disk_dvr = mock.MagicMock()
disk_dvr.mp_uuid = 'mp_uuid'
disk_dvr.connect_instance_disk_to_mgmt = verify_connect
disk_dvr.disconnect_disk_from_mgmt = verify_disconnect
# Good path - find_maps returns one result
mock_find.return_value = ['one_mapping']
tf = tf_stg.InstanceDiskToMgmt(disk_dvr, mock_instance)
self.assertEqual('connect_and_discover_instance_disk_to_mgmt', tf.name)
self.assertEqual((mock_stg, mock_vwrap, '/dev/disk'), tf.execute())
mock_find.assert_called_with(['mapping1'], 'mp_uuid',
stg_elem=mock_stg)
mock_discover.assert_called_with('one_mapping')
tf.revert('result', 'failures')
mock_rm.assert_called_with('/dev/disk')
# Good path - find_maps returns >1 result
mock_find.reset_mock()
mock_discover.reset_mock()
mock_rm.reset_mock()
mock_find.return_value = ['first_mapping', 'second_mapping']
tf = tf_stg.InstanceDiskToMgmt(disk_dvr, mock_instance)
self.assertEqual((mock_stg, mock_vwrap, '/dev/disk'), tf.execute())
mock_find.assert_called_with(['mapping1'], 'mp_uuid',
stg_elem=mock_stg)
mock_discover.assert_called_with('first_mapping')
tf.revert('result', 'failures')
mock_rm.assert_called_with('/dev/disk')
# Bad path - find_maps returns no results
mock_find.reset_mock()
mock_discover.reset_mock()
mock_rm.reset_mock()
mock_find.return_value = []
tf = tf_stg.InstanceDiskToMgmt(disk_dvr, mock_instance)
self.assertRaises(npvmex.NewMgmtMappingNotFoundException, tf.execute)
# find_maps was still called
mock_find.assert_called_with(['mapping1'], 'mp_uuid',
stg_elem=mock_stg)
# discover_vscsi_disk didn't get called
self.assertEqual(0, mock_discover.call_count)
tf.revert('result', 'failures')
# disconnect_disk_from_mgmt got called (still checked by
# verify_disconnect above), but remove_block_dev did not.
self.assertEqual(0, mock_rm.call_count)
# Bad path - connect raises
mock_find.reset_mock()
mock_discover.reset_mock()
mock_rm.reset_mock()
disk_dvr.connect_instance_disk_to_mgmt = mock.Mock(
side_effect=npvmex.InstanceDiskMappingFailed(
instance_name='inst_name'))
tf = tf_stg.InstanceDiskToMgmt(disk_dvr, mock_instance)
self.assertRaises(npvmex.InstanceDiskMappingFailed, tf.execute)
self.assertEqual(0, mock_find.call_count)
self.assertEqual(0, mock_discover.call_count)
# revert shouldn't call disconnect or remove
disk_dvr.disconnect_disk_from_mgmt = mock.Mock(side_effect=self.fail)
tf.revert('result', 'failures')
self.assertEqual(0, mock_rm.call_count)

View File

@@ -70,6 +70,13 @@ class InstanceDiskMappingFailed(AbstractDiskException):
"the management partition from any Virtual I/O Server.")
class NewMgmtMappingNotFoundException(nex.NovaException):
"""Just created a mapping to the mgmt partition, but can't find it."""
msg_fmt = _("Failed to find newly-created mapping of storage element "
"%(stg_name)s from Virtual I/O Server %(vios_name)s to the "
"management partition.")
class VGNotFound(AbstractDiskException):
msg_fmt = _("Unable to locate the volume group '%(vg_name)s' for this "
"operation.")

View File

@@ -24,7 +24,6 @@ The PowerVM Nova Compute service runs on the management partition.
"""
import glob
from nova import exception
from nova.i18n import _LI
from nova.storage import linuxscsi
import os
from os import path
@@ -80,8 +79,8 @@ def discover_vscsi_disk(mapping, scan_timeout=10):
# 32 chars of the field we get from PowerVM.
udid = mapping.backing_storage.udid[-32:]
LOG.info(_LI("Trying to discover VSCSI disk with UDID %(udid)s on slot "
"%(slot)x."), {'udid': udid, 'slot': lslot})
LOG.debug("Trying to discover VSCSI disk with UDID %(udid)s on slot "
"%(slot)x.", {'udid': udid, 'slot': lslot})
# Find the special file to scan the bus, and scan it.
# This glob should yield exactly one result, but use the loop just in case.
@@ -115,9 +114,9 @@ def discover_vscsi_disk(mapping, scan_timeout=10):
# The by-id path is a symlink. Resolve to the /dev/sdX path
dpath = path.realpath(disks[0])
LOG.info(_LI("Discovered VSCSI disk with UDID %(udid)s on slot %(slot)x "
"at path %(devname)s."),
{'udid': udid, 'slot': lslot, 'devname': dpath})
LOG.debug("Discovered VSCSI disk with UDID %(udid)s on slot %(slot)x at "
"path %(devname)s.",
{'udid': udid, 'slot': lslot, 'devname': dpath})
return dpath

View File

@@ -15,13 +15,16 @@
# under the License.
from nova.i18n import _LI, _LW
from pypowervm.tasks import scsi_mapper as pvm_smap
from oslo_log import log as logging
from taskflow import task
from taskflow.types import failure as task_fail
from nova_powervm.virt.powervm.disk import driver as disk_dvr
from nova_powervm.virt.powervm.disk import driver as disk_driver
from nova_powervm.virt.powervm import exception as npvmex
from nova_powervm.virt.powervm import media
from nova_powervm.virt.powervm import mgmt
LOG = logging.getLogger(__name__)
@@ -131,7 +134,7 @@ class CreateDiskForImg(task.Task):
"""The Task to create the disk from an image in the storage."""
def __init__(self, disk_dvr, context, instance, image_meta, disk_size=0,
image_type=disk_dvr.DiskType.BOOT):
image_type=disk_driver.DiskType.BOOT):
"""Create the Task.
Provides the 'disk_dev_info' for other tasks. Comes from the disk_dvr
@@ -208,6 +211,88 @@ class ConnectDisk(task.Task):
lpar_wrap.uuid)
class InstanceDiskToMgmt(task.Task):
"""Connect an instance's disk to the management partition, discover it.
We do these two pieces together because their reversion doesn't happen in
the opposite order.
"""
def __init__(self, disk_dvr, instance):
"""Create the Task for connecting boot disk to mgmt partition.
Provides:
stg_elem: The storage element wrapper (pypowervm LU, PV, etc.) that was
connected.
vios_wrap: The Virtual I/O Server wrapper
(pypowervm.wrappers.virtual_io_server.VIOS) from which the
storage element was mapped.
disk_path: The local path to the mapped-and-discovered device, e.g.
'/dev/sde'
:param disk_dvr: The disk driver.
:param instance: The nova instance whose boot disk is to be connected.
"""
super(InstanceDiskToMgmt, self).__init__(
name='connect_and_discover_instance_disk_to_mgmt',
provides=['stg_elem', 'vios_wrap', 'disk_path'])
self.disk_dvr = disk_dvr
self.instance = instance
self.stg_elem = None
self.vios_wrap = None
self.disk_path = None
def execute(self):
"""Map the instance's boot disk and discover it."""
LOG.info(_LI("Mapping boot disk of instance %(instance_name)s to "
"management partition."),
{'instance_name': self.instance.name})
self.stg_elem, self.vios_wrap = (
self.disk_dvr.connect_instance_disk_to_mgmt(self.instance))
new_maps = pvm_smap.find_maps(
self.vios_wrap.scsi_mappings, self.disk_dvr.mp_uuid,
stg_elem=self.stg_elem)
if not new_maps:
raise npvmex.NewMgmtMappingNotFoundException(
stg_name=self.stg_elem.name, vios_name=self.vios_wrap.name)
# new_maps should be length 1, but even if it's not - i.e. we somehow
# matched more than one mapping of the same dev to the management
# partition from the same VIOS - it is safe to use the first one.
the_map = new_maps[0]
# Scan the SCSI bus, discover the disk, find its canonical path.
LOG.info(_LI("Discovering device and path for mapping of %(dev_name)s "
"on the management partition."),
{'dev_name': self.stg_elem.name})
self.disk_path = mgmt.discover_vscsi_disk(the_map)
return self.stg_elem, self.vios_wrap, self.disk_path
def revert(self, result, flow_failures):
"""Unmap the disk and then remove it from the management partition.
We use this order to avoid rediscovering the device in case some other
thread scans the SCSI bus between when we remove and when we unmap.
"""
if self.vios_wrap is None or self.stg_elem is None:
# We never even got connected - nothing to do
return
LOG.warn(_LW("Unmapping boot disk %(disk_name)s of instance "
"%(instance_name)s from management partition via Virtual "
"I/O Server %(vios_name)s."),
{'disk_name': self.stg_elem.name,
'instance_name': self.instance.name,
'vios_name': self.vios_wrap.name})
self.disk_dvr.disconnect_disk_from_mgmt(self.vios_wrap.uuid,
self.stg_elem.name)
if self.disk_path is None:
# We did not discover the disk - nothing else to do.
return
LOG.warn(_LW("Removing disk %(disk_path)s from the management "
"partition."), {'disk_path': self.disk_path})
mgmt.remove_block_dev(self.disk_path)
class CreateAndConnectCfgDrive(task.Task):
"""The task to create the configuration drive."""