* [Blueprint](http://wiki.ceph.com/Planning/Blueprints/Firefly/Ceph-Brag)
* [CDS Etherpad](http://pad.ceph.com/p/cdsfirefly-ceph-brag)
-How to use:
-===========
-Pre-requisites:
----------------
+# Client
+
+## How to use:
+
+### Pre-requisites:
ceph-brag uses 'ceph' python script. Hence, before executing ceph-brag script ensure that ceph services are all running and 'ceph' script is in 'PATH' environment
-Runtime instructions:
----------------------
+### Runtime instructions:
Run 'ceph-brag -h' to get the usage information of this tool.
-Sample output:
---------------
+### Sample output:
{
"cluster_creation_date": "2014-01-16 13:38:41.928551",
}
]
}
+
+
+# Server
+
+## Info
+The ceph-brag server code is a python based web application.
+
+## How to use
+
+### Prerequisites
+* [pecan](http://pecanpy.org) is the web framework that is used by this application.
+
+### How to deploy
+* [Common recipes to deploy](http://pecan.readthedocs.org/en/latest/deployment.html#common-recipes)
+
+## URLs
+Following are the REST urls that are implemented with 'url-prefix' being the mount point for the WSGI script
+
+### GET
+
+##### * GET /url-prefix/
+Returns the list of clusters that are registered so far.
+Outputs - On success application/json of the following format is returned
+
+ [
+ {
+ "num_versions": 3,
+ "cluster_creation_date": "2014-01-16 13:38:41.928551",
+ "uuid": "20679d0e-04b1-4004-8ee9-45ac271510e9",
+ "cluster_name": "Cluster1",
+ "organization": "eNovance",
+ "email": "mail@enovance.com"
+ },
+ ...
+ ]
+
+##### * GET /url-prefix/UUID
+Returns the list of version information for a particular UUID.
+Outputs - On success application/json of the following format is returned
+
+ [
+ {
+ "version_number": 1,
+ "version_date": "2014-02-10 10:17:56.283499",
+ "version_id": 10
+ },
+ ...
+ ]
+
+##### * GET /url-prefix/UUID/version\_number
+Returns the entire brag report as mentioned in client's sample output for a particular version of a UUID
+
+### PUT
+
+##### * PUT /url-prefix
+Uploads the brag report and creates a new version for the UUID mentioned in the payload
+
+### DELETE
+
+##### * DELETE /url-prefix?uuid=xxxx
+Deletes all the versions of a cluster whose UUID is sent as a parameter
+
+
+++ /dev/null
-#!/usr/bin/env python
-
-import subprocess
-import uuid
-import re
-import json
-import sys
-import ast
-
-CLUSTER_UUID_NAME='cluster-uuid'
-CLUSTER_OWNERSHIP_NAME='cluster-ownership'
-
-def run_command(cmd):
- child = subprocess.Popen(cmd, stdout=subprocess.PIPE,
- stderr=subprocess.PIPE)
- (o, e) = child.communicate()
- return (child.returncode, o, e)
-
-def get_uuid():
- (rc,uid,e) = run_command(['ceph', 'config-key', 'get', CLUSTER_UUID_NAME])
- if rc is not 0:
- #uuid is not yet set.
- uid = str(uuid.uuid4())
- (rc, o, e) = run_command(['ceph', 'config-key', 'put',
- CLUSTER_UUID_NAME, uid])
- if rc is not 0:
- raise RuntimeError("\'ceph config-key put\' failed -" + e)
-
- return uid
-
-def get_cluster_creation_date():
- (rc, o, e) = run_command(['ceph', 'mon', 'dump'])
- if rc is not 0:
- raise RuntimeError("\'ceph mon dump\' failed - " + e)
-
- rec = re.compile('(.*created\ )(.*)(\n.*)')
-
- mo = rec.search(o);
- if mo and mo.group(2) != '0.000000':
- return mo.group(2)
-
- # Try and get the date from osd dump
- (rc, o, e) = run_command(['ceph', 'osd', 'dump'])
- if rc is not 0:
- raise RuntimeError("\'ceph osd dump\' failed - " + e)
-
- mo = rec.search(o);
- if not mo or mo.group(2) == '0.000000':
- print >> sys.stderr, "Unable to get cluster creation date"
-
- return mo.group(2)
-
-def get_nums():
- (rc, o, e) = run_command(['ceph', '-s'])
- if rc is not 0:
- raise RuntimeError("\'ceph -s\' failed - " + e)
-
- num_mons = 0
-
- mo = re.search('(.*monmap\ .*:\ )(\d+)(.*)', o)
- if not mo:
- raise RuntimeError("Unmatched pattern for monmap in \'ceph status\'")
- else:
- num_mons = int(mo.group(2))
-
- num_osds = 0
- mo = re.search('.*osdmap.*(\d+).*(\d+).*(\d+).*', o)
- if not mo:
- raise RuntimeError("Unmatched pattern for osdmap in \'ceph status\'")
- else:
- num_osds = int(mo.group(1))
-
- num_mdss = 0
- mo = re.search('.*mdsmap\ e\d+.*(\d+)/(\d+)/(\d+).*', o)
- if mo:
- num_mdss = int(mo.group(2));
-
- num_pgs = 0
- num_pools = 0
- num_bytes = 0
- num_objs = 0
- mo = re.search('.*pgmap\ v\d+:\ (\d+).*,\ (\d+).*,\ (\d+)\ (\S+)\ data,\ (\d+).*', o)
- if not mo:
- raise RuntimeError("Unmatched pattern for pgmap in \'ceph status\'")
- else:
- num_pgs = int(mo.group(1))
- num_pools = int(mo.group(2))
- byte_count = int(mo.group(3))
- byte_scale = mo.group(4)
- num_objs = int(mo.group(5))
- nums = {'mons':num_mons,
- 'osds':num_osds,
- 'mdss':num_mdss,
- 'pgs':num_pgs,
- 'pools':num_pools,
- 'bytes': {'count':byte_count, 'scale':byte_scale},
- 'objects':num_objs}
- return nums
-
-def get_crush_types():
- (rc, o, e) = run_command(['ceph', 'osd', 'crush', 'dump'])
- if rc is not 0:
- raise RuntimeError("\'ceph osd crush dump\' failed - " + e)
-
- crush_dump = json.loads(o)
- if crush_dump['types'] is None:
- raise RuntimeError("\'types\' item missing in \'ceph osd crush dump\'")
-
- crush_types = []
- for t in crush_dump['types']:
- crush_types.append(t['name'])
-
- return crush_types
-
-def get_pool_metadata():
- (rc, o, e) = run_command(['ceph', 'osd', 'dump'])
- if rc is not 0:
- raise RuntimeError("\'ceph osd dump\' failed - " + e)
-
- result = re.findall("pool\ (\d+)\ '(\S+)'\ rep\ size\ (\d+)", o)
- if len(result) is 0:
- #Check with replicated size
- result = re.findall("pool\ (\d+)\ '(\S+)'\ replicated\ size\ (\d+)", o)
- if len(result) is 0:
- raise RuntimeError("Unmatched pattern for \'pool\' in \'ceph osd dump\'")
-
- pool_meta = []
- proc = lambda x: {'id':x[0], 'name':x[1], 'rep_size':int(x[2])}
- for r in result:
- pool_meta.append(proc(r))
-
- return pool_meta
-
-def get_sysinfo(max_osds):
- sysinfo = []
- count = 0
- osd_metadata_available = False
-
- while count < max_osds:
- meta = {'id':count}
- (rc, o, e) = run_command(['ceph', 'osd', 'metadata', str(count)])
- if rc is 0:
- if osd_metadata_available is False:
- osd_metadata_available = True
- os_info = {}
- hw_info = {}
- nw_info = {}
-
- jmeta = json.loads(o)
-
- meta['ceph_version'] = jmeta['ceph_version']
-
- os_info['os'] = jmeta['os']
- os_info['version'] = jmeta['kernel_version']
- os_info['description'] = jmeta['kernel_description']
-
- try:
- distro = jmeta['distro'] + ' '
- distro += jmeta['distro_version'] + ' '
- distro += jmeta['distro_codename'] + ' ('
- distro += jmeta['distro_description'] + ')'
- os_info['distro'] = distro
- except KeyError as ke:
- pass
- meta['os_info'] = os_info
-
- hw_info['cpu'] = jmeta['cpu']
- hw_info['arch'] = jmeta['arch']
- hw_info['mem_kb'] = int(jmeta['mem_total_kb'])
- hw_info['swap_kb'] = int(jmeta['mem_swap_kb'])
- meta['hw_info'] = hw_info
-
- (ip, hname) = get_osd_host(count)
- nw_info['address'] = ip
- nw_info['hostname'] = hname
- meta['nw_info'] = nw_info
-
- sysinfo.append(meta)
- count = count + 1
-
- if osd_metadata_available is False:
- print >> sys.stderr, "'ceph osd metadata' is not available at all"
-
- return sysinfo
-
-def get_osd_host(osd_id):
- loc = {}
-
- (rc, o, e) = run_command(['ceph', 'osd', 'find', str(osd_id)])
- if rc is not 0:
- raise RuntimeError("\'ceph osd find\' failed - " + e)
-
- jloc = json.loads(o)
-
- mo = re.search("(\d+.\d+.\d+.\d+).*", jloc['ip'])
- if mo is None:
- #Might be in ipv6 format, TODO: Verify
- return None;
-
- ip = mo.group(1)
- host = jloc['crush_location']['host']
-
- return (ip, host)
-
-def get_ownership_info():
- (rc, o, e) = run_command(['ceph', 'config-key', 'get',
- CLUSTER_OWNERSHIP_NAME])
- if rc is not 0:
- return {}
-
- return ast.literal_eval(o)
-
-def output_json():
- out = {}
- out['uuid'] = get_uuid()
- out['cluster_creation_date'] = get_cluster_creation_date()
- nums = get_nums()
- num_osds = int(nums['osds'])
- out['components_count'] = nums
- out['crush_types'] = get_crush_types()
- out['pool_metadata'] = get_pool_metadata()
- out['sysinfo'] = get_sysinfo(num_osds)
-
- owner = get_ownership_info()
- if owner is not None:
- out['ownership'] = owner
-
- return json.dumps(out, indent=2, separators=(',', ': '))
-
-def describe_usage():
- print >> sys.stderr, "Usage:"
- print >> sys.stderr, "======\n"
-
- print >> sys.stderr, sys.argv[0] + " <commands> [command-options]\n"
- print >> sys.stderr, "commands:"
- print >> sys.stderr, "publish - publish the brag report to the server"
- print >> sys.stderr, "update-metadata <update-metadata-options> - Update"
- print >> sys.stderr, " ownership information for bragging"
- print >> sys.stderr, "clear-metadata - Clear information set by update-metadata"
- print >> sys.stderr, "unpublish --yes-i-am-shy - delete the brag report from the server"
- print >> sys.stderr, ""
-
- print >> sys.stderr, "update-metadata options:"
- print >> sys.stderr, "--name= - Name of the cluster"
- print >> sys.stderr, "--organization= - Name of the organization"
- print >> sys.stderr, "--email= - Email contact address"
- print >> sys.stderr, "--description= - Reporting use-case"
- print >> sys.stderr, ""
-
-def update_metadata():
- info = {}
- possibles = ['name', 'organization', 'email', 'description']
-
- #get the existing values
- info = get_ownership_info();
-
- for index in range(2, len(sys.argv)):
- mo = re.search("--(\S+)=(.*)", sys.argv[index])
- if not mo:
- describe_usage()
- return 22
-
- k = mo.group(1)
- v = mo.group(2)
-
- if k in possibles:
- info[k] = v
- else:
- print >> sys.stderr, "Unexpect option --" + k
- describe_usage()
- return 22
-
- (rc, o, e) = run_command(['ceph', 'config-key', 'put',
- CLUSTER_OWNERSHIP_NAME, str(info)])
- return rc
-
-def clear_metadata():
- (rc, o, e) = run_command(['ceph', 'config-key', 'del',
- CLUSTER_OWNERSHIP_NAME])
- return rc
-
-def main():
- if len(sys.argv) is 1:
- print output_json()
- return 0
- elif sys.argv[1] == 'update-metadata':
- return update_metadata()
- elif sys.argv[1] == 'clear-metadata':
- return clear_metadata()
- elif sys.argv[1] == 'publish':
- data = output_json()
- #TODO: post this data to the server
- return 0
- elif sys.argv[1] == 'unpublish':
- if len(sys.argv) <= 2 or sys.argv[2] != '--yes-i-am-shy':
- print >> sys.stderr, "unpublish should be followed by --yes-i-am-shy"
- return 22
-
- #TODO: delete this data from the server
- return 0
- else:
- describe_usage()
- return 22
-
-if __name__ == '__main__':
- sys.exit(main())
--- /dev/null
+#!/usr/bin/env python
+
+import subprocess
+import uuid
+import re
+import json
+import sys
+import ast
+
+CLUSTER_UUID_NAME='cluster-uuid'
+CLUSTER_OWNERSHIP_NAME='cluster-ownership'
+
+def run_command(cmd):
+ child = subprocess.Popen(cmd, stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+ (o, e) = child.communicate()
+ return (child.returncode, o, e)
+
+def get_uuid():
+ (rc,uid,e) = run_command(['ceph', 'config-key', 'get', CLUSTER_UUID_NAME])
+ if rc is not 0:
+ #uuid is not yet set.
+ uid = str(uuid.uuid4())
+ (rc, o, e) = run_command(['ceph', 'config-key', 'put',
+ CLUSTER_UUID_NAME, uid])
+ if rc is not 0:
+ raise RuntimeError("\'ceph config-key put\' failed -" + e)
+
+ return uid
+
+def get_cluster_creation_date():
+ (rc, o, e) = run_command(['ceph', 'mon', 'dump'])
+ if rc is not 0:
+ raise RuntimeError("\'ceph mon dump\' failed - " + e)
+
+ rec = re.compile('(.*created\ )(.*)(\n.*)')
+
+ mo = rec.search(o);
+ if mo and mo.group(2) != '0.000000':
+ return mo.group(2)
+
+ # Try and get the date from osd dump
+ (rc, o, e) = run_command(['ceph', 'osd', 'dump'])
+ if rc is not 0:
+ raise RuntimeError("\'ceph osd dump\' failed - " + e)
+
+ mo = rec.search(o);
+ if not mo or mo.group(2) == '0.000000':
+ print >> sys.stderr, "Unable to get cluster creation date"
+
+ return mo.group(2)
+
+def get_nums():
+ (rc, o, e) = run_command(['ceph', '-s'])
+ if rc is not 0:
+ raise RuntimeError("\'ceph -s\' failed - " + e)
+
+ num_mons = 0
+
+ mo = re.search('(.*monmap\ .*:\ )(\d+)(.*)', o)
+ if not mo:
+ raise RuntimeError("Unmatched pattern for monmap in \'ceph status\'")
+ else:
+ num_mons = int(mo.group(2))
+
+ num_osds = 0
+ mo = re.search('.*osdmap.*(\d+).*(\d+).*(\d+).*', o)
+ if not mo:
+ raise RuntimeError("Unmatched pattern for osdmap in \'ceph status\'")
+ else:
+ num_osds = int(mo.group(1))
+
+ num_mdss = 0
+ mo = re.search('.*mdsmap\ e\d+.*(\d+)/(\d+)/(\d+).*', o)
+ if mo:
+ num_mdss = int(mo.group(2));
+
+ num_pgs = 0
+ num_pools = 0
+ num_bytes = 0
+ num_objs = 0
+ mo = re.search('.*pgmap\ v\d+:\ (\d+).*,\ (\d+).*,\ (\d+)\ (\S+)\ data,\ (\d+).*', o)
+ if not mo:
+ raise RuntimeError("Unmatched pattern for pgmap in \'ceph status\'")
+ else:
+ num_pgs = int(mo.group(1))
+ num_pools = int(mo.group(2))
+ byte_count = int(mo.group(3))
+ byte_scale = mo.group(4)
+ num_objs = int(mo.group(5))
+ nums = {'mons':num_mons,
+ 'osds':num_osds,
+ 'mdss':num_mdss,
+ 'pgs':num_pgs,
+ 'pools':num_pools,
+ 'bytes': {'count':byte_count, 'scale':byte_scale},
+ 'objects':num_objs}
+ return nums
+
+def get_crush_types():
+ (rc, o, e) = run_command(['ceph', 'osd', 'crush', 'dump'])
+ if rc is not 0:
+ raise RuntimeError("\'ceph osd crush dump\' failed - " + e)
+
+ crush_dump = json.loads(o)
+ if crush_dump['types'] is None:
+ raise RuntimeError("\'types\' item missing in \'ceph osd crush dump\'")
+
+ crush_types = []
+ for t in crush_dump['types']:
+ crush_types.append(t['name'])
+
+ return crush_types
+
+def get_pool_metadata():
+ (rc, o, e) = run_command(['ceph', 'osd', 'dump'])
+ if rc is not 0:
+ raise RuntimeError("\'ceph osd dump\' failed - " + e)
+
+ result = re.findall("pool\ (\d+)\ '(\S+)'\ rep\ size\ (\d+)", o)
+ if len(result) is 0:
+ #Check with replicated size
+ result = re.findall("pool\ (\d+)\ '(\S+)'\ replicated\ size\ (\d+)", o)
+ if len(result) is 0:
+ raise RuntimeError("Unmatched pattern for \'pool\' in \'ceph osd dump\'")
+
+ pool_meta = []
+ proc = lambda x: {'id':x[0], 'name':x[1], 'rep_size':int(x[2])}
+ for r in result:
+ pool_meta.append(proc(r))
+
+ return pool_meta
+
+def get_sysinfo(max_osds):
+ sysinfo = []
+ count = 0
+ osd_metadata_available = False
+
+ while count < max_osds:
+ meta = {'id':count}
+ (rc, o, e) = run_command(['ceph', 'osd', 'metadata', str(count)])
+ if rc is 0:
+ if osd_metadata_available is False:
+ osd_metadata_available = True
+ os_info = {}
+ hw_info = {}
+ nw_info = {}
+
+ jmeta = json.loads(o)
+
+ meta['ceph_version'] = jmeta['ceph_version']
+
+ os_info['os'] = jmeta['os']
+ os_info['version'] = jmeta['kernel_version']
+ os_info['description'] = jmeta['kernel_description']
+
+ try:
+ distro = jmeta['distro'] + ' '
+ distro += jmeta['distro_version'] + ' '
+ distro += jmeta['distro_codename'] + ' ('
+ distro += jmeta['distro_description'] + ')'
+ os_info['distro'] = distro
+ except KeyError as ke:
+ pass
+ meta['os_info'] = os_info
+
+ hw_info['cpu'] = jmeta['cpu']
+ hw_info['arch'] = jmeta['arch']
+ hw_info['mem_kb'] = int(jmeta['mem_total_kb'])
+ hw_info['swap_kb'] = int(jmeta['mem_swap_kb'])
+ meta['hw_info'] = hw_info
+
+ (ip, hname) = get_osd_host(count)
+ nw_info['address'] = ip
+ nw_info['hostname'] = hname
+ meta['nw_info'] = nw_info
+
+ sysinfo.append(meta)
+ count = count + 1
+
+ if osd_metadata_available is False:
+ print >> sys.stderr, "'ceph osd metadata' is not available at all"
+
+ return sysinfo
+
+def get_osd_host(osd_id):
+ loc = {}
+
+ (rc, o, e) = run_command(['ceph', 'osd', 'find', str(osd_id)])
+ if rc is not 0:
+ raise RuntimeError("\'ceph osd find\' failed - " + e)
+
+ jloc = json.loads(o)
+
+ mo = re.search("(\d+.\d+.\d+.\d+).*", jloc['ip'])
+ if mo is None:
+ #Might be in ipv6 format, TODO: Verify
+ return None;
+
+ ip = mo.group(1)
+ host = jloc['crush_location']['host']
+
+ return (ip, host)
+
+def get_ownership_info():
+ (rc, o, e) = run_command(['ceph', 'config-key', 'get',
+ CLUSTER_OWNERSHIP_NAME])
+ if rc is not 0:
+ return {}
+
+ return ast.literal_eval(o)
+
+def output_json():
+ out = {}
+ url = None
+
+ out['uuid'] = get_uuid()
+ out['cluster_creation_date'] = get_cluster_creation_date()
+ nums = get_nums()
+ num_osds = int(nums['osds'])
+ out['components_count'] = nums
+ out['crush_types'] = get_crush_types()
+ out['pool_metadata'] = get_pool_metadata()
+ out['sysinfo'] = get_sysinfo(num_osds)
+
+ owner = get_ownership_info()
+ if owner is not None:
+ url = owner.pop['url']
+ out['ownership'] = owner
+
+ return json.dumps(out, indent=2, separators=(',', ': ')), url
+
+def describe_usage():
+ print >> sys.stderr, "Usage:"
+ print >> sys.stderr, "======\n"
+
+ print >> sys.stderr, sys.argv[0] + " <commands> [command-options]\n"
+ print >> sys.stderr, "commands:"
+ print >> sys.stderr, "publish - publish the brag report to the server"
+ print >> sys.stderr, "update-metadata <update-metadata-options> - Update"
+ print >> sys.stderr, " ownership information for bragging"
+ print >> sys.stderr, "clear-metadata - Clear information set by update-metadata"
+ print >> sys.stderr, "unpublish --yes-i-am-shy - delete the brag report from the server"
+ print >> sys.stderr, ""
+
+ print >> sys.stderr, "update-metadata options:"
+ print >> sys.stderr, "--name= - Name of the cluster"
+ print >> sys.stderr, "--organization= - Name of the organization"
+ print >> sys.stderr, "--email= - Email contact address"
+ print >> sys.stderr, "--description= - Reporting use-case"
+ print >> sys.stderr, "--url= - The URL that is used to publish and unpublish"
+ print >> sys.stderr, ""
+
+def update_metadata():
+ info = {}
+ possibles = ['name', 'organization', 'email', 'description', 'url']
+
+ #get the existing values
+ info = get_ownership_info();
+
+ for index in range(2, len(sys.argv)):
+ mo = re.search("--(\S+)=(.*)", sys.argv[index])
+ if not mo:
+ describe_usage()
+ return 22
+
+ k = mo.group(1)
+ v = mo.group(2)
+
+ if k in possibles:
+ info[k] = v
+ else:
+ print >> sys.stderr, "Unexpect option --" + k
+ describe_usage()
+ return 22
+
+ (rc, o, e) = run_command(['ceph', 'config-key', 'put',
+ CLUSTER_OWNERSHIP_NAME, str(info)])
+ return rc
+
+def clear_metadata():
+ (rc, o, e) = run_command(['ceph', 'config-key', 'del',
+ CLUSTER_OWNERSHIP_NAME])
+ return rc
+
+def publish():
+ data, url = output_json()
+ if url is None:
+ print >> sys.stderr, "Cannot publish until a URL is set using update-metadata"
+ return 1
+
+ #TODO: Execute the HTTP request
+ return 0
+
+def unpublish():
+ if len(sys.argv) <= 2 or sys.argv[2] != '--yes-i-am-shy':
+ print >> sys.stderr, "unpublish should be followed by --yes-i-am-shy"
+ return 22
+
+ fail = False
+ owner = get_ownership_info()
+ if owner is None:
+ fail = True
+ try:
+ url = owner['url']
+ except KeyError as e:
+ fail = True
+
+ if fail:
+ print >> sys.stderr, "URL is not updated yet"
+ return 1
+
+ uuid = get_uuid()
+
+ #TODO: Execute the HTTP request
+ return 0
+
+def main():
+ if len(sys.argv) is 1:
+ print output_json()[0]
+ return 0
+ elif sys.argv[1] == 'update-metadata':
+ return update_metadata()
+ elif sys.argv[1] == 'clear-metadata':
+ return clear_metadata()
+ elif sys.argv[1] == 'publish':
+ return publish()
+ elif sys.argv[1] == 'unpublish':
+ return unpublish()
+ else:
+ describe_usage()
+ return 22
+
+if __name__ == '__main__':
+ sys.exit(main())
--- /dev/null
+import os
+from pecan.deploy import deploy
+
+cur_path = os.path.dirname(__file__)
+application = deploy(cur_path + '/config.py')