From: Babu Shanmugam Date: Mon, 10 Feb 2014 07:08:26 +0000 (+0530) Subject: Server changes to deploy in a production env X-Git-Tag: v0.78~175^2~3^2~2 X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=4cf2c7264e5326be7c64fe213b717ec1bb792d32;p=ceph.git Server changes to deploy in a production env 1. Added the wsgi entry point app.wsgi 2. Updated client code to mandate the update-metadata to have url to publish and unpublish 3. Updated the README to describe a bit about the server operations as well. --- diff --git a/README.md b/README.md index 26de521d6b06..37468163b752 100644 --- a/README.md +++ b/README.md @@ -8,18 +8,17 @@ For more information please visit: * [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", @@ -95,3 +94,66 @@ Sample output: } ] } + + +# 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 + + diff --git a/ceph-brag b/ceph-brag deleted file mode 100755 index f37b61864f3f..000000000000 --- a/ceph-brag +++ /dev/null @@ -1,306 +0,0 @@ -#!/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] + " [command-options]\n" - print >> sys.stderr, "commands:" - print >> sys.stderr, "publish - publish the brag report to the server" - print >> sys.stderr, "update-metadata - 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()) diff --git a/client/ceph-brag b/client/ceph-brag new file mode 100755 index 000000000000..e7398a895baf --- /dev/null +++ b/client/ceph-brag @@ -0,0 +1,335 @@ +#!/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] + " [command-options]\n" + print >> sys.stderr, "commands:" + print >> sys.stderr, "publish - publish the brag report to the server" + print >> sys.stderr, "update-metadata - 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()) diff --git a/server/app.wsgi b/server/app.wsgi new file mode 100644 index 000000000000..01dfc72a111a --- /dev/null +++ b/server/app.wsgi @@ -0,0 +1,5 @@ +import os +from pecan.deploy import deploy + +cur_path = os.path.dirname(__file__) +application = deploy(cur_path + '/config.py')