summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlfredo Deza <alfredo.deza@inktank.com>2013-08-16 16:30:35 -0400
committerAlfredo Deza <alfredo.deza@inktank.com>2013-08-16 16:30:35 -0400
commit423b2da1b2ebd6e8daa7ea0303fb28d5a85e6393 (patch)
tree2f08544b7d81557ffca6301669ffa5e13cf90ab1
parent062efceea7c3e125113b5f1a1fc0d7c00819facb (diff)
downloadceph-423b2da1b2ebd6e8daa7ea0303fb28d5a85e6393.tar.gz
remove argparse from ceph-rest, fix imports
Signed-off-by: Alfredo Deza <alfredo.deza@inktank.com>
-rwxr-xr-xsrc/pybind/ceph-rest/ceph_rest/__init__.py2
-rw-r--r--src/pybind/ceph-rest/ceph_rest/argparse.py1090
2 files changed, 1 insertions, 1091 deletions
diff --git a/src/pybind/ceph-rest/ceph_rest/__init__.py b/src/pybind/ceph-rest/ceph_rest/__init__.py
index 0ff15ceaceb..80f957780dd 100755
--- a/src/pybind/ceph-rest/ceph_rest/__init__.py
+++ b/src/pybind/ceph-rest/ceph_rest/__init__.py
@@ -11,7 +11,7 @@ import xml.etree.ElementTree
import xml.sax.saxutils
import flask
-from ceph_rest.argparse import \
+from ceph_argparse import \
ArgumentError, CephPgid, CephOsdName, CephChoices, CephPrefix, \
concise_sig, descsort, parse_funcsig, parse_json_funcsigs, \
validate, json_command
diff --git a/src/pybind/ceph-rest/ceph_rest/argparse.py b/src/pybind/ceph-rest/ceph_rest/argparse.py
deleted file mode 100644
index 427a4621216..00000000000
--- a/src/pybind/ceph-rest/ceph_rest/argparse.py
+++ /dev/null
@@ -1,1090 +0,0 @@
-"""
-Types and routines used by the ceph CLI as well as the RESTful
-interface. These have to do with querying the daemons for
-command-description information, validating user command input against
-those descriptions, and submitting the command to the appropriate
-daemon.
-
-Copyright (C) 2013 Inktank Storage, Inc.
-
-LGPL2. See file COPYING.
-"""
-import copy
-import json
-import os
-import pprint
-import re
-import socket
-import stat
-import sys
-import types
-import uuid
-
-class ArgumentError(Exception):
- """
- Something wrong with arguments
- """
- pass
-
-class ArgumentNumber(ArgumentError):
- """
- Wrong number of a repeated argument
- """
- pass
-
-class ArgumentFormat(ArgumentError):
- """
- Argument value has wrong format
- """
- pass
-
-class ArgumentValid(ArgumentError):
- """
- Argument value is otherwise invalid (doesn't match choices, for instance)
- """
- pass
-
-class ArgumentTooFew(ArgumentError):
- """
- Fewer arguments than descriptors in signature; may mean to continue
- the search, so gets a special exception type
- """
-
-class ArgumentPrefix(ArgumentError):
- """
- Special for mismatched prefix; less severe, don't report by default
- """
- pass
-
-class JsonFormat(Exception):
- """
- some syntactic or semantic issue with the JSON
- """
- pass
-
-class CephArgtype(object):
- """
- Base class for all Ceph argument types
-
- Instantiating an object sets any validation parameters
- (allowable strings, numeric ranges, etc.). The 'valid'
- method validates a string against that initialized instance,
- throwing ArgumentError if there's a problem.
- """
- def __init__(self, **kwargs):
- """
- set any per-instance validation parameters here
- from kwargs (fixed string sets, integer ranges, etc)
- """
- pass
-
- def valid(self, s, partial=False):
- """
- Run validation against given string s (generally one word);
- partial means to accept partial string matches (begins-with).
- If cool, set self.val to the value that should be returned
- (a copy of the input string, or a numeric or boolean interpretation
- thereof, for example)
- if not, throw ArgumentError(msg-as-to-why)
- """
- self.val = s
-
- def __repr__(self):
- """
- return string representation of description of type. Note,
- this is not a representation of the actual value. Subclasses
- probably also override __str__() to give a more user-friendly
- 'name/type' description for use in command format help messages.
- """
- a = ''
- if hasattr(self, 'typeargs'):
- a = self.typeargs
- return '{0}(\'{1}\')'.format(self.__class__.__name__, a)
-
- def __str__(self):
- """
- where __repr__ (ideally) returns a string that could be used to
- reproduce the object, __str__ returns one you'd like to see in
- print messages. Use __str__ to format the argtype descriptor
- as it would be useful in a command usage message.
- """
- return '<{0}>'.format(self.__class__.__name__)
-
-class CephInt(CephArgtype):
- """
- range-limited integers, [+|-][0-9]+ or 0x[0-9a-f]+
- range: list of 1 or 2 ints, [min] or [min,max]
- """
- def __init__(self, range=''):
- if range == '':
- self.range = list()
- else:
- self.range = list(range.split('|'))
- self.range = map(long, self.range)
-
- def valid(self, s, partial=False):
- try:
- val = long(s)
- except ValueError:
- raise ArgumentValid("{0} doesn't represent an int".format(s))
- if len(self.range) == 2:
- if val < self.range[0] or val > self.range[1]:
- raise ArgumentValid("{0} not in range {1}".format(val, self.range))
- elif len(self.range) == 1:
- if val < self.range[0]:
- raise ArgumentValid("{0} not in range {1}".format(val, self.range))
- self.val = val
-
- def __str__(self):
- r = ''
- if len(self.range) == 1:
- r = '[{0}-]'.format(self.range[0])
- if len(self.range) == 2:
- r = '[{0}-{1}]'.format(self.range[0], self.range[1])
-
- return '<int{0}>'.format(r)
-
-
-class CephFloat(CephArgtype):
- """
- range-limited float type
- range: list of 1 or 2 floats, [min] or [min, max]
- """
- def __init__(self, range=''):
- if range == '':
- self.range = list()
- else:
- self.range = list(range.split('|'))
- self.range = map(float, self.range)
-
- def valid(self, s, partial=False):
- try:
- val = float(s)
- except ValueError:
- raise ArgumentValid("{0} doesn't represent a float".format(s))
- if len(self.range) == 2:
- if val < self.range[0] or val > self.range[1]:
- raise ArgumentValid("{0} not in range {1}".format(val, self.range))
- elif len(self.range) == 1:
- if val < self.range[0]:
- raise ArgumentValid("{0} not in range {1}".format(val, self.range))
- self.val = val
-
- def __str__(self):
- r = ''
- if len(self.range) == 1:
- r = '[{0}-]'.format(self.range[0])
- if len(self.range) == 2:
- r = '[{0}-{1}]'.format(self.range[0], self.range[1])
- return '<float{0}>'.format(r)
-
-class CephString(CephArgtype):
- """
- String; pretty generic. goodchars is a RE char class of valid chars
- """
- def __init__(self, goodchars=''):
- from string import printable
- try:
- re.compile(goodchars)
- except:
- raise ValueError('CephString(): "{0}" is not a valid RE'.\
- format(goodchars))
- self.goodchars = goodchars
- self.goodset = frozenset(
- [c for c in printable if re.match(goodchars, c)]
- )
-
- def valid(self, s, partial=False):
- sset = set(s)
- if self.goodset and not sset <= self.goodset:
- raise ArgumentFormat("invalid chars {0} in {1}".\
- format(''.join(sset - self.goodset), s))
- self.val = s
-
- def __str__(self):
- b = ''
- if self.goodchars:
- b += '(goodchars {0})'.format(self.goodchars)
- return '<string{0}>'.format(b)
-
-class CephSocketpath(CephArgtype):
- """
- Admin socket path; check that it's readable and S_ISSOCK
- """
- def valid(self, s, partial=False):
- mode = os.stat(s).st_mode
- if not stat.S_ISSOCK(mode):
- raise ArgumentValid('socket path {0} is not a socket'.format(s))
- self.val = s
-
- def __str__(self):
- return '<admin-socket-path>'
-
-class CephIPAddr(CephArgtype):
- """
- IP address (v4 or v6) with optional port
- """
- def valid(self, s, partial=False):
- # parse off port, use socket to validate addr
- type = 6
- if s.startswith('['):
- type = 6
- elif s.find('.') != -1:
- type = 4
- if type == 4:
- port = s.find(':')
- if (port != -1):
- a = s[:port]
- p = s[port+1:]
- if int(p) > 65535:
- raise ArgumentValid('{0}: invalid IPv4 port'.format(p))
- else:
- a = s
- p = None
- try:
- socket.inet_pton(socket.AF_INET, a)
- except:
- raise ArgumentValid('{0}: invalid IPv4 address'.format(a))
- else:
- # v6
- if s.startswith('['):
- end = s.find(']')
- if end == -1:
- raise ArgumentFormat('{0} missing terminating ]'.format(s))
- if s[end+1] == ':':
- try:
- p = int(s[end+2])
- except:
- raise ArgumentValid('{0}: bad port number'.format(s))
- a = s[1:end]
- else:
- a = s
- p = None
- try:
- socket.inet_pton(socket.AF_INET6, a)
- except:
- raise ArgumentValid('{0} not valid IPv6 address'.format(s))
- if p is not None and long(p) > 65535:
- raise ArgumentValid("{0} not a valid port number".format(p))
- self.val = s
- self.addr = a
- self.port = p
-
- def __str__(self):
- return '<IPaddr[:port]>'
-
-class CephEntityAddr(CephIPAddr):
- """
- EntityAddress, that is, IP address/nonce
- """
- def valid(self, s, partial=False):
- ip, nonce = s.split('/')
- super(self.__class__, self).valid(ip)
- self.nonce = nonce
- self.val = s
-
- def __str__(self):
- return '<EntityAddr>'
-
-class CephPoolname(CephArgtype):
- """
- Pool name; very little utility
- """
- def __str__(self):
- return '<poolname>'
-
-class CephObjectname(CephArgtype):
- """
- Object name. Maybe should be combined with Pool name as they're always
- present in pairs, and then could be checked for presence
- """
- def __str__(self):
- return '<objectname>'
-
-class CephPgid(CephArgtype):
- """
- pgid, in form N.xxx (N = pool number, xxx = hex pgnum)
- """
- def valid(self, s, partial=False):
- if s.find('.') == -1:
- raise ArgumentFormat('pgid has no .')
- poolid, pgnum = s.split('.')
- if poolid < 0:
- raise ArgumentFormat('pool {0} < 0'.format(poolid))
- try:
- pgnum = int(pgnum, 16)
- except:
- raise ArgumentFormat('pgnum {0} not hex integer'.format(pgnum))
- self.val = s
-
- def __str__(self):
- return '<pgid>'
-
-class CephName(CephArgtype):
- """
- Name (type.id) where:
- type is osd|mon|client|mds
- id is a base10 int, if type == osd, or a string otherwise
-
- Also accept '*'
- """
- def __init__(self):
- self.nametype = None
- self.nameid = None
-
- def valid(self, s, partial=False):
- if s == '*':
- self.val = s
- return
- if s.find('.') == -1:
- raise ArgumentFormat('CephName: no . in {0}'.format(s))
- else:
- t, i = s.split('.')
- if not t in ('osd', 'mon', 'client', 'mds'):
- raise ArgumentValid('unknown type ' + t)
- if t == 'osd':
- if i != '*':
- try:
- i = int(i)
- except:
- raise ArgumentFormat('osd id ' + i + ' not integer')
- self.nametype = t
- self.val = s
- self.nameid = i
-
- def __str__(self):
- return '<name (type.id)>'
-
-class CephOsdName(CephArgtype):
- """
- Like CephName, but specific to osds: allow <id> alone
-
- osd.<id>, or <id>, or *, where id is a base10 int
- """
- def __init__(self):
- self.nametype = None
- self.nameid = None
-
- def valid(self, s, partial=False):
- if s == '*':
- self.val = s
- return
- if s.find('.') != -1:
- t, i = s.split('.')
- if t != 'osd':
- raise ArgumentValid('unknown type ' + t)
- else:
- t = 'osd'
- i = s
- try:
- i = int(i)
- except:
- raise ArgumentFormat('osd id ' + i + ' not integer')
- self.nametype = t
- self.nameid = i
- self.val = i
-
- def __str__(self):
- return '<osdname (id|osd.id)>'
-
-class CephChoices(CephArgtype):
- """
- Set of string literals; init with valid choices
- """
- def __init__(self, strings='', **kwargs):
- self.strings = strings.split('|')
-
- def valid(self, s, partial=False):
- if not partial:
- if not s in self.strings:
- # show as __str__ does: {s1|s2..}
- raise ArgumentValid("{0} not in {1}".format(s, self))
- self.val = s
- return
-
- # partial
- for t in self.strings:
- if t.startswith(s):
- self.val = s
- return
- raise ArgumentValid("{0} not in {1}". format(s, self))
-
- def __str__(self):
- if len(self.strings) == 1:
- return '{0}'.format(self.strings[0])
- else:
- return '{0}'.format('|'.join(self.strings))
-
-class CephFilepath(CephArgtype):
- """
- Openable file
- """
- def valid(self, s, partial=False):
- try:
- f = open(s, 'a+')
- except Exception as e:
- raise ArgumentValid('can\'t open {0}: {1}'.format(s, e))
- f.close()
- self.val = s
-
- def __str__(self):
- return '<outfilename>'
-
-class CephFragment(CephArgtype):
- """
- 'Fragment' ??? XXX
- """
- def valid(self, s, partial=False):
- if s.find('/') == -1:
- raise ArgumentFormat('{0}: no /'.format(s))
- val, bits = s.split('/')
- # XXX is this right?
- if not val.startswith('0x'):
- raise ArgumentFormat("{0} not a hex integer".format(val))
- try:
- long(val)
- except:
- raise ArgumentFormat('can\'t convert {0} to integer'.format(val))
- try:
- long(bits)
- except:
- raise ArgumentFormat('can\'t convert {0} to integer'.format(bits))
- self.val = s
-
- def __str__(self):
- return "<CephFS fragment ID (0xvvv/bbb)>"
-
-
-class CephUUID(CephArgtype):
- """
- CephUUID: pretty self-explanatory
- """
- def valid(self, s, partial=False):
- try:
- uuid.UUID(s)
- except Exception as e:
- raise ArgumentFormat('invalid UUID {0}: {1}'.format(s, e))
- self.val = s
-
- def __str__(self):
- return '<uuid>'
-
-
-class CephPrefix(CephArgtype):
- """
- CephPrefix: magic type for "all the first n fixed strings"
- """
- def __init__(self, prefix=''):
- self.prefix = prefix
-
- def valid(self, s, partial=False):
- if partial:
- if self.prefix.startswith(s):
- self.val = s
- return
- else:
- if (s == self.prefix):
- self.val = s
- return
-
- raise ArgumentPrefix("no match for {0}".format(s))
-
- def __str__(self):
- return self.prefix
-
-
-class argdesc(object):
- """
- argdesc(typename, name='name', n=numallowed|N,
- req=False, helptext=helptext, **kwargs (type-specific))
-
- validation rules:
- typename: type(**kwargs) will be constructed
- later, type.valid(w) will be called with a word in that position
-
- name is used for parse errors and for constructing JSON output
- n is a numeric literal or 'n|N', meaning "at least one, but maybe more"
- req=False means the argument need not be present in the list
- helptext is the associated help for the command
- anything else are arguments to pass to the type constructor.
-
- self.instance is an instance of type t constructed with typeargs.
-
- valid() will later be called with input to validate against it,
- and will store the validated value in self.instance.val for extraction.
- """
- def __init__(self, t, name=None, n=1, req=True, **kwargs):
- if isinstance(t, types.StringTypes):
- self.t = CephPrefix
- self.typeargs = {'prefix':t}
- self.req = True
- else:
- self.t = t
- self.typeargs = kwargs
- self.req = bool(req == True or req == 'True')
-
- self.name = name
- self.N = (n in ['n', 'N'])
- if self.N:
- self.n = 1
- else:
- self.n = int(n)
- self.instance = self.t(**self.typeargs)
-
- def __repr__(self):
- r = 'argdesc(' + str(self.t) + ', '
- internals = ['N', 'typeargs', 'instance', 't']
- for (k, v) in self.__dict__.iteritems():
- if k.startswith('__') or k in internals:
- pass
- else:
- # undo modification from __init__
- if k == 'n' and self.N:
- v = 'N'
- r += '{0}={1}, '.format(k, v)
- for (k, v) in self.typeargs.iteritems():
- r += '{0}={1}, '.format(k, v)
- return r[:-2] + ')'
-
- def __str__(self):
- if ((self.t == CephChoices and len(self.instance.strings) == 1)
- or (self.t == CephPrefix)):
- s = str(self.instance)
- else:
- s = '{0}({1})'.format(self.name, str(self.instance))
- if self.N:
- s += ' [' + str(self.instance) + '...]'
- if not self.req:
- s = '{' + s + '}'
- return s
-
- def helpstr(self):
- """
- like str(), but omit parameter names (except for CephString,
- which really needs them)
- """
- if self.t == CephString:
- chunk = '<{0}>'.format(self.name)
- else:
- chunk = str(self.instance)
- s = chunk
- if self.N:
- s += ' [' + chunk + '...]'
- if not self.req:
- s = '{' + s + '}'
- return s
-
-def concise_sig(sig):
- """
- Return string representation of sig useful for syntax reference in help
- """
- return ' '.join([d.helpstr() for d in sig])
-
-def descsort(sh1, sh2):
- """
- sort descriptors by prefixes, defined as the concatenation of all simple
- strings in the descriptor; this works out to just the leading strings.
- """
- return cmp(concise_sig(sh1['sig']), concise_sig(sh2['sig']))
-
-def parse_funcsig(sig):
- """
- parse a single descriptor (array of strings or dicts) into a
- dict of function descriptor/validators (objects of CephXXX type)
- """
- newsig = []
- argnum = 0
- for desc in sig:
- argnum += 1
- if isinstance(desc, types.StringTypes):
- t = CephPrefix
- desc = {'type':t, 'name':'prefix', 'prefix':desc}
- else:
- # not a simple string, must be dict
- if not 'type' in desc:
- s = 'JSON descriptor {0} has no type'.format(sig)
- raise JsonFormat(s)
- # look up type string in our globals() dict; if it's an
- # object of type types.TypeType, it must be a
- # locally-defined class. otherwise, we haven't a clue.
- if desc['type'] in globals():
- t = globals()[desc['type']]
- if type(t) != types.TypeType:
- s = 'unknown type {0}'.format(desc['type'])
- raise JsonFormat(s)
- else:
- s = 'unknown type {0}'.format(desc['type'])
- raise JsonFormat(s)
-
- kwargs = dict()
- for key, val in desc.items():
- if key not in ['type', 'name', 'n', 'req']:
- kwargs[key] = val
- newsig.append(argdesc(t,
- name=desc.get('name', None),
- n=desc.get('n', 1),
- req=desc.get('req', True),
- **kwargs))
- return newsig
-
-
-def parse_json_funcsigs(s, consumer):
- """
- A function signature is mostly an array of argdesc; it's represented
- in JSON as
- {
- "cmd001": {"sig":[ "type": type, "name": name, "n": num, "req":true|false <other param>], "help":helptext, "module":modulename, "perm":perms, "avail":availability}
- .
- .
- .
- ]
-
- A set of sigs is in an dict mapped by a unique number:
- {
- "cmd1": {
- "sig": ["type.. ], "help":helptext...
- }
- "cmd2"{
- "sig": [.. ], "help":helptext...
- }
- }
-
- Parse the string s and return a dict of dicts, keyed by opcode;
- each dict contains 'sig' with the array of descriptors, and 'help'
- with the helptext, 'module' with the module name, 'perm' with a
- string representing required permissions in that module to execute
- this command (and also whether it is a read or write command from
- the cluster state perspective), and 'avail' as a hint for
- whether the command should be advertised by CLI, REST, or both.
- If avail does not contain 'consumer', don't include the command
- in the returned dict.
- """
- try:
- overall = json.loads(s)
- except Exception as e:
- print >> sys.stderr, "Couldn't parse JSON {0}: {1}".format(s, e)
- raise e
- sigdict = {}
- for cmdtag, cmd in overall.iteritems():
- if not 'sig' in cmd:
- s = "JSON descriptor {0} has no 'sig'".format(cmdtag)
- raise JsonFormat(s)
- # check 'avail' and possibly ignore this command
- if 'avail' in cmd:
- if not consumer in cmd['avail']:
- continue
- # rewrite the 'sig' item with the argdesc-ized version, and...
- cmd['sig'] = parse_funcsig(cmd['sig'])
- # just take everything else as given
- sigdict[cmdtag] = cmd
- return sigdict
-
-def validate_one(word, desc, partial=False):
- """
- validate_one(word, desc, partial=False)
-
- validate word against the constructed instance of the type
- in desc. May raise exception. If it returns false (and doesn't
- raise an exception), desc.instance.val will
- contain the validated value (in the appropriate type).
- """
- desc.instance.valid(word, partial)
- desc.numseen += 1
- if desc.N:
- desc.n = desc.numseen + 1
-
-def matchnum(args, signature, partial=False):
- """
- matchnum(s, signature, partial=False)
-
- Returns number of arguments matched in s against signature.
- Can be used to determine most-likely command for full or partial
- matches (partial applies to string matches).
- """
- words = args[:]
- mysig = copy.deepcopy(signature)
- matchcnt = 0
- for desc in mysig:
- setattr(desc, 'numseen', 0)
- while desc.numseen < desc.n:
- # if there are no more arguments, return
- if not words:
- return matchcnt
- word = words.pop(0)
-
- try:
- validate_one(word, desc, partial)
- valid = True
- except ArgumentError:
- # matchnum doesn't care about type of error
- valid = False
-
- if not valid:
- if not desc.req:
- # this wasn't required, so word may match the next desc
- words.insert(0, word)
- break
- else:
- # it was required, and didn't match, return
- return matchcnt
- if desc.req:
- matchcnt += 1
- return matchcnt
-
-def get_next_arg(desc, args):
- '''
- Get either the value matching key 'desc.name' or the next arg in
- the non-dict list. Return None if args are exhausted. Used in
- validate() below.
- '''
- arg = None
- if isinstance(args, dict):
- arg = args.pop(desc.name, None)
- # allow 'param=param' to be expressed as 'param'
- if arg == '':
- arg = desc.name
- # Hack, or clever? If value is a list, keep the first element,
- # push rest back onto myargs for later processing.
- # Could process list directly, but nesting here is already bad
- if arg and isinstance(arg, list):
- args[desc.name] = arg[1:]
- arg = arg[0]
- elif args:
- arg = args.pop(0)
- if arg and isinstance(arg, list):
- args = arg[1:] + args
- arg = arg[0]
- return arg
-
-def store_arg(desc, d):
- '''
- Store argument described by, and held in, thanks to valid(),
- desc into the dictionary d, keyed by desc.name. Three cases:
-
- 1) desc.N is set: value in d is a list
- 2) prefix: multiple args are joined with ' ' into one d{} item
- 3) single prefix or other arg: store as simple value
-
- Used in validate() below.
- '''
- if desc.N:
- # value should be a list
- if desc.name in d:
- d[desc.name] += [desc.instance.val]
- else:
- d[desc.name] = [desc.instance.val]
- elif (desc.t == CephPrefix) and (desc.name in d):
- # prefixes' values should be a space-joined concatenation
- d[desc.name] += ' ' + desc.instance.val
- else:
- # if first CephPrefix or any other type, just set it
- d[desc.name] = desc.instance.val
-
-def validate(args, signature, partial=False):
- """
- validate(args, signature, partial=False)
-
- args is a list of either words or k,v pairs representing a possible
- command input following format of signature. Runs a validation; no
- exception means it's OK. Return a dict containing all arguments keyed
- by their descriptor name, with duplicate args per name accumulated
- into a list (or space-separated value for CephPrefix).
-
- Mismatches of prefix are non-fatal, as this probably just means the
- search hasn't hit the correct command. Mismatches of non-prefix
- arguments are treated as fatal, and an exception raised.
-
- This matching is modified if partial is set: allow partial matching
- (with partial dict returned); in this case, there are no exceptions
- raised.
- """
-
- myargs = copy.deepcopy(args)
- mysig = copy.deepcopy(signature)
- reqsiglen = len([desc for desc in mysig if desc.req])
- matchcnt = 0
- d = dict()
- for desc in mysig:
- setattr(desc, 'numseen', 0)
- while desc.numseen < desc.n:
- myarg = get_next_arg(desc, myargs)
-
- # no arg, but not required? Continue consuming mysig
- # in case there are later required args
- if not myarg and not desc.req:
- break
-
- # out of arguments for a required param?
- # Either return (if partial validation) or raise
- if not myarg and desc.req:
- if desc.N and desc.numseen < 1:
- # wanted N, didn't even get 1
- if partial:
- return d
- raise ArgumentNumber(
- 'saw {0} of {1}, expected at least 1'.\
- format(desc.numseen, desc)
- )
- elif not desc.N and desc.numseen < desc.n:
- # wanted n, got too few
- if partial:
- return d
- raise ArgumentNumber(
- 'saw {0} of {1}, expected {2}'.\
- format(desc.numseen, desc, desc.n)
- )
- break
-
- # Have an arg; validate it
- try:
- validate_one(myarg, desc)
- valid = True
- except ArgumentError as e:
- valid = False
- if not valid:
- # argument mismatch
- if not desc.req:
- # if not required, just push back; it might match
- # the next arg
- print >> sys.stderr, myarg, 'not valid: ', str(e)
- myargs.insert(0, myarg)
- break
- else:
- # hm, it was required, so time to return/raise
- if partial:
- return d
- raise e
-
- # Whew, valid arg acquired. Store in dict
- matchcnt += 1
- store_arg(desc, d)
-
- # Done with entire list of argdescs
- if matchcnt < reqsiglen:
- raise ArgumentTooFew("not enough arguments given")
-
- if myargs and not partial:
- raise ArgumentError("unused arguments: " + str(myargs))
-
- # Finally, success
- return d
-
-def cmdsiglen(sig):
- sigdict = sig.values()
- assert len(sigdict) == 1
- return len(sig.values()[0]['sig'])
-
-def validate_command(sigdict, args, verbose=False):
- """
- turn args into a valid dictionary ready to be sent off as JSON,
- validated against sigdict.
- """
- found = []
- valid_dict = {}
- if args:
- # look for best match, accumulate possibles in bestcmds
- # (so we can maybe give a more-useful error message)
- best_match_cnt = 0
- bestcmds = []
- for cmdtag, cmd in sigdict.iteritems():
- sig = cmd['sig']
- matched = matchnum(args, sig, partial=True)
- if (matched > best_match_cnt):
- if verbose:
- print >> sys.stderr, \
- "better match: {0} > {1}: {2}:{3} ".format(matched,
- best_match_cnt, cmdtag, concise_sig(sig))
- best_match_cnt = matched
- bestcmds = [{cmdtag:cmd}]
- elif matched == best_match_cnt:
- if verbose:
- print >> sys.stderr, \
- "equal match: {0} > {1}: {2}:{3} ".format(matched,
- best_match_cnt, cmdtag, concise_sig(sig))
- bestcmds.append({cmdtag:cmd})
-
- # Sort bestcmds by number of args so we can try shortest first
- # (relies on a cmdsig being key,val where val is a list of len 1)
- bestcmds_sorted = sorted(bestcmds,
- cmp=lambda x,y:cmp(cmdsiglen(x), cmdsiglen(y)))
-
- if verbose:
- print >> sys.stderr, "bestcmds_sorted: "
- pprint.PrettyPrinter(stream=sys.stderr).pprint(bestcmds_sorted)
-
- # for everything in bestcmds, look for a true match
- for cmdsig in bestcmds_sorted:
- for cmd in cmdsig.itervalues():
- sig = cmd['sig']
- try:
- valid_dict = validate(args, sig)
- found = cmd
- break
- except ArgumentPrefix:
- # ignore prefix mismatches; we just haven't found
- # the right command yet
- pass
- except ArgumentTooFew:
- # It looked like this matched the beginning, but it
- # didn't have enough args supplied. If we're out of
- # cmdsigs we'll fall out unfound; if we're not, maybe
- # the next one matches completely. Whine, but pass.
- if verbose:
- print >> sys.stderr, 'Not enough args supplied for ', \
- concise_sig(sig)
- except ArgumentError as e:
- # Solid mismatch on an arg (type, range, etc.)
- # Stop now, because we have the right command but
- # some other input is invalid
- print >> sys.stderr, "Invalid command: ", str(e)
- return {}
- if found:
- break
-
- if not found:
- print >> sys.stderr, 'no valid command found; 10 closest matches:'
- for cmdsig in bestcmds[:10]:
- for (cmdtag, cmd) in cmdsig.iteritems():
- print >> sys.stderr, concise_sig(cmd['sig'])
- return None
-
- return valid_dict
-
-def find_cmd_target(childargs):
- """
- Using a minimal validation, figure out whether the command
- should be sent to a monitor or an osd. We do this before even
- asking for the 'real' set of command signatures, so we can ask the
- right daemon.
- Returns ('osd', osdid), ('pg', pgid), or ('mon', '')
- """
- sig = parse_funcsig(['tell', {'name':'target', 'type':'CephName'}])
- try:
- valid_dict = validate(childargs, sig, partial=True)
- except ArgumentError:
- pass
- else:
- if len(valid_dict) == 2:
- # revalidate to isolate type and id
- name = CephName()
- # if this fails, something is horribly wrong, as it just
- # validated successfully above
- name.valid(valid_dict['target'])
- return name.nametype, name.nameid
-
- sig = parse_funcsig(['tell', {'name':'pgid', 'type':'CephPgid'}])
- try:
- valid_dict = validate(childargs, sig, partial=True)
- except ArgumentError:
- pass
- else:
- if len(valid_dict) == 2:
- # pg doesn't need revalidation; the string is fine
- return 'pg', valid_dict['pgid']
-
- sig = parse_funcsig(['pg', {'name':'pgid', 'type':'CephPgid'}])
- try:
- valid_dict = validate(childargs, sig, partial=True)
- except ArgumentError:
- pass
- else:
- if len(valid_dict) == 2:
- return 'pg', valid_dict['pgid']
-
- return 'mon', ''
-
-def send_command(cluster, target=('mon', ''), cmd=None, inbuf='', timeout=0,
- verbose=False):
- """
- Send a command to a daemon using librados's
- mon_command, osd_command, or pg_command. Any bulk input data
- comes in inbuf.
-
- Returns (ret, outbuf, outs); ret is the return code, outbuf is
- the outbl "bulk useful output" buffer, and outs is any status
- or error message (intended for stderr).
-
- If target is osd.N, send command to that osd (except for pgid cmds)
- """
- cmd = cmd or []
- try:
- if target[0] == 'osd':
- osdid = target[1]
-
- if verbose:
- print >> sys.stderr, 'submit {0} to osd.{1}'.\
- format(cmd, osdid)
- ret, outbuf, outs = \
- cluster.osd_command(osdid, cmd, inbuf, timeout)
-
- elif target[0] == 'pg':
- pgid = target[1]
- # pgid will already be in the command for the pg <pgid>
- # form, but for tell <pgid>, we need to put it in
- if cmd:
- cmddict = json.loads(cmd[0])
- cmddict['pgid'] = pgid
- else:
- cmddict = dict(pgid=pgid)
- cmd = [json.dumps(cmddict)]
- if verbose:
- print >> sys.stderr, 'submit {0} for pgid {1}'.\
- format(cmd, pgid)
- ret, outbuf, outs = \
- cluster.pg_command(pgid, cmd, inbuf, timeout)
-
- elif target[0] == 'mon':
- if verbose:
- print >> sys.stderr, '{0} to {1}'.\
- format(cmd, target[0])
- if target[1] == '':
- ret, outbuf, outs = cluster.mon_command(cmd, inbuf, timeout)
- else:
- ret, outbuf, outs = cluster.mon_command(cmd, inbuf, timeout, target[1])
-
- except Exception as e:
- raise RuntimeError('"{0}": exception {1}'.format(cmd, e))
-
- return ret, outbuf, outs
-
-def json_command(cluster, target=('mon', ''), prefix=None, argdict=None,
- inbuf='', timeout=0, verbose=False):
- """
- Format up a JSON command and send it with send_command() above.
- Prefix may be supplied separately or in argdict. Any bulk input
- data comes in inbuf.
-
- If target is osd.N, send command to that osd (except for pgid cmds)
- """
- cmddict = {}
- if prefix:
- cmddict.update({'prefix':prefix})
- if argdict:
- cmddict.update(argdict)
-
- # grab prefix for error messages
- prefix = cmddict['prefix']
-
- try:
- if target[0] == 'osd':
- osdtarg = CephName()
- osdtarget = '{0}.{1}'.format(*target)
- # prefer target from cmddict if present and valid
- if 'target' in cmddict:
- osdtarget = cmddict.pop('target')
- try:
- osdtarg.valid(osdtarget)
- target = ('osd', osdtarg.nameid)
- except:
- # use the target we were originally given
- pass
-
- ret, outbuf, outs = send_command(cluster, target, [json.dumps(cmddict)],
- inbuf, timeout, verbose)
-
- except Exception as e:
- raise RuntimeError('"{0}": exception {1}'.format(prefix, e))
-
- return ret, outbuf, outs
-
-