├── cephclient ├── __init__.py ├── exceptions.py ├── client.py └── wrapper.py ├── .gitignore ├── setup.py ├── README.rst └── LICENSE /cephclient/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.idea 2 | *.pyc 3 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup( 4 | name='python-cephclient', 5 | packages=['cephclient'], 6 | version='0.1.0.5', 7 | url='https://github.com/dmsimard/python-cephclient', 8 | author='David Moreau Simard', 9 | author_email='moi@dmsimard.com', 10 | description='A client library in python for the Ceph REST API.', 11 | long_description=open('README.rst', 'rt').read(), 12 | license='Apache License, Version 2.0', 13 | keywords='ceph rest api ceph-rest-api client library', 14 | install_requires=['lxml>=3.2.5', 'requests>=2.2.1'], 15 | classifiers=[ 16 | 'License :: OSI Approved :: Apache Software License', 17 | 'Development Status :: 4 - Beta', 18 | 'Intended Audience :: Developers', 19 | 'Intended Audience :: System Administrators', 20 | 'Intended Audience :: Information Technology', 21 | 'Programming Language :: Python :: 2.7', 22 | 'Topic :: Utilities' 23 | ] 24 | ) 25 | -------------------------------------------------------------------------------- /cephclient/exceptions.py: -------------------------------------------------------------------------------- 1 | # Copyright 2013 David Moreau Simard 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | # Author: David Moreau Simard 16 | # 17 | 18 | 19 | class FunctionNotImplemented(Exception): 20 | """ 21 | Function not yet finished 22 | """ 23 | def __str__(self): 24 | return "This function is not yet available/completed." 25 | 26 | 27 | class UnsupportedRequestType(Exception): 28 | """ 29 | If a requested body type is not mapped 30 | """ 31 | def __str__(self): 32 | return "Unknown request type." 33 | 34 | 35 | class UnsupportedBodyType(Exception): 36 | """ 37 | If a requested body type is not mapped 38 | """ 39 | def __str__(self): 40 | return "This type of body is not supported for this API call." 41 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | python-cephclient is a python module to communicate with `Ceph's REST API`_ (``ceph-rest-api``). 2 | 3 | .. _Ceph's REST API: http://ceph.com/docs/master/man/8/ceph-rest-api/ 4 | 5 | This is currently a work in progress. 6 | 7 | ABOUT 8 | ================================================== 9 | 10 | Client 11 | -------------------------------------------------- 12 | 13 | The cephclient class takes care of sending calls to the API through HTTP and 14 | handle the responses. It supports queries for JSON, XML, plain text or binary. 15 | 16 | Wrapper 17 | -------------------------------------------------- 18 | 19 | The wrapper class extends the client and provides helper functions to 20 | communicate with the API. 21 | 22 | Nothing prevents you from calling the client directly exactly like the wrapper 23 | does. 24 | The wrapper exists for convenience. 25 | 26 | Development, Feedback, Bugs 27 | -------------------------------------------------- 28 | 29 | Want to contribute ? Feel free to send pull requests ! 30 | 31 | Have problems, bugs, feature ideas ? 32 | I am using the github `issue tracker`_ to manage them. 33 | 34 | .. _issue tracker: https://github.com/dmsimard/python-cephclient/issues 35 | 36 | 37 | HOW TO USE 38 | ================================================== 39 | 40 | Installation 41 | ---------------- 42 | Install the package through pip:: 43 | 44 | pip install python-cephclient 45 | 46 | Installation does not work ? 47 | 48 | python-cephclient depends on lxml which itself 49 | depends on some packages. To install lxml's dependencies on Ubuntu:: 50 | 51 | apt-get install python-dev libxml2-dev libxslt-dev 52 | 53 | 54 | Instanciate CephWrapper:: 55 | 56 | from cephclient.wrapper import * 57 | 58 | wrapper = CephWrapper( 59 | endpoint = 'http://apiserver:5000/api/v0.1/', 60 | debug = True # Optionally increases the verbosity of the client 61 | ) 62 | 63 | Do your request and specify the reponse type you are expecting. 64 | 65 | Either ``json``, ``xml``, ``text`` (default) or ``binary`` are available. 66 | 67 | json:: 68 | 69 | response, body = wrapper.get_fsid(body = 'json') 70 | print('Response: {0}, Body:\n{1}'.format(response, json.dumps(body, indent=4, separators=(',', ': ')))) 71 | 72 | ==== 73 | 74 | Response: , Body: 75 | { 76 | "status": "OK", 77 | "output": { 78 | "fsid": "d5252e7d-75bc-4083-85ed-fe51fa83f62b" 79 | } 80 | } 81 | 82 | 83 | xml:: 84 | 85 | response, body = wrapper.get_fsid(body = 'xml') 86 | print('Response: {0}, Body:\n{1}'.format(reponse, etree.tostring(body, pretty_print=True))) 87 | 88 | ==== 89 | 90 | Response: , Body: 91 | 92 | 93 | d5252e7d-75bc-4083-85ed-fe51fa83f62b 94 | 95 | 96 | OK 97 | 98 | 99 | 100 | 101 | 102 | text:: 103 | 104 | response, body = wrapper.get_fsid(body = 'text') 105 | print('Response: {0}, Body:\n{1}'.format(response, body)) 106 | 107 | ==== 108 | 109 | Response: , Body: 110 | d5252e7d-75bc-4083-85ed-fe51fa83f62b 111 | 112 | binary:: 113 | 114 | response, body = wrapper.mon_getmap(body = 'binary') 115 | # < Do something binary with 'body' > 116 | 117 | 118 | RELEASE NOTES 119 | ================================================== 120 | **0.1.0.5** 121 | 122 | dmsimard: 123 | 124 | - Add missing dependency on the requests library 125 | - Some PEP8 and code standardization cleanup 126 | - Add root "PUT" methods 127 | - Add mon "PUT" methods 128 | - Add mds "PUT" methods 129 | - Add auth "PUT" methods 130 | 131 | Donald Talton: 132 | 133 | - Add osd "PUT" methods 134 | 135 | **0.1.0.4** 136 | 137 | - Fix setup and PyPi installation 138 | 139 | **0.1.0.3** 140 | 141 | - GET API calls under '/tell' have been implemented. 142 | - GET API calls are are in root (/) have been renamed to be coherent with incoming future development 143 | 144 | **0.1.0.2** 145 | 146 | - Implemented or fixed missing GET calls (All API GET calls that are not under the '/tell' namespace are now supported) 147 | - Client can optionally raise an exception when requesting a unsupported body type for a provided API call (ex: requesting json through the wrapper for a call that is known to only return binary will raise an exception) 148 | - Client now supports binary type responses (ex: crush map, mon map, etc) 149 | - Improved the README (!) 150 | 151 | 152 | **0.1.0.1** 153 | 154 | - First public release of python-cephclient 155 | -------------------------------------------------------------------------------- /cephclient/client.py: -------------------------------------------------------------------------------- 1 | # Copyright 2010 Jacob Kaplan-Moss 2 | # Copyright 2011 OpenStack Foundation 3 | # Copyright 2011 Piston Cloud Computing, Inc. 4 | # Copyright 2013 David Moreau Simard 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | # Author: David Moreau Simard 19 | # Credit: python-novaclient 20 | # 21 | 22 | """ 23 | A ceph-rest-api python interface that handles REST calls and responses. 24 | """ 25 | 26 | import logging 27 | import requests 28 | 29 | try: 30 | from lxml import etree 31 | except ImportError as e: 32 | print("Missing required python module: " + str(e)) 33 | exit() 34 | 35 | try: 36 | import json 37 | except ImportError: 38 | import simplejson as json 39 | 40 | import cephclient.exceptions as exceptions 41 | 42 | 43 | class CephClient(object): 44 | 45 | def __init__(self, **params): 46 | """ 47 | Initialize the class, get the necessary parameters 48 | """ 49 | self.user_agent = 'python-cephclient' 50 | 51 | self.params = params 52 | self.log = self.log_wrapper() 53 | 54 | self.log.debug("Params: {0}".format(str(self.params))) 55 | 56 | self.endpoint = self.params['endpoint'] 57 | if 'timeout' not in self.params: 58 | self.timeout = None 59 | 60 | self.http = requests.Session() 61 | 62 | def _request(self, url, method, **kwargs): 63 | if self.timeout is not None: 64 | kwargs.setdefault('timeout', self.timeout) 65 | 66 | kwargs.setdefault('headers', kwargs.get('headers', {})) 67 | kwargs['headers']['User-Agent'] = self.user_agent 68 | 69 | try: 70 | if kwargs['body'] is 'json': 71 | kwargs['headers']['Accept'] = 'application/json' 72 | kwargs['headers']['Content-Type'] = 'application/json' 73 | elif kwargs['body'] is 'xml': 74 | kwargs['headers']['Accept'] = 'application/xml' 75 | kwargs['headers']['Content-Type'] = 'application/xml' 76 | elif kwargs['body'] is 'text': 77 | kwargs['headers']['Accept'] = 'text/plain' 78 | kwargs['headers']['Content-Type'] = 'text/plain' 79 | elif kwargs['body'] is 'binary': 80 | kwargs['headers']['Accept'] = 'application/octet-stream' 81 | kwargs['headers']['Content-Type'] = 'application/octet-stream' 82 | else: 83 | raise exceptions.UnsupportedRequestType() 84 | except KeyError: 85 | # Default if body type is unspecified is text/plain 86 | kwargs['headers']['Accept'] = 'text/plain' 87 | kwargs['headers']['Content-Type'] = 'text/plain' 88 | 89 | # Optionally verify if requested body type is supported 90 | try: 91 | if kwargs['body'] not in kwargs['supported_body_types']: 92 | raise exceptions.UnsupportedBodyType() 93 | else: 94 | del kwargs['supported_body_types'] 95 | except KeyError: 96 | pass 97 | 98 | del kwargs['body'] 99 | 100 | self.log.debug("{0} URL: {1}{2} - {3}" 101 | .format(method, self.endpoint, url, str(kwargs))) 102 | 103 | resp = self.http.request( 104 | method, 105 | self.endpoint + url, 106 | **kwargs) 107 | 108 | if resp.text: 109 | try: 110 | if kwargs['headers']['Content-Type'] is 'application/json': 111 | body = json.loads(resp.text) 112 | elif kwargs['headers']['Content-Type'] is 'application/xml': 113 | body = etree.XML(resp.text) 114 | else: 115 | body = resp.text 116 | except ValueError: 117 | body = None 118 | else: 119 | body = None 120 | 121 | return resp, body 122 | 123 | def get(self, url, **kwargs): 124 | return self._request(url, 'GET', **kwargs) 125 | 126 | def post(self, url, **kwargs): 127 | return self._request(url, 'POST', **kwargs) 128 | 129 | def put(self, url, **kwargs): 130 | return self._request(url, 'PUT', **kwargs) 131 | 132 | def delete(self, url, **kwargs): 133 | return self._request(url, 'DELETE', **kwargs) 134 | 135 | def log_wrapper(self): 136 | """ 137 | Wrapper to set logging parameters for output 138 | """ 139 | log = logging.getLogger('client.py') 140 | 141 | # Set the log format and log level 142 | try: 143 | debug = self.params["debug"] 144 | log.setLevel(logging.DEBUG) 145 | except KeyError: 146 | log.setLevel(logging.INFO) 147 | 148 | # Set the log format. 149 | stream = logging.StreamHandler() 150 | logformat = logging.Formatter( 151 | '%(asctime)s - %(name)s - %(levelname)s - %(message)s', 152 | datefmt='%b %d %H:%M:%S') 153 | stream.setFormatter(logformat) 154 | 155 | log.addHandler(stream) 156 | return log 157 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /cephclient/wrapper.py: -------------------------------------------------------------------------------- 1 | # Copyright 2013 David Moreau Simard 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | # Authors: David Moreau Simard 16 | # Donald Talton 17 | # 18 | 19 | import cephclient.client as client 20 | import cephclient.exceptions as exceptions 21 | 22 | 23 | class CephWrapper(client.CephClient): 24 | def __init__(self, **params): 25 | super(CephWrapper, self).__init__(**params) 26 | self.user_agent = 'python-cephclient-wrapper' 27 | 28 | ### 29 | # root GET calls 30 | ### 31 | def df(self, detail=None, **kwargs): 32 | if detail is not None: 33 | return self.get('df?detail={0}' 34 | .format(detail), **kwargs) 35 | else: 36 | return self.get('df', **kwargs) 37 | 38 | def fsid(self, **kwargs): 39 | return self.get('fsid', **kwargs) 40 | 41 | def health(self, detail=None, **kwargs): 42 | if detail is not None: 43 | return self.get('health?detail={0}' 44 | .format(detail), **kwargs) 45 | else: 46 | return self.get('health', **kwargs) 47 | 48 | def quorum_status(self, **kwargs): 49 | return self.get('quorum_status', **kwargs) 50 | 51 | def report(self, tags=None, **kwargs): 52 | if tags is not None: 53 | return self.get('report?tags={0}' 54 | .format(tags), **kwargs) 55 | else: 56 | return self.get('report', **kwargs) 57 | 58 | def status(self, **kwargs): 59 | return self.get('status', **kwargs) 60 | 61 | ### 62 | # root PUT calls 63 | ### 64 | def compact(self, **kwargs): 65 | return self.put('compact', **kwargs) 66 | 67 | def heap(self, heapcmd, **kwargs): 68 | return self.put('heap?heapcmd={0}' 69 | .format(heapcmd), **kwargs) 70 | 71 | def injectargs(self, injected_args, **kwargs): 72 | return self.put('injectargs?injected_args={0}' 73 | .format(injected_args), **kwargs) 74 | 75 | def log(self, logtext, **kwargs): 76 | return self.put('log?logtext={0}' 77 | .format(logtext), **kwargs) 78 | 79 | def quorum(self, quorumcmd, **kwargs): 80 | return self.put('quorum?quorumcmd={0}' 81 | .format(quorumcmd), **kwargs) 82 | 83 | def scrub(self, **kwargs): 84 | return self.put('scrub', **kwargs) 85 | 86 | def tell(self, target, args, **kwargs): 87 | return self.put('tell?target={0}&args={1}' 88 | .format(target, args), **kwargs) 89 | 90 | ### 91 | # auth GET calls 92 | ### 93 | def auth_export(self, entity=None, **kwargs): 94 | if entity is not None: 95 | return self.get('auth/export?entity={0}' 96 | .format(entity), **kwargs) 97 | else: 98 | return self.get('auth/export', **kwargs) 99 | 100 | def auth_get(self, entity, **kwargs): 101 | return self.get('auth/get?entity={0}' 102 | .format(entity), **kwargs) 103 | 104 | def auth_get_key(self, entity, **kwargs): 105 | return self.get('auth/get-key?entity={0}' 106 | .format(entity), **kwargs) 107 | 108 | def auth_list(self, **kwargs): 109 | return self.get('auth/list', **kwargs) 110 | 111 | def auth_print_key(self, entity, **kwargs): 112 | return self.get('auth/print-key?entity={0}' 113 | .format(entity), **kwargs) 114 | 115 | ### 116 | # auth PUT calls 117 | ### 118 | """ 119 | caps dictionary format: 120 | caps = { 121 | 'mon': 'allow rwx', 122 | 'osd': 'allow *', 123 | ... 124 | } 125 | """ 126 | def auth_add(self, entity, caps={}, file=None, **kwargs): 127 | # XXX-TODO: Implement file input 128 | full_caps = list() 129 | if caps: 130 | for key in caps: 131 | permissions = caps[key].replace(' ', '+') 132 | full_caps.append('&caps={0}&caps={1}' 133 | .format(key, permissions)) 134 | 135 | return self.put('auth/add?entity={0}{1}' 136 | .format(entity, ''.join(full_caps)), **kwargs) 137 | 138 | def auth_caps(self, entity, caps={}, **kwargs): 139 | full_caps = list() 140 | if caps: 141 | for key in caps: 142 | permissions = caps[key].replace(' ', '+') 143 | full_caps.append('&caps={0}&caps={1}' 144 | .format(key, permissions)) 145 | 146 | return self.put('auth/caps?entity={0}{1}' 147 | .format(entity, ''.join(full_caps)), **kwargs) 148 | 149 | def auth_del(self, entity, **kwargs): 150 | return self.put('auth/del?entity={0}' 151 | .format(entity), **kwargs) 152 | 153 | def auth_get_or_create(self, entity, caps={}, file=None, **kwargs): 154 | # XXX-TODO: Implement file input 155 | full_caps = list() 156 | if caps: 157 | for key in caps: 158 | permissions = caps[key].replace(' ', '+') 159 | full_caps.append('&caps={0}&caps={1}'.format(key, permissions)) 160 | 161 | return self.put('auth/get-or-create?entity={0}{1}' 162 | .format(entity, ''.join(full_caps)), **kwargs) 163 | 164 | def auth_get_or_create_key(self, entity, caps={}, **kwargs): 165 | # XXX-TODO: Implement file input 166 | full_caps = list() 167 | if caps: 168 | for key in caps: 169 | permissions = caps[key].replace(' ', '+') 170 | full_caps.append('&caps={0}&caps={1}'.format(key, permissions)) 171 | 172 | return self.put('auth/get-or-create-key?entity={0}{1}' 173 | .format(entity, ''.join(full_caps)), **kwargs) 174 | 175 | def auth_import(self, file): 176 | # XXX-TODO: Implement file input 177 | raise exceptions.FunctionNotImplemented() 178 | 179 | ### 180 | # config-key GET calls 181 | ### 182 | def config_key_exists(self, key, **kwargs): 183 | return self.get('config-key/exists?key={0}' 184 | .format(key), **kwargs) 185 | 186 | def config_key_get(self, key, **kwargs): 187 | return self.get('config-key/get?key={0}' 188 | .format(key), **kwargs) 189 | 190 | def config_key_list(self, **kwargs): 191 | return self.get('config-key/list', **kwargs) 192 | 193 | ### 194 | # mds GET calls 195 | ### 196 | def mds_compat_show(self, **kwargs): 197 | return self.get('mds/compat/show', **kwargs) 198 | 199 | def mds_dump(self, epoch=None, **kwargs): 200 | if epoch is not None: 201 | return self.get('mds/dump?epoch={0}' 202 | .format(epoch), **kwargs) 203 | else: 204 | return self.get('mds/dump', **kwargs) 205 | 206 | def mds_getmap(self, epoch=None, **kwargs): 207 | kwargs['supported_body_types'] = ['binary'] 208 | 209 | if epoch is not None: 210 | return self.get('mds/getmap?epoch={0}' 211 | .format(epoch), **kwargs) 212 | else: 213 | return self.get('mds/getmap', **kwargs) 214 | 215 | def mds_stat(self, **kwargs): 216 | return self.get('mds/stat', **kwargs) 217 | 218 | ### 219 | # mds PUT calls 220 | ### 221 | def mds_add_data_pool(self, pool, **kwargs): 222 | return self.put('mds/add_data_pool?pool={0}' 223 | .format(pool), **kwargs) 224 | 225 | def mds_cluster_down(self, **kwargs): 226 | return self.put('mds/cluster_down', **kwargs) 227 | 228 | def mds_cluster_up(self, **kwargs): 229 | return self.put('mds/cluster_up', **kwargs) 230 | 231 | def mds_compat_rm_compat(self, feature, **kwargs): 232 | return self.put('mds/compat/rm_compat?feature={0}' 233 | .format(feature), **kwargs) 234 | 235 | def mds_compat_rm_incompat(self, feature, **kwargs): 236 | return self.put('mds/compat/rm_incompat?feature={0}' 237 | .format(feature), **kwargs) 238 | 239 | def mds_deactivate(self, who, **kwargs): 240 | return self.put('mds/deactivate?who={0}' 241 | .format(who), **kwargs) 242 | 243 | def mds_fail(self, who, **kwargs): 244 | return self.put('mds/fail?who={0}' 245 | .format(who), **kwargs) 246 | 247 | def mds_newfs(self, metadata, data, sure, **kwargs): 248 | return self.put('mds/newfs?metadata={0}&data={1}&sure={2}' 249 | .format(metadata, data, sure), **kwargs) 250 | 251 | def mds_remove_data_pool(self, pool, **kwargs): 252 | return self.put('mds/remove_data_pool?pool={0}' 253 | .format(pool), **kwargs) 254 | 255 | def mds_rm(self, gid, who, **kwargs): 256 | return self.put('mds/rm?gid={0}&who={1}' 257 | .format(gid, who), **kwargs) 258 | 259 | def mds_rmfailed(self, who, **kwargs): 260 | return self.put('mds/rmfailed?who={0}' 261 | .format(who), **kwargs) 262 | 263 | def mds_set_allow_new_snaps(self, sure, **kwargs): 264 | """ 265 | mds/set?key=allow_new_snaps&sure= 266 | """ 267 | raise exceptions.FunctionNotImplemented() 268 | 269 | def mds_set_max_mds(self, maxmds, **kwargs): 270 | return self.put('mds/set_max_mds?maxmds={0}' 271 | .format(maxmds), **kwargs) 272 | 273 | def mds_setmap(self, epoch, **kwargs): 274 | return self.put('mds/setmap?epoch={0}' 275 | .format(epoch), **kwargs) 276 | 277 | def mds_stop(self, who, **kwargs): 278 | return self.put('mds/stop?who={0}' 279 | .format(who), **kwargs) 280 | 281 | def mds_tell(self, who, args, **kwargs): 282 | return self.put('mds/tell?who={0}&args={1}' 283 | .format(who, args), **kwargs) 284 | 285 | def mds_unset_allow_new_snaps(self, sure, **kwargs): 286 | """ 287 | mds/unset?key=allow_new_snaps&sure= 288 | """ 289 | raise exceptions.FunctionNotImplemented() 290 | 291 | ### 292 | # mon GET calls 293 | ### 294 | def mon_dump(self, epoch=None, **kwargs): 295 | if epoch is not None: 296 | return self.get('mon/dump?epoch={0}' 297 | .format(epoch), **kwargs) 298 | else: 299 | return self.get('mon/dump', **kwargs) 300 | 301 | def mon_getmap(self, epoch=None, **kwargs): 302 | kwargs['supported_body_types'] = ['binary'] 303 | 304 | if epoch is not None: 305 | return self.get('mon/getmap?epoch={0}' 306 | .format(epoch), **kwargs) 307 | else: 308 | return self.get('mon/getmap', **kwargs) 309 | 310 | def mon_stat(self, **kwargs): 311 | kwargs['supported_body_types'] = ['text', 'xml'] 312 | 313 | return self.get('mon/stat', **kwargs) 314 | 315 | def mon_status(self, **kwargs): 316 | return self.get('mon_status', **kwargs) 317 | 318 | ### 319 | # mon PUT calls 320 | ### 321 | def mon_add(self, name, addr, **kwargs): 322 | return self.put('mon/add?name={0}&addr={1}' 323 | .format(name, addr), **kwargs) 324 | 325 | def mon_remove(self, name, **kwargs): 326 | return self.put('mon/remove?name={0}' 327 | .format(name), **kwargs) 328 | 329 | ### 330 | # osd GET calls 331 | ### 332 | def osd_blacklist_ls(self, **kwargs): 333 | return self.get('osd/blacklist/ls', **kwargs) 334 | 335 | def osd_crush_dump(self, **kwargs): 336 | return self.get('osd/crush/dump', **kwargs) 337 | 338 | def osd_crush_rule_dump(self, **kwargs): 339 | return self.get('osd/crush/rule/dump', **kwargs) 340 | 341 | def osd_crush_rule_list(self, **kwargs): 342 | return self.get('osd/crush/rule/list', **kwargs) 343 | 344 | def osd_crush_rule_ls(self, **kwargs): 345 | return self.get('osd/crush/rule/ls', **kwargs) 346 | 347 | def osd_dump(self, epoch=None, **kwargs): 348 | if epoch is not None: 349 | return self.get('osd/dump?epoch={0}' 350 | .format(epoch), **kwargs) 351 | else: 352 | return self.get('osd/dump', **kwargs) 353 | 354 | def osd_find(self, id, **kwargs): 355 | return self.get('osd/find?id={0}' 356 | .format(id), **kwargs) 357 | 358 | def osd_getcrushmap(self, epoch=None, **kwargs): 359 | kwargs['supported_body_types'] = ['binary'] 360 | 361 | if epoch is not None: 362 | return self.get('osd/getcrushmap?epoch={0}' 363 | .format(epoch), **kwargs) 364 | else: 365 | return self.get('osd/getcrushmap', **kwargs) 366 | 367 | def osd_getmap(self, epoch=None, **kwargs): 368 | kwargs['supported_body_types'] = ['binary'] 369 | 370 | if epoch is not None: 371 | return self.get('osd/getmap?epoch={0}' 372 | .format(epoch), **kwargs) 373 | else: 374 | return self.get('osd/getmap', **kwargs) 375 | 376 | def osd_getmaxosd(self, **kwargs): 377 | return self.get('osd/getmaxosd', **kwargs) 378 | 379 | def osd_ls(self, epoch=None, **kwargs): 380 | if epoch is not None: 381 | return self.get('osd/ls?epoch={0}' 382 | .format(epoch), **kwargs) 383 | else: 384 | return self.get('osd/ls', **kwargs) 385 | 386 | def osd_lspools(self, auid=None, **kwargs): 387 | if auid is not None: 388 | return self.get('osd/lspools?auid={0}' 389 | .format(auid), **kwargs) 390 | else: 391 | return self.get('osd/lspools', **kwargs) 392 | 393 | def osd_map(self, pool, object, **kwargs): 394 | return self.get('osd/map?pool={0}&object={1}' 395 | .format(pool, object), **kwargs) 396 | 397 | def osd_perf(self, **kwargs): 398 | return self.get('osd/perf', **kwargs) 399 | 400 | def osd_pool_get(self, pool, var, **kwargs): 401 | return self.get('osd/pool/get?pool={0}&var={1}' 402 | .format(pool, var), **kwargs) 403 | 404 | def osd_pool_stats(self, name=None, **kwargs): 405 | if name is not None: 406 | return self.get('osd/pool/stats?name={0}' 407 | .format(name), **kwargs) 408 | else: 409 | return self.get('osd/pool/stats', **kwargs) 410 | 411 | def osd_stat(self, **kwargs): 412 | return self.get('osd/stat', **kwargs) 413 | 414 | def osd_tree(self, epoch=None, **kwargs): 415 | if epoch is not None: 416 | return self.get('osd/tree?epoch={0}' 417 | .format(epoch), **kwargs) 418 | else: 419 | return self.get('osd/tree', **kwargs) 420 | 421 | ### 422 | # osd PUT calls 423 | ### 424 | def osd_blacklist(self, blacklistop, addr, expire, **kwargs): 425 | return self.put('osd/blacklist?blacklistop={0}&addr={1}&expire={2}' 426 | .format(blacklistop, addr, expire), **kwargs) 427 | 428 | def osd_create(self, uuid, **kwargs): 429 | return self.put('osd/create?uuid={0}' 430 | .format(uuid), **kwargs) 431 | 432 | def osd_crush_add(self, id, weight, args, **kwargs): 433 | return self.put('osd/crush/add?id={0}&weight={1}&args={2}' 434 | .format(id, weight, args), **kwargs) 435 | 436 | def osd_crush_add_bucket(self, name, type, **kwargs): 437 | return self.put('osd/crush/add-bucket?name={0}&type={1}' 438 | .format(name, type), **kwargs) 439 | 440 | def osd_crush_create_or_move(self, id, weight, args, **kwargs): 441 | return self.put('osd/crush/create-or-move?id={0}&weight={1}&args={2}' 442 | .format(id, weight, args), **kwargs) 443 | 444 | def osd_crush_link(self, name, args, **kwargs): 445 | return self.put('osd/crush/link?name={0}&args={2}' 446 | .format(name, args), **kwargs) 447 | 448 | def osd_crush_move(self, name, args, **kwargs): 449 | return self.put('osd/crush/move?name={0}&args={1}' 450 | .format(name, args), **kwargs) 451 | 452 | def osd_crush_remove(self, name, ancestor, **kwargs): 453 | return self.put('osd/crush/remove?name={0}&ancestor={1}' 454 | .format(name, ancestor), **kwargs) 455 | 456 | def osd_crush_reweight(self, name, weight, **kwargs): 457 | return self.put('osd/crush/reweight?name={0}&weight={1}' 458 | .format(name, weight), **kwargs) 459 | 460 | def osd_crush_rm(self, name, ancestor, **kwargs): 461 | return self.put('osd/crush/rm?name={0}&ancestor={1}' 462 | .format(name, ancestor), **kwargs) 463 | 464 | def osd_crush_rule_create_simple(self, name, root, type, **kwargs): 465 | return self.put( 466 | 'osd/crush/rule/create-simple?name={0}&root={1}&type={2}' 467 | .format(name, root, type), **kwargs) 468 | 469 | def osd_crush_rule_rm(self, name, **kwargs): 470 | return self.put('osd/crush/rule/rm?name={0}' 471 | .format(name), **kwargs) 472 | 473 | def osd_crush_set(self, id, name, weight, args, **kwargs): 474 | return self.put('osd/crush/set?id={0}&weight={1}&args={2}' 475 | .format(id, name, weight, args), **kwargs) 476 | 477 | def osd_crush_tunables(self, profile, **kwargs): 478 | return self.put('osd/crush/tunables?profile={0}' 479 | .format(profile), **kwargs) 480 | 481 | def osd_crush_unlink(self, name, ancestor, **kwargs): 482 | return self.put('osd/crush/unlink?name={0}&ancestor={1}' 483 | .format(name, ancestor), **kwargs) 484 | 485 | def osd_deep_scrub(self, who, **kwargs): 486 | return self.put('osd/deep-scrub?who={0}' 487 | .format(who), **kwargs) 488 | 489 | def osd_down(self, ids, **kwargs): 490 | return self.put('osd/down?ids={0}' 491 | .format(ids), **kwargs) 492 | 493 | def osd_in(self, ids, **kwargs): 494 | return self.put('osd/in?ids={0}' 495 | .format(ids), **kwargs) 496 | 497 | def osd_lost(self, id, sure, **kwargs): 498 | return self.put('osd/lost?id={0}&sure={1}' 499 | .format(id, sure), **kwargs) 500 | 501 | def osd_out(self, ids, **kwargs): 502 | return self.put('osd/out?ids={0}' 503 | .format(ids), **kwargs) 504 | 505 | def osd_pool_create(self, pool, pg_num, pgp_num, **kwargs): 506 | return self.put( 507 | 'osd/pool/create?pool={0}&pg_num={1}&pgp_num={2}' 508 | .format(pool, pg_num, pgp_num), **kwargs) 509 | 510 | def osd_pool_delete(self, pool, sure, **kwargs): 511 | return self.put('osd/pool/delete?pool={0}&sure={1}' 512 | .format(pool, sure), **kwargs) 513 | 514 | def osd_pool_param(self, pool, var, **kwargs): 515 | return self.put('osd/pool/get?pool={0}&var={1}' 516 | .format(pool, var), **kwargs) 517 | 518 | def osd_pool_mksnap(self, pool, snap, **kwargs): 519 | return self.put('osd/pool/mksnap?pool={0}&snap={1}' 520 | .format(pool, snap), **kwargs) 521 | 522 | def osd_pool_rename(self, srcpool, destpool, **kwargs): 523 | return self.put('osd/pool/rename?srcpool={0}&destpool={1}' 524 | .format(srcpool, destpool), **kwargs) 525 | 526 | def osd_pool_rmsnap(self, pool, snap, **kwargs): 527 | return self.put('osd/pool/rmsnap?pool={0}&snap={1}' 528 | .format(pool, snap), **kwargs) 529 | 530 | def osd_set_pool_param(self, pool, var, **kwargs): 531 | return self.put('osd/pool/set?pool={0}&var={1}' 532 | .format(pool, var), **kwargs) 533 | 534 | def osd_set_pool_quota(self, pool, field, **kwargs): 535 | return self.put('osd/pool/set-quota?pool={0}&field={1}' 536 | .format(pool, field), **kwargs) 537 | 538 | def osd_repair(self, pool, who, **kwargs): 539 | return self.put('osd/repair?who={0}' 540 | .format(pool, who), **kwargs) 541 | 542 | def osd_reweight(self, id, weight, **kwargs): 543 | return self.put('osd/reweight?id={0}&weight={1}' 544 | .format(id, weight), **kwargs) 545 | 546 | def osd_reweight_by_utilization(self, oload, **kwargs): 547 | return self.put('osd/reweight-by-utilization?oload={0}' 548 | .format(oload), **kwargs) 549 | 550 | def osd_remove(self, ids, **kwargs): 551 | return self.put('osd/rm?ids={0}' 552 | .format(ids), **kwargs) 553 | 554 | def osd_scrub(self, who, **kwargs): 555 | return self.put('osd/scrub?who={0}' 556 | .format(who), **kwargs) 557 | 558 | def osd_set_key(self, key, **kwargs): 559 | return self.put('osd/set?key={0}' 560 | .format(key), **kwargs) 561 | 562 | def osd_setmaxosd(self, newmax, **kwargs): 563 | return self.put('osd/setmaxosd?newmax={0}' 564 | .format(newmax), **kwargs) 565 | 566 | def osd_thrash(self, num_epochs, **kwargs): 567 | return self.put('osd/thrash?num_epochs={0}' 568 | .format(num_epochs), **kwargs) 569 | 570 | def osd_tier_add(self, pool, tierpool, **kwargs): 571 | return self.put('osd/tier/add?pool={0}&tierpool={1}' 572 | .format(pool, tierpool), **kwargs) 573 | 574 | def osd_tier_cachemode(self, pool, mode, **kwargs): 575 | return self.put('osd/tier/cache-mode?pool={0}&mode={1}' 576 | .format(pool, mode), **kwargs) 577 | 578 | def osd_tier_remove(self, pool, tierpool, **kwargs): 579 | return self.put('osd/tier/remove?pool={0}&tierpool={1}' 580 | .format(pool, tierpool), **kwargs) 581 | 582 | def osd_tier_remove_overlay(self, pool, **kwargs): 583 | return self.put('osd/tier/remove-overlay?pool={0}' 584 | .format(pool), **kwargs) 585 | 586 | def osd_tier_set_overlay(self, pool, overlaypool, **kwargs): 587 | return self.put('osd/tier/set-overlay?pool={0}&overlaypool={1}' 588 | .format(pool, overlaypool), **kwargs) 589 | 590 | def osd_unset(self, key, **kwargs): 591 | return self.put('osd/unset?key={0}' 592 | .format(key), **kwargs) 593 | 594 | ### 595 | # pg GET calls 596 | ### 597 | def pg_debug(self, debugop, **kwargs): 598 | kwargs['supported_body_types'] = ['text', 'xml'] 599 | 600 | return self.get('pg/debug?debugop={0}' 601 | .format(debugop), **kwargs) 602 | 603 | def pg_dump(self, dumpcontents=None, **kwargs): 604 | if dumpcontents is not None: 605 | return self.get('pg/dump?dumpcontents={0}' 606 | .format(dumpcontents), **kwargs) 607 | else: 608 | return self.get('pg/dump', **kwargs) 609 | 610 | def pg_dump_json(self, dumpcontents=None, **kwargs): 611 | if dumpcontents is not None: 612 | return self.get('pg/dump_json?dumpcontents={0}' 613 | .format(dumpcontents), **kwargs) 614 | else: 615 | return self.get('pg/dump_json', **kwargs) 616 | 617 | def pg_dump_pools_json(self, **kwargs): 618 | return self.get('pg/dump_pools_json', **kwargs) 619 | 620 | def pg_dump_stuck(self, stuckops=None, **kwargs): 621 | if stuckops is not None: 622 | return self.get('pg/dump_stuck?stuckops={0}' 623 | .format(stuckops), **kwargs) 624 | else: 625 | return self.get('pg/dump_stuck', **kwargs) 626 | 627 | def pg_getmap(self, **kwargs): 628 | kwargs['supported_body_types'] = ['binary'] 629 | 630 | return self.get('pg/getmap', **kwargs) 631 | 632 | def pg_map(self, pgid, **kwargs): 633 | return self.get('pg/map?pgid={0}' 634 | .format(pgid), **kwargs) 635 | 636 | def pg_stat(self, **kwargs): 637 | return self.get('pg/stat', **kwargs) 638 | 639 | ### 640 | # tell GET calls 641 | ### 642 | def tell_debug_dump_missing(self, id, filename, **kwargs): 643 | return self.get('tell/{0}/debug_dump_missing?filename={1}' 644 | .format(id, filename), **kwargs) 645 | 646 | def tell_dump_pg_recovery_stats(self, id, **kwargs): 647 | return self.get('tell/{0}/dump_pg_recovery_stats' 648 | .format(id), **kwargs) 649 | 650 | def tell_list_missing(self, id, offset, **kwargs): 651 | return self.get('tell/{0}/list_missing?offset={1}' 652 | .format(id, offset), **kwargs) 653 | 654 | def tell_query(self, id, **kwargs): 655 | return self.get('tell/{0}/query' 656 | .format(id), **kwargs) 657 | 658 | def tell_version(self, id, **kwargs): 659 | return self.get('tell/{0}/version' 660 | .format(id), **kwargs) 661 | --------------------------------------------------------------------------------