1#!/usr/bin/env python
2
3# Copyright 2012 - 2013 Red Hat, Inc.
4# All Rights Reserved.
5#
6#    Licensed under the Apache License, Version 2.0 (the "License");
7#    you may not use this file except in compliance with the License.
8#    You may obtain a copy of the License at
9#
10#        http://www.apache.org/licenses/LICENSE-2.0
11#
12#    Unless required by applicable law or agreed to in writing, software
13#    distributed under the License is distributed on an "AS IS" BASIS,
14#    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15#    See the License for the specific language governing permissions and
16#    limitations under the License.
17
18import os
19import sys
20
21# We always use rtslib-fb, but until version 2.1.52 it didn't have its own
22# namespace, so we must be backwards compatible.
23try:
24    import rtslib_fb
25except ImportError:
26    import rtslib as rtslib_fb
27
28
29from cinder import i18n
30from cinder.i18n import _
31
32i18n.enable_lazy()
33
34
35class RtstoolError(Exception):
36    pass
37
38
39class RtstoolImportError(RtstoolError):
40    pass
41
42
43def create(backing_device, name, userid, password, iser_enabled,
44           initiator_iqns=None, portals_ips=None, portals_port=3260):
45    # List of IPS that will not raise an error when they fail binding.
46    # Originally we will fail on all binding errors.
47    ips_allow_fail = ()
48
49    try:
50        rtsroot = rtslib_fb.root.RTSRoot()
51    except rtslib_fb.utils.RTSLibError:
52        print(_('Ensure that configfs is mounted at /sys/kernel/config.'))
53        raise
54
55    # Look to see if BlockStorageObject already exists
56    for x in rtsroot.storage_objects:
57        if x.name == name:
58            # Already exists, use this one
59            return
60
61    so_new = rtslib_fb.BlockStorageObject(name=name,
62                                          dev=backing_device)
63
64    target_new = rtslib_fb.Target(rtslib_fb.FabricModule('iscsi'), name,
65                                  'create')
66
67    tpg_new = rtslib_fb.TPG(target_new, mode='create')
68    tpg_new.set_attribute('authentication', '1')
69
70    lun_new = rtslib_fb.LUN(tpg_new, storage_object=so_new)
71
72    if initiator_iqns:
73        initiator_iqns = initiator_iqns.strip(' ')
74        for i in initiator_iqns.split(','):
75            acl_new = rtslib_fb.NodeACL(tpg_new, i, mode='create')
76            acl_new.chap_userid = userid
77            acl_new.chap_password = password
78
79            rtslib_fb.MappedLUN(acl_new, lun_new.lun, lun_new.lun)
80
81    tpg_new.enable = 1
82
83    # If no ips are given we'll bind to all IPv4 and v6
84    if not portals_ips:
85        portals_ips = ('0.0.0.0', '[::0]')
86        # TODO(emh): Binding to IPv6 fails sometimes -- let pass for now.
87        ips_allow_fail = ('[::0]',)
88
89    for ip in portals_ips:
90        try:
91            # rtslib expects IPv6 addresses to be surrounded by brackets
92            portal = rtslib_fb.NetworkPortal(tpg_new, _canonicalize_ip(ip),
93                                             portals_port, mode='any')
94        except rtslib_fb.utils.RTSLibError:
95            raise_exc = ip not in ips_allow_fail
96            msg_type = 'Error' if raise_exc else 'Warning'
97            print(_('%(msg_type)s: creating NetworkPortal: ensure port '
98                  '%(port)d on ip %(ip)s is not in use by another service.')
99                  % {'msg_type': msg_type, 'port': portals_port, 'ip': ip})
100            if raise_exc:
101                raise
102        else:
103            try:
104                if iser_enabled == 'True':
105                    portal.iser = True
106            except rtslib_fb.utils.RTSLibError:
107                print(_('Error enabling iSER for NetworkPortal: please ensure '
108                        'that RDMA is supported on your iSCSI port %(port)d '
109                        'on ip %(ip)s.') % {'port': portals_port, 'ip': ip})
110                raise
111
112
113def _lookup_target(target_iqn, initiator_iqn):
114    try:
115        rtsroot = rtslib_fb.root.RTSRoot()
116    except rtslib_fb.utils.RTSLibError:
117        print(_('Ensure that configfs is mounted at /sys/kernel/config.'))
118        raise
119
120    # Look for the target
121    for t in rtsroot.targets:
122        if t.wwn == target_iqn:
123            return t
124    raise RtstoolError(_('Could not find target %s') % target_iqn)
125
126
127def add_initiator(target_iqn, initiator_iqn, userid, password):
128    target = _lookup_target(target_iqn, initiator_iqn)
129    tpg = next(target.tpgs)  # get the first one
130    for acl in tpg.node_acls:
131        # See if this ACL configuration already exists
132        if acl.node_wwn.lower() == initiator_iqn.lower():
133            # No further action required
134            return
135
136    acl_new = rtslib_fb.NodeACL(tpg, initiator_iqn, mode='create')
137    acl_new.chap_userid = userid
138    acl_new.chap_password = password
139
140    rtslib_fb.MappedLUN(acl_new, 0, tpg_lun=0)
141
142
143def delete_initiator(target_iqn, initiator_iqn):
144    target = _lookup_target(target_iqn, initiator_iqn)
145    tpg = next(target.tpgs)  # get the first one
146    for acl in tpg.node_acls:
147        if acl.node_wwn.lower() == initiator_iqn.lower():
148            acl.delete()
149            return
150
151    print(_('delete_initiator: %s ACL not found. Continuing.') % initiator_iqn)
152    # Return successfully.
153
154
155def get_targets():
156    rtsroot = rtslib_fb.root.RTSRoot()
157    for x in rtsroot.targets:
158        print(x.wwn)
159
160
161def delete(iqn):
162    rtsroot = rtslib_fb.root.RTSRoot()
163    for x in rtsroot.targets:
164        if x.wwn == iqn:
165            x.delete()
166            break
167
168    for x in rtsroot.storage_objects:
169        if x.name == iqn:
170            x.delete()
171            break
172
173
174def verify_rtslib():
175    for member in ['BlockStorageObject', 'FabricModule', 'LUN',
176                   'MappedLUN', 'NetworkPortal', 'NodeACL', 'root',
177                   'Target', 'TPG']:
178        if not hasattr(rtslib_fb, member):
179            raise RtstoolImportError(_("rtslib_fb is missing member %s: You "
180                                       "may need a newer python-rtslib-fb.") %
181                                     member)
182
183
184def usage():
185    print("Usage:")
186    print(sys.argv[0] +
187          " create [device] [name] [userid] [password] [iser_enabled]" +
188          " <initiator_iqn,iqn2,iqn3,...> [-a<IP1,IP2,...>] [-pPORT]")
189    print(sys.argv[0] +
190          " add-initiator [target_iqn] [userid] [password] [initiator_iqn]")
191    print(sys.argv[0] +
192          " delete-initiator [target_iqn] [initiator_iqn]")
193    print(sys.argv[0] + " get-targets")
194    print(sys.argv[0] + " delete [iqn]")
195    print(sys.argv[0] + " verify")
196    print(sys.argv[0] + " save [path_to_file]")
197    sys.exit(1)
198
199
200def save_to_file(destination_file):
201    rtsroot = rtslib_fb.root.RTSRoot()
202    try:
203        # If default destination use rtslib default save file
204        if not destination_file:
205            destination_file = rtslib_fb.root.default_save_file
206            path_to_file = os.path.dirname(destination_file)
207
208            # NOTE(geguileo): With default file we ensure path exists and
209            # create it if doesn't.
210            # Cinder's LIO target helper runs this as root, so it will have no
211            # problem creating directory /etc/target.
212            # If run manually from the command line without being root you will
213            # get an error, same as when creating and removing targets.
214            if not os.path.exists(path_to_file):
215                os.makedirs(path_to_file, 0o755)
216
217    except OSError as exc:
218        raise RtstoolError(_('targetcli not installed and could not create '
219                             'default directory (%(default_path)s): %(exc)s') %
220                           {'default_path': path_to_file, 'exc': exc})
221    try:
222        rtsroot.save_to_file(destination_file)
223    except (OSError, IOError) as exc:
224        raise RtstoolError(_('Could not save configuration to %(file_path)s: '
225                             '%(exc)s') %
226                           {'file_path': destination_file, 'exc': exc})
227
228
229def restore_from_file(configration_file):
230    rtsroot = rtslib_fb.root.RTSRoot()
231    # If configuration file is None, use rtslib default save file.
232    if not configration_file:
233        configration_file = rtslib_fb.root.default_save_file
234
235    try:
236        rtsroot.restore_from_file(configration_file)
237    except (OSError, IOError) as exc:
238        raise RtstoolError(_('Could not restore configuration file '
239                             '%(file_path)s: %(exc)s'),
240                           {'file_path': configration_file, 'exc': exc})
241
242
243def parse_optional_create(argv):
244    optional_args = {}
245
246    for arg in argv:
247        if arg.startswith('-a'):
248            ips = [ip for ip in arg[2:].split(',') if ip]
249            if not ips:
250                usage()
251            optional_args['portals_ips'] = ips
252        elif arg.startswith('-p'):
253            try:
254                optional_args['portals_port'] = int(arg[2:])
255            except ValueError:
256                usage()
257        else:
258            optional_args['initiator_iqns'] = arg
259    return optional_args
260
261
262def _canonicalize_ip(ip):
263    if ip.startswith('[') or "." in ip:
264        return ip
265    return "[" + ip + "]"
266
267
268def main(argv=None):
269    if argv is None:
270        argv = sys.argv
271
272    if len(argv) < 2:
273        usage()
274
275    if argv[1] == 'create':
276        if len(argv) < 7:
277            usage()
278
279        if len(argv) > 10:
280            usage()
281
282        backing_device = argv[2]
283        name = argv[3]
284        userid = argv[4]
285        password = argv[5]
286        iser_enabled = argv[6]
287
288        if len(argv) > 7:
289            optional_args = parse_optional_create(argv[7:])
290        else:
291            optional_args = {}
292
293        create(backing_device, name, userid, password, iser_enabled,
294               **optional_args)
295
296    elif argv[1] == 'add-initiator':
297        if len(argv) < 6:
298            usage()
299
300        target_iqn = argv[2]
301        userid = argv[3]
302        password = argv[4]
303        initiator_iqn = argv[5]
304
305        add_initiator(target_iqn, initiator_iqn, userid, password)
306
307    elif argv[1] == 'delete-initiator':
308        if len(argv) < 4:
309            usage()
310
311        target_iqn = argv[2]
312        initiator_iqn = argv[3]
313
314        delete_initiator(target_iqn, initiator_iqn)
315
316    elif argv[1] == 'get-targets':
317        get_targets()
318
319    elif argv[1] == 'delete':
320        if len(argv) < 3:
321            usage()
322
323        iqn = argv[2]
324        delete(iqn)
325
326    elif argv[1] == 'verify':
327        # This is used to verify that this script can be called by cinder,
328        # and that rtslib_fb is new enough to work.
329        verify_rtslib()
330        return 0
331
332    elif argv[1] == 'save':
333        if len(argv) > 3:
334            usage()
335
336        destination_file = argv[2] if len(argv) > 2 else None
337        save_to_file(destination_file)
338        return 0
339
340    elif argv[1] == 'restore':
341        if len(argv) > 3:
342            usage()
343
344        configuration_file = argv[2] if len(argv) > 2 else None
345        restore_from_file(configuration_file)
346        return 0
347
348    else:
349        usage()
350
351    return 0
352