00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018 import copy
00019
00020 from lsm import (Pool, Volume, System, Capabilities,
00021 IStorageAreaNetwork, INfs, FileSystem, FsSnapshot, NfsExport,
00022 LsmError, ErrorNumber, uri_parse, md5, VERSION,
00023 common_urllib2_error_handler, search_property,
00024 AccessGroup)
00025
00026 import urllib2
00027 import json
00028 import time
00029 import urlparse
00030 import socket
00031 import re
00032
00033 DEFAULT_USER = "admin"
00034 DEFAULT_PORT = 18700
00035 PATH = "/targetrpc"
00036
00037
00038 _LVM_SECTOR_SIZE = 512
00039
00040
00041 def handle_errors(method):
00042 def target_wrapper(*args, **kwargs):
00043 try:
00044 return method(*args, **kwargs)
00045 except TargetdError as te:
00046 raise LsmError(ErrorNumber.PLUGIN_BUG,
00047 "Got error %d from targetd: %s"
00048 % (te.errno, te.reason))
00049 except LsmError:
00050 raise
00051 except Exception as e:
00052 common_urllib2_error_handler(e)
00053
00054 return target_wrapper
00055
00056
00057 class TargetdError(Exception):
00058 VOLUME_MASKED = 303
00059 INVALID_METHOD = 32601
00060 INVALID_ARGUMENT = 32602
00061 NAME_CONFLICT = 50
00062 EXISTS_INITIATOR = 52
00063 NO_FREE_HOST_LUN_ID = 1000
00064 EMPTY_ACCESS_GROUP = 511
00065
00066 def __init__(self, errno, reason, *args, **kwargs):
00067 Exception.__init__(self, *args, **kwargs)
00068 self.errno = int(errno)
00069 self.reason = reason
00070
00071
00072 class TargetdStorage(IStorageAreaNetwork, INfs):
00073 _FAKE_AG_PREFIX = 'init.'
00074 _MAX_H_LUN_ID = 255
00075
00076 def __init__(self):
00077 self.uri = None
00078 self.password = None
00079 self.tmo = 0
00080 self.rpc_id = 1
00081 self.host_with_port = None
00082 self.scheme = None
00083 self.url = None
00084 self.headers = None
00085 self.system = System("targetd", "targetd storage appliance",
00086 System.STATUS_UNKNOWN, '')
00087
00088 @handle_errors
00089 def plugin_register(self, uri, password, timeout, flags=0):
00090 self.uri = uri_parse(uri)
00091 self.password = password
00092 self.tmo = timeout
00093 self._flag_ag_support = True
00094
00095 user = self.uri.get('username', DEFAULT_USER)
00096 port = self.uri.get('port', DEFAULT_PORT)
00097
00098 self.host_with_port = "%s:%s" % (self.uri['host'], port)
00099 if self.uri['scheme'].lower() == 'targetd+ssl':
00100 self.scheme = 'https'
00101 else:
00102 self.scheme = 'http'
00103
00104 self.url = urlparse.urlunsplit(
00105 (self.scheme, self.host_with_port, PATH, None, None))
00106
00107 auth = ('%s:%s' % (user, self.password)).encode('base64')[:-1]
00108 self.headers = {'Content-Type': 'application/json',
00109 'Authorization': 'Basic %s' % (auth,)}
00110
00111 try:
00112 self._jsonrequest('access_group_list')
00113 except TargetdError as te:
00114 if te.errno == TargetdError.INVALID_METHOD:
00115 self._flag_ag_support = False
00116 else:
00117 raise
00118
00119 @handle_errors
00120 def time_out_set(self, ms, flags=0):
00121 self.tmo = ms
00122
00123 @handle_errors
00124 def time_out_get(self, flags=0):
00125 return self.tmo
00126
00127 @handle_errors
00128 def plugin_unregister(self, flags=0):
00129 pass
00130
00131 @handle_errors
00132 def capabilities(self, system, flags=0):
00133 cap = Capabilities()
00134 cap.set(Capabilities.VOLUMES)
00135 cap.set(Capabilities.VOLUME_CREATE)
00136 cap.set(Capabilities.VOLUME_REPLICATE)
00137 cap.set(Capabilities.VOLUME_REPLICATE_COPY)
00138 cap.set(Capabilities.VOLUME_DELETE)
00139 cap.set(Capabilities.VOLUME_MASK)
00140 cap.set(Capabilities.VOLUME_UNMASK)
00141 cap.set(Capabilities.FS)
00142 cap.set(Capabilities.FS_CREATE)
00143 cap.set(Capabilities.FS_DELETE)
00144 cap.set(Capabilities.FS_CLONE)
00145 cap.set(Capabilities.FS_SNAPSHOT_CREATE)
00146 cap.set(Capabilities.FS_SNAPSHOT_DELETE)
00147 cap.set(Capabilities.FS_SNAPSHOTS)
00148 cap.set(Capabilities.EXPORT_AUTH)
00149 cap.set(Capabilities.EXPORTS)
00150 cap.set(Capabilities.EXPORT_FS)
00151 cap.set(Capabilities.EXPORT_REMOVE)
00152 cap.set(Capabilities.ACCESS_GROUPS)
00153 cap.set(Capabilities.ACCESS_GROUPS_GRANTED_TO_VOLUME)
00154 cap.set(Capabilities.VOLUMES_ACCESSIBLE_BY_ACCESS_GROUP)
00155 cap.set(Capabilities.VOLUME_ISCSI_CHAP_AUTHENTICATION)
00156
00157 if self._flag_ag_support:
00158 cap.set(Capabilities.ACCESS_GROUP_CREATE_ISCSI_IQN)
00159 cap.set(Capabilities.ACCESS_GROUP_INITIATOR_ADD_ISCSI_IQN)
00160 cap.set(Capabilities.ACCESS_GROUP_INITIATOR_DELETE)
00161 cap.set(Capabilities.ACCESS_GROUP_DELETE)
00162
00163 return cap
00164
00165 @handle_errors
00166 def plugin_info(self, flags=0):
00167 return "Linux LIO target support", VERSION
00168
00169 @handle_errors
00170 def systems(self, flags=0):
00171
00172 self._jsonrequest("pool_list")
00173
00174 return [self.system]
00175
00176 @handle_errors
00177 def job_status(self, job_id, flags=0):
00178 raise LsmError(ErrorNumber.NO_SUPPORT, "Not supported")
00179
00180 @handle_errors
00181 def job_free(self, job_id, flags=0):
00182 raise LsmError(ErrorNumber.NO_SUPPORT, "Not supported")
00183
00184 @staticmethod
00185 def _uuid_to_vpd83(uuid):
00186 """
00187 Convert LVM UUID to VPD 83 Device ID.
00188 LIO kernel module(target_core_mod.ko) does not expose VPD83 via
00189 ConfigFs.
00190 Targetd does not expose VPD83 via its API.
00191 Hence we have to do the convention here base on kernel code.
00192 """
00193
00194
00195 vpd83 = '6'
00196
00197
00198 vpd83 += '001405'
00199
00200
00201
00202 vpd83 += re.sub('[^a-f0-9]', '', uuid.lower())[:25]
00203
00204
00205 vpd83 += '0' * (32 - len(vpd83))
00206
00207 return vpd83
00208
00209 @handle_errors
00210 def volumes(self, search_key=None, search_value=None, flags=0):
00211 volumes = []
00212 for p_name in (p['name'] for p in self._jsonrequest("pool_list") if
00213 p['type'] == 'block'):
00214 for vol in self._jsonrequest("vol_list", dict(pool=p_name)):
00215 vpd83 = TargetdStorage._uuid_to_vpd83(vol['uuid'])
00216 volumes.append(
00217 Volume(vol['uuid'], vol['name'], vpd83, 512,
00218 long(vol['size'] / 512),
00219 Volume.ADMIN_STATE_ENABLED,
00220 self.system.id, p_name))
00221 return search_property(volumes, search_key, search_value)
00222
00223 @handle_errors
00224 def pools(self, search_key=None, search_value=None, flags=0):
00225 pools = []
00226 for pool in self._jsonrequest("pool_list"):
00227 if pool['name'].startswith('/'):
00228 et = Pool.ELEMENT_TYPE_FS
00229 else:
00230 et = Pool.ELEMENT_TYPE_VOLUME
00231
00232 pools.append(Pool(pool['name'],
00233 pool['name'], et, 0, pool['size'],
00234 pool['free_size'], Pool.STATUS_UNKNOWN, '',
00235 'targetd'))
00236 return search_property(pools, search_key, search_value)
00237
00238 @staticmethod
00239 def _tgt_ag_to_lsm(tgt_ag, sys_id):
00240 return AccessGroup(
00241 tgt_ag['name'], tgt_ag['name'], tgt_ag['init_ids'],
00242 AccessGroup.INIT_TYPE_ISCSI_IQN, sys_id)
00243
00244 @staticmethod
00245 def _tgt_init_to_lsm(tgt_init, sys_id):
00246 return AccessGroup(
00247 "%s%s" % (
00248 TargetdStorage._FAKE_AG_PREFIX, md5(tgt_init['init_id'])),
00249 'N/A', [tgt_init['init_id']], AccessGroup.INIT_TYPE_ISCSI_IQN,
00250 sys_id)
00251
00252 @handle_errors
00253 def access_groups(self, search_key=None, search_value=None, flags=0):
00254 rc_lsm_ags = []
00255
00256
00257 if self._flag_ag_support is True:
00258 tgt_inits = self._jsonrequest(
00259 'initiator_list', {'standalone_only': True})
00260 else:
00261 tgt_inits = list(
00262 {'init_id': x}
00263 for x in set(
00264 i['initiator_wwn']
00265 for i in self._jsonrequest("export_list")))
00266
00267 rc_lsm_ags.extend(
00268 list(
00269 TargetdStorage._tgt_init_to_lsm(i, self.system.id)
00270 for i in tgt_inits))
00271
00272 if self._flag_ag_support is True:
00273 for tgt_ag in self._jsonrequest('access_group_list'):
00274 rc_lsm_ags.append(
00275 TargetdStorage._tgt_ag_to_lsm(
00276 tgt_ag, self.system.id))
00277
00278 return search_property(rc_lsm_ags, search_key, search_value)
00279
00280 def _lsm_ag_of_id(self, ag_id, lsm_error_obj=None):
00281 """
00282 Raise provided error if defined when not found.
00283 Return lsm.AccessGroup if found.
00284 """
00285 lsm_ags = self.access_groups()
00286 for lsm_ag in lsm_ags:
00287 if lsm_ag.id == ag_id:
00288 return lsm_ag
00289
00290 if lsm_error_obj:
00291 raise lsm_error_obj
00292
00293 @handle_errors
00294 def access_group_create(self, name, init_id, init_type, system, flags=0):
00295 if system.id != self.system.id:
00296 raise LsmError(
00297 ErrorNumber.NOT_FOUND_SYSTEM,
00298 "System %s not found" % system.id)
00299 if self._flag_ag_support is False:
00300 raise LsmError(
00301 ErrorNumber.NO_SUPPORT,
00302 "Please upgrade your targetd package to support "
00303 "access_group_create()")
00304
00305 if init_type != AccessGroup.INIT_TYPE_ISCSI_IQN:
00306 raise LsmError(ErrorNumber.NO_SUPPORT, "Only iSCSI yet")
00307
00308 try:
00309 self._jsonrequest(
00310 "access_group_create",
00311 dict(ag_name=name, init_id=init_id, init_type='iscsi'))
00312 except TargetdError as tgt_error:
00313 if tgt_error.errno == TargetdError.EXISTS_INITIATOR:
00314 raise LsmError(
00315 ErrorNumber.EXISTS_INITIATOR,
00316 "Initiator is already used by other access group")
00317 elif tgt_error.errno == TargetdError.NAME_CONFLICT:
00318 raise LsmError(
00319 ErrorNumber.NAME_CONFLICT,
00320 "Requested access group name is already used by other "
00321 "access group")
00322 elif tgt_error.errno == TargetdError.INVALID_ARGUMENT:
00323 raise LsmError(
00324 ErrorNumber.INVALID_ARGUMENT,
00325 str(tgt_error))
00326 else:
00327 raise
00328
00329 return self._lsm_ag_of_id(
00330 name,
00331 LsmError(
00332 ErrorNumber.PLUGIN_BUG,
00333 "access_group_create(): Failed to find the newly created "
00334 "access group"))
00335
00336 @handle_errors
00337 def access_group_initiator_add(self, access_group, init_id, init_type,
00338 flags=0):
00339 if init_type != AccessGroup.INIT_TYPE_ISCSI_IQN:
00340 raise LsmError(
00341 ErrorNumber.NO_SUPPORT, "Targetd only support iscsi")
00342
00343 lsm_ag = self._lsm_ag_of_id(
00344 access_group.name,
00345 LsmError(
00346 ErrorNumber.NOT_FOUND_ACCESS_GROUP, "Access group not found"))
00347
00348
00349
00350 if init_id in lsm_ag.init_ids:
00351 raise LsmError(
00352 ErrorNumber.NO_STATE_CHANGE,
00353 "Requested init_id is already in defined access group")
00354
00355 try:
00356 self._jsonrequest(
00357 "access_group_init_add",
00358 dict(
00359 ag_name=access_group.name, init_id=init_id,
00360 init_type='iscsi'))
00361 except TargetdError as tgt_error:
00362 if tgt_error.errno == TargetdError.EXISTS_INITIATOR:
00363 raise LsmError(
00364 ErrorNumber.EXISTS_INITIATOR,
00365 "Initiator is already used by other access group")
00366 else:
00367 raise
00368
00369 return self._lsm_ag_of_id(
00370 access_group.name,
00371 LsmError(
00372 ErrorNumber.PLUGIN_BUG,
00373 "access_group_initiator_add(): "
00374 "Failed to find the updated access group"))
00375
00376 @handle_errors
00377 def access_group_initiator_delete(self, access_group, init_id, init_type,
00378 flags=0):
00379 if init_type != AccessGroup.INIT_TYPE_ISCSI_IQN:
00380 raise LsmError(
00381 ErrorNumber.NO_SUPPORT,
00382 "Targetd only support iscsi")
00383
00384
00385
00386 lsm_ag = self._lsm_ag_of_id(
00387 access_group.name,
00388 LsmError(
00389 ErrorNumber.NOT_FOUND_ACCESS_GROUP, "Access group not found"))
00390
00391 if init_id not in lsm_ag.init_ids:
00392 raise LsmError(
00393 ErrorNumber.NO_STATE_CHANGE,
00394 "Requested initiator is not in defined access group")
00395
00396 if len(lsm_ag.init_ids) == 1:
00397 raise LsmError(
00398 ErrorNumber.LAST_INIT_IN_ACCESS_GROUP,
00399 "Refused to remove the last initiator from access group")
00400
00401 self._jsonrequest(
00402 "access_group_init_del",
00403 dict(
00404 ag_name=access_group.name,
00405 init_id=init_id,
00406 init_type='iscsi'))
00407
00408 return self._lsm_ag_of_id(
00409 access_group.name,
00410 LsmError(
00411 ErrorNumber.PLUGIN_BUG,
00412 "access_group_initiator_delete(): "
00413 "Failed to find the updated access group"))
00414
00415 @handle_errors
00416 def access_group_delete(self, access_group, flags=0):
00417 if access_group.id.startswith(TargetdStorage._FAKE_AG_PREFIX):
00418 raise LsmError(
00419 ErrorNumber.NO_SUPPORT,
00420 "Cannot delete old initiator simulated access group, "
00421 "they will be automatically deleted when no volume masked to")
00422
00423 if self._flag_ag_support is False:
00424 raise LsmError(
00425 ErrorNumber.NO_SUPPORT,
00426 "Please upgrade your targetd package to support "
00427 "access_group_delete()")
00428
00429 self._lsm_ag_of_id(
00430 access_group.id,
00431 LsmError(
00432 ErrorNumber.NOT_FOUND_ACCESS_GROUP,
00433 "Access group not found"))
00434
00435 if list(m for m in self._tgt_masks() if m['ag_id'] == access_group.id):
00436 raise LsmError(
00437 ErrorNumber.IS_MASKED,
00438 "Cannot delete access group which has volume masked to")
00439
00440 self._jsonrequest(
00441 "access_group_destroy", {'ag_name': access_group.name})
00442 return None
00443
00444 def _tgt_masks(self):
00445 """
00446 Return a list of tgt_mask:
00447 {
00448 'pool_name': pool_name,
00449 'vol_name': vol_name,
00450 'ag_id': lsm_ag.id,
00451 'h_lun_id': h_lun_id,
00452 }
00453 """
00454 tgt_masks = []
00455 for tgt_exp in self._jsonrequest("export_list"):
00456 tgt_masks.append({
00457 'ag_id': "%s%s" % (
00458 TargetdStorage._FAKE_AG_PREFIX,
00459 md5(tgt_exp['initiator_wwn'])),
00460 'vol_name': tgt_exp['vol_name'],
00461 'pool_name': tgt_exp['pool'],
00462 'h_lun_id': tgt_exp['lun'],
00463 })
00464 if self._flag_ag_support:
00465 for tgt_ag_map in self._jsonrequest("access_group_map_list"):
00466 tgt_masks.append({
00467 'ag_id': tgt_ag_map['ag_name'],
00468 'vol_name': tgt_ag_map['vol_name'],
00469 'pool_name': tgt_ag_map['pool_name'],
00470 'h_lun_id': tgt_ag_map['h_lun_id'],
00471 })
00472
00473 return tgt_masks
00474
00475 def _is_masked(self, ag_id, pool_name, vol_name, tgt_masks=None):
00476 """
00477 Check whether volume is masked to certain access group.
00478 Return True or False
00479 """
00480 if tgt_masks is None:
00481 tgt_masks = self._tgt_masks()
00482 return list(
00483 m for m in tgt_masks
00484 if (m['vol_name'] == vol_name and
00485 m['pool_name'] == pool_name and
00486 m['ag_id'] == ag_id)
00487 ) != []
00488
00489 def _lsm_vol_of_id(self, vol_id, error=None):
00490 try:
00491 return list(v for v in self.volumes() if v.id == vol_id)[0]
00492 except IndexError:
00493 if error:
00494 raise error
00495 else:
00496 return None
00497
00498 @handle_errors
00499 def volume_mask(self, access_group, volume, flags=0):
00500 self._lsm_ag_of_id(
00501 access_group.id,
00502 LsmError(
00503 ErrorNumber.NOT_FOUND_ACCESS_GROUP, "Access group not found"))
00504
00505 self._lsm_vol_of_id(
00506 volume.id,
00507 LsmError(
00508 ErrorNumber.NOT_FOUND_VOLUME, "Volume not found"))
00509
00510 tgt_masks = self._tgt_masks()
00511 if self._is_masked(
00512 access_group.id, volume.pool_id, volume.name, tgt_masks):
00513 raise LsmError(
00514 ErrorNumber.NO_STATE_CHANGE,
00515 "Volume is already masked to requested access group")
00516
00517 if access_group.id.startswith(TargetdStorage._FAKE_AG_PREFIX):
00518 free_h_lun_ids = (
00519 set(range(TargetdStorage._MAX_H_LUN_ID + 1)) -
00520 set([m['h_lun_id'] for m in tgt_masks]))
00521
00522 if len(free_h_lun_ids) == 0:
00523
00524 raise LsmError(
00525 ErrorNumber.PLUGIN_BUG,
00526 "System limit: targetd only allows %s LUN masked" %
00527 TargetdStorage._MAX_H_LUN_ID)
00528
00529 h_lun_id = free_h_lun_ids.pop()
00530
00531 self._jsonrequest(
00532 "export_create",
00533 {
00534 'pool': volume.pool_id,
00535 'vol': volume.name,
00536 'initiator_wwn': access_group.init_ids[0],
00537 'lun': h_lun_id
00538 })
00539 else:
00540 try:
00541 self._jsonrequest(
00542 'access_group_map_create',
00543 {
00544 'pool_name': volume.pool_id,
00545 'vol_name': volume.name,
00546 'ag_name': access_group.id,
00547 })
00548 except TargetdError as tgt_error:
00549 if tgt_error.errno == TargetdError.NO_FREE_HOST_LUN_ID:
00550
00551 raise LsmError(
00552 ErrorNumber.PLUGIN_BUG,
00553 "System limit: targetd only allows %s LUN masked" %
00554 TargetdStorage._MAX_H_LUN_ID)
00555 elif tgt_error.errno == TargetdError.EMPTY_ACCESS_GROUP:
00556 raise LsmError(
00557 ErrorNumber.NOT_FOUND_ACCESS_GROUP,
00558 "Access group not found")
00559 else:
00560 raise
00561
00562 return None
00563
00564 @handle_errors
00565 def volume_unmask(self, volume, access_group, flags=0):
00566 self._lsm_ag_of_id(
00567 access_group.id,
00568 LsmError(
00569 ErrorNumber.NOT_FOUND_ACCESS_GROUP, "Access group not found"))
00570
00571 self._lsm_vol_of_id(
00572 volume.id,
00573 LsmError(
00574 ErrorNumber.NOT_FOUND_VOLUME, "Volume not found"))
00575
00576
00577 if not self._is_masked(access_group.id, volume.pool_id, volume.name):
00578 raise LsmError(ErrorNumber.NO_STATE_CHANGE,
00579 "Volume is not masked to requested access group")
00580
00581 if access_group.id.startswith(TargetdStorage._FAKE_AG_PREFIX):
00582 self._jsonrequest("export_destroy",
00583 dict(pool=volume.pool_id,
00584 vol=volume.name,
00585 initiator_wwn=access_group.init_ids[0]))
00586 else:
00587 self._jsonrequest(
00588 "access_group_map_destroy",
00589 {
00590 'pool_name': volume.pool_id,
00591 'vol_name': volume.name,
00592 'ag_name': access_group.id,
00593 })
00594
00595 return None
00596
00597 @handle_errors
00598 def volumes_accessible_by_access_group(self, access_group, flags=0):
00599 tgt_masks = self._tgt_masks()
00600
00601 vol_infos = list(
00602 [m['vol_name'], m['pool_name']]
00603 for m in tgt_masks
00604 if m['ag_id'] == access_group.id)
00605
00606 if len(vol_infos) == 0:
00607 return []
00608
00609 rc_lsm_vols = []
00610 return list(
00611 lsm_vol
00612 for lsm_vol in self.volumes(flags=flags)
00613 if [lsm_vol.name, lsm_vol.pool_id] in vol_infos)
00614
00615 @handle_errors
00616 def access_groups_granted_to_volume(self, volume, flags=0):
00617 tgt_masks = self._tgt_masks()
00618 ag_ids = list(
00619 m['ag_id']
00620 for m in tgt_masks
00621 if (m['vol_name'] == volume.name and
00622 m['pool_name'] == volume.pool_id))
00623
00624 lsm_ags = self.access_groups(flags=flags)
00625 return [x for x in lsm_ags if x.id in ag_ids]
00626
00627 def _get_volume(self, pool_id, volume_name):
00628 vol = [v for v in self._jsonrequest("vol_list", dict(pool=pool_id))
00629 if v['name'] == volume_name][0]
00630
00631 vpd83 = TargetdStorage._uuid_to_vpd83(vol['uuid'])
00632 return Volume(vol['uuid'], vol['name'], vpd83, 512,
00633 vol['size'] / 512,
00634 Volume.ADMIN_STATE_ENABLED,
00635 self.system.id,
00636 pool_id)
00637
00638 def _get_fs(self, pool_id, fs_name):
00639 fs = self.fs()
00640 for f in fs:
00641 if f.name == fs_name and f.pool_id == pool_id:
00642 return f
00643 return None
00644
00645 def _get_ss(self, fs, ss_name):
00646 ss = self.fs_snapshots(fs)
00647 for s in ss:
00648 if s.name == ss_name:
00649 return s
00650 return None
00651
00652 @handle_errors
00653 def volume_create(self, pool, volume_name, size_bytes, provisioning,
00654 flags=0):
00655 if provisioning != Volume.PROVISION_DEFAULT:
00656 raise LsmError(ErrorNumber.INVALID_ARGUMENT,
00657 "Unsupported provisioning")
00658
00659
00660 if size_bytes:
00661 remainder = size_bytes % _LVM_SECTOR_SIZE
00662 if remainder:
00663 size_bytes = size_bytes + _LVM_SECTOR_SIZE - remainder
00664 else:
00665 size_bytes = _LVM_SECTOR_SIZE
00666
00667 self._jsonrequest("vol_create", dict(pool=pool.id,
00668 name=volume_name,
00669 size=size_bytes))
00670
00671 return None, self._get_volume(pool.id, volume_name)
00672
00673 @handle_errors
00674 def volume_delete(self, volume, flags=0):
00675 try:
00676 self._jsonrequest("vol_destroy",
00677 dict(pool=volume.pool_id, name=volume.name))
00678 except TargetdError as te:
00679 if te.errno == TargetdError.VOLUME_MASKED:
00680 raise LsmError(ErrorNumber.IS_MASKED,
00681 "Volume is masked to access group")
00682 raise
00683
00684 @handle_errors
00685 def volume_replicate(self, pool, rep_type, volume_src, name, flags=0):
00686 if rep_type != Volume.REPLICATE_COPY:
00687 raise LsmError(ErrorNumber.NO_SUPPORT, "Not supported")
00688
00689
00690 pool_id = volume_src.pool_id
00691 if pool:
00692 pool_id = pool.id
00693
00694 self._jsonrequest("vol_copy",
00695 dict(pool=pool_id, vol_orig=volume_src.name,
00696 vol_new=name))
00697
00698 return None, self._get_volume(pool_id, name)
00699
00700 @handle_errors
00701 def iscsi_chap_auth(self, init_id, in_user, in_password, out_user,
00702 out_password, flags=0):
00703 self._jsonrequest("initiator_set_auth",
00704 dict(initiator_wwn=init_id,
00705 in_user=in_user,
00706 in_pass=in_password,
00707 out_user=out_user,
00708 out_pass=out_password))
00709
00710 return None
00711
00712 @handle_errors
00713 def fs(self, search_key=None, search_value=None, flags=0):
00714 rc = []
00715 for fs in self._jsonrequest("fs_list"):
00716
00717 rc.append(FileSystem(fs['uuid'], fs['name'], fs['total_space'],
00718 fs['free_space'], fs['pool'], self.system.id))
00719 return search_property(rc, search_key, search_value)
00720
00721 @handle_errors
00722 def fs_delete(self, fs, flags=0):
00723 self._jsonrequest("fs_destroy", dict(uuid=fs.id))
00724
00725 @handle_errors
00726 def fs_create(self, pool, name, size_bytes, flags=0):
00727 self._jsonrequest("fs_create", dict(pool_name=pool.id, name=name,
00728 size_bytes=size_bytes))
00729
00730 return None, self._get_fs(pool.name, name)
00731
00732 @handle_errors
00733 def fs_clone(self, src_fs, dest_fs_name, snapshot=None, flags=0):
00734
00735 ss_id = None
00736 if snapshot:
00737 ss_id = snapshot.id
00738
00739 self._jsonrequest("fs_clone", dict(fs_uuid=src_fs.id,
00740 dest_fs_name=dest_fs_name,
00741 snapshot_id=ss_id))
00742
00743 return None, self._get_fs(src_fs.pool_id, dest_fs_name)
00744
00745 @handle_errors
00746 def fs_snapshots(self, fs, flags=0):
00747 rc = []
00748 for ss in self._jsonrequest("ss_list", dict(fs_uuid=fs.id)):
00749
00750 rc.append(FsSnapshot(ss['uuid'], ss['name'], ss['timestamp']))
00751 return rc
00752
00753 @handle_errors
00754 def fs_snapshot_create(self, fs, snapshot_name, flags=0):
00755
00756 self._jsonrequest("fs_snapshot", dict(fs_uuid=fs.id,
00757 dest_ss_name=snapshot_name))
00758
00759 return None, self._get_ss(fs, snapshot_name)
00760
00761 @handle_errors
00762 def fs_snapshot_delete(self, fs, snapshot, flags=0):
00763 self._jsonrequest("fs_snapshot_delete", dict(fs_uuid=fs.id,
00764 ss_uuid=snapshot.id))
00765
00766 @handle_errors
00767 def export_auth(self, flags=0):
00768 exports = self._jsonrequest("nfs_export_auth_list")
00769 return exports
00770
00771 @staticmethod
00772 def _get_value(options, key):
00773 for o in options:
00774 if '=' in o:
00775 k, v = o.split('=')
00776 if k == key:
00777 return v
00778 return None
00779
00780 @staticmethod
00781 def _option_string(nfs_options):
00782 cpy = copy.copy(nfs_options)
00783 if 'ro' in cpy:
00784 cpy.remove('ro')
00785 if 'rw' in cpy:
00786 cpy.remove('rw')
00787 if 'no_root_squash' in cpy:
00788 cpy.remove('no_root_squash')
00789 if 'root_squash' in cpy:
00790 cpy.remove('root_squash')
00791
00792 cpy.sort()
00793 s = ','.join(cpy)
00794 return s
00795
00796 @staticmethod
00797 def _calculate_export_md5(export_path, options):
00798 opts = TargetdStorage._option_string(options)
00799 return md5(export_path + opts)
00800
00801 @handle_errors
00802 def exports(self, search_key=None, search_value=None, flags=0):
00803 tmp_exports = {}
00804 exports = []
00805 fs_full_paths = {}
00806 all_nfs_exports = self._jsonrequest("nfs_export_list")
00807 nfs_exports = []
00808
00809
00810 fs_list = self._jsonrequest("fs_list")
00811 for f in fs_list:
00812 fs_full_paths[f['full_path']] = f
00813
00814 for export in all_nfs_exports:
00815 if export['path'] in fs_full_paths:
00816 nfs_exports.append(export)
00817
00818
00819 for export in nfs_exports:
00820 key = export['path'] + \
00821 TargetdStorage._option_string(export['options'])
00822 if key in tmp_exports:
00823 tmp_exports[key].append(export)
00824 else:
00825 tmp_exports[key] = [export]
00826
00827
00828 for le in tmp_exports.values():
00829 export_id = ""
00830 root = []
00831 rw = []
00832 ro = []
00833 sec = None
00834 anonuid = NfsExport.ANON_UID_GID_NA
00835 anongid = NfsExport.ANON_UID_GID_NA
00836
00837 options = None
00838
00839 for export in le:
00840
00841 host = export['host']
00842 export_id += host
00843 export_id += export['path']
00844 export_id += fs_full_paths[export['path']]['uuid']
00845
00846 options = export['options']
00847
00848 if 'rw' in options:
00849 rw.append(host)
00850
00851 if 'ro' in options:
00852 ro.append(host)
00853
00854 sec = TargetdStorage._get_value(options, 'sec')
00855 if sec is None:
00856 sec = 'sys'
00857
00858 if 'no_root_squash' in options:
00859 root.append(host)
00860
00861 uid = TargetdStorage._get_value(options, 'anonuid')
00862 if uid is not None:
00863 anonuid = uid
00864 gid = TargetdStorage._get_value(options, 'anongid')
00865 if gid is not None:
00866 anongid = gid
00867
00868 exports.append(
00869 NfsExport(TargetdStorage._calculate_export_md5(export['path'],
00870 options),
00871 fs_full_paths[export['path']]['uuid'],
00872 export['path'], sec, root, rw, ro, anonuid, anongid,
00873 TargetdStorage._option_string(options)))
00874
00875 return search_property(exports, search_key, search_value)
00876
00877 def _get_fs_path(self, fs_id):
00878 for fs in self._jsonrequest("fs_list"):
00879 if fs_id == fs['uuid']:
00880 return fs['full_path']
00881 return None
00882
00883 @handle_errors
00884 def export_fs(
00885 self, fs_id, export_path, root_list, rw_list, ro_list,
00886 anon_uid=NfsExport.ANON_UID_GID_NA,
00887 anon_gid=NfsExport.ANON_UID_GID_NA,
00888 auth_type=None, options=None, flags=0):
00889
00890 if export_path is not None:
00891 raise LsmError(ErrorNumber.INVALID_ARGUMENT,
00892 'export_path required to be None')
00893
00894 base_opts = []
00895
00896 if anon_uid is not None:
00897 base_opts.append('anonuid=%s' % str(anon_uid))
00898
00899 if anon_gid is not None:
00900 base_opts.append('anongid=%s' % str(anon_gid))
00901
00902 if auth_type is not None:
00903 base_opts.append('sec=%s' % str(auth_type))
00904
00905 fs_path = self._get_fs_path(fs_id)
00906 if fs_path is None:
00907 raise LsmError(ErrorNumber.NOT_FOUND_FS, "File system not found")
00908
00909 for host in rw_list:
00910 tmp_opts = copy.copy(base_opts)
00911 if host in root_list:
00912 tmp_opts.append('no_root_squash')
00913
00914 tmp_opts.append('rw')
00915
00916 self._jsonrequest("nfs_export_add",
00917 dict(host=host, path=fs_path,
00918 export_path=None, options=tmp_opts))
00919
00920 for host in ro_list:
00921 tmp_opts = copy.copy(base_opts)
00922 if host in root_list:
00923 tmp_opts.append('no_root_squash')
00924
00925 tmp_opts.append('ro')
00926
00927 self._jsonrequest("nfs_export_add",
00928 dict(host=host, path=fs_path,
00929 export_path=None, options=tmp_opts))
00930
00931
00932
00933
00934
00935 exports = self.exports()
00936 h = []
00937 h.extend(rw_list)
00938 h.extend(ro_list)
00939 for host in exports:
00940 if host.fs_id == fs_id:
00941 l = []
00942 l.extend(host.ro)
00943 l.extend(host.rw)
00944 for host_entry in h:
00945 if host_entry in l:
00946 return host
00947
00948 raise LsmError(ErrorNumber.PLUGIN_BUG, "Failed to create export")
00949
00950 @handle_errors
00951 def export_remove(self, export, flags=0):
00952
00953 for host in export.rw:
00954 params = dict(host=host, path=export.export_path)
00955 self._jsonrequest("nfs_export_remove", params)
00956
00957 for host in export.ro:
00958 params = dict(host=host, path=export.export_path)
00959 self._jsonrequest("nfs_export_remove", params)
00960
00961 def _jsonrequest(self, method, params=None):
00962 data = json.dumps(dict(id=self.rpc_id, method=method,
00963 params=params, jsonrpc="2.0"))
00964 self.rpc_id += 1
00965
00966 try:
00967 request = urllib2.Request(self.url, data, self.headers)
00968 response_obj = urllib2.urlopen(request)
00969 except socket.error:
00970 raise LsmError(ErrorNumber.NETWORK_ERROR,
00971 "Unable to connect to targetd, uri right?")
00972
00973 response_data = response_obj.read()
00974 response = json.loads(response_data)
00975 if response.get('error', None) is None:
00976 return response.get('result')
00977 else:
00978 if response['error']['code'] <= 0:
00979
00980
00981
00982 raise TargetdError(abs(int(response['error']['code'])),
00983 response['error'].get('message', ''))
00984 else:
00985
00986 async_code = response['error']['code']
00987 while True:
00988 time.sleep(1)
00989 results = self._jsonrequest('async_list')
00990 status = results.get(str(async_code), None)
00991 if status:
00992 if status[0]:
00993 raise LsmError(
00994 ErrorNumber.PLUGIN_BUG,
00995 "%d has error %d" % (async_code, status[0]))