]>
git.proxmox.com Git - ceph.git/blob - ceph/src/pybind/ceph_argparse.py
2 Types and routines used by the ceph CLI as well as the RESTful
3 interface. These have to do with querying the daemons for
4 command-description information, validating user command input against
5 those descriptions, and submitting the command to the appropriate
8 Copyright (C) 2013 Inktank Storage, Inc.
10 LGPL2. See file COPYING.
12 from __future__
import print_function
26 FLAG_MGR
= 8 # command is intended for mgr
35 class ArgumentError(Exception):
37 Something wrong with arguments
42 class ArgumentNumber(ArgumentError
):
44 Wrong number of a repeated argument
49 class ArgumentFormat(ArgumentError
):
51 Argument value has wrong format
56 class ArgumentValid(ArgumentError
):
58 Argument value is otherwise invalid (doesn't match choices, for instance)
63 class ArgumentTooFew(ArgumentError
):
65 Fewer arguments than descriptors in signature; may mean to continue
66 the search, so gets a special exception type
70 class ArgumentPrefix(ArgumentError
):
72 Special for mismatched prefix; less severe, don't report by default
77 class JsonFormat(Exception):
79 some syntactic or semantic issue with the JSON
84 class CephArgtype(object):
86 Base class for all Ceph argument types
88 Instantiating an object sets any validation parameters
89 (allowable strings, numeric ranges, etc.). The 'valid'
90 method validates a string against that initialized instance,
91 throwing ArgumentError if there's a problem.
93 def __init__(self
, **kwargs
):
95 set any per-instance validation parameters here
96 from kwargs (fixed string sets, integer ranges, etc)
100 def valid(self
, s
, partial
=False):
102 Run validation against given string s (generally one word);
103 partial means to accept partial string matches (begins-with).
104 If cool, set self.val to the value that should be returned
105 (a copy of the input string, or a numeric or boolean interpretation
106 thereof, for example)
107 if not, throw ArgumentError(msg-as-to-why)
113 return string representation of description of type. Note,
114 this is not a representation of the actual value. Subclasses
115 probably also override __str__() to give a more user-friendly
116 'name/type' description for use in command format help messages.
119 if hasattr(self
, 'typeargs'):
121 return '{0}(\'{1}\')'.format(self
.__class
__.__name
__, a
)
125 where __repr__ (ideally) returns a string that could be used to
126 reproduce the object, __str__ returns one you'd like to see in
127 print messages. Use __str__ to format the argtype descriptor
128 as it would be useful in a command usage message.
130 return '<{0}>'.format(self
.__class
__.__name
__)
132 def complete(self
, s
):
136 class CephInt(CephArgtype
):
138 range-limited integers, [+|-][0-9]+ or 0x[0-9a-f]+
139 range: list of 1 or 2 ints, [min] or [min,max]
141 def __init__(self
, range=''):
145 self
.range = list(range.split('|'))
146 self
.range = [int(x
) for x
in self
.range]
148 def valid(self
, s
, partial
=False):
152 raise ArgumentValid("{0} doesn't represent an int".format(s
))
153 if len(self
.range) == 2:
154 if val
< self
.range[0] or val
> self
.range[1]:
155 raise ArgumentValid("{0} not in range {1}".format(val
, self
.range))
156 elif len(self
.range) == 1:
157 if val
< self
.range[0]:
158 raise ArgumentValid("{0} not in range {1}".format(val
, self
.range))
163 if len(self
.range) == 1:
164 r
= '[{0}-]'.format(self
.range[0])
165 if len(self
.range) == 2:
166 r
= '[{0}-{1}]'.format(self
.range[0], self
.range[1])
168 return '<int{0}>'.format(r
)
171 class CephFloat(CephArgtype
):
173 range-limited float type
174 range: list of 1 or 2 floats, [min] or [min, max]
176 def __init__(self
, range=''):
180 self
.range = list(range.split('|'))
181 self
.range = [float(x
) for x
in self
.range]
183 def valid(self
, s
, partial
=False):
187 raise ArgumentValid("{0} doesn't represent a float".format(s
))
188 if len(self
.range) == 2:
189 if val
< self
.range[0] or val
> self
.range[1]:
190 raise ArgumentValid("{0} not in range {1}".format(val
, self
.range))
191 elif len(self
.range) == 1:
192 if val
< self
.range[0]:
193 raise ArgumentValid("{0} not in range {1}".format(val
, self
.range))
198 if len(self
.range) == 1:
199 r
= '[{0}-]'.format(self
.range[0])
200 if len(self
.range) == 2:
201 r
= '[{0}-{1}]'.format(self
.range[0], self
.range[1])
202 return '<float{0}>'.format(r
)
205 class CephString(CephArgtype
):
207 String; pretty generic. goodchars is a RE char class of valid chars
209 def __init__(self
, goodchars
=''):
210 from string
import printable
212 re
.compile(goodchars
)
214 raise ValueError('CephString(): "{0}" is not a valid RE'.
216 self
.goodchars
= goodchars
217 self
.goodset
= frozenset(
218 [c
for c
in printable
if re
.match(goodchars
, c
)]
221 def valid(self
, s
, partial
=False):
223 if self
.goodset
and not sset
<= self
.goodset
:
224 raise ArgumentFormat("invalid chars {0} in {1}".
225 format(''.join(sset
- self
.goodset
), s
))
231 b
+= '(goodchars {0})'.format(self
.goodchars
)
232 return '<string{0}>'.format(b
)
234 def complete(self
, s
):
241 class CephSocketpath(CephArgtype
):
243 Admin socket path; check that it's readable and S_ISSOCK
245 def valid(self
, s
, partial
=False):
246 mode
= os
.stat(s
).st_mode
247 if not stat
.S_ISSOCK(mode
):
248 raise ArgumentValid('socket path {0} is not a socket'.format(s
))
252 return '<admin-socket-path>'
255 class CephIPAddr(CephArgtype
):
257 IP address (v4 or v6) with optional port
259 def valid(self
, s
, partial
=False):
260 # parse off port, use socket to validate addr
262 if s
.startswith('['):
264 elif s
.find('.') != -1:
272 raise ArgumentValid('{0}: invalid IPv4 port'.format(p
))
277 socket
.inet_pton(socket
.AF_INET
, a
)
279 raise ArgumentValid('{0}: invalid IPv4 address'.format(a
))
282 if s
.startswith('['):
285 raise ArgumentFormat('{0} missing terminating ]'.format(s
))
286 if s
[end
+ 1] == ':':
290 raise ArgumentValid('{0}: bad port number'.format(s
))
296 socket
.inet_pton(socket
.AF_INET6
, a
)
298 raise ArgumentValid('{0} not valid IPv6 address'.format(s
))
299 if p
is not None and int(p
) > 65535:
300 raise ArgumentValid("{0} not a valid port number".format(p
))
306 return '<IPaddr[:port]>'
309 class CephEntityAddr(CephIPAddr
):
311 EntityAddress, that is, IP address[/nonce]
313 def valid(self
, s
, partial
=False):
316 ip
, nonce
= s
.split('/')
319 super(self
.__class
__, self
).valid(ip
)
323 nonce_int
= int(nonce
)
326 if nonce_int
is None or nonce_int
< 0:
328 '{0}: invalid entity, nonce {1} not integer > 0'.
334 return '<EntityAddr>'
337 class CephPoolname(CephArgtype
):
339 Pool name; very little utility
345 class CephObjectname(CephArgtype
):
347 Object name. Maybe should be combined with Pool name as they're always
348 present in pairs, and then could be checked for presence
351 return '<objectname>'
354 class CephPgid(CephArgtype
):
356 pgid, in form N.xxx (N = pool number, xxx = hex pgnum)
358 def valid(self
, s
, partial
=False):
359 if s
.find('.') == -1:
360 raise ArgumentFormat('pgid has no .')
361 poolid
, pgnum
= s
.split('.', 1)
365 raise ArgumentFormat('pool {0} not integer'.format(poolid
))
367 raise ArgumentFormat('pool {0} < 0'.format(poolid
))
369 pgnum
= int(pgnum
, 16)
371 raise ArgumentFormat('pgnum {0} not hex integer'.format(pgnum
))
378 class CephName(CephArgtype
):
380 Name (type.id) where:
381 type is osd|mon|client|mds
382 id is a base10 int, if type == osd, or a string otherwise
390 def valid(self
, s
, partial
=False):
395 self
.nametype
= "mgr"
399 self
.nametype
= "mon"
402 if s
.find('.') == -1:
403 raise ArgumentFormat('CephName: no . in {0}'.format(s
))
405 t
, i
= s
.split('.', 1)
406 if t
not in ('osd', 'mon', 'client', 'mds', 'mgr'):
407 raise ArgumentValid('unknown type ' + t
)
413 raise ArgumentFormat('osd id ' + i
+ ' not integer')
419 return '<name (type.id)>'
422 class CephOsdName(CephArgtype
):
424 Like CephName, but specific to osds: allow <id> alone
426 osd.<id>, or <id>, or *, where id is a base10 int
432 def valid(self
, s
, partial
=False):
436 if s
.find('.') != -1:
437 t
, i
= s
.split('.', 1)
439 raise ArgumentValid('unknown type ' + t
)
446 raise ArgumentFormat('osd id ' + i
+ ' not integer')
448 raise ArgumentFormat('osd id {0} is less than 0'.format(i
))
454 return '<osdname (id|osd.id)>'
457 class CephChoices(CephArgtype
):
459 Set of string literals; init with valid choices
461 def __init__(self
, strings
='', **kwargs
):
462 self
.strings
= strings
.split('|')
464 def valid(self
, s
, partial
=False):
466 if s
not in self
.strings
:
467 # show as __str__ does: {s1|s2..}
468 raise ArgumentValid("{0} not in {1}".format(s
, self
))
473 for t
in self
.strings
:
477 raise ArgumentValid("{0} not in {1}". format(s
, self
))
480 if len(self
.strings
) == 1:
481 return '{0}'.format(self
.strings
[0])
483 return '{0}'.format('|'.join(self
.strings
))
485 def complete(self
, s
):
486 all_elems
= [token
for token
in self
.strings
if token
.startswith(s
)]
490 class CephFilepath(CephArgtype
):
494 def valid(self
, s
, partial
=False):
497 except Exception as e
:
498 raise ArgumentValid('can\'t open {0}: {1}'.format(s
, e
))
503 return '<outfilename>'
506 class CephFragment(CephArgtype
):
510 def valid(self
, s
, partial
=False):
511 if s
.find('/') == -1:
512 raise ArgumentFormat('{0}: no /'.format(s
))
513 val
, bits
= s
.split('/')
515 if not val
.startswith('0x'):
516 raise ArgumentFormat("{0} not a hex integer".format(val
))
520 raise ArgumentFormat('can\'t convert {0} to integer'.format(val
))
524 raise ArgumentFormat('can\'t convert {0} to integer'.format(bits
))
528 return "<CephFS fragment ID (0xvvv/bbb)>"
531 class CephUUID(CephArgtype
):
533 CephUUID: pretty self-explanatory
535 def valid(self
, s
, partial
=False):
538 except Exception as e
:
539 raise ArgumentFormat('invalid UUID {0}: {1}'.format(s
, e
))
546 class CephPrefix(CephArgtype
):
548 CephPrefix: magic type for "all the first n fixed strings"
550 def __init__(self
, prefix
=''):
553 def valid(self
, s
, partial
=False):
556 if isinstance(s
, bytes
):
557 # `prefix` can always be converted into unicode when being compared,
558 # but `s` could be anything passed by user.
559 s
= s
.decode('ascii')
560 except UnicodeEncodeError:
561 raise ArgumentPrefix(u
"no match for {0}".format(s
))
562 except UnicodeDecodeError:
563 raise ArgumentPrefix("no match for {0}".format(s
))
566 if self
.prefix
.startswith(s
):
574 raise ArgumentPrefix("no match for {0}".format(s
))
579 def complete(self
, s
):
580 if self
.prefix
.startswith(s
):
581 return [self
.prefix
.rstrip(' ')]
586 class argdesc(object):
588 argdesc(typename, name='name', n=numallowed|N,
589 req=False, helptext=helptext, **kwargs (type-specific))
592 typename: type(**kwargs) will be constructed
593 later, type.valid(w) will be called with a word in that position
595 name is used for parse errors and for constructing JSON output
596 n is a numeric literal or 'n|N', meaning "at least one, but maybe more"
597 req=False means the argument need not be present in the list
598 helptext is the associated help for the command
599 anything else are arguments to pass to the type constructor.
601 self.instance is an instance of type t constructed with typeargs.
603 valid() will later be called with input to validate against it,
604 and will store the validated value in self.instance.val for extraction.
606 def __init__(self
, t
, name
=None, n
=1, req
=True, **kwargs
):
607 if isinstance(t
, basestring
):
609 self
.typeargs
= {'prefix': t
}
613 self
.typeargs
= kwargs
614 self
.req
= bool(req
== True or req
== 'True')
617 self
.N
= (n
in ['n', 'N'])
622 self
.instance
= self
.t(**self
.typeargs
)
625 r
= 'argdesc(' + str(self
.t
) + ', '
626 internals
= ['N', 'typeargs', 'instance', 't']
627 for (k
, v
) in self
.__dict
__.items():
628 if k
.startswith('__') or k
in internals
:
631 # undo modification from __init__
632 if k
== 'n' and self
.N
:
634 r
+= '{0}={1}, '.format(k
, v
)
635 for (k
, v
) in self
.typeargs
.items():
636 r
+= '{0}={1}, '.format(k
, v
)
640 if ((self
.t
== CephChoices
and len(self
.instance
.strings
) == 1)
641 or (self
.t
== CephPrefix
)):
642 s
= str(self
.instance
)
644 s
= '{0}({1})'.format(self
.name
, str(self
.instance
))
646 s
+= ' [' + str(self
.instance
) + '...]'
653 like str(), but omit parameter names (except for CephString,
654 which really needs them)
656 if self
.t
== CephString
:
657 chunk
= '<{0}>'.format(self
.name
)
659 chunk
= str(self
.instance
)
662 s
+= ' [' + chunk
+ '...]'
667 def complete(self
, s
):
668 return self
.instance
.complete(s
)
671 def concise_sig(sig
):
673 Return string representation of sig useful for syntax reference in help
675 return ' '.join([d
.helpstr() for d
in sig
])
678 def descsort_key(sh
):
680 sort descriptors by prefixes, defined as the concatenation of all simple
681 strings in the descriptor; this works out to just the leading strings.
683 return concise_sig(sh
['sig'])
686 def descsort(sh1
, sh2
):
688 Deprecated; use (key=descsort_key) instead of (cmp=descsort)
690 return cmp(descsort_key(sh1
), descsort_key(sh2
))
693 def parse_funcsig(sig
):
695 parse a single descriptor (array of strings or dicts) into a
696 dict of function descriptor/validators (objects of CephXXX type)
702 if isinstance(desc
, basestring
):
704 desc
= {'type': t
, 'name': 'prefix', 'prefix': desc
}
706 # not a simple string, must be dict
707 if 'type' not in desc
:
708 s
= 'JSON descriptor {0} has no type'.format(sig
)
710 # look up type string in our globals() dict; if it's an
711 # object of type `type`, it must be a
712 # locally-defined class. otherwise, we haven't a clue.
713 if desc
['type'] in globals():
714 t
= globals()[desc
['type']]
715 if not isinstance(t
, type):
716 s
= 'unknown type {0}'.format(desc
['type'])
719 s
= 'unknown type {0}'.format(desc
['type'])
723 for key
, val
in desc
.items():
724 if key
not in ['type', 'name', 'n', 'req']:
726 newsig
.append(argdesc(t
,
727 name
=desc
.get('name', None),
729 req
=desc
.get('req', True),
734 def parse_json_funcsigs(s
, consumer
):
736 A function signature is mostly an array of argdesc; it's represented
739 "cmd001": {"sig":[ "type": type, "name": name, "n": num, "req":true|false <other param>], "help":helptext, "module":modulename, "perm":perms, "avail":availability}
745 A set of sigs is in an dict mapped by a unique number:
748 "sig": ["type.. ], "help":helptext...
751 "sig": [.. ], "help":helptext...
755 Parse the string s and return a dict of dicts, keyed by opcode;
756 each dict contains 'sig' with the array of descriptors, and 'help'
757 with the helptext, 'module' with the module name, 'perm' with a
758 string representing required permissions in that module to execute
759 this command (and also whether it is a read or write command from
760 the cluster state perspective), and 'avail' as a hint for
761 whether the command should be advertised by CLI, REST, or both.
762 If avail does not contain 'consumer', don't include the command
763 in the returned dict.
766 overall
= json
.loads(s
)
767 except Exception as e
:
768 print("Couldn't parse JSON {0}: {1}".format(s
, e
), file=sys
.stderr
)
771 for cmdtag
, cmd
in overall
.items():
773 s
= "JSON descriptor {0} has no 'sig'".format(cmdtag
)
775 # check 'avail' and possibly ignore this command
777 if consumer
not in cmd
['avail']:
779 # rewrite the 'sig' item with the argdesc-ized version, and...
780 cmd
['sig'] = parse_funcsig(cmd
['sig'])
781 # just take everything else as given
782 sigdict
[cmdtag
] = cmd
786 def validate_one(word
, desc
, partial
=False):
788 validate_one(word, desc, partial=False)
790 validate word against the constructed instance of the type
791 in desc. May raise exception. If it returns false (and doesn't
792 raise an exception), desc.instance.val will
793 contain the validated value (in the appropriate type).
795 desc
.instance
.valid(word
, partial
)
798 desc
.n
= desc
.numseen
+ 1
801 def matchnum(args
, signature
, partial
=False):
803 matchnum(s, signature, partial=False)
805 Returns number of arguments matched in s against signature.
806 Can be used to determine most-likely command for full or partial
807 matches (partial applies to string matches).
810 mysig
= copy
.deepcopy(signature
)
813 setattr(desc
, 'numseen', 0)
814 while desc
.numseen
< desc
.n
:
815 # if there are no more arguments, return
821 # only allow partial matching if we're on the last supplied
822 # word; avoid matching foo bar and foot bar just because
824 validate_one(word
, desc
, partial
and (len(words
) == 0))
826 except ArgumentError
:
827 # matchnum doesn't care about type of error
832 # this wasn't required, so word may match the next desc
833 words
.insert(0, word
)
836 # it was required, and didn't match, return
843 def get_next_arg(desc
, args
):
845 Get either the value matching key 'desc.name' or the next arg in
846 the non-dict list. Return None if args are exhausted. Used in
850 if isinstance(args
, dict):
851 arg
= args
.pop(desc
.name
, None)
852 # allow 'param=param' to be expressed as 'param'
855 # Hack, or clever? If value is a list, keep the first element,
856 # push rest back onto myargs for later processing.
857 # Could process list directly, but nesting here is already bad
858 if arg
and isinstance(arg
, list):
859 args
[desc
.name
] = arg
[1:]
863 if arg
and isinstance(arg
, list):
864 args
= arg
[1:] + args
869 def store_arg(desc
, d
):
871 Store argument described by, and held in, thanks to valid(),
872 desc into the dictionary d, keyed by desc.name. Three cases:
874 1) desc.N is set: value in d is a list
875 2) prefix: multiple args are joined with ' ' into one d{} item
876 3) single prefix or other arg: store as simple value
878 Used in validate() below.
881 # value should be a list
883 d
[desc
.name
] += [desc
.instance
.val
]
885 d
[desc
.name
] = [desc
.instance
.val
]
886 elif (desc
.t
== CephPrefix
) and (desc
.name
in d
):
887 # prefixes' values should be a space-joined concatenation
888 d
[desc
.name
] += ' ' + desc
.instance
.val
890 # if first CephPrefix or any other type, just set it
891 d
[desc
.name
] = desc
.instance
.val
894 def validate(args
, signature
, flags
=0, partial
=False):
896 validate(args, signature, flags=0, partial=False)
898 args is a list of either words or k,v pairs representing a possible
899 command input following format of signature. Runs a validation; no
900 exception means it's OK. Return a dict containing all arguments keyed
901 by their descriptor name, with duplicate args per name accumulated
902 into a list (or space-separated value for CephPrefix).
904 Mismatches of prefix are non-fatal, as this probably just means the
905 search hasn't hit the correct command. Mismatches of non-prefix
906 arguments are treated as fatal, and an exception raised.
908 This matching is modified if partial is set: allow partial matching
909 (with partial dict returned); in this case, there are no exceptions
913 myargs
= copy
.deepcopy(args
)
914 mysig
= copy
.deepcopy(signature
)
915 reqsiglen
= len([desc
for desc
in mysig
if desc
.req
])
918 save_exception
= None
921 setattr(desc
, 'numseen', 0)
922 while desc
.numseen
< desc
.n
:
923 myarg
= get_next_arg(desc
, myargs
)
925 # no arg, but not required? Continue consuming mysig
926 # in case there are later required args
927 if myarg
in (None, []) and not desc
.req
:
930 # out of arguments for a required param?
931 # Either return (if partial validation) or raise
932 if myarg
in (None, []) and desc
.req
:
933 if desc
.N
and desc
.numseen
< 1:
934 # wanted N, didn't even get 1
937 raise ArgumentNumber(
938 'saw {0} of {1}, expected at least 1'.
939 format(desc
.numseen
, desc
)
941 elif not desc
.N
and desc
.numseen
< desc
.n
:
942 # wanted n, got too few
945 # special-case the "0 expected 1" case
946 if desc
.numseen
== 0 and desc
.n
== 1:
947 raise ArgumentNumber(
948 'missing required parameter {0}'.format(desc
)
950 raise ArgumentNumber(
951 'saw {0} of {1}, expected {2}'.
952 format(desc
.numseen
, desc
, desc
.n
)
956 # Have an arg; validate it
958 validate_one(myarg
, desc
)
960 except ArgumentError
as e
:
966 # if not required, just push back; it might match
968 save_exception
= [ myarg
, exc
]
969 myargs
.insert(0, myarg
)
972 # hm, it was required, so time to return/raise
977 # Whew, valid arg acquired. Store in dict
980 # Clear prior exception
981 save_exception
= None
983 # Done with entire list of argdescs
984 if matchcnt
< reqsiglen
:
985 raise ArgumentTooFew("not enough arguments given")
987 if myargs
and not partial
:
989 print(save_exception
[0], 'not valid: ', save_exception
[1], file=sys
.stderr
)
990 raise ArgumentError("unused arguments: " + str(myargs
))
993 d
['target'] = ('mgr','')
1000 sigdict
= sig
.values()
1001 assert len(sigdict
) == 1
1002 some_value
= next(iter(sig
.values()))
1003 return len(some_value
['sig'])
1006 def validate_command(sigdict
, args
, verbose
=False):
1008 turn args into a valid dictionary ready to be sent off as JSON,
1009 validated against sigdict.
1012 print("validate_command: " + " ".join(args
), file=sys
.stderr
)
1016 # look for best match, accumulate possibles in bestcmds
1017 # (so we can maybe give a more-useful error message)
1020 for cmdtag
, cmd
in sigdict
.items():
1022 matched
= matchnum(args
, sig
, partial
=True)
1023 if matched
> best_match_cnt
:
1025 print("better match: {0} > {1}: {2}:{3} ".format(
1026 matched
, best_match_cnt
, cmdtag
, concise_sig(sig
)
1028 best_match_cnt
= matched
1029 bestcmds
= [{cmdtag
: cmd
}]
1030 elif matched
== best_match_cnt
:
1032 print("equal match: {0} > {1}: {2}:{3} ".format(
1033 matched
, best_match_cnt
, cmdtag
, concise_sig(sig
)
1035 bestcmds
.append({cmdtag
: cmd
})
1037 # Sort bestcmds by number of args so we can try shortest first
1038 # (relies on a cmdsig being key,val where val is a list of len 1)
1039 bestcmds_sorted
= sorted(bestcmds
, key
=cmdsiglen
)
1042 print("bestcmds_sorted: ", file=sys
.stderr
)
1043 pprint
.PrettyPrinter(stream
=sys
.stderr
).pprint(bestcmds_sorted
)
1045 # for everything in bestcmds, look for a true match
1046 for cmdsig
in bestcmds_sorted
:
1047 for cmd
in cmdsig
.values():
1050 valid_dict
= validate(args
, sig
, flags
=cmd
.get('flags', 0))
1053 except ArgumentPrefix
:
1054 # ignore prefix mismatches; we just haven't found
1055 # the right command yet
1057 except ArgumentTooFew
:
1058 # It looked like this matched the beginning, but it
1059 # didn't have enough args supplied. If we're out of
1060 # cmdsigs we'll fall out unfound; if we're not, maybe
1061 # the next one matches completely. Whine, but pass.
1063 print('Not enough args supplied for ',
1064 concise_sig(sig
), file=sys
.stderr
)
1065 except ArgumentError
as e
:
1066 # Solid mismatch on an arg (type, range, etc.)
1067 # Stop now, because we have the right command but
1068 # some other input is invalid
1069 print("Invalid command: ", e
, file=sys
.stderr
)
1070 print(concise_sig(sig
), ': ', cmd
['help'], file=sys
.stderr
)
1076 print('no valid command found; 10 closest matches:', file=sys
.stderr
)
1077 for cmdsig
in bestcmds
[:10]:
1078 for (cmdtag
, cmd
) in cmdsig
.items():
1079 print(concise_sig(cmd
['sig']), file=sys
.stderr
)
1085 def find_cmd_target(childargs
):
1087 Using a minimal validation, figure out whether the command
1088 should be sent to a monitor or an osd. We do this before even
1089 asking for the 'real' set of command signatures, so we can ask the
1091 Returns ('osd', osdid), ('pg', pgid), ('mgr', '') or ('mon', '')
1093 sig
= parse_funcsig(['tell', {'name': 'target', 'type': 'CephName'}])
1095 valid_dict
= validate(childargs
, sig
, partial
=True)
1096 except ArgumentError
:
1099 if len(valid_dict
) == 2:
1100 # revalidate to isolate type and id
1102 # if this fails, something is horribly wrong, as it just
1103 # validated successfully above
1104 name
.valid(valid_dict
['target'])
1105 return name
.nametype
, name
.nameid
1107 sig
= parse_funcsig(['tell', {'name': 'pgid', 'type': 'CephPgid'}])
1109 valid_dict
= validate(childargs
, sig
, partial
=True)
1110 except ArgumentError
:
1113 if len(valid_dict
) == 2:
1114 # pg doesn't need revalidation; the string is fine
1115 return 'pg', valid_dict
['pgid']
1117 # If we reached this far it must mean that so far we've been unable to
1118 # obtain a proper target from childargs. This may mean that we are not
1119 # dealing with a 'tell' command, or that the specified target is invalid.
1120 # If the latter, we likely were unable to catch it because we were not
1121 # really looking for it: first we tried to parse a 'CephName' (osd, mon,
1122 # mds, followed by and id); given our failure to parse, we tried to parse
1123 # a 'CephPgid' instead (e.g., 0.4a). Considering we got this far though
1124 # we were unable to do so.
1126 # We will now check if this is a tell and, if so, forcefully validate the
1127 # target as a 'CephName'. This must be so because otherwise we will end
1128 # up sending garbage to a monitor, which is the default target when a
1129 # target is not explicitly specified.
1131 # 'ceph status' -> target is any one monitor
1132 # 'ceph tell mon.* status -> target is all monitors
1133 # 'ceph tell foo status -> target is invalid!
1134 if len(childargs
) > 1 and childargs
[0] == 'tell':
1136 # CephName.valid() raises on validation error; find_cmd_target()'s
1137 # caller should handle them
1138 name
.valid(childargs
[1])
1139 return name
.nametype
, name
.nameid
1141 sig
= parse_funcsig(['pg', {'name': 'pgid', 'type': 'CephPgid'}])
1143 valid_dict
= validate(childargs
, sig
, partial
=True)
1144 except ArgumentError
:
1147 if len(valid_dict
) == 2:
1148 return 'pg', valid_dict
['pgid']
1153 class RadosThread(threading
.Thread
):
1154 def __init__(self
, target
, *args
, **kwargs
):
1156 self
.kwargs
= kwargs
1157 self
.target
= target
1158 self
.exception
= None
1159 threading
.Thread
.__init
__(self
)
1163 self
.retval
= self
.target(*self
.args
, **self
.kwargs
)
1164 except Exception as e
:
1168 # time in seconds between each call to t.join() for child thread
1169 POLL_TIME_INCR
= 0.5
1172 def run_in_thread(target
, *args
, **kwargs
):
1174 timeout
= kwargs
.pop('timeout', 0)
1176 t
= RadosThread(target
, *args
, **kwargs
)
1178 # allow the main thread to exit (presumably, avoid a join() on this
1179 # subthread) before this thread terminates. This allows SIGINT
1180 # exit of a blocked call. See below.
1185 # poll for thread exit
1187 t
.join(POLL_TIME_INCR
)
1188 if timeout
and t
.is_alive():
1189 countdown
= countdown
- POLL_TIME_INCR
1191 raise KeyboardInterrupt
1193 t
.join() # in case t exits before reaching the join() above
1194 except KeyboardInterrupt:
1195 # ..but allow SIGINT to terminate the waiting. Note: this
1196 # relies on the Linux kernel behavior of delivering the signal
1197 # to the main thread in preference to any subthread (all that's
1198 # strictly guaranteed is that *some* thread that has the signal
1199 # unblocked will receive it). But there doesn't seem to be
1200 # any interface to create t with SIGINT blocked.
1204 t
.retval
= -errno
.EINTR
, None, 'Interrupted!'
1210 def send_command_retry(*args
, **kwargs
):
1213 return send_command(*args
, **kwargs
)
1214 except Exception as e
:
1215 if ('get_command_descriptions' in str(e
) and
1216 'object in state configuring' in str(e
)):
1221 def send_command(cluster
, target
=('mon', ''), cmd
=None, inbuf
=b
'', timeout
=0,
1224 Send a command to a daemon using librados's
1225 mon_command, osd_command, or pg_command. Any bulk input data
1228 Returns (ret, outbuf, outs); ret is the return code, outbuf is
1229 the outbl "bulk useful output" buffer, and outs is any status
1230 or error message (intended for stderr).
1232 If target is osd.N, send command to that osd (except for pgid cmds)
1236 if target
[0] == 'osd':
1240 print('submit {0} to osd.{1}'.format(cmd
, osdid
),
1242 ret
, outbuf
, outs
= run_in_thread(
1243 cluster
.osd_command
, osdid
, cmd
, inbuf
, timeout
)
1245 elif target
[0] == 'mgr':
1246 ret
, outbuf
, outs
= run_in_thread(
1247 cluster
.mgr_command
, cmd
, inbuf
, timeout
)
1249 elif target
[0] == 'pg':
1251 # pgid will already be in the command for the pg <pgid>
1252 # form, but for tell <pgid>, we need to put it in
1254 cmddict
= json
.loads(cmd
[0])
1255 cmddict
['pgid'] = pgid
1257 cmddict
= dict(pgid
=pgid
)
1258 cmd
= [json
.dumps(cmddict
)]
1260 print('submit {0} for pgid {1}'.format(cmd
, pgid
),
1262 ret
, outbuf
, outs
= run_in_thread(
1263 cluster
.pg_command
, pgid
, cmd
, inbuf
, timeout
)
1265 elif target
[0] == 'mon':
1267 print('{0} to {1}'.format(cmd
, target
[0]),
1269 if len(target
) < 2 or target
[1] == '':
1270 ret
, outbuf
, outs
= run_in_thread(
1271 cluster
.mon_command
, cmd
, inbuf
, timeout
)
1273 ret
, outbuf
, outs
= run_in_thread(
1274 cluster
.mon_command
, cmd
, inbuf
, timeout
, target
[1])
1275 elif target
[0] == 'mds':
1276 mds_spec
= target
[1]
1279 print('submit {0} to mds.{1}'.format(cmd
, mds_spec
),
1283 from cephfs
import LibCephFS
1285 raise RuntimeError("CephFS unavailable, have you installed libcephfs?")
1287 filesystem
= LibCephFS(cluster
.conf_defaults
, cluster
.conffile
)
1288 filesystem
.conf_parse_argv(cluster
.parsed_args
)
1291 ret
, outbuf
, outs
= \
1292 filesystem
.mds_command(mds_spec
, cmd
, inbuf
)
1293 filesystem
.shutdown()
1295 raise ArgumentValid("Bad target type '{0}'".format(target
[0]))
1297 except Exception as e
:
1298 if not isinstance(e
, ArgumentError
):
1299 raise RuntimeError('"{0}": exception {1}'.format(cmd
, e
))
1303 return ret
, outbuf
, outs
1306 def json_command(cluster
, target
=('mon', ''), prefix
=None, argdict
=None,
1307 inbuf
=b
'', timeout
=0, verbose
=False):
1309 Format up a JSON command and send it with send_command() above.
1310 Prefix may be supplied separately or in argdict. Any bulk input
1311 data comes in inbuf.
1313 If target is osd.N, send command to that osd (except for pgid cmds)
1317 cmddict
.update({'prefix': prefix
})
1319 cmddict
.update(argdict
)
1320 if 'target' in argdict
:
1321 target
= argdict
.get('target')
1323 # grab prefix for error messages
1324 prefix
= cmddict
['prefix']
1327 if target
[0] == 'osd':
1328 osdtarg
= CephName()
1329 osdtarget
= '{0}.{1}'.format(*target
)
1330 # prefer target from cmddict if present and valid
1331 if 'target' in cmddict
:
1332 osdtarget
= cmddict
.pop('target')
1334 osdtarg
.valid(osdtarget
)
1335 target
= ('osd', osdtarg
.nameid
)
1337 # use the target we were originally given
1340 ret
, outbuf
, outs
= send_command_retry(cluster
,
1341 target
, [json
.dumps(cmddict
)],
1342 inbuf
, timeout
, verbose
)
1344 except Exception as e
:
1345 if not isinstance(e
, ArgumentError
):
1346 raise RuntimeError('"{0}": exception {1}'.format(argdict
, e
))
1350 return ret
, outbuf
, outs