summaryrefslogtreecommitdiff
path: root/ironic_python_agent/extensions/iscsi.py
blob: bc7ef0d44c1a6c034feb340b18142f973c881d5b (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
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
# Copyright 2015 Red Hat, Inc.
# 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.


from ironic_lib import disk_utils
from oslo_concurrency import processutils
from oslo_log import log
from oslo_utils import uuidutils
try:
    import rtslib_fb
except ImportError:
    import rtslib as rtslib_fb

from ironic_python_agent import errors
from ironic_python_agent.extensions import base
from ironic_python_agent import hardware
from ironic_python_agent import netutils
from ironic_python_agent import utils

LOG = log.getLogger(__name__)
DEFAULT_ISCSI_PORTAL_PORT = 3260


def _execute(cmd, error_msg, **kwargs):
    try:
        stdout, stderr = utils.execute(*cmd, **kwargs)
    except processutils.ProcessExecutionError as e:
        LOG.error(error_msg)
        raise errors.ISCSICommandError(error_msg, e.exit_code,
                                       e.stdout, e.stderr)
    except OSError as e:
        LOG.error("Error: %(error)s: OS Error: %(os_error)s",
                  {'error': error_msg, 'os_error': e})
        raise errors.ISCSICommandError(e, e.errno, None, None)


def _wait_for_tgtd(attempts=10):
    """Wait for the ISCSI daemon to start."""
    # here, iscsi daemon is considered not running in case
    # tgtadm is not able to talk to tgtd to show iscsi targets
    cmd = ['tgtadm', '--lld', 'iscsi', '--mode', 'target', '--op', 'show']
    _execute(cmd, "ISCSI daemon didn't initialize", attempts=attempts)


def _start_tgtd(iqn, portal_port, device):
    """Start a ISCSI target for the device."""
    # Start ISCSI Target daemon
    _execute(['tgtd'], "Unable to start the ISCSI daemon")

    _wait_for_tgtd()

    # tgt service will create default portal on default port 3260.
    # so no need to create again if input portal_port == 3260.
    if portal_port != DEFAULT_ISCSI_PORTAL_PORT:
        cmd = ['tgtadm', '--lld', 'iscsi', '--mode', 'portal', '--op',
               'new', '--param', 'portal=0.0.0.0:' + str(portal_port)]
        _execute(cmd, "Error when adding a new portal with portal_port %d"
                 % portal_port)

    cmd = ['tgtadm', '--lld', 'iscsi', '--mode', 'target', '--op',
           'new', '--tid', '1', '--targetname', iqn]
    _execute(cmd, "Error when adding a new target for iqn %s" % iqn)

    cmd = ['tgtadm', '--lld', 'iscsi', '--mode', 'logicalunit', '--op',
           'new', '--tid', '1', '--lun', '1', '--backing-store', device]
    _execute(cmd, "Error when adding a new logical unit for iqn %s" % iqn)

    cmd = ['tgtadm', '--lld', 'iscsi', '--mode', 'target', '--op',
           'bind', '--tid', '1', '--initiator-address', 'ALL']
    _execute(cmd, "Error when enabling the target to accept the specific "
                  "initiators for iqn %s" % iqn)


def _start_lio(iqn, portal_port, device):
    try:
        storage = rtslib_fb.BlockStorageObject(name=iqn, dev=device)
        target = rtslib_fb.Target(rtslib_fb.FabricModule('iscsi'), iqn,
                                  mode='create')
        tpg = rtslib_fb.TPG(target, mode='create')
        # disable all authentication
        tpg.set_attribute('authentication', '0')
        tpg.set_attribute('demo_mode_write_protect', '0')
        tpg.set_attribute('generate_node_acls', '1')
        # lun=1 is hardcoded in ironic
        rtslib_fb.LUN(tpg, storage_object=storage, lun=1)
        tpg.enable = 1
    except rtslib_fb.utils.RTSLibError as exc:
        msg = 'Failed to create a target: {}'.format(exc)
        raise errors.ISCSIError(msg)

    try:
        # bind to the default port on all interfaces
        listen_ip = netutils.wrap_ipv6(netutils.get_wildcard_address())
        rtslib_fb.NetworkPortal(tpg, listen_ip, portal_port)
    except rtslib_fb.utils.RTSLibError as exc:
        msg = 'Failed to publish a target: {}'.format(exc)
        raise errors.ISCSIError(msg)


def clean_up(device):
    """Clean up iSCSI for a given device."""
    try:
        rts_root = rtslib_fb.RTSRoot()
    except (EnvironmentError, rtslib_fb.RTSLibError) as exc:
        LOG.info('Linux-IO is not available, not cleaning up. Error: %s.', exc)
        return

    storage = None
    for x in rts_root.storage_objects:
        if x.udev_path == device:
            storage = x
            break

    if storage is None:
        LOG.info('Device %(dev)s not found in the current iSCSI mounts '
                 '%(mounts)s.',
                 {'dev': device,
                  'mounts': [x.udev_path for x in rts_root.storage_objects]})
        return
    else:
        LOG.info('Deleting iSCSI target %(target)s for device %(dev)s.',
                 {'target': storage.name, 'dev': device})

    try:
        for x in rts_root.targets:
            if x.wwn == storage.name:
                x.delete()
                break

        storage.delete()
    except rtslib_fb.utils.RTSLibError as exc:
        msg = ('Failed to delete iSCSI target %(target)s for device %(dev)s: '
               '%(error)s') % {'target': storage.name,
                               'dev': device,
                               'error': exc}
        raise errors.ISCSIError(msg)


class ISCSIExtension(base.BaseAgentExtension):
    @base.sync_command('start_iscsi_target')
    def start_iscsi_target(self, iqn=None, wipe_disk_metadata=False,
                           portal_port=None):
        """Expose the disk as an ISCSI target.

        :param iqn: IQN for iSCSI target. If None, a new IQN is generated.
        :param wipe_disk_metadata: if the disk metadata should be wiped out
                                   before the disk is exposed.
        :param portal_port: customized port for iSCSI port, can be None.
        :returns: a dict that provides IQN of iSCSI target.
        """
        # If iqn is not given, generate one
        if iqn is None:
            iqn = 'iqn.2008-10.org.openstack:%s' % uuidutils.generate_uuid()

        device = hardware.dispatch_to_managers('get_os_install_device')

        if wipe_disk_metadata:
            disk_utils.destroy_disk_metadata(
                device,
                self.agent.get_node_uuid())

        LOG.debug("Starting ISCSI target with iqn %(iqn)s on device "
                  "%(device)s", {'iqn': iqn, 'device': device})

        try:
            rts_root = rtslib_fb.RTSRoot()
        except (EnvironmentError, rtslib_fb.RTSLibError) as exc:
            LOG.warning('Linux-IO is not available, falling back to TGT. '
                        'Error: %s.', exc)
            rts_root = None

        if portal_port is None:
            portal_port = DEFAULT_ISCSI_PORTAL_PORT

        if rts_root is None:
            _start_tgtd(iqn, portal_port, device)
        else:
            _start_lio(iqn, portal_port, device)
            LOG.debug('Linux-IO configuration: %s', rts_root.dump())

        LOG.info('Created iSCSI target with iqn %(iqn)s, portal port %(port)d,'
                 ' on device %(dev)s using %(method)s',
                 {'iqn': iqn, 'port': portal_port, 'dev': device,
                  'method': 'tgtd' if rts_root is None else 'linux-io'})

        return {"iscsi_target_iqn": iqn}