$(srcdir)/upstart/radosgw.conf \
$(srcdir)/upstart/radosgw-all.conf \
$(srcdir)/upstart/radosgw-all-starter.conf \
- ceph \
+ ceph.in \
ceph-disk \
ceph-disk-prepare \
ceph-disk-activate \
common/version.cc: ./ceph_ver.h
test/encoding/ceph_dencoder.cc: ./ceph_ver.h
+# assemble Python script with global version variables
+# NB: depends on format of ceph_ver.h
+
+ceph: ceph.in ./ceph_ver.h Makefile
+ rm -f $@ $@.tmp
+ echo "#!/usr/bin/python" >$@.tmp
+ grep "#define CEPH_GIT_NICE_VER" ./ceph_ver.h | \
+ sed -e 's/#define \(.*VER\) /\1=/' >>$@.tmp
+ grep "#define CEPH_GIT_VER" ./ceph_ver.h | \
+ sed -e 's/#define \(.*VER\) /\1=/' -e 's/=\(.*\)$$/="\1"/' >>$@.tmp
+ cat $@.in >>$@.tmp
+ chmod a+x $@.tmp
+ chmod a-w $@.tmp
+ mv $@.tmp $@
+
# cleaning
clean-local:
-rm *.so *.gcno *.gcda
+++ /dev/null
-#!/usr/bin/python
-
-import argparse
-import copy
-import errno
-import json
-import os
-import rados
-# for raw_input to do readline cmd editing
-import readline
-import socket
-import stat
-import string
-import struct
-import subprocess
-import sys
-import types
-import uuid
-
-# just a couple of globals
-
-verbose = False
-cluster_handle = None
-
-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 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), and return True
- if not, throw ArgumentError(msg-as-to-why)
- """
- self.val = s
- return True
-
- 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
- return True
-
- 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
- return True
-
- 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.
- """
- def __init__(self, badchars=''):
- self.badchars = badchars
-
- def valid(self, s, partial=False):
- for c in self.badchars:
- if c in s:
- raise ArgumentFormat("bad char {0} in {1}".format(c, s))
- self.val = s
- return True
-
- def __str__(self):
- b = ''
- if len(self.badchars):
- b = '(without chars in {0})'.format(self.badchars)
- 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
- return True
- 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
- return True
-
- 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('/')
- if not super(self.__class__, self).valid(ip):
- raise ArgumentValid('CephEntityAddr {0}: ip address invalid'.format(s))
- self.val = s
- return True
-
- 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 valid(self, s, partial=False):
- self.val = s
- return True
-
- 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
- return True
-
- def __str__(self):
- return '<pgid>'
-
-class CephName(CephArgtype):
- """
- Name, or type.id, where type is osd|mon|client|mds, and id is a base10 int,
- or just id.
-
- Also accept '*'
- """
- def valid(self, s, partial=False):
- if s == '*':
- self.val = s
- self.nametype = None
- self.nameid = None
- return True
- if s.find('.') == -1:
- i = s
- else:
- t, i = s.split('.')
- if not t in ('osd', 'mon', 'client', 'mds'):
- raise ArgumentValid('unknown type ' + self.t)
- if t == 'osd':
- try:
- i = int(i)
- except:
- raise ArgumentFormat('osd id ' + i + ' not integer')
- self.nametype = t
- self.val = s
- self.nameid = i
- return True
-
- def __str__(self):
- return '<name (type.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 True
-
- # partial
- for t in self.strings:
- if t.startswith(s):
- self.val = s
- return True
- 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
- return True
-
- 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
- return True
-
- 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
- return True
-
- 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 True
- else:
- if (s == self.prefix):
- self.val = s
- return True
- 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 mods above
- 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 = '{0}'.format(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 = '{0}'.format(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
- """
- first = True
- s = ''
- for d in sig:
- if first:
- first = False
- else:
- s += ' '
- s += d.helpstr()
- return s
-
-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):
- """
- parse_json_funcsigs(s)
-
- 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}
- .
- .
- .
- ]
-
- A set of sigs is in an dict mapped by a unique number:
- {
- "cmd1": {
- "sig": ["type.. ], "help":{"text":helptext}
- }
- "cmd2"{
- "sig": [.. ], "help":{"text":helptext}
- }
- }
-
- Parse the string s and return an dict of dicts, keyed by opcode;
- each dict contains 'sig' with the array of descriptors, and 'help'
- with the helptext.
- """
- 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():
- helptext = cmd.get('help', 'no help available')
- try:
- sig = cmd['sig']
- except KeyError:
- s = "JSON descriptor {0} has no 'sig'".format(cmdtag)
- raise JsonFormat(s)
- newsig = parse_funcsig(sig)
- sigdict[cmdtag] = {'sig':newsig, 'helptext':helptext}
- 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).
- """
- if desc.instance.valid(word, partial):
- desc.numseen += 1
- if desc.N:
- desc.n = desc.numseen + 1
- return True
- return False
-
-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)
- except:
- 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 validate(args, signature, partial=False):
- """
- validate(s, signature, partial=False)
-
- Assumes s represents a possible command input following format of
- signature. Runs a validation; no exception means it's OK. Return
- a dict containing all arguments named by their descriptor name
- (with duplicate args per name accumulated into a space-separated
- value).
-
- If partial is set, allow partial matching (with partial dict returned)
- """
- words = args[:]
- mysig = copy.deepcopy(signature)
- d = dict()
- for desc in mysig:
- setattr(desc, 'numseen', 0)
- while desc.numseen < desc.n:
- if words:
- word = words.pop(0)
- else:
- if 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
- try:
- validate_one(word, desc)
- except Exception as e:
- # not valid; if not required, just push back for the next one
- if not desc.req:
- words.insert(0, word)
- break
- else:
- # hm, but it was required, so quit
- if partial:
- return d
- raise e
-
- 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):
- # value 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
- return d
-
-def osdids():
- ret, outbuf, outs = json_command(prefix='osd ls')
- if ret:
- raise RuntimeError('Can\'t contact mon for osd list')
- return [i for i in outbuf.split('\n')]
-
-def monids():
- ret, outbuf, outs = json_command(prefix='mon dump',
- argdict={'format':'json'})
- if ret:
- raise RuntimeError('Can\'t contact mon for mon list')
- d = json.loads(outbuf)
- return [m['name'] for m in d['mons']]
-
-def mdsids():
- ret, outbuf, outs = json_command(prefix='mds dump',
- argdict={'format':'json'})
- if ret:
- raise RuntimeError('Can\'t contact mon for mds list')
- d = json.loads(outbuf)
- l = []
- infodict = d['info']
- for mdsdict in infodict.values():
- l.append(mdsdict['name'])
- return l
-
-def parse_cmdargs(args=None, target=''):
- # alias: let the line-wrapping be sane
- AP = argparse.ArgumentParser
-
- # format our own help
- parser = AP(description='Frontend for ceph CLI', add_help=False)
-
- parser.add_argument('--completion', action='store_true')
-
- parser.add_argument('-h', '--help', help='request mon help',
- action='store_true')
- parser.add_argument('--help-all', help='request help for all daemons',
- action='store_true')
-
- parser.add_argument('-c', '--conf', dest='cephconf',
- help='ceph configuration file')
- parser.add_argument('-i', '--in-file', dest='input_file',
- help='input file')
- parser.add_argument('-o', '--out-file', dest='output_file',
- help='output file')
- parser.add_argument('-k', '--keyring', dest='keyring_file',
- help='keyring file')
-
- parser.add_argument('--id', '--user', dest='client_id',
- help='client id for authentication')
- parser.add_argument('--name', '-n', dest='client_name',
- help='client name for authentication')
- parser.add_argument('--cluster', help='cluster name')
-
- parser.add_argument('--admin-daemon', dest='admin_socket',
- help='submit admin-socket commands (\"help\" for help')
-
- parser.add_argument('-s', '--status', action='store_true',
- help='show cluster status')
-
- parser.add_argument('-w', '--watch', action='store_true',
- help='watch live cluster changes')
- parser.add_argument('--watch-debug', action='store_true',
- help='watch debug events')
- parser.add_argument('--watch-info', action='store_true',
- help='watch info events')
- parser.add_argument('--watch-sec', action='store_true',
- help='watch security events')
- parser.add_argument('--watch-warn', action='store_true',
- help='watch warn events')
- parser.add_argument('--watch-error', action='store_true',
- help='watch error events')
-
- parser.add_argument('--verbose', action="store_true")
- parser.add_argument('--concise', dest='verbose', action="store_false")
-
- parser.add_argument('-f', '--format', choices=['json', 'json-pretty',
- 'xml', 'xml-pretty', 'plain'], dest='output_format')
- # for pg dump_stuck
- parser.add_argument('--threshold', type=int, help='number of seconds for a pg to be considered stuck for pg dump_stuck')
-
- # returns a Namespace with the parsed args, and a list of all extras
- parsed_args, extras = parser.parse_known_args(args)
-
- return parser, parsed_args, extras
-
-def do_help(parser, help_all = False):
- """
- Print basic parser help
- If the cluster is available:
- get and print monitor help;
- if help_all, print help for daemon commands as well
- """
-
- def help_for_target(target):
- ret, outbuf, outs = json_command(target=target,
- prefix='get_command_descriptions',
- timeout=10)
- if ret:
- print >> sys.stderr, \
- "couldn't get command descriptions for {0}: {1}".\
- format(target, outs)
- else:
- sys.stdout.write(format_help(parse_json_funcsigs(outbuf)))
-
- parser.print_help()
- print '\n'
- if (cluster_handle):
- help_for_target(target=('mon', ''))
-
- if help_all and cluster_handle:
- # try/except in case there are no daemons of that type
- try:
- firstosd = osdids()[0]
- print '\nOSD.{0} tell commands and pg pgid commands:\n\n'.\
- format(firstosd)
- help_for_target(target=('osd', osdids()[0]))
-
- print '\nOSD daemon commands:\n\n'
- sys.stdout.write(format_help(parse_json_funcsigs(admin_socket(ceph_conf('admin_socket', 'osd.' + firstosd), ['get_command_descriptions']))))
- except:
- pass
-
- try:
- firstmon = monids()[0]
- print '\nmon.{0} daemon commands:\n\n'.format(firstmon)
- sys.stdout.write(format_help(parse_json_funcsigs(admin_socket(ceph_conf('admin_socket', 'mon.' + firstmon), ['get_command_descriptions']))))
- except:
- pass
-
- try:
- firstmds = mdsids()[0]
- print '\nmds.{0} daemon commands:\n\n'.format(firstmds)
- sys.stdout.write(format_help(parse_json_funcsigs(admin_socket(ceph_conf('admin_socket', 'mds.' + firstmds), ['get_command_descriptions']))))
- except:
- pass
-
- return 0
-
-
-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']))
-
-
-dontsplit = string.letters + '{[<>]}'
-
-def wrap(s, width, indent):
- """
- generator to transform s into a sequence of strings width or shorter,
- for wrapping text to a specific column width.
- Attempt to break on anything but dontsplit characters.
- indent is amount to indent 2nd-through-nth lines.
-
- so "long string long string long string" width=11 indent=1 becomes
- 'long string', ' long string', ' long string' so that it can be printed
- as
- long string
- long string
- long string
-
- Consumes s.
- """
- result = ''
- leader = ''
- while len(s):
-
- if (len(s) <= width):
- # no splitting; just possibly indent
- result = leader + s
- s = ''
- yield result
-
- else:
- splitpos = width
- while (splitpos > 0) and (s[splitpos-1] in dontsplit):
- splitpos -= 1
-
- if splitpos == 0:
- splitpos = width
-
- if result:
- # prior result means we're mid-iteration, indent
- result = leader
- else:
- # first time, set leader and width for next
- leader = ' ' * indent
- width -= 1 # for subsequent space additions
-
- # remove any leading spaces in this chunk of s
- result += s[:splitpos].lstrip()
- s = s[splitpos:]
-
- yield result
-
- raise StopIteration
-
-def format_help(cmddict):
- """
- Formats all the cmdsigs and helptexts from cmddict into a sorted-by-
- cmdsig 2-column display, with each column wrapped and indented to
- fit into 40 characters.
- """
-
- fullusage = ''
- for cmd in sorted(cmddict.itervalues(), cmp=descsort):
-
- if not cmd['helptext']:
- continue
- siglines = [l for l in wrap(concise_sig(cmd['sig']), 40, 1)]
- helplines = [l for l in wrap(cmd['helptext'], 39, 1)]
-
- # make lists the same length
- maxlen = max(len(siglines), len(helplines))
- siglines.extend([''] * (maxlen - len(siglines)))
- helplines.extend([''] * (maxlen - len(helplines)))
-
- # so we can zip them for output
- for (s, h) in zip(siglines, helplines):
- fullusage += '{0:40s} {1}\n'.format(s, h)
-
- return fullusage
-
-def validate_command(parsed_args, sigdict, args):
- """
- turn args into a valid dictionary ready to be sent off as JSON,
- validated against sigdict.
- parsed_args is the namespace back from argparse
- """
- found = []
- valid_dict = {}
- if args:
- # Repulsive hack to handle tell: lop off 'tell' and target
- # and validate the rest of the command. 'target' is already
- # determined in our callers, so it's ok to remove it here.
- if args[0] == 'tell':
- args = args[2:]
- # 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})
-
- if verbose:
- print >> sys.stderr, "bestcmds: ", bestcmds
-
- # for everything in bestcmds, look for a true match
- for cmdsig in bestcmds:
- for cmd in cmdsig.itervalues():
- sig = cmd['sig']
- helptext = cmd['helptext']
- try:
- valid_dict = validate(args, sig)
- found = sig
- break
- except ArgumentPrefix:
- # this means a CephPrefix type didn't match; since
- # this is common, just eat it
- pass
- except ArgumentError as e:
- # prefixes matched, but some other arg didn't;
- # this is interesting information
- print >> sys.stderr, '{0}: invalid command'.\
- format(' '.join(args))
- print >> sys.stderr, '{0}'.format(e)
- print >> sys.stderr, "did you mean {0}?\n\t{1}".\
- format(concise_sig(sig), helptext)
- pass
-
- if not found:
- print >> sys.stderr, 'no valid command found'
- print >> sys.stderr, 'close matches:'
- for cmdsig in bestcmds:
- for (cmdtag, cmd) in cmdsig.iteritems():
- print >> sys.stderr, concise_sig(cmd['sig'])
- return None
-
- if parsed_args.output_format:
- valid_dict['format'] = parsed_args.output_format
-
- if parsed_args.threshold:
- valid_dict['threshold'] = parsed_args.threshold
-
- return valid_dict
-
-def json_command(target=('mon', ''), prefix=None, argdict=None, inbuf='',
- timeout=0):
- """
- Send a new_style command to a daemon using librados's
- mon_command, osd_command, or pg_command. Prefix may be supplied
- separately or in argdict. 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)
- """
- 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()
- if 'target' in cmddict:
- osdtarget = cmddict.pop('target')
- else:
- osdtarget = '{0}.{1}'.format(*target)
-
- try:
- osdtarg.valid(osdtarget)
- osdid = osdtarg.nameid
- except:
- # uh..I dunno, try osd.0?
- osdid = 0
-
- if verbose:
- print >> sys.stderr, 'submit {0} to osd.{1}'.\
- format(json.dumps(cmddict), osdid)
- ret, outbuf, outs = \
- cluster_handle.osd_command(osdid, json.dumps(cmddict), inbuf,
- timeout)
-
- elif target[0] == 'pg':
- # leave it in cmddict for the OSD to use too
- pgid = target[1]
- if verbose:
- print >> sys.stderr, 'submit {0} for pgid {1}'.\
- format(json.dumps(cmddict), pgid)
- ret, outbuf, outs = \
- cluster_handle.pg_command(pgid, json.dumps(cmddict), inbuf,
- timeout)
-
- elif target[0] == 'mon':
- if verbose:
- print >> sys.stderr, '{0} to {1}'.\
- format(json.dumps(cmddict), target[0])
- ret, outbuf, outs = cluster_handle.mon_command(json.dumps(cmddict),
- inbuf, timeout)
-
- except Exception as e:
- raise RuntimeError('"{0}": exception {1}'.format(prefix, e))
-
- return ret, outbuf, outs
-
-def admin_socket(asok_path, cmd):
- sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
- try:
- sock.connect(asok_path)
- sock.sendall(' '.join(cmd) + '\0')
-
- len_str = sock.recv(4)
- l, = struct.unpack(">I", len_str)
- ret = ''
-
- got = 0
- while got < l:
- bit = sock.recv(l - got)
- ret += bit
- got += len(bit)
-
- except Exception as e:
- raise RuntimeError('exception: {0}'.format(e))
-
- return ret
-
-
-def ceph_conf(field, name):
- p = subprocess.Popen(
- args=[
- 'ceph-conf',
- field,
- '-n',
- name,
- ],
- stdout=subprocess.PIPE,
- stderr=subprocess.PIPE)
- outdata, errdata = p.communicate()
- if (len(errdata)):
- raise RuntimeError('unable to get conf option %s for %s: %s' % (field, name, errdata))
- return outdata.rstrip()
-
-def new_style_command(parsed_args, cmdargs, target, sigdict, inbuf, verbose):
- """
- Do new-style command dance.
- target: daemon to receive command: mon (any) or osd.N
- sigdict - the parsed output from the new monitor describing commands
- inbuf - any -i input file data
- verbose - bool
- """
- if verbose:
- for cmdtag in sorted(sigdict.keys()):
- cmd = sigdict[cmdtag]
- sig = cmd['sig']
- print '{0}: {1}'.format(cmdtag, concise_sig(sig))
-
- got_command = False
-
- if not got_command:
- if cmdargs:
- # Validate input args against list of sigs
- valid_dict = validate_command(parsed_args, sigdict, cmdargs)
- if valid_dict:
- got_command = True
- else:
- return -errno.EINVAL, '', 'invalid command'
- else:
- # do the command-interpreter looping
- while True:
- interactive_input = raw_input('ceph> ')
- if interactive_input in ['q', 'quit', 'Q']:
- return 0, '', ''
- cmdargs = parse_cmdargs(interactive_input.split())[2]
- target = find_cmd_target(cmdargs)
- valid_dict = validate_command(parsed_args, sigdict, cmdargs)
- if valid_dict:
- if verbose:
- print >> sys.stderr, "Submitting command ", valid_dict
- ret, outbuf, outs = json_command(target=target,
- argdict=valid_dict)
- if ret:
- sys.stderr.write('Error {0}: {1}'.format(ret, outs))
- return ret, '', outs
- else:
- print "invalid command"
-
- if verbose:
- print >> sys.stderr, "Submitting command ", valid_dict
- return json_command(target=target, argdict=valid_dict, inbuf=inbuf)
-
-OSD_TELL_MATCH = { \
- 'sig': ['tell', {'name':'target','type':'CephName'}], \
- 'matchnum': 2, \
- 'return_key': 'target', \
-}
-PGID_MATCH = { \
- 'sig': ['pg', {'name':'pgid','type':'CephPgid'}], \
- 'matchnum': 2, \
- 'return_key': 'pgid', \
-}
-
-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'}])
- valid_dict = validate(childargs, sig, partial=True);
- if len(valid_dict) == 2:
- name = CephName()
- name.valid(valid_dict['target'])
- return 'osd', name.nameid
-
- sig = parse_funcsig(['pg', {'name':'pgid','type':'CephPgid'}])
- valid_dict = validate(childargs, sig, partial=True);
- if len(valid_dict) == 2:
- return 'pg', valid_dict['pgid']
-
- return 'mon', ''
-
-def complete(sigdict, args, target):
- """
- Command completion. Match as much of [args] as possible,
- and print every possible match separated by newlines.
- Return exitcode.
- """
- # XXX this looks a lot like the front of validate_command(). Refactor?
-
- complete_verbose = 'COMPVERBOSE' in os.environ
-
- # Repulsive hack to handle tell: lop off 'tell' and target
- # and validate the rest of the command. 'target' is already
- # determined in our callers, so it's ok to remove it here.
- if len(args) and args[0] == 'tell':
- args = args[2:]
- # 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 complete_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 complete_verbose:
- print >> sys.stderr, \
- "equal match: {0} > {1}: {2}:{3} ".format(matched,
- best_match_cnt, cmdtag, concise_sig(sig))
- bestcmds.append({cmdtag:cmd})
-
- # look through all matching sigs
- comps = []
- for cmddict in bestcmds:
- for cmd in cmddict.itervalues():
- sig = cmd['sig']
- # either:
- # we match everything fully, so we want the next desc, or
- # we match more partially, so we want the partial match
- fullindex = matchnum(args, sig, partial=False) - 1
- partindex = matchnum(args, sig, partial=True) - 1
- if complete_verbose:
- print >> sys.stderr, '{}: f {} p {} len {}'.format(sig, fullindex, partindex, len(sig))
- if fullindex == partindex and fullindex + 1 < len(sig):
- d = sig[fullindex + 1]
- else:
- d = sig[partindex]
- comps.append(str(d))
- if complete_verbose:
- print >> sys.stderr, '\n'.join(comps)
- print '\n'.join(comps)
-
- return 0
-
-###
-# main
-###
-
-def main():
-
- parser, parsed_args, childargs = parse_cmdargs()
-
- global verbose
- verbose = parsed_args.verbose
-
- # pass on --id, --name, --conf
- name = 'client.admin'
- if parsed_args.client_id:
- name = 'client.' + parsed_args.client_id
- if parsed_args.client_name:
- name = parsed_args.client_name
-
- # default '' means default conf search
- conffile = ''
- if parsed_args.cephconf:
- conffile = parsed_args.cephconf
- # For now, --admin-daemon is handled as usual. Try it
- # first in case we can't connect() to the cluster
- if parsed_args.admin_socket:
- try:
- print admin_socket(parsed_args.admin_socket, childargs)
- except Exception as e:
- print >> sys.stderr, 'admin_socket: {0}'.format(e)
- return 0
-
- if len(childargs) > 0 and childargs[0] == "daemon":
- if len(childargs) > 2:
- if childargs[1].find('/') >= 0:
- try:
- print admin_socket(childargs[1], childargs[2:])
- except Exception as e:
- print >> sys.stderr, 'admin_socket: {0}'.format(e)
- return 0
- else:
- # try resolve daemon name
- path = ceph_conf('admin_socket', childargs[1])
- try:
- print admin_socket(path, childargs[2:])
- except Exception as e:
- print >> sys.stderr, 'admin_socket: {0}'.format(e)
- return 0
- else:
- print >> sys.stderr, 'Daemon requires at least 2 arguments'
- return 1
-
- # handle any 'generic' ceph arguments that we didn't parse here
- global cluster_handle
-
- # rados.Rados() will call rados_create2, and then read the conf file,
- # and then set the keys from the dict. So we must do these
- # "pre-file defaults" first (see common_preinit in librados)
- conf_defaults = {
- 'log_to_stderr':'true',
- 'err_to_stderr':'true',
- 'log_flush_on_exit':'true',
- }
-
- clustername = 'ceph'
- if parsed_args.cluster:
- clustername = parsed_args.cluster
-
- cluster_handle = rados.Rados(name=name, clustername=clustername,
- conf_defaults=conf_defaults, conffile='')
-
- retargs = cluster_handle.conf_parse_argv(childargs)
- #tmp = childargs
- childargs = retargs
- if not childargs:
- childargs = []
-
- # -- means "stop parsing args", but we don't want to see it either
- if '--' in childargs:
- childargs.remove('--')
-
- try:
- cluster_handle.connect()
- except KeyboardInterrupt:
- print >> sys.stderr, 'Cluster connection aborted'
- return 1
- except Exception as e:
- print >> sys.stderr, 'Error connecting to cluster: {0}'.\
- format(e.__class__.__name__)
- return 1
-
- if parsed_args.help or parsed_args.help_all:
- return do_help(parser, parsed_args.help_all)
-
- # implement -w/--watch_*
- # This is ugly, but Namespace() isn't quite rich enough.
- level = ''
- for k,v in parsed_args._get_kwargs():
- if k.startswith('watch') and v:
- if k == 'watch':
- level = 'info'
- else:
- level = k.replace('watch_', '')
- if level:
-
- # an awfully simple callback
- def watch_cb(arg, line, who, stamp_sec, stamp_nsec, seq, level, msg):
- print line
-
- # first do a ceph status
- ret, outbuf, outs = json_command(prefix='status')
- if ret:
- print >> sys.stderr, "status query failed: ", outs
- return ret
- print outbuf
-
- # this instance keeps the watch connection alive, but is
- # otherwise unused
- logwatch = rados.MonitorLog(cluster_handle, level, watch_cb, 0)
- # loop forever letting watch_cb print lines
- while True:
- try:
- pass
- except KeyboardInterrupt:
- # or until ^C, at least
- return 0
-
- # read input file, if any
- inbuf = ''
- if parsed_args.input_file:
- try:
- with open(parsed_args.input_file, 'r') as f:
- inbuf = f.read()
- except Exception as e:
- print >> sys.stderr, 'Can\'t open input file {0}: {1}'.format(parsed_args.input_file, e)
- return 1
-
- # prepare output file, if any
- if parsed_args.output_file:
- try:
- outf = open(parsed_args.output_file, 'w')
- except:
- print >> sys.stderr, \
- 'Can\'t open output file {0}: {1}'.\
- format(parsed_args.output_file, e)
- return 1
-
- # -s behaves like a command (ceph status).
- if parsed_args.status:
- childargs.insert(0, 'status')
-
- target = find_cmd_target(childargs)
-
- # fetch JSON sigs from command
- # each line contains one command signature (a placeholder name
- # of the form 'cmdNNN' followed by an array of argument descriptors)
- # as part of the validated argument JSON object
-
- ret, outbuf, outs = json_command(target=target,
- prefix='get_command_descriptions')
- if ret == -errno.EINVAL:
- # send command to old monitor
- if verbose:
- print '{0} to old monitor'.format(' '.join(childargs))
- ret, outbuf, outs = cluster_handle.mon_command(childargs, inbuf)
- elif ret:
- if ret < 0:
- ret = -ret
- print >> sys.stderr, \
- 'Problem getting command descriptions from {0}, {1}'.\
- format(target, errno.errorcode[ret])
- return ret
- else:
- sigdict = parse_json_funcsigs(outbuf)
-
- if parsed_args.completion:
- return complete(sigdict, childargs, target)
-
- ret, outbuf, outs = new_style_command(parsed_args, childargs, target,
- sigdict, inbuf, verbose)
-
- if ret < 0:
- ret = -ret
- print >> sys.stderr, 'Error {0}: {1}'.format(errno.errorcode[ret], outs)
- return ret
-
- # this assumes outs never has useful command output, only status
- if outs:
- print >> sys.stderr, outs
-
- if (parsed_args.output_file):
- outf.write(outbuf)
- outf.close()
- else:
- # hack: old code printed status line before many json outputs
- # (osd dump, etc.) that consumers know to ignore. Add blank line
- # to satisfy consumers that skip the first line, but not annoy
- # consumers that don't.
- if parsed_args.output_format and \
- parsed_args.output_format.startswith('json'):
- sys.stdout.write('\n');
-
- sys.stdout.write(outbuf)
-
- return 0
-
-if __name__ == '__main__':
- sys.exit(main())
--- /dev/null
+#
+# Processed in Makefile to add python #! line and version variable
+#
+
+import argparse
+import copy
+import errno
+import json
+import os
+import rados
+# for raw_input to do readline cmd editing
+import readline
+import socket
+import stat
+import string
+import struct
+import subprocess
+import sys
+import types
+import uuid
+
+# just a couple of globals
+
+verbose = False
+cluster_handle = None
+
+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 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), and return True
+ if not, throw ArgumentError(msg-as-to-why)
+ """
+ self.val = s
+ return True
+
+ 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
+ return True
+
+ 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
+ return True
+
+ 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.
+ """
+ def __init__(self, badchars=''):
+ self.badchars = badchars
+
+ def valid(self, s, partial=False):
+ for c in self.badchars:
+ if c in s:
+ raise ArgumentFormat("bad char {0} in {1}".format(c, s))
+ self.val = s
+ return True
+
+ def __str__(self):
+ b = ''
+ if len(self.badchars):
+ b = '(without chars in {0})'.format(self.badchars)
+ 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
+ return True
+ 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
+ return True
+
+ 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('/')
+ if not super(self.__class__, self).valid(ip):
+ raise ArgumentValid('CephEntityAddr {0}: ip address invalid'.format(s))
+ self.val = s
+ return True
+
+ 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 valid(self, s, partial=False):
+ self.val = s
+ return True
+
+ 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
+ return True
+
+ def __str__(self):
+ return '<pgid>'
+
+class CephName(CephArgtype):
+ """
+ Name, or type.id, where type is osd|mon|client|mds, and id is a base10 int,
+ or just id.
+
+ Also accept '*'
+ """
+ def valid(self, s, partial=False):
+ if s == '*':
+ self.val = s
+ self.nametype = None
+ self.nameid = None
+ return True
+ if s.find('.') == -1:
+ i = s
+ else:
+ t, i = s.split('.')
+ if not t in ('osd', 'mon', 'client', 'mds'):
+ raise ArgumentValid('unknown type ' + self.t)
+ if t == 'osd':
+ try:
+ i = int(i)
+ except:
+ raise ArgumentFormat('osd id ' + i + ' not integer')
+ self.nametype = t
+ self.val = s
+ self.nameid = i
+ return True
+
+ def __str__(self):
+ return '<name (type.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 True
+
+ # partial
+ for t in self.strings:
+ if t.startswith(s):
+ self.val = s
+ return True
+ 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
+ return True
+
+ 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
+ return True
+
+ 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
+ return True
+
+ 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 True
+ else:
+ if (s == self.prefix):
+ self.val = s
+ return True
+ 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 mods above
+ 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 = '{0}'.format(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 = '{0}'.format(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
+ """
+ first = True
+ s = ''
+ for d in sig:
+ if first:
+ first = False
+ else:
+ s += ' '
+ s += d.helpstr()
+ return s
+
+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):
+ """
+ parse_json_funcsigs(s)
+
+ 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}
+ .
+ .
+ .
+ ]
+
+ A set of sigs is in an dict mapped by a unique number:
+ {
+ "cmd1": {
+ "sig": ["type.. ], "help":{"text":helptext}
+ }
+ "cmd2"{
+ "sig": [.. ], "help":{"text":helptext}
+ }
+ }
+
+ Parse the string s and return an dict of dicts, keyed by opcode;
+ each dict contains 'sig' with the array of descriptors, and 'help'
+ with the helptext.
+ """
+ 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():
+ helptext = cmd.get('help', 'no help available')
+ try:
+ sig = cmd['sig']
+ except KeyError:
+ s = "JSON descriptor {0} has no 'sig'".format(cmdtag)
+ raise JsonFormat(s)
+ newsig = parse_funcsig(sig)
+ sigdict[cmdtag] = {'sig':newsig, 'helptext':helptext}
+ 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).
+ """
+ if desc.instance.valid(word, partial):
+ desc.numseen += 1
+ if desc.N:
+ desc.n = desc.numseen + 1
+ return True
+ return False
+
+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)
+ except:
+ 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 validate(args, signature, partial=False):
+ """
+ validate(s, signature, partial=False)
+
+ Assumes s represents a possible command input following format of
+ signature. Runs a validation; no exception means it's OK. Return
+ a dict containing all arguments named by their descriptor name
+ (with duplicate args per name accumulated into a space-separated
+ value).
+
+ If partial is set, allow partial matching (with partial dict returned)
+ """
+ words = args[:]
+ mysig = copy.deepcopy(signature)
+ d = dict()
+ for desc in mysig:
+ setattr(desc, 'numseen', 0)
+ while desc.numseen < desc.n:
+ if words:
+ word = words.pop(0)
+ else:
+ if 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
+ try:
+ validate_one(word, desc)
+ except Exception as e:
+ # not valid; if not required, just push back for the next one
+ if not desc.req:
+ words.insert(0, word)
+ break
+ else:
+ # hm, but it was required, so quit
+ if partial:
+ return d
+ raise e
+
+ 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):
+ # value 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
+ return d
+
+def osdids():
+ ret, outbuf, outs = json_command(prefix='osd ls')
+ if ret:
+ raise RuntimeError('Can\'t contact mon for osd list')
+ return [i for i in outbuf.split('\n')]
+
+def monids():
+ ret, outbuf, outs = json_command(prefix='mon dump',
+ argdict={'format':'json'})
+ if ret:
+ raise RuntimeError('Can\'t contact mon for mon list')
+ d = json.loads(outbuf)
+ return [m['name'] for m in d['mons']]
+
+def mdsids():
+ ret, outbuf, outs = json_command(prefix='mds dump',
+ argdict={'format':'json'})
+ if ret:
+ raise RuntimeError('Can\'t contact mon for mds list')
+ d = json.loads(outbuf)
+ l = []
+ infodict = d['info']
+ for mdsdict in infodict.values():
+ l.append(mdsdict['name'])
+ return l
+
+def parse_cmdargs(args=None, target=''):
+ # alias: let the line-wrapping be sane
+ AP = argparse.ArgumentParser
+
+ # format our own help
+ parser = AP(description='Frontend for ceph CLI', add_help=False)
+
+ parser.add_argument('--completion', action='store_true')
+
+ parser.add_argument('-h', '--help', help='request mon help',
+ action='store_true')
+ parser.add_argument('--help-all', help='request help for all daemons',
+ action='store_true')
+
+ parser.add_argument('-c', '--conf', dest='cephconf',
+ help='ceph configuration file')
+ parser.add_argument('-i', '--in-file', dest='input_file',
+ help='input file')
+ parser.add_argument('-o', '--out-file', dest='output_file',
+ help='output file')
+ parser.add_argument('-k', '--keyring', dest='keyring_file',
+ help='keyring file')
+
+ parser.add_argument('--id', '--user', dest='client_id',
+ help='client id for authentication')
+ parser.add_argument('--name', '-n', dest='client_name',
+ help='client name for authentication')
+ parser.add_argument('--cluster', help='cluster name')
+
+ parser.add_argument('--admin-daemon', dest='admin_socket',
+ help='submit admin-socket commands (\"help\" for help')
+
+ parser.add_argument('-s', '--status', action='store_true',
+ help='show cluster status')
+
+ parser.add_argument('-w', '--watch', action='store_true',
+ help='watch live cluster changes')
+ parser.add_argument('--watch-debug', action='store_true',
+ help='watch debug events')
+ parser.add_argument('--watch-info', action='store_true',
+ help='watch info events')
+ parser.add_argument('--watch-sec', action='store_true',
+ help='watch security events')
+ parser.add_argument('--watch-warn', action='store_true',
+ help='watch warn events')
+ parser.add_argument('--watch-error', action='store_true',
+ help='watch error events')
+
+ parser.add_argument('-v', action="store_true")
+ parser.add_argument('--verbose', action="store_true")
+ parser.add_argument('--concise', dest='verbose', action="store_false")
+
+ parser.add_argument('-f', '--format', choices=['json', 'json-pretty',
+ 'xml', 'xml-pretty', 'plain'], dest='output_format')
+ # for pg dump_stuck
+ parser.add_argument('--threshold', type=int, help='number of seconds for a pg to be considered stuck for pg dump_stuck')
+
+ # returns a Namespace with the parsed args, and a list of all extras
+ parsed_args, extras = parser.parse_known_args(args)
+
+ return parser, parsed_args, extras
+
+def do_help(parser, help_all = False):
+ """
+ Print basic parser help
+ If the cluster is available:
+ get and print monitor help;
+ if help_all, print help for daemon commands as well
+ """
+
+ def help_for_target(target):
+ ret, outbuf, outs = json_command(target=target,
+ prefix='get_command_descriptions',
+ timeout=10)
+ if ret:
+ print >> sys.stderr, \
+ "couldn't get command descriptions for {0}: {1}".\
+ format(target, outs)
+ else:
+ sys.stdout.write(format_help(parse_json_funcsigs(outbuf)))
+
+ parser.print_help()
+ print '\n'
+ if (cluster_handle):
+ help_for_target(target=('mon', ''))
+
+ if help_all and cluster_handle:
+ # try/except in case there are no daemons of that type
+ try:
+ firstosd = osdids()[0]
+ print '\nOSD.{0} tell commands and pg pgid commands:\n\n'.\
+ format(firstosd)
+ help_for_target(target=('osd', osdids()[0]))
+
+ print '\nOSD daemon commands:\n\n'
+ sys.stdout.write(format_help(parse_json_funcsigs(admin_socket(ceph_conf('admin_socket', 'osd.' + firstosd), ['get_command_descriptions']))))
+ except:
+ pass
+
+ try:
+ firstmon = monids()[0]
+ print '\nmon.{0} daemon commands:\n\n'.format(firstmon)
+ sys.stdout.write(format_help(parse_json_funcsigs(admin_socket(ceph_conf('admin_socket', 'mon.' + firstmon), ['get_command_descriptions']))))
+ except:
+ pass
+
+ try:
+ firstmds = mdsids()[0]
+ print '\nmds.{0} daemon commands:\n\n'.format(firstmds)
+ sys.stdout.write(format_help(parse_json_funcsigs(admin_socket(ceph_conf('admin_socket', 'mds.' + firstmds), ['get_command_descriptions']))))
+ except:
+ pass
+
+ return 0
+
+
+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']))
+
+
+dontsplit = string.letters + '{[<>]}'
+
+def wrap(s, width, indent):
+ """
+ generator to transform s into a sequence of strings width or shorter,
+ for wrapping text to a specific column width.
+ Attempt to break on anything but dontsplit characters.
+ indent is amount to indent 2nd-through-nth lines.
+
+ so "long string long string long string" width=11 indent=1 becomes
+ 'long string', ' long string', ' long string' so that it can be printed
+ as
+ long string
+ long string
+ long string
+
+ Consumes s.
+ """
+ result = ''
+ leader = ''
+ while len(s):
+
+ if (len(s) <= width):
+ # no splitting; just possibly indent
+ result = leader + s
+ s = ''
+ yield result
+
+ else:
+ splitpos = width
+ while (splitpos > 0) and (s[splitpos-1] in dontsplit):
+ splitpos -= 1
+
+ if splitpos == 0:
+ splitpos = width
+
+ if result:
+ # prior result means we're mid-iteration, indent
+ result = leader
+ else:
+ # first time, set leader and width for next
+ leader = ' ' * indent
+ width -= 1 # for subsequent space additions
+
+ # remove any leading spaces in this chunk of s
+ result += s[:splitpos].lstrip()
+ s = s[splitpos:]
+
+ yield result
+
+ raise StopIteration
+
+def format_help(cmddict):
+ """
+ Formats all the cmdsigs and helptexts from cmddict into a sorted-by-
+ cmdsig 2-column display, with each column wrapped and indented to
+ fit into 40 characters.
+ """
+
+ fullusage = ''
+ for cmd in sorted(cmddict.itervalues(), cmp=descsort):
+
+ if not cmd['helptext']:
+ continue
+ siglines = [l for l in wrap(concise_sig(cmd['sig']), 40, 1)]
+ helplines = [l for l in wrap(cmd['helptext'], 39, 1)]
+
+ # make lists the same length
+ maxlen = max(len(siglines), len(helplines))
+ siglines.extend([''] * (maxlen - len(siglines)))
+ helplines.extend([''] * (maxlen - len(helplines)))
+
+ # so we can zip them for output
+ for (s, h) in zip(siglines, helplines):
+ fullusage += '{0:40s} {1}\n'.format(s, h)
+
+ return fullusage
+
+def validate_command(parsed_args, sigdict, args):
+ """
+ turn args into a valid dictionary ready to be sent off as JSON,
+ validated against sigdict.
+ parsed_args is the namespace back from argparse
+ """
+ found = []
+ valid_dict = {}
+ if args:
+ # Repulsive hack to handle tell: lop off 'tell' and target
+ # and validate the rest of the command. 'target' is already
+ # determined in our callers, so it's ok to remove it here.
+ if args[0] == 'tell':
+ args = args[2:]
+ # 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})
+
+ if verbose:
+ print >> sys.stderr, "bestcmds: ", bestcmds
+
+ # for everything in bestcmds, look for a true match
+ for cmdsig in bestcmds:
+ for cmd in cmdsig.itervalues():
+ sig = cmd['sig']
+ helptext = cmd['helptext']
+ try:
+ valid_dict = validate(args, sig)
+ found = sig
+ break
+ except ArgumentPrefix:
+ # this means a CephPrefix type didn't match; since
+ # this is common, just eat it
+ pass
+ except ArgumentError as e:
+ # prefixes matched, but some other arg didn't;
+ # this is interesting information
+ print >> sys.stderr, '{0}: invalid command'.\
+ format(' '.join(args))
+ print >> sys.stderr, '{0}'.format(e)
+ print >> sys.stderr, "did you mean {0}?\n\t{1}".\
+ format(concise_sig(sig), helptext)
+ pass
+
+ if not found:
+ print >> sys.stderr, 'no valid command found'
+ print >> sys.stderr, 'close matches:'
+ for cmdsig in bestcmds:
+ for (cmdtag, cmd) in cmdsig.iteritems():
+ print >> sys.stderr, concise_sig(cmd['sig'])
+ return None
+
+ if parsed_args.output_format:
+ valid_dict['format'] = parsed_args.output_format
+
+ if parsed_args.threshold:
+ valid_dict['threshold'] = parsed_args.threshold
+
+ return valid_dict
+
+def json_command(target=('mon', ''), prefix=None, argdict=None, inbuf='',
+ timeout=0):
+ """
+ Send a new_style command to a daemon using librados's
+ mon_command, osd_command, or pg_command. Prefix may be supplied
+ separately or in argdict. 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)
+ """
+ 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()
+ if 'target' in cmddict:
+ osdtarget = cmddict.pop('target')
+ else:
+ osdtarget = '{0}.{1}'.format(*target)
+
+ try:
+ osdtarg.valid(osdtarget)
+ osdid = osdtarg.nameid
+ except:
+ # uh..I dunno, try osd.0?
+ osdid = 0
+
+ if verbose:
+ print >> sys.stderr, 'submit {0} to osd.{1}'.\
+ format(json.dumps(cmddict), osdid)
+ ret, outbuf, outs = \
+ cluster_handle.osd_command(osdid, json.dumps(cmddict), inbuf,
+ timeout)
+
+ elif target[0] == 'pg':
+ # leave it in cmddict for the OSD to use too
+ pgid = target[1]
+ if verbose:
+ print >> sys.stderr, 'submit {0} for pgid {1}'.\
+ format(json.dumps(cmddict), pgid)
+ ret, outbuf, outs = \
+ cluster_handle.pg_command(pgid, json.dumps(cmddict), inbuf,
+ timeout)
+
+ elif target[0] == 'mon':
+ if verbose:
+ print >> sys.stderr, '{0} to {1}'.\
+ format(json.dumps(cmddict), target[0])
+ ret, outbuf, outs = cluster_handle.mon_command(json.dumps(cmddict),
+ inbuf, timeout)
+
+ except Exception as e:
+ raise RuntimeError('"{0}": exception {1}'.format(prefix, e))
+
+ return ret, outbuf, outs
+
+def admin_socket(asok_path, cmd):
+ sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+ try:
+ sock.connect(asok_path)
+ sock.sendall(' '.join(cmd) + '\0')
+
+ len_str = sock.recv(4)
+ l, = struct.unpack(">I", len_str)
+ ret = ''
+
+ got = 0
+ while got < l:
+ bit = sock.recv(l - got)
+ ret += bit
+ got += len(bit)
+
+ except Exception as e:
+ raise RuntimeError('exception: {0}'.format(e))
+
+ return ret
+
+
+def ceph_conf(field, name):
+ p = subprocess.Popen(
+ args=[
+ 'ceph-conf',
+ field,
+ '-n',
+ name,
+ ],
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+ outdata, errdata = p.communicate()
+ if (len(errdata)):
+ raise RuntimeError('unable to get conf option %s for %s: %s' % (field, name, errdata))
+ return outdata.rstrip()
+
+def new_style_command(parsed_args, cmdargs, target, sigdict, inbuf, verbose):
+ """
+ Do new-style command dance.
+ target: daemon to receive command: mon (any) or osd.N
+ sigdict - the parsed output from the new monitor describing commands
+ inbuf - any -i input file data
+ verbose - bool
+ """
+ if verbose:
+ for cmdtag in sorted(sigdict.keys()):
+ cmd = sigdict[cmdtag]
+ sig = cmd['sig']
+ print '{0}: {1}'.format(cmdtag, concise_sig(sig))
+
+ got_command = False
+
+ if not got_command:
+ if cmdargs:
+ # Validate input args against list of sigs
+ valid_dict = validate_command(parsed_args, sigdict, cmdargs)
+ if valid_dict:
+ got_command = True
+ else:
+ return -errno.EINVAL, '', 'invalid command'
+ else:
+ # do the command-interpreter looping
+ while True:
+ interactive_input = raw_input('ceph> ')
+ if interactive_input in ['q', 'quit', 'Q']:
+ return 0, '', ''
+ cmdargs = parse_cmdargs(interactive_input.split())[2]
+ target = find_cmd_target(cmdargs)
+ valid_dict = validate_command(parsed_args, sigdict, cmdargs)
+ if valid_dict:
+ if verbose:
+ print >> sys.stderr, "Submitting command ", valid_dict
+ ret, outbuf, outs = json_command(target=target,
+ argdict=valid_dict)
+ if ret:
+ sys.stderr.write('Error {0}: {1}'.format(ret, outs))
+ return ret, '', outs
+ else:
+ print "invalid command"
+
+ if verbose:
+ print >> sys.stderr, "Submitting command ", valid_dict
+ return json_command(target=target, argdict=valid_dict, inbuf=inbuf)
+
+OSD_TELL_MATCH = { \
+ 'sig': ['tell', {'name':'target','type':'CephName'}], \
+ 'matchnum': 2, \
+ 'return_key': 'target', \
+}
+PGID_MATCH = { \
+ 'sig': ['pg', {'name':'pgid','type':'CephPgid'}], \
+ 'matchnum': 2, \
+ 'return_key': 'pgid', \
+}
+
+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'}])
+ valid_dict = validate(childargs, sig, partial=True);
+ if len(valid_dict) == 2:
+ name = CephName()
+ name.valid(valid_dict['target'])
+ return 'osd', name.nameid
+
+ sig = parse_funcsig(['pg', {'name':'pgid','type':'CephPgid'}])
+ valid_dict = validate(childargs, sig, partial=True);
+ if len(valid_dict) == 2:
+ return 'pg', valid_dict['pgid']
+
+ return 'mon', ''
+
+def complete(sigdict, args, target):
+ """
+ Command completion. Match as much of [args] as possible,
+ and print every possible match separated by newlines.
+ Return exitcode.
+ """
+ # XXX this looks a lot like the front of validate_command(). Refactor?
+
+ complete_verbose = 'COMPVERBOSE' in os.environ
+
+ # Repulsive hack to handle tell: lop off 'tell' and target
+ # and validate the rest of the command. 'target' is already
+ # determined in our callers, so it's ok to remove it here.
+ if len(args) and args[0] == 'tell':
+ args = args[2:]
+ # 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 complete_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 complete_verbose:
+ print >> sys.stderr, \
+ "equal match: {0} > {1}: {2}:{3} ".format(matched,
+ best_match_cnt, cmdtag, concise_sig(sig))
+ bestcmds.append({cmdtag:cmd})
+
+ # look through all matching sigs
+ comps = []
+ for cmddict in bestcmds:
+ for cmd in cmddict.itervalues():
+ sig = cmd['sig']
+ # either:
+ # we match everything fully, so we want the next desc, or
+ # we match more partially, so we want the partial match
+ fullindex = matchnum(args, sig, partial=False) - 1
+ partindex = matchnum(args, sig, partial=True) - 1
+ if complete_verbose:
+ print >> sys.stderr, '{}: f {} p {} len {}'.format(sig, fullindex, partindex, len(sig))
+ if fullindex == partindex and fullindex + 1 < len(sig):
+ d = sig[fullindex + 1]
+ else:
+ d = sig[partindex]
+ comps.append(str(d))
+ if complete_verbose:
+ print >> sys.stderr, '\n'.join(comps)
+ print '\n'.join(comps)
+
+ return 0
+
+###
+# main
+###
+
+def main():
+
+ parser, parsed_args, childargs = parse_cmdargs()
+
+ if parsed_args.v:
+ print 'ceph version {0} ({1})'.format(CEPH_GIT_NICE_VER, CEPH_GIT_VER)
+ return 0
+
+ global verbose
+ verbose = parsed_args.verbose
+
+ # pass on --id, --name, --conf
+ name = 'client.admin'
+ if parsed_args.client_id:
+ name = 'client.' + parsed_args.client_id
+ if parsed_args.client_name:
+ name = parsed_args.client_name
+
+ # default '' means default conf search
+ conffile = ''
+ if parsed_args.cephconf:
+ conffile = parsed_args.cephconf
+ # For now, --admin-daemon is handled as usual. Try it
+ # first in case we can't connect() to the cluster
+ if parsed_args.admin_socket:
+ try:
+ print admin_socket(parsed_args.admin_socket, childargs)
+ except Exception as e:
+ print >> sys.stderr, 'admin_socket: {0}'.format(e)
+ return 0
+
+ if len(childargs) > 0 and childargs[0] == "daemon":
+ if len(childargs) > 2:
+ if childargs[1].find('/') >= 0:
+ try:
+ print admin_socket(childargs[1], childargs[2:])
+ except Exception as e:
+ print >> sys.stderr, 'admin_socket: {0}'.format(e)
+ return 0
+ else:
+ # try resolve daemon name
+ path = ceph_conf('admin_socket', childargs[1])
+ try:
+ print admin_socket(path, childargs[2:])
+ except Exception as e:
+ print >> sys.stderr, 'admin_socket: {0}'.format(e)
+ return 0
+ else:
+ print >> sys.stderr, 'Daemon requires at least 2 arguments'
+ return 1
+
+ # handle any 'generic' ceph arguments that we didn't parse here
+ global cluster_handle
+
+ # rados.Rados() will call rados_create2, and then read the conf file,
+ # and then set the keys from the dict. So we must do these
+ # "pre-file defaults" first (see common_preinit in librados)
+ conf_defaults = {
+ 'log_to_stderr':'true',
+ 'err_to_stderr':'true',
+ 'log_flush_on_exit':'true',
+ }
+
+ clustername = 'ceph'
+ if parsed_args.cluster:
+ clustername = parsed_args.cluster
+
+ cluster_handle = rados.Rados(name=name, clustername=clustername,
+ conf_defaults=conf_defaults, conffile='')
+
+ retargs = cluster_handle.conf_parse_argv(childargs)
+ #tmp = childargs
+ childargs = retargs
+ if not childargs:
+ childargs = []
+
+ # -- means "stop parsing args", but we don't want to see it either
+ if '--' in childargs:
+ childargs.remove('--')
+
+ try:
+ cluster_handle.connect()
+ except KeyboardInterrupt:
+ print >> sys.stderr, 'Cluster connection aborted'
+ return 1
+ except Exception as e:
+ print >> sys.stderr, 'Error connecting to cluster: {0}'.\
+ format(e.__class__.__name__)
+ return 1
+
+ if parsed_args.help or parsed_args.help_all:
+ return do_help(parser, parsed_args.help_all)
+
+ # implement -w/--watch_*
+ # This is ugly, but Namespace() isn't quite rich enough.
+ level = ''
+ for k,v in parsed_args._get_kwargs():
+ if k.startswith('watch') and v:
+ if k == 'watch':
+ level = 'info'
+ else:
+ level = k.replace('watch_', '')
+ if level:
+
+ # an awfully simple callback
+ def watch_cb(arg, line, who, stamp_sec, stamp_nsec, seq, level, msg):
+ print line
+
+ # first do a ceph status
+ ret, outbuf, outs = json_command(prefix='status')
+ if ret:
+ print >> sys.stderr, "status query failed: ", outs
+ return ret
+ print outbuf
+
+ # this instance keeps the watch connection alive, but is
+ # otherwise unused
+ logwatch = rados.MonitorLog(cluster_handle, level, watch_cb, 0)
+ # loop forever letting watch_cb print lines
+ while True:
+ try:
+ pass
+ except KeyboardInterrupt:
+ # or until ^C, at least
+ return 0
+
+ # read input file, if any
+ inbuf = ''
+ if parsed_args.input_file:
+ try:
+ with open(parsed_args.input_file, 'r') as f:
+ inbuf = f.read()
+ except Exception as e:
+ print >> sys.stderr, 'Can\'t open input file {0}: {1}'.format(parsed_args.input_file, e)
+ return 1
+
+ # prepare output file, if any
+ if parsed_args.output_file:
+ try:
+ outf = open(parsed_args.output_file, 'w')
+ except:
+ print >> sys.stderr, \
+ 'Can\'t open output file {0}: {1}'.\
+ format(parsed_args.output_file, e)
+ return 1
+
+ # -s behaves like a command (ceph status).
+ if parsed_args.status:
+ childargs.insert(0, 'status')
+
+ target = find_cmd_target(childargs)
+
+ # fetch JSON sigs from command
+ # each line contains one command signature (a placeholder name
+ # of the form 'cmdNNN' followed by an array of argument descriptors)
+ # as part of the validated argument JSON object
+
+ ret, outbuf, outs = json_command(target=target,
+ prefix='get_command_descriptions')
+ if ret == -errno.EINVAL:
+ # send command to old monitor
+ if verbose:
+ print '{0} to old monitor'.format(' '.join(childargs))
+ ret, outbuf, outs = cluster_handle.mon_command(childargs, inbuf)
+ elif ret:
+ if ret < 0:
+ ret = -ret
+ print >> sys.stderr, \
+ 'Problem getting command descriptions from {0}, {1}'.\
+ format(target, errno.errorcode[ret])
+ return ret
+ else:
+ sigdict = parse_json_funcsigs(outbuf)
+
+ if parsed_args.completion:
+ return complete(sigdict, childargs, target)
+
+ ret, outbuf, outs = new_style_command(parsed_args, childargs, target,
+ sigdict, inbuf, verbose)
+
+ if ret < 0:
+ ret = -ret
+ print >> sys.stderr, 'Error {0}: {1}'.format(errno.errorcode[ret], outs)
+ return ret
+
+ # this assumes outs never has useful command output, only status
+ if outs:
+ print >> sys.stderr, outs
+
+ if (parsed_args.output_file):
+ outf.write(outbuf)
+ outf.close()
+ else:
+ # hack: old code printed status line before many json outputs
+ # (osd dump, etc.) that consumers know to ignore. Add blank line
+ # to satisfy consumers that skip the first line, but not annoy
+ # consumers that don't.
+ if parsed_args.output_format and \
+ parsed_args.output_format.startswith('json'):
+ sys.stdout.write('\n');
+
+ sys.stdout.write(outbuf)
+
+ return 0
+
+if __name__ == '__main__':
+ sys.exit(main())