00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017 import urllib2
00018 import socket
00019 import sys
00020 from xml.etree import ElementTree
00021 import time
00022 from binascii import hexlify
00023 from _ssl import SSLError
00024
00025 from M2Crypto import RC4
00026
00027 from lsm.external.xmltodict import convert_xml_to_dict
00028 from lsm import (ErrorNumber)
00029
00030
00031
00032 xml_debug = None
00033
00034
00035 def netapp_filer_parse_response(resp):
00036 if xml_debug:
00037 out = open(xml_debug, "wb")
00038 out.write(resp)
00039 out.close()
00040
00041 return convert_xml_to_dict(ElementTree.fromstring(resp))
00042
00043
00044 def param_value(val):
00045 """
00046 Given a parameter to pass to filer, convert to XML
00047 """
00048 rc = ""
00049 if type(val) is dict or isinstance(val, dict):
00050 for k, v in val.items():
00051 rc += "<%s>%s</%s>" % (k, param_value(v), k)
00052 elif type(val) is list or isinstance(val, list):
00053 for i in val:
00054 rc += param_value(i)
00055 else:
00056 rc = val
00057 return rc
00058
00059
00060 def netapp_filer(host, username, password, timeout, command, parameters=None,
00061 ssl=False):
00062 """
00063 Issue a command to the NetApp filer.
00064 Note: Change to default ssl on before we ship a release version.
00065 """
00066 proto = 'http'
00067 if ssl:
00068 proto = 'https'
00069
00070 url = "%s://%s/servlets/netapp.servlets.admin.XMLrequest_filer" % \
00071 (proto, host)
00072
00073 req = urllib2.Request(url)
00074 req.add_header('Content-Type', 'text/xml')
00075
00076 password_manager = urllib2.HTTPPasswordMgrWithDefaultRealm()
00077 password_manager.add_password(None, url, username, password)
00078 auth_manager = urllib2.HTTPBasicAuthHandler(password_manager)
00079
00080 opener = urllib2.build_opener(auth_manager)
00081 urllib2.install_opener(opener)
00082
00083
00084 p = ""
00085
00086 if parameters:
00087 for k, v in parameters.items():
00088 p += "<%s>%s</%s>" % (k, param_value(v), k)
00089
00090 payload = "<%s>\n%s\n</%s>" % (command, p, command)
00091
00092 data = """<?xml version="1.0" encoding="UTF-8"?>
00093 <!DOCTYPE netapp SYSTEM "file:/etc/netapp_filer.dtd">
00094 <netapp xmlns="http://www.netapp.com/filer/admin" version="1.1">
00095 %s
00096 </netapp>
00097 """ % payload
00098
00099 handler = None
00100 rc = None
00101 try:
00102 handler = urllib2.urlopen(req, data, float(timeout))
00103
00104 if handler.getcode() == 200:
00105 rc = netapp_filer_parse_response(handler.read())
00106 except urllib2.HTTPError as he:
00107 raise
00108 except urllib2.URLError as ue:
00109 if isinstance(ue.reason, socket.timeout):
00110 raise FilerError(Filer.ETIMEOUT, "Connection timeout")
00111 else:
00112 raise
00113 except socket.timeout:
00114 raise FilerError(Filer.ETIMEOUT, "Connection timeout")
00115 except SSLError as sse:
00116
00117
00118
00119
00120 if "timed out" in str(sse).lower():
00121 raise FilerError(Filer.ETIMEOUT, "Connection timeout (SSL)")
00122 else:
00123 raise FilerError(Filer.EUNKNOWN,
00124 "SSL error occurred (%s)", str(sse))
00125 finally:
00126 if handler:
00127 handler.close()
00128
00129 return rc
00130
00131
00132 class FilerError(Exception):
00133 """
00134 Class represents a NetApp bad return code
00135 """
00136 IGROUP_NOT_CONTAIN_GIVEN_INIT = 9007
00137 IGROUP_ALREADY_HAS_INIT = 9008
00138 NO_SUCH_IGROUP = 9003
00139
00140
00141 EVDISK_ERROR_VDISK_EXISTS = 9012
00142 EVDISK_ERROR_VDISK_EXPORTED = 9013
00143 EVDISK_ERROR_VDISK_NOT_ENABLED = 9014
00144 EVDISK_ERROR_VDISK_NOT_DISABLED = 9015
00145 EVDISK_ERROR_NO_SUCH_LUNMAP = 9016
00146 EVDISK_ERROR_INITGROUP_MAPS_EXIST = 9029
00147
00148 EVDISK_ERROR_SIZE_TOO_LARGE = 9034
00149 EVDISK_ERROR_NO_SUCH_VOLUME = 9036
00150 EVDISK_ERROR_SIZE_TOO_SMALL = 9041
00151 EVDISK_ERROR_SIZE_UNCHANGED = 9042
00152 EVDISK_ERROR_INITGROUP_HAS_VDISK = 9023
00153
00154 def __init__(self, errno, reason, *args, **kwargs):
00155 Exception.__init__(self, *args, **kwargs)
00156 self.errno = int(errno)
00157 self.reason = reason
00158
00159
00160 def to_list(v):
00161 """
00162 The return values in hash form can either be a single hash item or a list
00163 of hash items, this code handles both to make callers always get a list.
00164 """
00165 rc = []
00166 if v is not None:
00167 if isinstance(v, list):
00168 rc = v
00169 else:
00170 rc.append(v)
00171 return rc
00172
00173
00174 class Filer(object):
00175 """
00176 Class to handle NetApp API calls.
00177 Note: These are using lsm terminology.
00178 """
00179 EUNKNOWN = 10
00180 ENAVOL_NAME_DUPE = 17
00181 ENOSPC = 28
00182 ETIMEOUT = 60
00183 EINVALID_ISCSI_NAME = 9006
00184 EDUPE_VOLUME_PATH = 9012
00185 ENO_SUCH_VOLUME = 9017
00186 ESIZE_TOO_LARGE = 9034
00187 ENO_SUCH_FS = 9036
00188 EVOLUME_TOO_SMALL = 9041
00189 EAPILICENSE = 13008
00190 EFSDOESNOTEXIST = 13040
00191 EFSOFFLINE = 13042
00192 EFSNAMEINVALID = 13044
00193 ENOSPACE = 13062
00194 ESERVICENOTLICENSED = 13902
00195 ECLONE_NAME_EXISTS = 14952
00196 ECLONE_LICENSE_EXPIRED = 14955
00197 ECLONE_NOT_LICENSED = 14956
00198
00199 (LSM_VOL_PREFIX, LSM_INIT_PREFIX) = ('lsm_lun_container', 'lsm_init_')
00200
00201 def _invoke(self, command, parameters=None):
00202
00203 rc = netapp_filer(self.host, self.username, self.password,
00204 self.timeout, command, parameters, self.ssl)
00205
00206 t = rc['netapp']['results']['attrib']
00207
00208 if t['status'] != 'passed':
00209 raise FilerError(t['errno'], t['reason'])
00210
00211 return rc['netapp']['results']
00212
00213 def __init__(self, host, username, password, timeout, ssl=True):
00214 self.host = host
00215 self.username = username
00216 self.password = password
00217 self.timeout = timeout
00218 self.ssl = ssl
00219
00220 def system_info(self):
00221 rc = self._invoke('system-get-info')
00222 return rc['system-info']
00223
00224 def validate(self):
00225
00226 self._invoke('system-api-list')
00227 return None
00228
00229 def disks(self):
00230 disks = self._invoke('disk-list-info')
00231 return disks['disk-details']['disk-detail-info']
00232
00233 def aggregates(self, aggr_name=None):
00234 """
00235 Return a list of aggregates
00236 If aggr_name provided, return [na_aggr]
00237 """
00238 if aggr_name:
00239 pools = self._invoke('aggr-list-info', {'aggregate': aggr_name})
00240 else:
00241 pools = self._invoke('aggr-list-info')
00242 tmp = pools['aggregates']['aggr-info']
00243 return to_list(tmp)
00244
00245 def aggregate_volume_names(self, aggr_name):
00246 """
00247 Return a list of volume names that are on an aggregate
00248 """
00249 vol_names = []
00250 rc = self._invoke('aggr-list-info', {'aggregate': aggr_name})
00251
00252 aggr = rc['aggregates']['aggr-info']
00253
00254 if aggr is not None and aggr['volumes'] is not None:
00255 vols = aggr['volumes']['contained-volume-info']
00256 vol_names = [e['name'] for e in to_list(vols)]
00257 return vol_names
00258
00259 def lun_build_name(self, volume_name, file_name):
00260 """
00261 Given a volume name and file return full path"
00262 """
00263 return '/vol/%s/%s' % (volume_name, file_name)
00264
00265 def luns_get_specific(self, aggr, na_lun_name=None, na_volume_name=None):
00266 """
00267 Return all logical units, or information about one or for all those
00268 on a volume name.
00269 """
00270 rc = []
00271
00272 if na_lun_name is not None:
00273 luns = self._invoke('lun-list-info', {'path': na_lun_name})
00274 elif na_volume_name is not None:
00275 luns = self._invoke('lun-list-info',
00276 {'volume-name': na_volume_name})
00277 else:
00278 luns = self._invoke('lun-list-info')
00279
00280 return to_list(luns['luns']['lun-info'])
00281
00282 def _get_aggr_info(self):
00283 aggrs = self._invoke('aggr-list-info')
00284 tmp = to_list(aggrs['aggregates']['aggr-info'])
00285 return [x for x in tmp if x['volumes'] is not None]
00286
00287 def luns_get_all(self):
00288 """
00289 Return all lun-info
00290 """
00291 try:
00292 return to_list(self._invoke('lun-list-info')['luns']['lun-info'])
00293 except TypeError:
00294
00295 return []
00296
00297 def lun_min_size(self):
00298 return self._invoke('lun-get-minsize', {'type': 'image'})['min-size']
00299
00300 def lun_create(self, full_path_name, size_bytes, flag_thin=False):
00301 """
00302 Creates a lun
00303 If flag_thin set to True, will set 'space-reservation-enabled' as
00304 'false' which means "create a LUN without any space being reserved".
00305 """
00306 params = {'path': full_path_name,
00307 'size': size_bytes}
00308 if flag_thin is True:
00309 params['space-reservation-enabled'] = 'false'
00310
00311 self._invoke('lun-create-by-size', params)
00312
00313 def lun_delete(self, lun_path):
00314 """
00315 Deletes a lun given a lun path
00316 """
00317 self._invoke('lun-destroy', {'path': lun_path})
00318
00319 def lun_resize(self, lun_path, size_bytes):
00320 """
00321 Re-sizes a lun
00322 """
00323 self._invoke('lun-resize', {'path': lun_path, 'size': size_bytes,
00324 'force': 'true'})
00325
00326 def volume_resize(self, na_vol_name, size_diff_kb):
00327 """
00328 Given a NetApp volume name and a size change in kb, re-size the
00329 NetApp volume.
00330 """
00331 params = {'volume': na_vol_name}
00332
00333 if size_diff_kb > 0:
00334 params['new-size'] = '+' + str(size_diff_kb) + 'k'
00335 else:
00336 params['new-size'] = str(size_diff_kb) + 'k'
00337
00338 self._invoke('volume-size', params)
00339 return None
00340
00341 def volumes(self, volume_name=None):
00342 """
00343 Return a list of NetApp volumes
00344 """
00345 if not volume_name:
00346 v = self._invoke('volume-list-info')
00347 else:
00348 v = self._invoke('volume-list-info', {'volume': volume_name})
00349
00350 t = v['volumes']['volume-info']
00351 rc = to_list(t)
00352 return rc
00353
00354 def volume_create(self, aggr_name, vol_name, size_in_bytes):
00355 """
00356 Creates a volume given an aggr_name, volume name and size in bytes.
00357 """
00358 params = {'containing-aggr-name': aggr_name,
00359 'size': int(size_in_bytes * 1.30),
00360
00361 'volume': vol_name}
00362
00363 self._invoke('volume-create', params)
00364
00365
00366 self._invoke('volume-set-option', {'volume': vol_name,
00367 'option-name': 'nosnap',
00368 'option-value': 'on', })
00369
00370
00371 self.nfs_export_remove(['/vol/' + vol_name])
00372
00373 def volume_clone(self, src_volume, dest_volume, snapshot=None):
00374 """
00375 Clones a volume given a source volume name, destination volume name
00376 and optional backing snapshot.
00377 """
00378 params = {'parent-volume': src_volume, 'volume': dest_volume}
00379 if snapshot:
00380 params['parent-snapshot'] = snapshot.name
00381 self._invoke('volume-clone-create', params)
00382
00383 def volume_delete(self, vol_name):
00384 """
00385 Deletes a volume and everything on it.
00386 """
00387 online = False
00388
00389 try:
00390 self._invoke('volume-offline', {'name': vol_name})
00391 online = True
00392 except FilerError as f_error:
00393 if f_error.errno != Filer.EFSDOESNOTEXIST:
00394 raise
00395
00396 try:
00397 self._invoke('volume-destroy', {'name': vol_name})
00398 except FilerError as f_error:
00399
00400
00401 exception_info = sys.exc_info()
00402
00403 if online:
00404 try:
00405 self._invoke('volume-online', {'name': vol_name})
00406 except FilerError:
00407 pass
00408 raise exception_info[1], None, exception_info[2]
00409
00410 def volume_names(self):
00411 """
00412 Return a list of volume names
00413 """
00414 vols = self.volumes()
00415 return [v['name'] for v in vols]
00416
00417 def clone(self, source_path, dest_path, backing_snapshot=None,
00418 ranges=None):
00419 """
00420 Creates a file clone
00421 """
00422 params = {'source-path': source_path}
00423
00424
00425 if source_path != dest_path:
00426 params['destination-path'] = dest_path
00427
00428 if backing_snapshot:
00429 raise FilerError(ErrorNumber.NO_SUPPORT,
00430 "Support for backing luns not implemented "
00431 "for this API version")
00432
00433
00434 if ranges:
00435 block_ranges = []
00436 for r in ranges:
00437 values = {'block-count': r.block_count,
00438 'destination-block-number': r.dest_block,
00439 'source-block-number': r.src_block}
00440
00441 block_ranges.append({'block-range': values})
00442
00443 params['block-ranges'] = block_ranges
00444
00445 rc = self._invoke('clone-start', params)
00446
00447 c_id = rc['clone-id']
00448
00449 while True:
00450 progress = self._invoke('clone-list-status',
00451 {'clone-id': c_id})
00452
00453
00454
00455 if 'status' in progress:
00456 progress = progress['status']['ops-info']
00457
00458 if progress['clone-state'] == 'failed':
00459 self._invoke('clone-clear', {'clone-id': c_id})
00460 raise FilerError(progress['error'], progress['reason'])
00461 elif progress['clone-state'] == 'running' \
00462 or progress['clone-state'] == 'fail exit':
00463
00464
00465 time.sleep(0.2)
00466 elif progress['clone-state'] == 'completed':
00467 return
00468 else:
00469 raise FilerError(ErrorNumber.NO_SUPPORT,
00470 'Unexpected state=' +
00471 progress['clone-state'])
00472 else:
00473 return
00474
00475 def lun_online(self, lun_path):
00476 self._invoke('lun-online', {'path': lun_path})
00477
00478 def lun_offline(self, lun_path):
00479 self._invoke('lun-offline', {'path': lun_path})
00480
00481 def igroups(self, group_name=None):
00482 rc = []
00483
00484 if group_name:
00485 g = self._invoke('igroup-list-info',
00486 {'initiator-group-name': group_name})
00487 else:
00488 g = self._invoke('igroup-list-info')
00489
00490 if g['initiator-groups']:
00491 rc = to_list(g['initiator-groups']['initiator-group-info'])
00492 return rc
00493
00494 def igroup_create(self, name, igroup_type):
00495 params = {'initiator-group-name': name,
00496 'initiator-group-type': igroup_type}
00497 self._invoke('igroup-create', params)
00498
00499 def igroup_delete(self, name):
00500 self._invoke('igroup-destroy', {'initiator-group-name': name})
00501
00502 @staticmethod
00503 def encode(password):
00504 rc4 = RC4.RC4()
00505 rc4.set_key("#u82fyi8S5\017pPemw")
00506 return hexlify(rc4.update(password))
00507
00508 def iscsi_initiator_add_auth(self, initiator, user_name, password,
00509 out_user, out_password):
00510 pw = self.encode(password)
00511
00512 args = {'initiator': initiator}
00513
00514 if user_name and len(user_name) and password and len(password):
00515 args.update({'user-name': user_name,
00516 'password': pw, 'auth-type': "CHAP"})
00517
00518 if out_user and len(out_user) and \
00519 out_password and len(out_password):
00520 args.update({'outbound-user-name': out_user,
00521 'outbound-password': out_password})
00522 else:
00523 args.update({'initiator': initiator, 'auth-type': "none"})
00524
00525 self._invoke('iscsi-initiator-add-auth', args)
00526
00527 def igroup_add_initiator(self, ig, initiator):
00528 self._invoke('igroup-add',
00529 {'initiator-group-name': ig, 'initiator': initiator})
00530
00531 def igroup_del_initiator(self, ig, initiator):
00532 self._invoke('igroup-remove',
00533 {'initiator-group-name': ig,
00534 'initiator': initiator,
00535 'force': 'true'})
00536
00537 def lun_map(self, igroup, lun_path):
00538 self._invoke('lun-map', {'initiator-group': igroup, 'path': lun_path})
00539
00540 def lun_unmap(self, igroup, lun_path):
00541 self._invoke(
00542 'lun-unmap', {'initiator-group': igroup, 'path': lun_path})
00543
00544 def lun_map_list_info(self, lun_path):
00545 initiator_groups = []
00546 rc = self._invoke('lun-map-list-info', {'path': lun_path})
00547 if rc['initiator-groups'] is not None:
00548 igi = to_list(rc['initiator-groups'])
00549 for i in igi:
00550 group_name = i['initiator-group-info']['initiator-group-name']
00551 initiator_groups.append(self.igroups(group_name)[0])
00552
00553 return initiator_groups
00554
00555 def lun_initiator_list_map_info(self, initiator_id, initiator_group_name):
00556 """
00557 Given an initiator_id and initiator group name, return a list of
00558 lun-info
00559 """
00560 luns = []
00561
00562 rc = self._invoke('lun-initiator-list-map-info',
00563 {'initiator': initiator_id})
00564
00565 if rc['lun-maps']:
00566
00567 lun_name_list = to_list(rc['lun-maps']['lun-map-info'])
00568
00569
00570 all_luns = self.luns_get_all()
00571
00572 for l in lun_name_list:
00573 if l['initiator-group'] == initiator_group_name:
00574 for al in all_luns:
00575 if al['path'] == l['path']:
00576 luns.append(al)
00577 return luns
00578
00579 def snapshots(self, volume_name):
00580 rc = []
00581 args = {'target-type': 'volume', 'target-name': volume_name}
00582 ss = self._invoke('snapshot-list-info', args)
00583 if ss['snapshots']:
00584 rc = to_list(ss['snapshots']['snapshot-info'])
00585 return rc
00586
00587 def snapshot_create(self, volume_name, snapshot_name):
00588 self._invoke('snapshot-create', {'volume': volume_name,
00589 'snapshot': snapshot_name})
00590 return [v for v in self.snapshots(volume_name)
00591 if v['name'] == snapshot_name][0]
00592
00593 def snapshot_file_restore_num(self):
00594 """
00595 Returns the number of executing file restore snapshots.
00596 """
00597 rc = self._invoke('snapshot-restore-file-info')
00598 if 'sfsr-in-progress' in rc:
00599 return int(rc['sfsr-in-progress'])
00600
00601 return 0
00602
00603 def snapshot_restore_volume(self, fs_name, snapshot_name):
00604 """
00605 Restores all files on a volume
00606 """
00607 params = {'snapshot': snapshot_name, 'volume': fs_name}
00608 self._invoke('snapshot-restore-volume', params)
00609
00610 def snapshot_restore_file(self, snapshot_name, restore_path, restore_file):
00611 """
00612 Restore a list of files
00613 """
00614 params = {'snapshot': snapshot_name, 'path': restore_path}
00615
00616 if restore_file:
00617 params['restore-path'] = restore_file
00618
00619 self._invoke('snapshot-restore-file', params)
00620
00621 def snapshot_delete(self, volume_name, snapshot_name):
00622 self._invoke('snapshot-delete',
00623 {'volume': volume_name, 'snapshot': snapshot_name})
00624
00625 def export_auth_types(self):
00626 rc = self._invoke('nfs-get-supported-sec-flavors')
00627 return [e['flavor'] for e in
00628 to_list(rc['sec-flavor']['sec-flavor-info'])]
00629
00630 @staticmethod
00631 def _build_list(pylist, list_name, elem_name):
00632 """
00633 Given a python list, build the appropriate dict that contains the
00634 list items so that it can be converted to xml to be sent on the wire.
00635 """
00636 return [{list_name: {elem_name: l}} for l in pylist]
00637
00638 @staticmethod
00639 def _build_export_fs_all():
00640 return Filer._build_list(
00641 ['true'], 'exports-hostname-info', 'all-hosts')
00642
00643 @staticmethod
00644 def _build_export_fs_list(hosts):
00645 if hosts[0] == '*':
00646 return Filer._build_export_fs_all()
00647 else:
00648 return Filer._build_list(hosts, 'exports-hostname-info', 'name')
00649
00650 def _build_export_rules(self, volume_path, export_path, ro_list, rw_list,
00651 root_list, anonuid=None, sec_flavor=None):
00652 """
00653 Common logic to build up the rules for nfs
00654 """
00655
00656
00657
00658 rule = {'pathname': volume_path}
00659 if volume_path != export_path:
00660 rule['actual-pathname'] = volume_path
00661 rule['pathname'] = export_path
00662
00663 rule['security-rules'] = {}
00664 rule['security-rules']['security-rule-info'] = {}
00665
00666 r = rule['security-rules']['security-rule-info']
00667
00668 if len(ro_list):
00669 r['read-only'] = Filer._build_export_fs_list(ro_list)
00670
00671 if len(rw_list):
00672 r['read-write'] = Filer._build_export_fs_list(rw_list)
00673
00674 if len(root_list):
00675 r['root'] = Filer._build_export_fs_list(root_list)
00676
00677 if anonuid:
00678 uid = long(anonuid)
00679 if uid != -1 and uid != 0xFFFFFFFFFFFFFFFF:
00680 r['anon'] = str(uid)
00681
00682 if sec_flavor:
00683 r['sec-flavor'] = Filer._build_list(
00684 [sec_flavor], 'sec-flavor-info', 'flavor')
00685
00686 return rule
00687
00688 def nfs_export_fs2(self, volume_path, export_path, ro_list, rw_list,
00689 root_list, anonuid=None, sec_flavor=None):
00690 """
00691 NFS export a volume.
00692 """
00693
00694 rule = self._build_export_rules(
00695 volume_path, export_path, ro_list, rw_list, root_list, anonuid,
00696 sec_flavor)
00697
00698 params = {'persistent': 'true',
00699 'rules': {'exports-rule-info-2': [rule]}, 'verbose': 'true'}
00700 self._invoke('nfs-exportfs-append-rules-2', params)
00701
00702 def nfs_export_fs_modify2(self, volume_path, export_path, ro_list, rw_list,
00703 root_list, anonuid=None, sec_flavor=None):
00704
00705 """
00706 Modifies an existing rule.
00707 """
00708 rule = self._build_export_rules(
00709 volume_path, export_path, ro_list, rw_list, root_list, anonuid,
00710 sec_flavor)
00711
00712 params = {
00713 'persistent': 'true', 'rule': {'exports-rule-info-2': [rule]}}
00714 self._invoke('nfs-exportfs-modify-rule-2', params)
00715
00716 def nfs_export_remove(self, export_paths):
00717 """
00718 Removes an existing export
00719 """
00720 assert (type(export_paths) is list)
00721 paths = Filer._build_list(export_paths, 'pathname-info', 'name')
00722 self._invoke('nfs-exportfs-delete-rules',
00723 {'pathnames': paths, 'persistent': 'true'})
00724
00725 def nfs_exports(self):
00726 """
00727 Returns a list of exports (in hash form)
00728 """
00729 rc = []
00730 exports = self._invoke('nfs-exportfs-list-rules')
00731 if 'rules' in exports and exports['rules']:
00732 rc = to_list(exports['rules']['exports-rule-info'])
00733 return rc
00734
00735 def volume_children(self, volume):
00736 params = {'volume': volume}
00737
00738 rc = self._invoke('volume-list-info', params)
00739
00740 if 'clone-children' in rc['volumes']['volume-info']:
00741 tmp = rc['volumes']['volume-info']['clone-children'][
00742 'clone-child-info']
00743 rc = [c['clone-child-name'] for c in to_list(tmp)]
00744 else:
00745 rc = None
00746
00747 return rc
00748
00749 def volume_split_clone(self, volume):
00750 self._invoke('volume-clone-split-start', {'volume': volume})
00751
00752 def volume_split_status(self):
00753 result = []
00754
00755 rc = self._invoke('volume-clone-split-status')
00756
00757 if 'clone-split-details' in rc:
00758 tmp = rc['clone-split-details']['clone-split-detail-info']
00759 result = [r['name'] for r in to_list(tmp)]
00760
00761 return result
00762
00763 def fcp_list(self):
00764 fcp_list = []
00765
00766 try:
00767
00768 rc = self._invoke('fcp-adapter-list-info')
00769
00770 if 'fcp-config-adapters' in rc:
00771 if 'fcp-config-adapter-info' in rc['fcp-config-adapters']:
00772 fc_config = rc['fcp-config-adapters']
00773 adapters = fc_config['fcp-config-adapter-info']
00774 for f in adapters:
00775 fcp_list.append(dict(addr=f['port-name'],
00776 adapter=f['adapter']))
00777 except FilerError as na:
00778 if na.errno != Filer.EAPILICENSE:
00779 raise
00780
00781 return fcp_list
00782
00783 def iscsi_node_name(self):
00784 try:
00785 rc = self._invoke('iscsi-node-get-name')
00786 if 'node-name' in rc:
00787 return rc['node-name']
00788 except FilerError as na:
00789 if na.errno != Filer.EAPILICENSE:
00790 raise
00791 return None
00792
00793 def interface_get_infos(self):
00794 i_info = {}
00795
00796 rc = self._invoke('net-ifconfig-get')
00797
00798 if 'interface-config-info' in rc:
00799 i_config = rc['interface-config-info']
00800 if 'interface-config-info' in i_config:
00801 tmp = to_list(i_config['interface-config-info'])
00802 for i in tmp:
00803 i_info[i['interface-name']] = i
00804
00805 return i_info
00806
00807 def iscsi_list(self):
00808 i_list = []
00809
00810
00811 i_info = self.interface_get_infos()
00812
00813 try:
00814 rc = self._invoke('iscsi-portal-list-info')
00815
00816 if 'iscsi-portal-list-entries' in rc:
00817 portal_entries = rc['iscsi-portal-list-entries']
00818 if 'iscsi-portal-list-entry-info' in portal_entries:
00819 tmp = portal_entries['iscsi-portal-list-entry-info']
00820 portals = to_list(tmp)
00821 for p in portals:
00822 mac = i_info[p['interface-name']]['mac-address']
00823 i_list.append(dict(interface=p['interface-name'],
00824 ip=p['ip-address'],
00825 port=p['ip-port'],
00826 mac=mac))
00827 except FilerError as na:
00828 if na.errno != Filer.EAPILICENSE:
00829 raise
00830
00831 return i_list
00832
00833 if __name__ == '__main__':
00834 try:
00835
00836 pass
00837
00838 except FilerError as fe:
00839 print 'Errno=', fe.errno, 'reason=', fe.reason