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