00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018 import os
00019 import sys
00020 import getpass
00021 import time
00022 import tty
00023 import termios
00024
00025 try:
00026 from collections import OrderedDict
00027 except ImportError:
00028
00029 from ordereddict import OrderedDict
00030
00031 from argparse import ArgumentParser
00032 from argparse import RawTextHelpFormatter
00033
00034 from lsm import (Client, Pool, VERSION, LsmError, Disk,
00035 Volume, JobStatus, ErrorNumber, BlockRange,
00036 uri_parse, Proxy, size_human_2_size_bytes,
00037 AccessGroup, FileSystem, NfsExport, TargetPort)
00038
00039 from lsm.lsmcli.data_display import (
00040 DisplayData, PlugData, out,
00041 vol_provision_str_to_type, vol_rep_type_str_to_type, VolumeRAIDInfo,
00042 PoolRAIDInfo, VcrCap)
00043
00044
00045
00046
00047 def cmd_line_wrapper(c=None):
00048 """
00049 Common command line code, called.
00050 """
00051 err_exit = 0
00052 cli = None
00053
00054 try:
00055 cli = CmdLine()
00056 cli.process(c)
00057 except ArgError as ae:
00058 sys.stderr.write(str(ae))
00059 sys.stderr.flush()
00060 err_exit = 2
00061 except LsmError as le:
00062 sys.stderr.write(str(le) + "\n")
00063 sys.stderr.flush()
00064 err_exit = 4
00065 except KeyboardInterrupt:
00066 err_exit = 1
00067 except SystemExit as se:
00068
00069 err_exit = se.code
00070 except:
00071
00072 err_exit = 2
00073 finally:
00074
00075
00076 if cli:
00077 try:
00078
00079 cli.shutdown(err_exit)
00080 except Exception:
00081 pass
00082 sys.exit(err_exit)
00083
00084
00085
00086
00087 def getch():
00088 fd = sys.stdin.fileno()
00089 prev = termios.tcgetattr(fd)
00090 try:
00091 tty.setraw(sys.stdin.fileno())
00092 ch = sys.stdin.read(1)
00093 finally:
00094 termios.tcsetattr(fd, termios.TCSADRAIN, prev)
00095 return ch
00096
00097
00098 def parse_convert_init(init_id):
00099 """
00100 If init_id is a WWPN, convert it into LSM standard version:
00101 (?:[0-9a-f]{2}:){7}[0-9a-f]{2}
00102
00103 Return (converted_init_id, lsm_init_type)
00104 """
00105 valid, init_type, init_id = AccessGroup.initiator_id_verify(init_id)
00106
00107 if valid:
00108 return (init_id, init_type)
00109
00110 raise ArgError("--init-id %s is not a valid WWPN or iSCSI IQN" % init_id)
00111
00112
00113
00114 class ArgError(Exception):
00115 def __init__(self, message, *args, **kwargs):
00116 """
00117 Class represents an error.
00118 """
00119 Exception.__init__(self, *args, **kwargs)
00120 self.msg = message
00121
00122 def __str__(self):
00123 return "%s: error: %s\n" % (os.path.basename(sys.argv[0]), self.msg)
00124
00125
00126
00127
00128
00129
00130
00131 def _get_item(l, the_id, friendly_name='item', raise_error=True):
00132 for item in l:
00133 if item.id == the_id:
00134 return item
00135 if raise_error:
00136 raise ArgError('%s with ID %s not found!' % (friendly_name, the_id))
00137 else:
00138 return None
00139
00140 list_choices = ['VOLUMES', 'POOLS', 'FS', 'SNAPSHOTS',
00141 'EXPORTS', "NFS_CLIENT_AUTH", 'ACCESS_GROUPS',
00142 'SYSTEMS', 'DISKS', 'PLUGINS', 'TARGET_PORTS']
00143
00144 provision_types = ('DEFAULT', 'THIN', 'FULL')
00145 provision_help = "provisioning type: " + ", ".join(provision_types)
00146
00147 replicate_types = ('CLONE', 'COPY', 'MIRROR_ASYNC', 'MIRROR_SYNC')
00148 replicate_help = "replication type: " + ", ".join(replicate_types)
00149
00150 size_help = 'Can use B, KiB, MiB, GiB, TiB, PiB postfix (IEC sizing)'
00151
00152 sys_id_opt = dict(name='--sys', metavar='<SYS_ID>', help='System ID')
00153 sys_id_filter_opt = sys_id_opt.copy()
00154 sys_id_filter_opt['help'] = 'Search by System ID'
00155
00156 pool_id_opt = dict(name='--pool', metavar='<POOL_ID>', help='Pool ID')
00157 pool_id_filter_opt = pool_id_opt.copy()
00158 pool_id_filter_opt['help'] = 'Search by Pool ID'
00159
00160 vol_id_opt = dict(name='--vol', metavar='<VOL_ID>', help='Volume ID')
00161 vol_id_filter_opt = vol_id_opt.copy()
00162 vol_id_filter_opt['help'] = 'Search by Volume ID'
00163
00164 fs_id_opt = dict(name='--fs', metavar='<FS_ID>', help='File System ID')
00165
00166 ag_id_opt = dict(name='--ag', metavar='<AG_ID>', help='Access Group ID')
00167 ag_id_filter_opt = ag_id_opt.copy()
00168 ag_id_filter_opt['help'] = 'Search by Access Group ID'
00169
00170 init_id_opt = dict(name='--init', metavar='<INIT_ID>', help='Initiator ID')
00171 snap_id_opt = dict(name='--snap', metavar='<SNAP_ID>', help='Snapshot ID')
00172 export_id_opt = dict(name='--export', metavar='<EXPORT_ID>', help='Export ID')
00173
00174 nfs_export_id_filter_opt = dict(
00175 name='--nfs-export', metavar='<NFS_EXPORT_ID>',
00176 help='Search by NFS Export ID')
00177
00178 disk_id_filter_opt = dict(name='--disk', metavar='<DISK_ID>',
00179 help='Search by Disk ID')
00180
00181 size_opt = dict(name='--size', metavar='<SIZE>', help=size_help)
00182
00183 tgt_id_opt = dict(name="--tgt", help="Search by target port ID",
00184 metavar='<TGT_ID>')
00185
00186 cmds = (
00187 dict(
00188 name='list',
00189 help="List records of different types",
00190 args=[
00191 dict(name='--type',
00192 help="List records of type:\n " +
00193 "\n ".join(list_choices) +
00194 "\n\nWhen listing SNAPSHOTS, it requires --fs <FS_ID>.",
00195 metavar='<TYPE>',
00196 choices=list_choices,
00197 type=str.upper),
00198 ],
00199 optional=[
00200 dict(sys_id_filter_opt),
00201 dict(pool_id_filter_opt),
00202 dict(vol_id_filter_opt),
00203 dict(disk_id_filter_opt),
00204 dict(ag_id_filter_opt),
00205 dict(fs_id_opt),
00206 dict(nfs_export_id_filter_opt),
00207 dict(tgt_id_opt),
00208 ],
00209 ),
00210
00211 dict(
00212 name='job-status',
00213 help='Retrieve information about a job',
00214 args=[
00215 dict(name="--job", metavar="<JOB_ID>", help='job status id'),
00216 ],
00217 ),
00218
00219 dict(
00220 name='capabilities',
00221 help='Retrieves array capabilities',
00222 args=[
00223 dict(sys_id_opt),
00224 ],
00225 ),
00226
00227 dict(
00228 name='plugin-info',
00229 help='Retrieves plugin description and version',
00230 ),
00231
00232 dict(
00233 name='volume-create',
00234 help='Creates a volume (logical unit)',
00235 args=[
00236 dict(name="--name", help='volume name', metavar='<NAME>'),
00237 dict(size_opt),
00238 dict(pool_id_opt),
00239 ],
00240 optional=[
00241 dict(name="--provisioning", help=provision_help,
00242 default='DEFAULT',
00243 choices=provision_types,
00244 type=str.upper),
00245 ],
00246 ),
00247
00248 dict(
00249 name='volume-raid-create',
00250 help='Creates a RAIDed volume on hardware RAID',
00251 args=[
00252 dict(name="--name", help='volume name', metavar='<NAME>'),
00253 dict(name="--disk", metavar='<DISK>',
00254 help='Free disks for new RAIDed volume.\n'
00255 'This is repeatable argument.',
00256 action='append'),
00257 dict(name="--raid-type",
00258 help="RAID type for the new RAID group. "
00259 "Should be one of these:\n %s" %
00260 "\n ".
00261 join(VolumeRAIDInfo.VOL_CREATE_RAID_TYPES_STR),
00262 choices=VolumeRAIDInfo.VOL_CREATE_RAID_TYPES_STR,
00263 type=str.upper),
00264 ],
00265 optional=[
00266 dict(name="--strip-size",
00267 help="Strip size. " + size_help),
00268 ],
00269 ),
00270
00271 dict(
00272 name='volume-raid-create-cap',
00273 help='Query capablity of creating a RAIDed volume on hardware RAID',
00274 args=[
00275 dict(sys_id_opt),
00276 ],
00277 ),
00278
00279 dict(
00280 name='volume-delete',
00281 help='Deletes a volume given its id',
00282 args=[
00283 dict(vol_id_opt),
00284 ],
00285 ),
00286
00287 dict(
00288 name='volume-resize',
00289 help='Re-sizes a volume',
00290 args=[
00291 dict(vol_id_opt),
00292 dict(name='--size', metavar='<NEW_SIZE>',
00293 help="New size. %s" % size_help),
00294 ],
00295 ),
00296
00297 dict(
00298 name='volume-replicate',
00299 help='Creates a new volume and replicates provided volume to it.',
00300 args=[
00301 dict(vol_id_opt),
00302 dict(name="--name", metavar='<NEW_VOL_NAME>',
00303 help='The name for New replicated volume'),
00304 dict(name="--rep-type", metavar='<REPL_TYPE>',
00305 help=replicate_help, choices=replicate_types),
00306 ],
00307 optional=[
00308 dict(name="--pool",
00309 help='Pool ID to contain the new volume.\nBy default, '
00310 'new volume will be created in the same pool.'),
00311 ],
00312 ),
00313
00314 dict(
00315 name='volume-replicate-range',
00316 help='Replicates a portion of a volume',
00317 args=[
00318 dict(name="--src-vol", metavar='<SRC_VOL_ID>',
00319 help='Source volume id'),
00320 dict(name="--dst-vol", metavar='<DST_VOL_ID>',
00321 help='Destination volume id'),
00322 dict(name="--rep-type", metavar='<REP_TYPE>', help=replicate_help,
00323 choices=replicate_types),
00324 dict(name="--src-start", metavar='<SRC_START_BLK>',
00325 help='Source volume start block number.\n'
00326 'This is repeatable argument.',
00327 action='append'),
00328 dict(name="--dst-start", metavar='<DST_START_BLK>',
00329 help='Destination volume start block number.\n'
00330 'This is repeatable argument.',
00331 action='append'),
00332 dict(name="--count", metavar='<BLK_COUNT>',
00333 help='Number of blocks to replicate.\n'
00334 'This is repeatable argument.',
00335 action='append'),
00336 ],
00337 ),
00338
00339 dict(
00340 name='volume-replicate-range-block-size',
00341 help='Size of each replicated block on a system in bytes',
00342 args=[
00343 dict(sys_id_opt),
00344 ],
00345 ),
00346
00347 dict(
00348 name='volume-dependants',
00349 help='Returns True if volume has a dependant child, like replication',
00350 args=[
00351 dict(vol_id_opt),
00352 ],
00353 ),
00354
00355 dict(
00356 name='volume-dependants-rm',
00357 help='Removes dependencies',
00358 args=[
00359 dict(vol_id_opt),
00360 ],
00361 ),
00362
00363 dict(
00364 name='volume-access-group',
00365 help='Lists the access group(s) that have access to volume',
00366 args=[
00367 dict(vol_id_opt),
00368 ],
00369 ),
00370
00371 dict(
00372 name='volume-mask',
00373 help='Grants access to an access group to a volume, '
00374 'like LUN Masking',
00375 args=[
00376 dict(vol_id_opt),
00377 dict(ag_id_opt),
00378 ],
00379 ),
00380
00381 dict(
00382 name='volume-unmask',
00383 help='Revoke the access of specified access group to a volume',
00384 args=[
00385 dict(ag_id_opt),
00386 dict(vol_id_opt),
00387 ],
00388 ),
00389
00390 dict(
00391 name='volume-enable',
00392 help='Enable block access of a volume',
00393 args=[
00394 dict(vol_id_opt),
00395 ],
00396 ),
00397
00398 dict(
00399 name='volume-disable',
00400 help='Disable block access of a volume',
00401 args=[
00402 dict(vol_id_opt),
00403 ],
00404 ),
00405
00406 dict(
00407 name='volume-raid-info',
00408 help='Query volume RAID infomation',
00409 args=[
00410 dict(vol_id_opt),
00411 ],
00412 ),
00413
00414 dict(
00415 name='pool-member-info',
00416 help='Query Pool membership infomation',
00417 args=[
00418 dict(pool_id_opt),
00419 ],
00420 ),
00421
00422 dict(
00423 name='access-group-create',
00424 help='Create an access group',
00425 args=[
00426 dict(name='--name', metavar='<AG_NAME>',
00427 help="Human readable name for access group"),
00428
00429
00430 dict(init_id_opt),
00431 dict(sys_id_opt),
00432 ],
00433 ),
00434
00435 dict(
00436 name='access-group-add',
00437 help='Add an initiator into existing access group',
00438 args=[
00439 dict(ag_id_opt),
00440 dict(init_id_opt),
00441 ],
00442 ),
00443 dict(
00444 name='access-group-remove',
00445 help='Remove an initiator from existing access group',
00446 args=[
00447 dict(ag_id_opt),
00448 dict(init_id_opt),
00449 ],
00450 ),
00451
00452 dict(
00453 name='access-group-delete',
00454 help='Deletes an access group',
00455 args=[
00456 dict(ag_id_opt),
00457 ],
00458 ),
00459
00460 dict(
00461 name='access-group-volumes',
00462 help='Lists the volumes that the access group has'
00463 ' been granted access to',
00464 args=[
00465 dict(ag_id_opt),
00466 ],
00467 ),
00468
00469 dict(
00470 name='iscsi-chap',
00471 help='Configures iSCSI inbound/outbound CHAP authentication',
00472 args=[
00473 dict(init_id_opt),
00474 ],
00475 optional=[
00476 dict(name="--in-user", metavar='<IN_USER>',
00477 help='Inbound chap user name'),
00478 dict(name="--in-pass", metavar='<IN_PASS>',
00479 help='Inbound chap password'),
00480 dict(name="--out-user", metavar='<OUT_USER>',
00481 help='Outbound chap user name'),
00482 dict(name="--out-pass", metavar='<OUT_PASS>',
00483 help='Outbound chap password'),
00484 ],
00485 ),
00486
00487 dict(
00488 name='fs-create',
00489 help='Creates a file system',
00490 args=[
00491 dict(name="--name", metavar='<FS_NAME>',
00492 help='name of the file system'),
00493 dict(size_opt),
00494 dict(pool_id_opt),
00495 ],
00496 ),
00497
00498 dict(
00499 name='fs-delete',
00500 help='Delete a filesystem',
00501 args=[
00502 dict(fs_id_opt)
00503 ],
00504 ),
00505
00506 dict(
00507 name='fs-resize',
00508 help='Re-sizes a filesystem',
00509 args=[
00510 dict(fs_id_opt),
00511 dict(name="--size", metavar="<NEW_SIZE>",
00512 help="New size. %s" % size_help),
00513 ],
00514 ),
00515
00516 dict(
00517 name='fs-export',
00518 help='Export a filesystem via NFS.',
00519 args=[
00520 dict(fs_id_opt),
00521 ],
00522 optional=[
00523 dict(name="--exportpath", metavar='<EXPORT_PATH>',
00524 help="NFS server export path. e.g. '/foo/bar'."),
00525 dict(name="--anonuid", metavar='<ANON_UID>',
00526 help='UID(User ID) to map to anonymous user',
00527 default=NfsExport.ANON_UID_GID_NA,
00528 type=long),
00529 dict(name="--anongid", metavar='<ANON_GID>',
00530 help='GID(Group ID) to map to anonymous user',
00531 default=NfsExport.ANON_UID_GID_NA,
00532 type=long),
00533 dict(name="--auth-type", metavar='<AUTH_TYPE>',
00534 help='NFS client authentication type'),
00535 dict(name="--root-host", metavar='<ROOT_HOST>',
00536 help="The host/IP has root access.\n"
00537 "This is repeatable argument.",
00538 action='append',
00539 default=[]),
00540 dict(name="--ro-host", metavar='<RO_HOST>',
00541 help="The host/IP has readonly access.\n"
00542 "This is repeatable argument.",
00543 action='append', default=[]),
00544 dict(name="--rw-host", metavar='<RW_HOST>',
00545 help="The host/IP has readwrite access.\n"
00546 "This is repeatable argument.",
00547 action='append', default=[]),
00548 ],
00549 ),
00550
00551 dict(
00552 name='fs-unexport',
00553 help='Remove an NFS export',
00554 args=[
00555 dict(export_id_opt),
00556 ],
00557 ),
00558
00559 dict(
00560 name='fs-clone',
00561 help='Creates a file system clone',
00562 args=[
00563 dict(name="--src-fs", metavar='<SRC_FS_ID>',
00564 help='The ID of existing source file system.'),
00565 dict(name="--dst-name", metavar='<DST_FS_NAME>',
00566 help='The name for newly created destination file system.'),
00567 ],
00568 optional=[
00569 dict(name="--backing-snapshot", metavar='<BE_SS_ID>',
00570 help='backing snapshot id'),
00571 ],
00572 ),
00573
00574 dict(
00575 name='fs-snap-create',
00576 help='Creates a snapshot',
00577 args=[
00578 dict(name="--name", metavar="<SNAP_NAME>",
00579 help='The human friendly name of new snapshot'),
00580 dict(fs_id_opt),
00581 ],
00582 ),
00583
00584 dict(
00585 name='fs-snap-delete',
00586 help='Deletes a snapshot',
00587 args=[
00588 dict(snap_id_opt),
00589 dict(fs_id_opt),
00590 ],
00591 ),
00592
00593 dict(
00594 name='fs-snap-restore',
00595 help='Restores a FS or specified files to '
00596 'previous snapshot state',
00597 args=[
00598 dict(snap_id_opt),
00599 dict(fs_id_opt),
00600 ],
00601 optional=[
00602 dict(name="--file", metavar="<FILE_PATH>",
00603 help="Only restore provided file\n"
00604 "Without this argument, all files will be restored\n"
00605 "This is a repeatable argument.",
00606 action='append', default=[]),
00607 dict(name="--fileas", metavar="<NEW_FILE_PATH>",
00608 help="store restore file name to another name.\n"
00609 "This is a repeatable argument.",
00610 action='append',
00611 default=[]),
00612 ],
00613 ),
00614
00615 dict(
00616 name='fs-dependants',
00617 help='Returns True if filesystem has a child '
00618 'dependency(clone/snapshot) exists',
00619 args=[
00620 dict(fs_id_opt),
00621 ],
00622 optional=[
00623 dict(name="--file", metavar="<FILE_PATH>",
00624 action="append", default=[],
00625 help="For file check\nThis is a repeatable argument."),
00626 ],
00627 ),
00628
00629 dict(
00630 name='fs-dependants-rm',
00631 help='Removes file system dependencies',
00632 args=[
00633 dict(fs_id_opt),
00634 ],
00635 optional=[
00636 dict(name="--file", action='append', default=[],
00637 help='File or files to remove dependencies for.\n'
00638 "This is a repeatable argument.",),
00639 ],
00640 ),
00641
00642
00643 dict(
00644 name='file-clone',
00645 help='Creates a clone of a file (thin provisioned)',
00646 args=[
00647 dict(fs_id_opt),
00648 dict(name="--src", metavar="<SRC_FILE_PATH>",
00649 help='source file to clone (relative path)\n'
00650 "This is a repeatable argument.",),
00651 dict(name="--dst", metavar="<DST_FILE_PATH>",
00652 help='Destination file (relative path)'
00653 ", this is a repeatable argument."),
00654 ],
00655 optional=[
00656 dict(name="--backing-snapshot", help='backing snapshot id'),
00657 ],
00658 ),
00659
00660 )
00661
00662 aliases = (
00663 ['ls', 'list --type systems'],
00664 ['lp', 'list --type pools'],
00665 ['lv', 'list --type volumes'],
00666 ['ld', 'list --type disks'],
00667 ['la', 'list --type access_groups'],
00668 ['lf', 'list --type fs'],
00669 ['lt', 'list --type target_ports'],
00670 ['c', 'capabilities'],
00671 ['p', 'plugin-info'],
00672 ['vc', 'volume-create'],
00673 ['vrc', 'volume-raid-create'],
00674 ['vrcc', 'volume-raid-create-cap'],
00675 ['vd', 'volume-delete'],
00676 ['vr', 'volume-resize'],
00677 ['vm', 'volume-mask'],
00678 ['vu', 'volume-unmask'],
00679 ['ve', 'volume-enable'],
00680 ['vi', 'volume-disable'],
00681 ['ac', 'access-group-create'],
00682 ['aa', 'access-group-add'],
00683 ['ar', 'access-group-remove'],
00684 ['ad', 'access-group-delete'],
00685 ['vri', 'volume-raid-info'],
00686 ['pmi', 'pool-member-info'],
00687 )
00688
00689
00690
00691
00692 class CmdLine:
00693 """
00694 Command line interface class.
00695 """
00696
00697
00698
00699
00700
00701
00702 def confirm_prompt(self, deleting):
00703 """
00704 Give the user a chance to bail.
00705 """
00706 if not self.args.force:
00707 msg = "will" if deleting else "may"
00708 out("Warning: You are about to do an operation that %s cause data "
00709 "to be lost!\nPress [Y|y] to continue, any other key to abort"
00710 % msg)
00711
00712 pressed = getch()
00713 if pressed.upper() == 'Y':
00714 return True
00715 else:
00716 out('Operation aborted!')
00717 return False
00718 else:
00719 return True
00720
00721
00722
00723
00724
00725 def display_data(self, objects):
00726 display_all = False
00727
00728 if len(objects) == 0:
00729 return
00730
00731 display_way = DisplayData.DISPLAY_WAY_DEFAULT
00732
00733 flag_with_header = True
00734 if self.args.sep:
00735 flag_with_header = False
00736 if self.args.header:
00737 flag_with_header = True
00738
00739 if self.args.script:
00740 display_way = DisplayData.DISPLAY_WAY_SCRIPT
00741
00742 DisplayData.display_data(
00743 objects, display_way=display_way, flag_human=self.args.human,
00744 flag_enum=self.args.enum,
00745 splitter=self.args.sep, flag_with_header=flag_with_header,
00746 flag_dsp_all_data=display_all)
00747
00748 def display_available_plugins(self):
00749 d = []
00750 sep = '<}{>'
00751 plugins = Client.available_plugins(sep)
00752
00753 for p in plugins:
00754 desc, version = p.split(sep)
00755 d.append(PlugData(desc, version))
00756
00757 self.display_data(d)
00758
00759 def handle_alias(self, args):
00760 cmd_arguments = args.cmd
00761 cmd_arguments.extend(self.unknown_args)
00762 new_args = self.parser.parse_args(cmd_arguments)
00763 new_args.func(new_args)
00764
00765
00766 def cli(self):
00767 """
00768 Command line interface parameters
00769 """
00770 parent_parser = ArgumentParser(add_help=False)
00771
00772 parent_parser.add_argument(
00773 '-v', '--version', action='version',
00774 version="%s %s" % (sys.argv[0], VERSION))
00775
00776 parent_parser.add_argument(
00777 '-u', '--uri', action="store", type=str, metavar='<URI>',
00778 dest="uri", help='Uniform resource identifier (env LSMCLI_URI)')
00779
00780 parent_parser.add_argument(
00781 '-P', '--prompt', action="store_true", dest="prompt",
00782 help='Prompt for password (env LSMCLI_PASSWORD)')
00783
00784 parent_parser.add_argument(
00785 '-H', '--human', action="store_true", dest="human",
00786 help='Print sizes in human readable format\n'
00787 '(e.g., MiB, GiB, TiB)')
00788
00789 parent_parser.add_argument(
00790 '-t', '--terse', action="store", dest="sep", metavar='<SEP>',
00791 help='Print output in terse form with "SEP" '
00792 'as a record separator')
00793
00794 parent_parser.add_argument(
00795 '-e', '--enum', action="store_true", dest="enum", default=False,
00796 help='Display enumerated types as numbers instead of text')
00797
00798 parent_parser.add_argument(
00799 '-f', '--force', action="store_true", dest="force", default=False,
00800 help='Bypass confirmation prompt for data loss operations')
00801
00802 parent_parser.add_argument(
00803 '-w', '--wait', action="store", type=int, dest="wait",
00804 default=30000, help="Command timeout value in ms (default = 30s)")
00805
00806 parent_parser.add_argument(
00807 '--header', action="store_true", dest="header",
00808 help='Include the header with terse')
00809
00810 parent_parser.add_argument(
00811 '-b', action="store_true", dest="async", default=False,
00812 help='Run the command async. Instead of waiting for completion.\n '
00813 'Command will exit(7) and job id written to stdout.')
00814
00815 parent_parser.add_argument(
00816 '-s', '--script', action="store_true", dest="script",
00817 default=False, help='Displaying data in script friendly way.')
00818
00819 parser = ArgumentParser(
00820 description='The libStorageMgmt command line interface.'
00821 ' Run %(prog)s <command> -h for more on each command.',
00822 epilog='Copyright 2012-2015 Red Hat, Inc.\n'
00823 'Please report bugs to '
00824 '<libstoragemgmt-devel@lists.fedorahosted.org>\n',
00825 formatter_class=RawTextHelpFormatter,
00826 parents=[parent_parser])
00827
00828 subparsers = parser.add_subparsers(metavar="command")
00829
00830
00831 for cmd in cmds:
00832 sub_parser = subparsers.add_parser(
00833 cmd['name'], help=cmd['help'], parents=[parent_parser],
00834 formatter_class=RawTextHelpFormatter)
00835
00836 group = sub_parser.add_argument_group("cmd required arguments")
00837 for arg in cmd.get('args', []):
00838 name = arg['name']
00839 del arg['name']
00840 group.add_argument(name, required=True, **arg)
00841
00842 group = sub_parser.add_argument_group("cmd optional arguments")
00843 for arg in cmd.get('optional', []):
00844 flags = arg['name']
00845 del arg['name']
00846 if not isinstance(flags, tuple):
00847 flags = (flags,)
00848 group.add_argument(*flags, **arg)
00849
00850 sub_parser.set_defaults(
00851 func=getattr(self, cmd['name'].replace("-", "_")))
00852
00853 for alias in aliases:
00854 sub_parser = subparsers.add_parser(
00855 alias[0], help="Alias of '%s'" % alias[1],
00856 parents=[parent_parser],
00857 formatter_class=RawTextHelpFormatter, add_help=False)
00858 sub_parser.set_defaults(
00859 cmd=alias[1].split(" "), func=self.handle_alias)
00860
00861 self.parser = parser
00862 known_agrs, self.unknown_args = parser.parse_known_args()
00863 return known_agrs
00864
00865
00866
00867 def display_nfs_client_authentication(self):
00868 """
00869 Dump the supported nfs client authentication types
00870 """
00871 if self.args.sep:
00872 out(self.args.sep.join(self.c.export_auth()))
00873 else:
00874 out(", ".join(self.c.export_auth()))
00875
00876
00877
00878 def list(self, args):
00879 search_key = None
00880 search_value = None
00881 if args.sys:
00882 search_key = 'system_id'
00883 search_value = args.sys
00884 if args.pool:
00885 search_key = 'pool_id'
00886 search_value = args.pool
00887 if args.vol:
00888 search_key = 'volume_id'
00889 search_value = args.vol
00890 if args.disk:
00891 search_key = 'disk_id'
00892 search_value = args.disk
00893 if args.ag:
00894 search_key = 'access_group_id'
00895 search_value = args.ag
00896 if args.fs:
00897 search_key = 'fs_id'
00898 search_value = args.ag
00899 if args.nfs_export:
00900 search_key = 'nfs_export_id'
00901 search_value = args.nfs_export
00902 if args.tgt:
00903 search_key = 'tgt_port_id'
00904 search_value = args.tgt
00905
00906 if args.type == 'VOLUMES':
00907 if search_key == 'volume_id':
00908 search_key = 'id'
00909 if search_key == 'access_group_id':
00910 lsm_ag = _get_item(self.c.access_groups(), args.ag,
00911 "Access Group", raise_error=False)
00912 if lsm_ag:
00913 return self.display_data(
00914 self.c.volumes_accessible_by_access_group(lsm_ag))
00915 else:
00916 return self.display_data([])
00917 elif search_key and search_key not in Volume.SUPPORTED_SEARCH_KEYS:
00918 raise ArgError("Search key '%s' is not supported by "
00919 "volume listing." % search_key)
00920 self.display_data(self.c.volumes(search_key, search_value))
00921 elif args.type == 'POOLS':
00922 if search_key == 'pool_id':
00923 search_key = 'id'
00924 if search_key and search_key not in Pool.SUPPORTED_SEARCH_KEYS:
00925 raise ArgError("Search key '%s' is not supported by "
00926 "pool listing." % search_key)
00927 self.display_data(
00928 self.c.pools(search_key, search_value))
00929 elif args.type == 'FS':
00930 if search_key == 'fs_id':
00931 search_key = 'id'
00932 if search_key and \
00933 search_key not in FileSystem.SUPPORTED_SEARCH_KEYS:
00934 raise ArgError("Search key '%s' is not supported by "
00935 "volume listing." % search_key)
00936 self.display_data(self.c.fs(search_key, search_value))
00937 elif args.type == 'SNAPSHOTS':
00938 if args.fs is None:
00939 raise ArgError("--fs <file system id> required")
00940 fs = _get_item(self.c.fs(), args.fs, 'File System')
00941 self.display_data(self.c.fs_snapshots(fs))
00942 elif args.type == 'EXPORTS':
00943 if search_key == 'nfs_export_id':
00944 search_key = 'id'
00945 if search_key and \
00946 search_key not in NfsExport.SUPPORTED_SEARCH_KEYS:
00947 raise ArgError("Search key '%s' is not supported by "
00948 "NFS Export listing" % search_key)
00949 self.display_data(self.c.exports(search_key, search_value))
00950 elif args.type == 'NFS_CLIENT_AUTH':
00951 self.display_nfs_client_authentication()
00952 elif args.type == 'ACCESS_GROUPS':
00953 if search_key == 'access_group_id':
00954 search_key = 'id'
00955 if search_key == 'volume_id':
00956 lsm_vol = _get_item(self.c.volumes(), args.vol,
00957 "Volume", raise_error=False)
00958 if lsm_vol:
00959 return self.display_data(
00960 self.c.access_groups_granted_to_volume(lsm_vol))
00961 else:
00962 return self.display_data([])
00963 elif (search_key and
00964 search_key not in AccessGroup.SUPPORTED_SEARCH_KEYS):
00965 raise ArgError("Search key '%s' is not supported by "
00966 "Access Group listing" % search_key)
00967 self.display_data(
00968 self.c.access_groups(search_key, search_value))
00969 elif args.type == 'SYSTEMS':
00970 if search_key:
00971 raise ArgError("System listing with search is not supported")
00972 self.display_data(self.c.systems())
00973 elif args.type == 'DISKS':
00974 if search_key == 'disk_id':
00975 search_key = 'id'
00976 if search_key and search_key not in Disk.SUPPORTED_SEARCH_KEYS:
00977 raise ArgError("Search key '%s' is not supported by "
00978 "disk listing" % search_key)
00979 self.display_data(
00980 self.c.disks(search_key, search_value))
00981 elif args.type == 'TARGET_PORTS':
00982 if search_key == 'tgt_port_id':
00983 search_key = 'id'
00984 if search_key and \
00985 search_key not in TargetPort.SUPPORTED_SEARCH_KEYS:
00986 raise ArgError("Search key '%s' is not supported by "
00987 "target port listing" % search_key)
00988 self.display_data(
00989 self.c.target_ports(search_key, search_value))
00990 elif args.type == 'PLUGINS':
00991 self.display_available_plugins()
00992 else:
00993 raise ArgError("unsupported listing type=%s" % args.type)
00994
00995
00996 def access_group_create(self, args):
00997 system = _get_item(self.c.systems(), args.sys, "System")
00998 (init_id, init_type) = parse_convert_init(args.init)
00999 access_group = self.c.access_group_create(args.name, init_id,
01000 init_type, system)
01001 self.display_data([access_group])
01002
01003 def _add_rm_access_grp_init(self, args, op):
01004 lsm_ag = _get_item(self.c.access_groups(), args.ag, "Access Group")
01005 (init_id, init_type) = parse_convert_init(args.init)
01006
01007 if op:
01008 return self.c.access_group_initiator_add(lsm_ag, init_id,
01009 init_type)
01010 else:
01011 return self.c.access_group_initiator_delete(lsm_ag, init_id,
01012 init_type)
01013
01014
01015 def access_group_add(self, args):
01016 self.display_data([self._add_rm_access_grp_init(args, True)])
01017
01018
01019 def access_group_remove(self, args):
01020 self.display_data([self._add_rm_access_grp_init(args, False)])
01021
01022 def access_group_volumes(self, args):
01023 agl = self.c.access_groups()
01024 group = _get_item(agl, args.ag, "Access Group")
01025 vols = self.c.volumes_accessible_by_access_group(group)
01026 self.display_data(vols)
01027
01028 def iscsi_chap(self, args):
01029 (init_id, init_type) = parse_convert_init(args.init)
01030 if init_type != AccessGroup.INIT_TYPE_ISCSI_IQN:
01031 raise ArgError("--init-id %s is not a valid iSCSI IQN" % args.init)
01032
01033 self.c.iscsi_chap_auth(init_id, args.in_user,
01034 self.args.in_pass,
01035 self.args.out_user,
01036 self.args.out_pass)
01037
01038 def volume_access_group(self, args):
01039 vol = _get_item(self.c.volumes(), args.vol, "Volume")
01040 groups = self.c.access_groups_granted_to_volume(vol)
01041 self.display_data(groups)
01042
01043
01044 def access_group_delete(self, args):
01045 agl = self.c.access_groups()
01046 group = _get_item(agl, args.ag, "Access Group")
01047 return self.c.access_group_delete(group)
01048
01049
01050 def fs_delete(self, args):
01051 fs = _get_item(self.c.fs(), args.fs, "File System")
01052 if self.confirm_prompt(True):
01053 self._wait_for_it("fs-delete", self.c.fs_delete(fs), None)
01054
01055
01056 def fs_create(self, args):
01057 p = _get_item(self.c.pools(), args.pool, "Pool")
01058 fs = self._wait_for_it("fs-create",
01059 *self.c.fs_create(p, args.name,
01060 self._size(args.size)))
01061 self.display_data([fs])
01062
01063
01064 def fs_resize(self, args):
01065 fs = _get_item(self.c.fs(), args.fs, "File System")
01066 size = self._size(args.size)
01067
01068 if self.confirm_prompt(False):
01069 fs = self._wait_for_it("fs-resize",
01070 *self.c.fs_resize(fs, size))
01071 self.display_data([fs])
01072
01073
01074 def fs_clone(self, args):
01075 src_fs = _get_item(
01076 self.c.fs(), args.src_fs, "Source File System")
01077
01078 ss = None
01079 if args.backing_snapshot:
01080
01081 ss = _get_item(self.c.fs_snapshots(src_fs),
01082 args.backing_snapshot, "Snapshot")
01083
01084 fs = self._wait_for_it(
01085 "fs_clone", *self.c.fs_clone(src_fs, args.dst_name, ss))
01086 self.display_data([fs])
01087
01088
01089 def file_clone(self, args):
01090 fs = _get_item(self.c.fs(), args.fs, "File System")
01091 if self.args.backing_snapshot:
01092
01093 ss = _get_item(self.c.fs_snapshots(fs),
01094 args.backing_snapshot, "Snapshot")
01095 else:
01096 ss = None
01097
01098 self._wait_for_it(
01099 "fs_file_clone", self.c.fs_file_clone(fs, args.src, args.dst, ss),
01100 None)
01101
01102
01103
01104
01105 @staticmethod
01106 def _size(s):
01107 size_bytes = size_human_2_size_bytes(s)
01108 if size_bytes <= 0:
01109 raise ArgError("Incorrect size argument format: '%s'" % s)
01110 return size_bytes
01111
01112 def _cp(self, cap, val):
01113 if self.args.sep is not None:
01114 s = self.args.sep
01115 else:
01116 s = ':'
01117
01118 if val:
01119 v = "SUPPORTED"
01120 else:
01121 v = "UNSUPPORTED"
01122
01123 out("%s%s%s" % (cap, s, v))
01124
01125 def capabilities(self, args):
01126 s = _get_item(self.c.systems(), args.sys, "System")
01127
01128 cap = self.c.capabilities(s)
01129 sup_caps = sorted(cap.get_supported().values())
01130 all_caps = sorted(cap.get_supported(True).values())
01131
01132 sep = DisplayData.DEFAULT_SPLITTER
01133 if self.args.sep is not None:
01134 sep = self.args.sep
01135
01136 cap_data = OrderedDict()
01137
01138 for v in sup_caps:
01139 cap_data[v] = 'SUPPORTED'
01140
01141 for v in all_caps:
01142 if v not in sup_caps:
01143 cap_data[v] = 'UNSUPPORTED'
01144
01145 DisplayData.display_data_script_way([cap_data], sep)
01146
01147 def plugin_info(self, args):
01148 desc, version = self.c.plugin_info()
01149
01150 if args.sep:
01151 out("%s%s%s" % (desc, args.sep, version))
01152 else:
01153 out("Description: %s Version: %s" % (desc, version))
01154
01155
01156 def volume_create(self, args):
01157
01158 p = _get_item(self.c.pools(), args.pool, "Pool")
01159 vol = self._wait_for_it(
01160 "volume-create",
01161 *self.c.volume_create(
01162 p,
01163 args.name,
01164 self._size(args.size),
01165 vol_provision_str_to_type(args.provisioning)))
01166 self.display_data([vol])
01167
01168
01169 def fs_snap_create(self, args):
01170
01171 fs = _get_item(self.c.fs(), args.fs, "File System")
01172 ss = self._wait_for_it("snapshot-create",
01173 *self.c.fs_snapshot_create(
01174 fs,
01175 args.name))
01176
01177 self.display_data([ss])
01178
01179
01180 def fs_snap_restore(self, args):
01181
01182 fs = _get_item(self.c.fs(), args.fs, "File System")
01183 ss = _get_item(self.c.fs_snapshots(fs), args.snap, "Snapshot")
01184
01185 flag_all_files = True
01186
01187 if self.args.file:
01188 flag_all_files = False
01189 if self.args.fileas:
01190 if len(self.args.file) != len(self.args.fileas):
01191 raise ArgError(
01192 "number of --file not equal to --fileas")
01193
01194 if self.confirm_prompt(True):
01195 self._wait_for_it(
01196 'fs-snap-restore',
01197 self.c.fs_snapshot_restore(
01198 fs, ss, self.args.file, self.args.fileas, flag_all_files),
01199 None)
01200
01201
01202 def volume_delete(self, args):
01203 v = _get_item(self.c.volumes(), args.vol, "Volume")
01204 if self.confirm_prompt(True):
01205 self._wait_for_it("volume-delete", self.c.volume_delete(v),
01206 None)
01207
01208
01209 def fs_snap_delete(self, args):
01210 fs = _get_item(self.c.fs(), args.fs, "File System")
01211 ss = _get_item(self.c.fs_snapshots(fs), args.snap, "Snapshot")
01212
01213 if self.confirm_prompt(True):
01214 self._wait_for_it("fs_snap_delete",
01215 self.c.fs_snapshot_delete(fs, ss), None)
01216
01217
01218
01219
01220
01221
01222 def _wait_for_it(self, msg, job, item):
01223 if not job:
01224 return item
01225 else:
01226
01227
01228 if self.args.async:
01229 out(job)
01230 self.shutdown(ErrorNumber.JOB_STARTED)
01231
01232 while True:
01233 (s, percent, item) = self.c.job_status(job)
01234
01235 if s == JobStatus.INPROGRESS:
01236
01237
01238 time.sleep(0.25)
01239 elif s == JobStatus.COMPLETE:
01240 self.c.job_free(job)
01241 return item
01242 else:
01243
01244 raise ArgError(msg + " job error code= " + str(s))
01245
01246
01247 def job_status(self, args):
01248 (s, percent, item) = self.c.job_status(args.job)
01249
01250 if s == JobStatus.COMPLETE:
01251 if item:
01252 self.display_data([item])
01253
01254 self.c.job_free(args.job)
01255 else:
01256 out(str(percent))
01257 self.shutdown(ErrorNumber.JOB_STARTED)
01258
01259
01260 def volume_replicate(self, args):
01261 p = None
01262 if args.pool:
01263 p = _get_item(self.c.pools(), args.pool, "Pool")
01264
01265 v = _get_item(self.c.volumes(), args.vol, "Volume")
01266
01267 rep_type = vol_rep_type_str_to_type(args.rep_type)
01268 if rep_type == Volume.REPLICATE_UNKNOWN:
01269 raise ArgError("invalid replication type= %s" % rep_type)
01270
01271 vol = self._wait_for_it(
01272 "replicate volume",
01273 *self.c.volume_replicate(p, rep_type, v, args.name))
01274 self.display_data([vol])
01275
01276
01277 def volume_replicate_range(self, args):
01278 src = _get_item(self.c.volumes(), args.src_vol, "Source Volume")
01279 dst = _get_item(self.c.volumes(), args.dst_vol,
01280 "Destination Volume")
01281
01282 rep_type = vol_rep_type_str_to_type(args.rep_type)
01283 if rep_type == Volume.REPLICATE_UNKNOWN:
01284 raise ArgError("invalid replication type= %s" % rep_type)
01285
01286 src_starts = args.src_start
01287 dst_starts = args.dst_start
01288 counts = args.count
01289
01290 if not len(src_starts) \
01291 or not (len(src_starts) == len(dst_starts) == len(counts)):
01292 raise ArgError("Differing numbers of src_start, dest_start, "
01293 "and count parameters")
01294
01295 ranges = []
01296 for b in range(len(src_starts)):
01297 ranges.append(BlockRange(src_starts[b], dst_starts[b], counts[b]))
01298
01299 if self.confirm_prompt(False):
01300 self.c.volume_replicate_range(rep_type, src, dst, ranges)
01301
01302
01303
01304
01305 def volume_replicate_range_block_size(self, args):
01306 s = _get_item(self.c.systems(), args.sys, "System")
01307 out(self.c.volume_replicate_range_block_size(s))
01308
01309 def volume_mask(self, args):
01310 vol = _get_item(self.c.volumes(), args.vol, 'Volume')
01311 ag = _get_item(self.c.access_groups(), args.ag, 'Access Group')
01312 self.c.volume_mask(ag, vol)
01313
01314 def volume_unmask(self, args):
01315 ag = _get_item(self.c.access_groups(), args.ag, "Access Group")
01316 vol = _get_item(self.c.volumes(), args.vol, "Volume")
01317 return self.c.volume_unmask(ag, vol)
01318
01319
01320 def volume_resize(self, args):
01321 v = _get_item(self.c.volumes(), args.vol, "Volume")
01322 size = self._size(args.size)
01323
01324 if self.confirm_prompt(False):
01325 vol = self._wait_for_it("resize",
01326 *self.c.volume_resize(v, size))
01327 self.display_data([vol])
01328
01329
01330 def volume_enable(self, args):
01331 v = _get_item(self.c.volumes(), args.vol, "Volume")
01332 self.c.volume_enable(v)
01333
01334
01335 def volume_disable(self, args):
01336 v = _get_item(self.c.volumes(), args.vol, "Volume")
01337 self.c.volume_disable(v)
01338
01339
01340 def fs_unexport(self, args):
01341 export = _get_item(self.c.exports(), args.export, "NFS Export")
01342 self.c.export_remove(export)
01343
01344
01345 def fs_export(self, args):
01346 fs = _get_item(self.c.fs(), args.fs, "File System")
01347
01348
01349 if len(args.rw_host) == 0 \
01350 and len(args.ro_host) == 0:
01351 raise ArgError(" please specify --ro-host or --rw-host")
01352
01353 export = self.c.export_fs(
01354 fs.id,
01355 args.exportpath,
01356 args.root_host,
01357 args.rw_host,
01358 args.ro_host,
01359 args.anonuid,
01360 args.anongid,
01361 args.auth_type,
01362 None)
01363 self.display_data([export])
01364
01365
01366 def volume_dependants(self, args):
01367 v = _get_item(self.c.volumes(), args.vol, "Volume")
01368 rc = self.c.volume_child_dependency(v)
01369 out(rc)
01370
01371
01372 def volume_dependants_rm(self, args):
01373 v = _get_item(self.c.volumes(), args.vol, "Volume")
01374 self._wait_for_it("volume-dependant-rm",
01375 self.c.volume_child_dependency_rm(v), None)
01376
01377 def volume_raid_info(self, args):
01378 lsm_vol = _get_item(self.c.volumes(), args.vol, "Volume")
01379 self.display_data(
01380 [
01381 VolumeRAIDInfo(
01382 lsm_vol.id, *self.c.volume_raid_info(lsm_vol))])
01383
01384 def pool_member_info(self, args):
01385 lsm_pool = _get_item(self.c.pools(), args.pool, "Pool")
01386 self.display_data(
01387 [
01388 PoolRAIDInfo(
01389 lsm_pool.id, *self.c.pool_member_info(lsm_pool))])
01390
01391 def volume_raid_create(self, args):
01392 raid_type = VolumeRAIDInfo.raid_type_str_to_lsm(args.raid_type)
01393
01394 all_lsm_disks = self.c.disks()
01395 lsm_disks = [d for d in all_lsm_disks if d.id in args.disk]
01396 if len(lsm_disks) != len(args.disk):
01397 raise LsmError(
01398 ErrorNumber.NOT_FOUND_DISK,
01399 "Disk ID %s not found" %
01400 ', '.join(set(args.disk) - set(d.id for d in all_lsm_disks)))
01401
01402 busy_disks = [d.id for d in lsm_disks
01403 if not d.status & Disk.STATUS_FREE]
01404
01405 if len(busy_disks) >= 1:
01406 raise LsmError(
01407 ErrorNumber.DISK_NOT_FREE,
01408 "Disk %s is not free" % ", ".join(busy_disks))
01409
01410 if args.strip_size:
01411 strip_size = size_human_2_size_bytes(args.strip_size)
01412 else:
01413 strip_size = Volume.VCR_STRIP_SIZE_DEFAULT
01414
01415 self.display_data([
01416 self.c.volume_raid_create(
01417 args.name, raid_type, lsm_disks, strip_size)])
01418
01419 def volume_raid_create_cap(self, args):
01420 lsm_sys = _get_item(self.c.systems(), args.sys, "System")
01421 self.display_data([
01422 VcrCap(lsm_sys.id, *self.c.volume_raid_create_cap_get(lsm_sys))])
01423
01424
01425 def fs_dependants(self, args):
01426 fs = _get_item(self.c.fs(), args.fs, "File System")
01427 rc = self.c.fs_child_dependency(fs, args.file)
01428 out(rc)
01429
01430
01431 def fs_dependants_rm(self, args):
01432 fs = _get_item(self.c.fs(), args.fs, "File System")
01433 self._wait_for_it("fs-dependants-rm",
01434 self.c.fs_child_dependency_rm(fs,
01435 args.file),
01436 None)
01437
01438 def _read_configfile(self):
01439 """
01440 Set uri from config file. Will be overridden by cmdline option or
01441 env var if present.
01442 """
01443
01444 allowed_config_options = ("uri",)
01445
01446 config_path = os.path.expanduser("~") + "/.lsmcli"
01447 if not os.path.exists(config_path):
01448 return
01449
01450 with open(config_path) as f:
01451 for line in f:
01452
01453 if line.lstrip().startswith("#"):
01454 continue
01455
01456 try:
01457 name, val = [x.strip() for x in line.split("=", 1)]
01458 if name in allowed_config_options:
01459 setattr(self, name, val)
01460 except ValueError:
01461 pass
01462
01463
01464 def __init__(self):
01465 self.uri = None
01466 self.c = None
01467 self.parser = None
01468 self.unknown_args = None
01469 self.args = self.cli()
01470
01471 self.cleanup = None
01472
01473 self.tmo = int(self.args.wait)
01474 if not self.tmo or self.tmo < 0:
01475 raise ArgError("[-w|--wait] requires a non-zero positive integer")
01476
01477 self._read_configfile()
01478 if os.getenv('LSMCLI_URI') is not None:
01479 self.uri = os.getenv('LSMCLI_URI')
01480 self.password = os.getenv('LSMCLI_PASSWORD')
01481 if self.args.uri is not None:
01482 self.uri = self.args.uri
01483
01484 if self.uri is None:
01485
01486
01487
01488
01489
01490 if ('type' in self.args and self.args.type == "PLUGINS"):
01491 self.uri = "sim://"
01492 self.password = None
01493 else:
01494 raise ArgError("--uri missing or export LSMCLI_URI")
01495
01496
01497 if self.args.prompt:
01498 self.password = getpass.getpass()
01499
01500 if self.password is not None:
01501
01502 u = uri_parse(self.uri)
01503 if u['username'] is None:
01504 raise ArgError("password specified with no user name in uri")
01505
01506
01507
01508 def shutdown(self, ec=None):
01509 if self.cleanup:
01510 self.cleanup()
01511
01512 if ec:
01513 sys.exit(ec)
01514
01515
01516
01517 def process(self, cli=None):
01518 """
01519 Process the parsed command.
01520 """
01521 if cli:
01522
01523
01524 self.c = Proxy(cli())
01525 self.c.plugin_register(self.uri, self.password, self.tmo)
01526 self.cleanup = self.c.plugin_unregister
01527 else:
01528
01529 self.c = Proxy(Client(self.uri, self.password, self.tmo))
01530
01531 if os.getenv('LSM_DEBUG_PLUGIN'):
01532 raw_input(
01533 "Attach debugger to plug-in, press <return> when ready...")
01534
01535 self.cleanup = self.c.close
01536
01537 self.args.func(self.args)
01538 self.shutdown()