summaryrefslogtreecommitdiff
path: root/nova/virt/powervm/mgmt.py
blob: a62fa29bde7d3e42f542f55f6445de8022f3c84d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
# Copyright 2015, 2018 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.

"""Utilities related to the PowerVM management partition.

The management partition is a special LPAR that runs the PowerVM REST API
service.  It itself appears through the REST API as a LogicalPartition of type
aixlinux, but with the is_mgmt_partition property set to True.
The PowerVM Nova Compute service runs on the management partition.
"""
import glob
import os
from os import path

from oslo_concurrency import lockutils
from oslo_log import log as logging
from pypowervm.tasks import partition as pvm_par
import retrying

from nova import exception
import nova.privsep.path


LOG = logging.getLogger(__name__)

_MP_UUID = None


@lockutils.synchronized("mgmt_lpar_uuid")
def mgmt_uuid(adapter):
    """Returns the management partitions UUID."""
    global _MP_UUID
    if not _MP_UUID:
        _MP_UUID = pvm_par.get_this_partition(adapter).uuid
    return _MP_UUID


def discover_vscsi_disk(mapping, scan_timeout=300):
    """Bring a mapped device into the management partition and find its name.

    Based on a VSCSIMapping, scan the appropriate virtual SCSI host bus,
    causing the operating system to discover the mapped device. Find and
    return the path of the newly-discovered device based on its UDID in the
    mapping.

    Note: scanning the bus will cause the operating system to discover *all*
    devices on that bus. However, this method will only return the path for
    the specific device from the input mapping, based on its UDID.

    :param mapping: The pypowervm.wrappers.virtual_io_server.VSCSIMapping
                    representing the mapping of the desired disk to the
                    management partition.
    :param scan_timeout: The maximum number of seconds after scanning to wait
                         for the specified device to appear.
    :return: The udev-generated ("/dev/sdX") name of the discovered disk.
    :raise NoDiskDiscoveryException: If the disk did not appear after the
                                     specified timeout.
    :raise UniqueDiskDiscoveryException: If more than one disk appears with the
                                         expected UDID.
    """
    # Calculate the Linux slot number from the client adapter slot number.
    lslot = 0x30000000 | mapping.client_adapter.lpar_slot_num
    # We'll match the device ID based on the UDID, which is actually the last
    # 32 chars of the field we get from PowerVM.
    udid = mapping.backing_storage.udid[-32:]

    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.
    for scanpath in glob.glob(
            '/sys/bus/vio/devices/%x/host*/scsi_host/host*/scan' % lslot):
        # Writing '- - -' to this sysfs file triggers bus rescan
        nova.privsep.path.writefile(scanpath, 'a', '- - -')

    # Now see if our device showed up. If so, we can reliably match it based
    # on its Linux ID, which ends with the disk's UDID.
    dpathpat = '/dev/disk/by-id/*%s' % udid

    # The bus scan is asynchronous.  Need to poll, waiting for the device to
    # spring into existence. Stop when glob finds at least one device, or
    # after the specified timeout.  Sleep 1/4 second between polls.
    @retrying.retry(retry_on_result=lambda result: not result, wait_fixed=250,
                    stop_max_delay=scan_timeout * 1000)
    def _poll_for_dev(globpat):
        return glob.glob(globpat)
    try:
        disks = _poll_for_dev(dpathpat)
    except retrying.RetryError as re:
        raise exception.NoDiskDiscoveryException(
            bus=lslot, udid=udid, polls=re.last_attempt.attempt_number,
            timeout=scan_timeout)
    # If we get here, _poll_for_dev returned a nonempty list. If not exactly
    # one entry, this is an error.
    if len(disks) != 1:
        raise exception.UniqueDiskDiscoveryException(path_pattern=dpathpat,
                                                     count=len(disks))

    # The by-id path is a symlink. Resolve to the /dev/sdX path
    dpath = path.realpath(disks[0])
    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


def remove_block_dev(devpath, scan_timeout=10):
    """Remove a block device from the management partition.

    This method causes the operating system of the management partition to
    delete the device special files associated with the specified block device.

    :param devpath: Any path to the block special file associated with the
                    device to be removed.
    :param scan_timeout: The maximum number of seconds after scanning to wait
                         for the specified device to disappear.
    :raise InvalidDevicePath: If the specified device or its 'delete' special
                              file cannot be found.
    :raise DeviceDeletionException: If the deletion was attempted, but the
                                    device special file is still present
                                    afterward.
    """
    # Resolve symlinks, if any, to get to the /dev/sdX path
    devpath = path.realpath(devpath)
    try:
        os.stat(devpath)
    except OSError:
        raise exception.InvalidDevicePath(path=devpath)
    devname = devpath.rsplit('/', 1)[-1]
    delpath = '/sys/block/%s/device/delete' % devname
    try:
        os.stat(delpath)
    except OSError:
        raise exception.InvalidDevicePath(path=delpath)
    LOG.debug("Deleting block device %(devpath)s from the management "
              "partition via special file %(delpath)s.",
              {'devpath': devpath, 'delpath': delpath})
    # Writing '1' to this sysfs file deletes the block device and rescans.
    nova.privsep.path.writefile(delpath, 'a', '1')

    # The bus scan is asynchronous. Need to poll, waiting for the device to
    # disappear. Stop when stat raises OSError (dev file not found) - which is
    # success - or after the specified timeout (which is failure). Sleep 1/4
    # second between polls.
    @retrying.retry(retry_on_result=lambda result: result, wait_fixed=250,
                    stop_max_delay=scan_timeout * 1000)
    def _poll_for_del(statpath):
        try:
            os.stat(statpath)
            return True
        except OSError:
            # Device special file is absent, as expected
            return False
    try:
        _poll_for_del(devpath)
    except retrying.RetryError as re:
        # stat just kept returning (dev file continued to exist).
        raise exception.DeviceDeletionException(
            devpath=devpath, polls=re.last_attempt.attempt_number,
            timeout=scan_timeout)
    # Else stat raised - the device disappeared - all done.