From fc5818cc11e7287b003e993f9c860033e210fc7b Mon Sep 17 00:00:00 2001 From: Kurt Martin Date: Thu, 12 Dec 2013 16:25:45 -0800 Subject: [PATCH] Initial commit of the hplefthand client --- LICENSE.txt | 202 + README.rst | 73 + docs/Makefile | 153 + .../api/hplefthandclient/client.doctree | Bin 0 -> 122720 bytes .../api/hplefthandclient/exceptions.doctree | Bin 0 -> 10447 bytes .../api/hplefthandclient/http.doctree | Bin 0 -> 5579 bytes .../api/hplefthandclient/index.doctree | Bin 0 -> 7731 bytes docs/_build/doctrees/api/index.doctree | Bin 0 -> 4034 bytes docs/_build/doctrees/changelog.doctree | Bin 0 -> 3188 bytes docs/_build/doctrees/environment.pickle | Bin 0 -> 103654 bytes docs/_build/doctrees/hplefthandclient.doctree | Bin 0 -> 302624 bytes docs/_build/doctrees/index.doctree | Bin 0 -> 17329 bytes docs/_build/doctrees/installation.doctree | Bin 0 -> 9045 bytes docs/_build/doctrees/tutorial.doctree | Bin 0 -> 10890 bytes docs/_build/html/.buildinfo | 4 + .../html/_modules/hplefthandclient.html | 128 + .../_modules/hplefthandclient/__init__.html | 130 + .../_modules/hplefthandclient/client.html | 594 ++ .../_modules/hplefthandclient/exceptions.html | 404 + .../html/_modules/hplefthandclient/http.html | 427 + docs/_build/html/_modules/index.html | 96 + .../_sources/api/hplefthandclient/client.txt | 34 + .../api/hplefthandclient/exceptions.txt | 8 + .../_sources/api/hplefthandclient/http.txt | 24 + .../_sources/api/hplefthandclient/index.txt | 18 + docs/_build/html/_sources/api/index.txt | 11 + docs/_build/html/_sources/changelog.txt | 9 + .../_build/html/_sources/hplefthandclient.txt | 37 + docs/_build/html/_sources/index.txt | 65 + docs/_build/html/_sources/installation.txt | 43 + docs/_build/html/_sources/tutorial.txt | 68 + docs/_build/html/_static/ajax-loader.gif | Bin 0 -> 673 bytes docs/_build/html/_static/basic.css | 540 + docs/_build/html/_static/comment-bright.png | Bin 0 -> 3500 bytes docs/_build/html/_static/comment-close.png | Bin 0 -> 3578 bytes docs/_build/html/_static/comment.png | Bin 0 -> 3445 bytes docs/_build/html/_static/default.css | 256 + docs/_build/html/_static/doctools.js | 247 + docs/_build/html/_static/down-pressed.png | Bin 0 -> 368 bytes docs/_build/html/_static/down.png | Bin 0 -> 363 bytes docs/_build/html/_static/empty_dir | 0 docs/_build/html/_static/file.png | Bin 0 -> 392 bytes docs/_build/html/_static/jquery.js | 9266 +++++++++++++++++ docs/_build/html/_static/minus.png | Bin 0 -> 199 bytes docs/_build/html/_static/plus.png | Bin 0 -> 199 bytes docs/_build/html/_static/pygments.css | 62 + docs/_build/html/_static/searchtools.js | 560 + docs/_build/html/_static/sidebar.js | 151 + docs/_build/html/_static/underscore.js | 807 ++ docs/_build/html/_static/up-pressed.png | Bin 0 -> 372 bytes docs/_build/html/_static/up.png | Bin 0 -> 363 bytes docs/_build/html/_static/websupport.js | 808 ++ .../html/api/hplefthandclient/client.html | 662 ++ .../html/api/hplefthandclient/exceptions.html | 146 + .../html/api/hplefthandclient/http.html | 118 + .../html/api/hplefthandclient/index.html | 150 + docs/_build/html/api/index.html | 130 + docs/_build/html/changelog.html | 132 + docs/_build/html/genindex.html | 755 ++ docs/_build/html/hplefthandclient.html | 1377 +++ docs/_build/html/index.html | 169 + docs/_build/html/installation.html | 164 + docs/_build/html/objects.inv | Bin 0 -> 1291 bytes docs/_build/html/py-modindex.html | 131 + docs/_build/html/search.html | 105 + docs/_build/html/searchindex.js | 1 + docs/_build/html/tutorial.html | 187 + docs/_static/empty_dir | 0 docs/api/hplefthandclient/client.rst | 34 + docs/api/hplefthandclient/exceptions.rst | 8 + docs/api/hplefthandclient/http.rst | 24 + docs/api/hplefthandclient/index.rst | 18 + docs/api/index.rst | 11 + docs/changelog.rst | 9 + docs/conf.py | 289 + docs/hplefthandclient.rst | 37 + docs/index.rst | 65 + docs/installation.rst | 43 + docs/make.bat | 190 + docs/tutorial.rst | 68 + hplefthandclient/__init__.py | 36 + hplefthandclient/client.py | 497 + hplefthandclient/exceptions.py | 308 + hplefthandclient/http.py | 331 + samples/README.rst | 20 + samples/test_client.py | 233 + samples/utils.py | 148 + setup.py | 36 + test/README.rst | 7 + test/config.ini | 9 + test/test_HPLeftHandClient_base.py | 102 + test/test_HPLeftHandClient_volume.py | 259 + test/test_HPLeftHandMockServer_flask.py | 391 + 93 files changed, 22625 insertions(+) create mode 100644 LICENSE.txt create mode 100644 README.rst create mode 100644 docs/Makefile create mode 100644 docs/_build/doctrees/api/hplefthandclient/client.doctree create mode 100644 docs/_build/doctrees/api/hplefthandclient/exceptions.doctree create mode 100644 docs/_build/doctrees/api/hplefthandclient/http.doctree create mode 100644 docs/_build/doctrees/api/hplefthandclient/index.doctree create mode 100644 docs/_build/doctrees/api/index.doctree create mode 100644 docs/_build/doctrees/changelog.doctree create mode 100644 docs/_build/doctrees/environment.pickle create mode 100644 docs/_build/doctrees/hplefthandclient.doctree create mode 100644 docs/_build/doctrees/index.doctree create mode 100644 docs/_build/doctrees/installation.doctree create mode 100644 docs/_build/doctrees/tutorial.doctree create mode 100644 docs/_build/html/.buildinfo create mode 100644 docs/_build/html/_modules/hplefthandclient.html create mode 100644 docs/_build/html/_modules/hplefthandclient/__init__.html create mode 100644 docs/_build/html/_modules/hplefthandclient/client.html create mode 100644 docs/_build/html/_modules/hplefthandclient/exceptions.html create mode 100644 docs/_build/html/_modules/hplefthandclient/http.html create mode 100644 docs/_build/html/_modules/index.html create mode 100644 docs/_build/html/_sources/api/hplefthandclient/client.txt create mode 100644 docs/_build/html/_sources/api/hplefthandclient/exceptions.txt create mode 100644 docs/_build/html/_sources/api/hplefthandclient/http.txt create mode 100644 docs/_build/html/_sources/api/hplefthandclient/index.txt create mode 100644 docs/_build/html/_sources/api/index.txt create mode 100644 docs/_build/html/_sources/changelog.txt create mode 100644 docs/_build/html/_sources/hplefthandclient.txt create mode 100644 docs/_build/html/_sources/index.txt create mode 100644 docs/_build/html/_sources/installation.txt create mode 100644 docs/_build/html/_sources/tutorial.txt create mode 100644 docs/_build/html/_static/ajax-loader.gif create mode 100644 docs/_build/html/_static/basic.css create mode 100644 docs/_build/html/_static/comment-bright.png create mode 100644 docs/_build/html/_static/comment-close.png create mode 100644 docs/_build/html/_static/comment.png create mode 100644 docs/_build/html/_static/default.css create mode 100644 docs/_build/html/_static/doctools.js create mode 100644 docs/_build/html/_static/down-pressed.png create mode 100644 docs/_build/html/_static/down.png create mode 100644 docs/_build/html/_static/empty_dir create mode 100644 docs/_build/html/_static/file.png create mode 100644 docs/_build/html/_static/jquery.js create mode 100644 docs/_build/html/_static/minus.png create mode 100644 docs/_build/html/_static/plus.png create mode 100644 docs/_build/html/_static/pygments.css create mode 100644 docs/_build/html/_static/searchtools.js create mode 100644 docs/_build/html/_static/sidebar.js create mode 100644 docs/_build/html/_static/underscore.js create mode 100644 docs/_build/html/_static/up-pressed.png create mode 100644 docs/_build/html/_static/up.png create mode 100644 docs/_build/html/_static/websupport.js create mode 100644 docs/_build/html/api/hplefthandclient/client.html create mode 100644 docs/_build/html/api/hplefthandclient/exceptions.html create mode 100644 docs/_build/html/api/hplefthandclient/http.html create mode 100644 docs/_build/html/api/hplefthandclient/index.html create mode 100644 docs/_build/html/api/index.html create mode 100644 docs/_build/html/changelog.html create mode 100644 docs/_build/html/genindex.html create mode 100644 docs/_build/html/hplefthandclient.html create mode 100644 docs/_build/html/index.html create mode 100644 docs/_build/html/installation.html create mode 100644 docs/_build/html/objects.inv create mode 100644 docs/_build/html/py-modindex.html create mode 100644 docs/_build/html/search.html create mode 100644 docs/_build/html/searchindex.js create mode 100644 docs/_build/html/tutorial.html create mode 100644 docs/_static/empty_dir create mode 100644 docs/api/hplefthandclient/client.rst create mode 100644 docs/api/hplefthandclient/exceptions.rst create mode 100644 docs/api/hplefthandclient/http.rst create mode 100644 docs/api/hplefthandclient/index.rst create mode 100644 docs/api/index.rst create mode 100644 docs/changelog.rst create mode 100644 docs/conf.py create mode 100644 docs/hplefthandclient.rst create mode 100644 docs/index.rst create mode 100644 docs/installation.rst create mode 100644 docs/make.bat create mode 100644 docs/tutorial.rst create mode 100644 hplefthandclient/__init__.py create mode 100644 hplefthandclient/client.py create mode 100644 hplefthandclient/exceptions.py create mode 100644 hplefthandclient/http.py create mode 100644 samples/README.rst create mode 100644 samples/test_client.py create mode 100644 samples/utils.py create mode 100644 setup.py create mode 100644 test/README.rst create mode 100644 test/config.ini create mode 100644 test/test_HPLeftHandClient_base.py create mode 100644 test/test_HPLeftHandClient_volume.py create mode 100755 test/test_HPLeftHandMockServer_flask.py diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..8c1d7a0 --- /dev/null +++ b/README.rst @@ -0,0 +1,73 @@ +HP LeftHand/StoreVirtual REST Client +=================== +This is a Client library that can talk to the HP LeftHand/StoreVirtual Storage array. +The HP LeftHand storage array has a REST web service interface. +This client library implements a simple interface to talk with that REST +interface using the python httplib2 http library. + +Requirements +============ +This branch requires 11.5 version of the LeftHand OS firmware. + +Capabilities +============ +* Get Volume(s) +* Get Volume by Name +* Create Volume +* Delete Volume +* Modify Volume +* Clone Volume +* Get Snapshot(s) +* Delete Shapshot +* Get Shapshot by Name +* Create Snapshot +* Delete Snapshot +* Clone Snapshot +* Get Cluster(s) +* Get Cluster by Name +* Get Server(s) +* Get Server by Name +* Create Server +* Delete Server +* Add Server Access +* Remove Server Access + + +Installation +============ + +:: + + $ python setup.py install + + +Unit Tests +========== + +:: + + $ pip install nose + $ pip install nose-testconfig + $ cd test + $ nosetests --tc-file config.ini + + +Folders +======= +* docs -- contains the documentation. +* hplefthandlient -- the actual client.py library +* test -- unit tests +* samples -- some sample uses + + +Documentation +============= + +To view the built documentation point your browser to + +:: + + python-hplefthand/docs/_build/html/index.html + + + diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..12fea54 --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,153 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = _build + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . +# the i18n builder cannot share the environment and doctrees with the others +I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . + +.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext + +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " texinfo to make Texinfo files" + @echo " info to make Texinfo files and run them through makeinfo" + @echo " gettext to make PO message catalogs" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + +clean: + -rm -rf $(BUILDDIR)/* + +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/hplefthandclient.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/hplefthandclient.qhc" + +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/hplefthandclient" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/hplefthandclient" + @echo "# devhelp" + +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +texinfo: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo + @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." + @echo "Run \`make' in that directory to run these through makeinfo" \ + "(use \`make info' here to do that automatically)." + +info: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo "Running Texinfo files through makeinfo..." + make -C $(BUILDDIR)/texinfo info + @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." + +gettext: + $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale + @echo + @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." + +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." diff --git a/docs/_build/doctrees/api/hplefthandclient/client.doctree b/docs/_build/doctrees/api/hplefthandclient/client.doctree new file mode 100644 index 0000000000000000000000000000000000000000..b2bcfa2774b93dd79b323711d4bafb031c0c051b GIT binary patch literal 122720 zcmeFa2Xq`o);4T{lgK%Ph_(g?axlrp7%&DCY=H>IjHR(hjVyUe5@4XoIm423&Y300 zCFh)$u;gTNTGqt>d7kR(sp+1UEx-Hz{?2zkAD^zSy0LEEuBv;x)N{z3jZJNhogK|B z?Sop|nhNbTtWGYpcGQ&Ss5`VYXIPr6e|_K9{NzH%)G3Ab(%fOGXa9!vd-Uj0oYGR5 z*ip>4HZ`_1gJ4ipY`w#_Xx|#`!)<$0p{3AK7}J`c(q3%qDD@gON5j&fx9GJ42i7Jz zm3ohESgSh{CKWn%Zfk+sr9NQZzZ;~bz91Of4T3=}ZIjwMJ4!X6+n_siv5uQh9Sv)i z<^h9syThQ-6!I`*!R%8orFmyTyd{mq&j$wUbVoe&kB4x65U<@$;^8fw?H$m3fmxun zgWFzO5VRZ4n%t<{3(Z2eBhA}O3xjwThAeW;ML<2Io60+jN7^q67TvOYQ(;2qr17TE z-cec%v>SCtyeUj>+b8n3VU3N2_V&`^;L$CoX>4h0Ekyla0>s_2-sHBX=801y>Lo$l ztyw2}T->a)RClFEvzC3_mIifqXQb#!T?VAgs#c_X0Ppd!<~8L!ps^Yf7twbU;`d*uMz;Vy-x6eXodqP?E+JrkKvw z=0>c+HKoA~Tf^pqi*1t&gWEguje8HCGPR@F);ch`QVzyy-99)!rFn1)60ODtkNz4* z9$H#6EUh)6zIR7{64&z5+V#ELt(K*A>TB{H9j19gX9rBOZdh8ce|?{(&MCxeFReeW zzIS6wzP%my8;q;(GXdHd8g#>P^>Z~hwU>sBD{a)jVR`6_=e4t?kkT>Q7nC-xU&88? z!YFN0sPAdTZ91;BS+RG%vwrTD=GH=K=%_iNOSB^?4TAyuG!~m%nqc?R=Hp7kQ-g;= zZ-D`~fZ<1kr7dCjh83aO5a>2!FDrg8ZaI>hl+soho&m){#Wjm-*Y^~c($+;7V`Ts0 z`s0clj4N(9t~g{|ano^~MJQE-Dn%%;P5;LBDaGd2{RZK&v-_thh1RC#)=A^{gOzGZ zqoByPVQD)mGG9`W(r5@B5ec2wCoFAG**o;F?+q^8cs0yr4XlQJ^QN`5*uDuN_!T^s3TLR4g}}Y_=0ElTyb0S-z&*~Y+*laR`Z?#N)zDljbW+D zJ$JixPRL_idq=923aEEZDCQd$g?XyF!$feH6qbq{w@jpF4qxX4a7%eXb8Ay!znW5j zF7F+dTAU*Au=>6m=Q}&vVD3#zlj&?iEw$1hZT;;yl}DyGCK%IPXdhRa(jSut9IK^J zDmBzX@8lC7J!iH3?U>Zf#4}mnqoZW<7`oAsL!&2r+PrW8b6kFs$xkWPl-kjmj+3enFZJJTU=NgAfHZL$rM4uV+@4oip7 zEK8!#*8D?KQ;aoBk?Qu2h)ai3*~751>^He(Nc)sLR+hu@kYU5d#jtcl!X5aK&E z{>8(K)l}-~R5}4Fb;+`HB6K<_ES>ChI<~59CpH&an#Q*@x8uP+1?rv}mQHgNoTO>P znle)9Z#3iS)yBM+Q|*k@QX%@9(wX3YR#-aQ@jo@<-&63?Ib?lqm#ojL&U!pow3^cS z;D13_x{&7P96d~iE?q>v7pM8=if3__y9Ac>urGz#E(=SSgGuosjLQWuTiFI$(XU89 zyVgPz+M1@;l&*vtSB0gksm44|!yD()HPq$WOkJ)k^2xtGEZsoy*FgML)<#7=Yd59| zhmPvSPTYh}BxT)#S+v(i26^1W#ex#Hic`F)Y*KFR&i{DH9aAT_@SMz~93 zP=wu!H10!T=^vED5N~X;xxE(uJBo!`mkb#^rlZXic5XHuo%xn%t^`89qqZa8vNv$s zh+cY_ianC4*rOVY$HLO%RO4Z&@sK|jPoxQlj_S=GJ&7Lu4^?{#26#FwJ>w)l=7z3l zhYRbVw0kxzJx5D$CUCv4-F}Q+B)H3OZY&HcJrCWZXW9DE3)OV*&YIxQLP6xt_N?T2mf6K{)ryH6_(y+kEzV7w#VFbyc3q* zMYEn7Sb21D?V@%V|LS6g@m~L8Nl{yQVYf?qZGY|gYMWcL7U8;LU+yK|PwXX
Am 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 SS-1d1f5S&if5(4R9Qt4D*Ik+b literal 0 HcmV?d00001 diff --git a/docs/_build/doctrees/api/hplefthandclient/http.doctree b/docs/_build/doctrees/api/hplefthandclient/http.doctree new file mode 100644 index 0000000000000000000000000000000000000000..91d704ffc77ce35de0ca081f4b4b45773cdd7102 GIT binary patch literal 5579 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 blEhFanJfB-PXEkqjktdPE2V!Yjmm!jq8sHi literal 0 HcmV?d00001 diff --git a/docs/_build/doctrees/api/hplefthandclient/index.doctree b/docs/_build/doctrees/api/hplefthandclient/index.doctree new file mode 100644 index 0000000000000000000000000000000000000000..fdd6ba3417dbfe95779cf742e0fdcb60206ba60c GIT binary patch literal 7731 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 literal 0 HcmV?d00001 diff --git a/docs/_build/doctrees/api/index.doctree b/docs/_build/doctrees/api/index.doctree new file mode 100644 index 0000000000000000000000000000000000000000..68e5841d98bac28d004ab0eb3ff4eaf41a0dc8ba GIT binary patch literal 4034 zcmbVP_k$b76+X__q$@rf7lQ2|z&L_%CqjrJPNUh_98nzOM=lA=dbK;J*?6_PZ)eu0 zOR~XE0o4`;ltN~O?g3B3{v#UmMyE^Zha8ggR4(mU&DKMHxQu4scw8%p~c7NgMi z#FFCij3;O&b`mA*mQc!R!c58HGR?SYlE9U%qBL;@Pu8jI2{%f*_NMBz(Mff*6h%6W zw6K+S63x>KXVJI`$oCY_WV~LE(FQ~_cWN}ViY8p?2Od1|28%ZiCaQ#Mf-Ljd$a7Q1 zn~`VYEElnCp&=SW&Od{eW4Sg5jmHB>)y8%s2MlDf8wC>SbyrN|k z&dE3sOIkXi=LSA())1_!A?lhRdZIm_D9z{Iae}vHyuCr=+G&~$c?XRbVe}Z|% zjZ_1_^D?efX~Iimb58O37LB`sqZB@0V9`Vaz7vD~g%+(dK<%{nB01~OX!rasiyt7z zoRmfaKNMVD7>2QedA?WD8z9RE0>$#L z8-?2OLsfAqds>mV=g1(5M=Ckwm1XI>vVvGe=$X^4C=unR2u0$%y{lTsz2i$!g39s3 zX64Y{n-5o9CkRv@UZvHD`Jo20J!isfq=(tVs`B#1OYT!L{BQvGh>Ra;aJX+*5$2VE zSjEy0+w+d+b$dpP#F3BPhUCipD8PJm#@870TM^kkfXCMwYlTRr`c3e4ChzO3G!A>F z?JTZ?8Bmb_uPEgJrdDnmlwII!kvKt_ z9}P6>89&CT^jeeG!GceIY?Vd~Vth#6usD_nI0wsov*;_2t@3JP|1elSQ;tmF@|KJr z2lF%pK;V!MxEv z-?qlK%=xuX=U49S$bE|=xG8bx4E!DWul&jV@_@XtF8g?Kq`|+Dv1{1ici zTrBf3I9<-T-E$fh6+B*?;1%QZ$rw&y+G>bIc>I(Fqs#+T&L@G2!eE7;+M$tV5H&dK zbU9tC!g0*U#K$0en#H%*7taNzrWyy!yE{4g_?>*hp)u)u9tQo>J2d5oZjfU2@B`R* zhQ-g6#~qqzIc-nGTJjwW!=`Mp`Lz6mjLIvJh0Orm*O|LE@ z_(g^Yn(FOG`HX%s)}hr1*EXq_So~5GRvabhIQ+6YP5551D!m-5(sX|;ieF*zD`~pt ztQ)|qGJZA9ViEBZ>_m3AsVaVr#jm9aCA3CwSNyuZbzmvMY( -{J%*mPUSk#Ba#> zjaAy%gTgi|2m(K<_)Q4oY?zJ`4=bVR^dfW(!Edh9RI=paD0ag%3HU9v%~bxLT1`h0 zR#IQ+rTHjn)}(F)HGV6VfPgEEX?`25&cq@%IY4Hi3xjX3(~N?6VjCr3wphjQ0FjVp zUpViiQKPUGeiuy#PS{K_>~Y=`{B9bChTcOZ&%u(VomR~6H6iBU+JQJG0)8J>r75I= zEjR2gJMYKZDxE+V2R^VcO4CIU#YXnQx~$b{yGW8Ku@PyMPs{FCvl9j&?n5+d(o(o0 z;V?j&{9zjPL?dnTN9r^yP)&x|706l&OudhGXh)RlIMw%L^J7?BfrhL3ZiEDGQIdAs zD4ahI^wxW6E4EiPrT7!T3iHnom!jg>Ck@DE;+pXeTf%Aar|PuX;BE@cux4w~LcCh= zr!gu;Ja^G$S9tsx+929Kc6-P=RRuPm1vX>YtCsvGf3DyPiaX{jUq=al9+ur8a*o;p zQ#-J+U?g}kfY>jbq#eklwOVOwtlJ6;o8T{YXq^a;@t5jFUa=CXhdf#Vf7!s-jw7hd zhi;S52^4H=`5QEv>ZQ4>_?uKN zt{Xvr3%C8SpCbM?mGYBw3Lt!k*4v)1oCcsQaQQBF3{-Fohkl#ChsIRWPQzaIJv6WH zFW@b8s*d;vG)jl}!SxSmy!Vv)5$*mzUm-uH^;oGiwpmj{fL8NQPEsjfXB|V*PmxkC zoHZzah9WVFSgqrq(|JW5FE208ci%vH9ed(f4JTENe?i+%1;#$L9)2nBrWw<_>@_fc zMZ>x-Q_&Rjc@S7H!G(i~U|C`D0at zh6Rs*56MsBA&8>t{_qFM^E C|HXCy literal 0 HcmV?d00001 diff --git a/docs/_build/doctrees/changelog.doctree b/docs/_build/doctrees/changelog.doctree new file mode 100644 index 0000000000000000000000000000000000000000..24ee0e37284345dfe4d6531dde1961028f9c3feb GIT binary patch literal 3188 zcmbVO`F9&v6?U9jmgLy6-6o_>LIiD!N-N8O5JG`M8=6uKqO`ujEW;y0Xf( z-bQ2mJ&1=q+skN4WqqZ64Oekwdt#uto^yj15+_x{4uw(E@xDCr5|{3%r{$n%Oo*MxW;>vPk@(`IX?%yq|SB)@f1x_4Y2?< z-zul6R=vM%(o8CnG>d!}WHt&MCg#qFFa^ zl*02J7S;RkotmVax9F(h?@o(fAeSAQDbKepzDv$J8Qm24k>D4uO~Y8F6uw(dOE7j% zdoot!J3y2j5Vn$Yw=GW<%D7S}$B@1%ZEA~z9511MQwSzCGW80c}w1D z$+neAxRjNn7foqw)XyP)q?y7X{I*A7r=?qgRkxhOK zc-C_!gXivNhlj5LP;UfiY?I$?0Bt-2ptZUwh_@h<&(_mhk=(cC{C1OE<8{4AE_92Z zD1MI7EzRlFiPRuYLy@x8qxo?&EXw#DIonjnV_ClM%-NwuwHx;1z#g4c+_%`Jr9-W> zxb0ai=$47&cJXOo@epm(FY~8ZTI?J4>DZ`e7Y*WFXwhSY``vxh;=sU4;#327*rU4d zRqZ=M`<_3DMR9C#Lh~bMB>>JjrA0sT{1gq@M&}<2tyo#Csjh_9el%2^9q2`aG>){0 zyyi70$S~n}E9UK-ciOZ(qQW-)55PAS?*hinFde5J##DlPB65AS$#?hYSUPZV6!R)e z1AY&kmRcwGcDp)`F+Y8w2P<(p>`EO5U4Ab$z40#>0<~3lQ$}7=Z7##r4yhr6bGJKo1ys%L^Td>^yGyeoe^mor#6tr9vHRD zzh)o>NZcMR8i)#4Bpe0^lRr!|p6F*o{z#7&Mc5Z6vAcm#QV7i--J`Q{rjtznH=7^B z%mf>*=BEJ(xs9^W-Z2X2kAuDAUKS?ySW}8W0j@BD{AdtY$3AID-geT+0ER8$g#4)< zoiMx){GiC1jot*jTJWc-jt`yls~0`~3@wQrANvMmovDhO&w`tp8%G0w$e*in1;uUo z%GYtqpNC~Ph@DMagpMEBj)&Eu7^q*kL1z(3IIgt)tlKIvTzzqmj*4iDztpSZN|eYv z#1RJkWkcUi5<_J^bel#_p!h4OC&LN0sA5c35r1`$=E_9_jXv7suK}jf=mC{|9WEw_ z=WozVrU#wN{7q_B*NvdRh1-5~Fh%@rY7{3s3M71oj@zEEoIa?mc=>LR8mQm|4*ebe z9vV|aJAHfG_q62iuhnVcu#Wf#G(+oG;rfR(J6i94M3?^073jxw9M!Ke{!I;mTFpPX zL5-n^1a?Iv>8A*(5Y9f8KSPliMU3nC=X9>Bev%oU7S>1{so;mOw2?* z{8Bzm3#ND3`(*rzrhSip4gWC~M=U~B&VI8;r^|{Ho zohrxTOiyxf`pv^G`ldwv}mv{+jdOL$BBjMyoECI4o>f5U4R~tBU_&@jt0% P)PbGyUpfCf>#zI=I*W&@ literal 0 HcmV?d00001 diff --git a/docs/_build/doctrees/environment.pickle b/docs/_build/doctrees/environment.pickle new file mode 100644 index 0000000000000000000000000000000000000000..a4a633dc03d3b4bffcf0a2ff47d7d6822aea8adf GIT binary patch literal 103654 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 literal 0 HcmV?d00001 diff --git a/docs/_build/doctrees/hplefthandclient.doctree b/docs/_build/doctrees/hplefthandclient.doctree new file mode 100644 index 0000000000000000000000000000000000000000..ef362460a1d4b4bbff196e806027da5ab8683157 GIT binary patch literal 302624 zcmeFa2Y405_dkqExA(5sN>BlN!HS5AC?F7x1u-NSk~JjUxd}yGdpEK7-g}F^cm3LX z?_Dfkd#~^3bI#7**>-c`_xr#6pZ9rrcy@N?)H!o@cFvi(n?vTUt8b}mYi(>Q4s33z z&lmf$IwjxS+P5%I|3eG&`i1$bs{1t8PRX~(;GdYEynvYeQ{w zeO*%{2nJ4=(AeDAI$^@VX?as@Y-uj^95GMLMroJQ#*N;#wtfdtO|P9fa7w;dteu=M^akmmG}0`=O)ZmK z+FA>JK(|#IT}s3O#gvAf#^eia`C@BC*#$wqR%PUY znF|YnWYaW~Eb~szx6)*Jv+c~0@T9`RV6fS2G8o#_R%{J9ECLRL(>TOBy>q^Ka%%(o z-DvXlk&Z747TZ<8A~0IMeoSri4Y zrNA(CSYy2yZCYl`9Ma~Di?zej;IMg`N?D#|@>5!-<<+@{)YaknFDwHd8_o`ox~7(9 zJLxP7;;u~T*koMelsqSn<+Aj4dalpZY+)$+pYk|6w<;G}}uWM

yTgl5Rc;5$; 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

j4o{bpJ73X;gC)7S) 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åxWMBuW|hXwAxr~frrzno&M#R1B@u|3 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 literal 0 HcmV?d00001 diff --git a/docs/_build/doctrees/installation.doctree b/docs/_build/doctrees/installation.doctree new file mode 100644 index 0000000000000000000000000000000000000000..f4f2030ee0cbec1b97fcb2a0c9b5a9ab828e58c0 GIT binary patch literal 9045 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 N;U%W`<2Nc7{tHw2KDGb= literal 0 HcmV?d00001 diff --git a/docs/_build/html/.buildinfo b/docs/_build/html/.buildinfo new file mode 100644 index 0000000..2e6a491 --- /dev/null +++ b/docs/_build/html/.buildinfo @@ -0,0 +1,4 @@ +# Sphinx build info version 1 +# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. +config: 526306b7904f34b2dc7f0e35d98b76a4 +tags: fbb0d17656682115ca4d033fb2f83ba1 diff --git a/docs/_build/html/_modules/hplefthandclient.html b/docs/_build/html/_modules/hplefthandclient.html new file mode 100644 index 0000000..0a4b478 --- /dev/null +++ b/docs/_build/html/_modules/hplefthandclient.html @@ -0,0 +1,128 @@ + + + + + + + + + + hplefthandclient — HP LeftHand REST Client 1.0.0 documentation + + + + + + + + + + + + +

+ +
+
+
+
+ +

Source code for hplefthandclient

+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2013 Hewlett Packard Development Company, L.P.
+# All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+"""
+HP LeftHand REST Client
+
+:Author: Walter A. Boring IV
+:Copyright: Copyright 2013, Hewlett Packard Development Company, L.P.
+:License: Apache v2.0
+ 
+"""
+
+version_tuple = (1, 0, 0)
+
+def get_version_string():
+    if isinstance(version_tuple[-1], basestring):
+        return '.'.join(map(str, version_tuple[:-1])) + version_tuple[-1]
+    return '.'.join(map(str, version_tuple))
+
+version = get_version_string()
+"""Current version of HPLeftHandClient."""
+
+ +
+
+
+
+
+ + +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/hplefthandclient/__init__.html b/docs/_build/html/_modules/hplefthandclient/__init__.html new file mode 100644 index 0000000..9f6405a --- /dev/null +++ b/docs/_build/html/_modules/hplefthandclient/__init__.html @@ -0,0 +1,130 @@ + + + + + + + + + + hplefthandclient.__init__ — HP LeftHand REST Client 1.0.0 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for hplefthandclient.__init__

+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2013 Hewlett Packard Development Company, L.P.
+# All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+"""
+HP LeftHand REST Client
+
+:Author: Walter A. Boring IV
+:Copyright: Copyright 2013, Hewlett Packard Development Company, L.P.
+:License: Apache v2.0
+ 
+"""
+
+version_tuple = (1, 0, 0)
+
+
[docs]def get_version_string(): + if isinstance(version_tuple[-1], basestring): + return '.'.join(map(str, version_tuple[:-1])) + version_tuple[-1] + return '.'.join(map(str, version_tuple)) +
+version = get_version_string() +"""Current version of HPLeftHandClient.""" +
+ +
+
+
+
+
+ + +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/hplefthandclient/client.html b/docs/_build/html/_modules/hplefthandclient/client.html new file mode 100644 index 0000000..3fb18cb --- /dev/null +++ b/docs/_build/html/_modules/hplefthandclient/client.html @@ -0,0 +1,594 @@ + + + + + + + + + + hplefthandclient.client — HP LeftHand REST Client 1.0.0 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for hplefthandclient.client

+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2012 Hewlett Packard Development Company, L.P.
+# All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+"""
+HPLeftHand REST Client
+
+.. module: HPLeftHandClient
+.. moduleauthor: Kurt Martin
+.. moduleauthor: Walter A. Boring IV
+
+:Author: Kurt Martin
+:Description: This is the LeftHand/StoreVirtual Client that talks to the
+LeftHand OS REST Service.
+
+This client requires and works with version 11.5 of the LeftHand firmware
+
+"""
+
+from hplefthandclient import http
+
+
+
[docs]class HPLeftHandClient: + + def __init__(self, api_url): + self.api_url = api_url + self.http = http.HTTPJSONRESTClient(self.api_url) + +
[docs] def debug_rest(self, flag): + """ + This is useful for debugging requests to 3PAR + + :param flag: set to True to enable debugging + :type flag: bool + + """ + self.http.set_debug_flag(flag) +
+
[docs] def login(self, username, password): + """ + This authenticates against the LH OS REST server and creates a session. + + :param username: The username + :type username: str + :param password: The password + :type password: str + + :returns: None + + """ + self.http.authenticate(username, password) +
+
[docs] def logout(self): + """ + This destroys the session and logs out from the LH OS server + + :returns: None + + """ + self.http.unauthenticate() +
+
[docs] def getClusters(self): + """ + Get the list of Clusters + + :returns: list of Clusters + """ + response, body = self.http.get('/clusters') + return body +
+
[docs] def getCluster(self, cluster_id): + """ + Get information about a Cluster + + :param cluster_id: The id of the cluster to find + :type cluster_id: str + + :returns: cluster + """ + response, body = self.http.get('/clusters/%s' % cluster_id) + return body +
+
[docs] def getClusterByName(self, name): + """ + Get information about a cluster by name + + :param name: The name of the cluster to find + :type name: str + + :returns: cluster + :raises: :class:`~hplefthandclient.exceptions.HTTPNotFound` - + NON_EXISTENT_CLUSTER - cluster doesn't exist + """ + response, body = self.http.get('/clusters?name=%s' % name) + return body +
+
[docs] def getServers(self): + """ + Get the list of Servers + + :returns: list of Servers + """ + response, body = self.http.get('/servers') + return body +
+
[docs] def getServer(self, server_id): + """ + Get information about a server + + :param server_id: The id of the server to find + :type server_id: str + + :returns: server + :raises: :class:`~hplefthandclient.exceptions.HTTPServerError` + """ + response, body = self.http.get('/servers/%s' % server_id) + return body +
+
[docs] def getServerByName(self, name): + """ + Get information about a server by name + + :param name: The name of the server to find + :type name: str + + :returns: server + :raises: :class:`~hplefthandclient.exceptions.HTTPNotFound` - + NON_EXISTENT_SERVER - server doesn't exist + """ + response, body = self.http.get('/servers?name=%s' % name) + return body +
+
[docs] def createServer(self, name, iqn, optional=None): + """ + Create a server by name + + :param name: The name of the server to create + :type name: str + :param iqn: The iSCSI qualified name + :type name: str + :param optional: Dictionary of optional params + :type optional: dict + + .. code-block:: python + + optional = { + 'description' : "some comment", + 'iscsiEnabled' : True, + 'chapName': "some chap name", + 'chapAuthenticationRequired': False, + 'chapInitiatorSecret': "initiator secret", + 'chapTargetSecret': "target secret", + 'iscsiLoadBalancingEnabled': True, + 'controllingServerName': "server name", + 'fibreChannelEnabled': False, + 'inServerCluster": True + } + + :returns: server + :raises: :class:`~hplefthandclient.exceptions.HTTPNotFound` - + NON_EXISTENT_SERVER - server doesn't exist + """ + info = {'name': name, 'iscsiIQN': iqn} + if optional: + info = self._mergeDict(info, optional) + + response, body = self.http.post('/servers', body=info) + return body +
+
[docs] def deleteServer(self, server_id): + """ + Delete a Server + + :param server_id: the server ID to delete + + :raises: :class:`~hplefthandclient.exceptions.HTTPNotFound` - + NON_EXISTENT_SERVER - The server does not exist + """ + response, body = self.http.delete('/servers/%s' % server_id) + return body +
+
[docs] def getSnapshots(self): + """ + Get the list of Snapshots + + :returns: list of Snapshots + """ + response, body = self.http.get('/snapshots') + return body +
+
[docs] def getSnapshot(self, snapshot_id): + """ + Get information about a Snapshot + + :returns: snapshot + :raises: :class:`~hplefthandclient.exceptions.HTTPServerError` + """ + response, body = self.http.get('/snapshots/%s' % snapshot_id) + return body +
+
[docs] def getSnapshotByName(self, name): + """ + Get information about a snapshot by name + + :param name: The name of the snapshot to find + + :returns: volume + :raises: :class:`~hplefthandclient.exceptions.HTTPNotFound` - + NON_EXISTENT_SNAP - shapshot doesn't exist + """ + response, body = self.http.get('/snapshots?name=%s' % name) + return body +
+
[docs] def createSnapshot(self, name, source_volume_id, optional=None): + """ + Create a snapshot of an existing Volume + + :param name: Name of the Snapshot + :type name: str + :param source_volume_id: The volume you want to snapshot + :type source_volume_id: int + :param optional: Dictionary of optional params + :type optional: dict + + .. code-block:: python + + optional = { + 'description' : "some comment", + 'inheritAccess' : false + } + + """ + parameters = {'name': name} + if optional: + parameters = self._mergeDict(parameters, optional) + + info = {'action': 'createSnapshot', + 'parameters': parameters} + + response, body = self.http.post('/volumes/%s' % source_volume_id, + body=info) + return body +
+
[docs] def deleteSnapshot(self, snapshot_id): + """ + Delete a Snapshot + + :param snapshot_id: the snapshot ID to delete + + :raises: :class:`~hplefthandclient.exceptions.HTTPNotFound` - + NON_EXISTENT_SNAPSHOT - The snapshot does not exist + """ + response, body = self.http.delete('/snapshots/%s' % snapshot_id) + return body +
+
[docs] def cloneSnapshot(self, name, source_snapshot_id, optional=None): + """ + Create a clone of an existing Shapshot + + :param name: Name of the Snapshot clone + :type name: str + :param source_snapshot_id: The snapshot you want to clone + :type source_snapshot_id: int + :param optional: Dictionary of optional params + :type optional: dict + + .. code-block:: python + + optional = { + 'description' : "some comment" + } + + """ + parameters = {'name': name} + if optional: + parameters = self._mergeDict(parameters, optional) + + info = {'action': 'createSmartClone', + 'parameters': parameters} + + response, body = self.http.post('/snapshots/%s' % source_snapshot_id, + body=info) + return body +
+
[docs] def getVolumes(self): + """ + Get the list of Volumes + + :returns: list of Volumes + """ + response, body = self.http.get('/volumes') + return body +
+
[docs] def getVolume(self, volume_id): + """ + Get information about a volume + + :param volume_id: The id of the volume to find + :type volume_id: str + + :returns: volume + :raises: :class:`~hplefthandclient.exceptions.HTTPNotFound` - + NON_EXISTENT_VOL - volume doesn't exist + """ + response, body = self.http.get('/volumes/%s' % volume_id) + return body +
+
[docs] def getVolumeByName(self, name): + """ + Get information about a volume by name + + :param name: The name of the volume to find + :type volume_id: str + + :returns: volume + :raises: :class:`~hplefthandclient.exceptions.HTTPNotFound` - + NON_EXISTENT_VOL - volume doesn't exist + """ + response, body = self.http.get('/volumes?name=%s' % name) + return body +
+
[docs] def createVolume(self, name, cluster_id, size, optional=None): + """ Create a new volume + + :param name: the name of the volume + :type name: str + :param cluster_id: the cluster Id + :type cluster_id: int + :param sizeKB: size in KB for the volume + :type sizeKB: int + :param optional: dict of other optional items + :type optional: dict + + .. code-block:: python + + optional = { + 'description': 'some comment', + 'isThinProvisioned': 'true', + 'autogrowSeconds': 200, + 'clusterName': 'somename', + 'isAdaptiveOptimizationEnabled': 'true', + 'dataProtectionLevel': 2, + } + + :returns: List of Volumes + + :raises: :class:`~hplefthandclient.exceptions.HTTPConflict` - + EXISTENT_SV - Volume Exists already + """ + info = {'name': name, 'clusterID': cluster_id, 'size': size} + if optional: + info = self._mergeDict(info, optional) + + response, body = self.http.post('/volumes', body=info) + return body +
+
[docs] def deleteVolume(self, volume_id): + """ + Delete a volume + + :param name: the name of the volume + :type name: str + + :raises: :class:`~hplefthandclient.exceptions.HTTPNotFound` - + NON_EXISTENT_VOL - The volume does not exist + """ + response, body = self.http.delete('/volumes/%s' % volume_id) + return body +
+
[docs] def modifyVolume(self, volume_id, optional): + """Modify an existing volume. + + :param volume_id: The id of the volume to find + :type volume_id: str + + :returns: volume + :raises: :class:`~hplefthandclient.exceptions.HTTPNotFound` - + NON_EXISTENT_VOL - volume doesn't exist + """ + info = {'volume_id': volume_id} + info = self._mergeDict(info, optional) + response, body = self.http.put('/volumes/%s' % volume_id, body=info) + return body +
+
[docs] def cloneVolume(self, name, source_volume_id, optional=None): + """ + Create a clone of an existing Volume + + :param name: Name of the Volume clone + :type name: str + :param source_volume_id: The Volume you want to clone + :type source_volume_id: int + :param optional: Dictionary of optional params + :type optional: dict + + .. code-block:: python + + optional = { + 'description' : "some comment" + } + + """ + parameters = {'name': name} + if optional: + parameters = self._mergeDict(parameters, optional) + + info = {'action': 'createSmartClone', + 'parameters': parameters} + + response, body = self.http.post('/volumes/%s' % source_volume_id, + body=info) + return body +
+
[docs] def addServerAccess(self, volume_id, server_id, optional=None): + """ + Assign a Volume to a Server + + :param volume_id: Volume ID of the volume + :type name: int + :param server_id: Server ID of the server to add the volume to + :type source_volume_id: int + :param optional: Dictionary of optional params + :type optional: dict + + .. code-block:: python + + optional = { + 'Transport' : 0, + 'Lun' : 1, + } + + """ + parameters = {'serverID': server_id, + 'exclusiveAccess': True, + 'readAccess': True, + 'writeAccess': True} + if optional: + parameters = self._mergeDict(parameters, optional) + + info = {'action': 'addServerAccess', + 'parameters': parameters} + + response, body = self.http.post('/volumes/%s' % volume_id, + body=info) + return body +
+
[docs] def removeServerAccess(self, volume_id, server_id): + """ + Unassign a Volume from a Server + + :param volume_id: Volume ID of the volume + :type name: int + :param server_id: Server ID of the server to remove the volume fom + :type source_volume_id: int + + """ + parameters = {'serverID': server_id} + + info = {'action': 'removeServerAccess', + 'parameters': parameters} + + response, body = self.http.post('/volumes/%s' % volume_id, + body=info) + return body +
+ def _mergeDict(self, dict1, dict2): + """ + Safely merge 2 dictionaries together + + :param dict1: The first dictionary + :type dict1: dict + :param dict2: The second dictionary + :type dict2: dict + + :returns: dict + + :raises Exception: dict1, dict2 is not a dictionary + """ + if type(dict1) is not dict: + raise Exception("dict1 is not a dictionary") + if type(dict2) is not dict: + raise Exception("dict2 is not a dictionary") + + dict3 = dict1.copy() + dict3.update(dict2) + return dict3
+
+ +
+
+
+
+
+ + +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/hplefthandclient/exceptions.html b/docs/_build/html/_modules/hplefthandclient/exceptions.html new file mode 100644 index 0000000..7826ccc --- /dev/null +++ b/docs/_build/html/_modules/hplefthandclient/exceptions.html @@ -0,0 +1,404 @@ + + + + + + + + + + hplefthandclient.exceptions — HP LeftHand REST Client 1.0.0 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for hplefthandclient.exceptions

+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2012 Hewlett Packard Development Company, L.P.
+# All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+""" 
+Exceptions for the client
+
+.. module: Exceptions
+
+:Author: Walter A. Boring IV
+:Description: This contains the HTTP exceptions that can come back from the REST calls
+"""
+
+
+
[docs]class UnsupportedVersion(Exception): + """ + Indicates that the user is trying to use an unsupported version of the API + """ + pass +
+
[docs]class CommandError(Exception): + pass +
+
[docs]class AuthorizationFailure(Exception): + pass +
+
[docs]class NoUniqueMatch(Exception): + pass +
+
[docs]class ClientException(Exception): + """ + The base exception class for all exceptions this library raises. + + :param error: The error array + :type error: array + + """ + _error_code = None + _error_desc = None + + _debug1 = None + _debug2 = None + + def __init__(self, error=None): + if error: + if 'messageID' in error: + self._error_code = error['messageID'] + if 'message' in error: + self._error_desc = error['message'] + + if 'debug1' in error: + self._debug1 = error['debug1'] + if 'debug2' in error: + self._debug2 = error['debug2'] + +
[docs] def get_code(self): + return self._error_code +
+
[docs] def get_description(self): + return self._error_desc + +
+ def __str__(self): + formatted_string = "%s (HTTP %s)" % (self.message, self.http_status) + if self._error_code: + formatted_string += " %s" % self._error_code + if self._error_desc: + formatted_string += " - %s" % self._error_desc + + if self._debug1: + formatted_string += " (1: '%s')" % self._debug1 + + if self._debug2: + formatted_string += " (2: '%s')" % self._debug2 + + return formatted_string + + +## +## 400 Errors +## + +
+
[docs]class HTTPBadRequest(ClientException): + """ + HTTP 400 - Bad request: you sent some malformed data. + """ + http_status = 400 + message = "Bad request" + +
+
[docs]class HTTPUnauthorized(ClientException): + """ + HTTP 401 - Unauthorized: bad credentials. + """ + http_status = 401 + message = "Unauthorized" + +
+
[docs]class HTTPForbidden(ClientException): + """ + HTTP 403 - Forbidden: your credentials don't give you access to this + resource. + """ + http_status = 403 + message = "Forbidden" + +
+
[docs]class HTTPNotFound(ClientException): + """ + HTTP 404 - Not found + """ + http_status = 404 + message = "Not found" +
+
[docs]class HTTPMethodNotAllowed(ClientException): + """ + HTTP 405 - Method not Allowed + """ + http_status = 405 + message = "Method Not Allowed" +
+
[docs]class HTTPNotAcceptable(ClientException): + """ + HTTP 406 - Method not Acceptable + """ + http_status = 406 + message = "Method Not Acceptable" +
+
[docs]class HTTPProxyAuthRequired(ClientException): + """ + HTTP 407 - The client must first authenticate itself with the proxy. + """ + http_status = 407 + message = "Proxy Authentication Required" +
+
[docs]class HTTPRequestTimeout(ClientException): + """ + HTTP 408 - The server timed out waiting for the request. + """ + http_status = 408 + message = "Request Timeout" + +
+
[docs]class HTTPConflict(ClientException): + """ + HTTP 409 - Conflict: A Conflict happened on the server + """ + http_status = 409 + message = "Conflict" +
+
[docs]class HTTPGone(ClientException): + """ + HTTP 410 - Indicates that the resource requested is no longer available and will not be available again. + """ + http_status = 410 + message = "Gone" +
+
[docs]class HTTPLengthRequired(ClientException): + """ + HTTP 411 - The request did not specify the length of its content, which is required by the requested resource. + """ + http_status = 411 + message = "Length Required" +
+
[docs]class HTTPPreconditionFailed(ClientException): + """ + HTTP 412 - The server does not meet one of the preconditions that the requester put on the request. + """ + http_status = 412 + message = "Over limit" +
+
[docs]class HTTPRequestEntityTooLarge(ClientException): + """ + HTTP 413 - The request is larger than the server is willing or able to process + """ + http_status = 413 + message = "Request Entity Too Large" +
+
[docs]class HTTPRequestURITooLong(ClientException): + """ + HTTP 414 - The URI provided was too long for the server to process. + """ + http_status = 414 + message = "Request URI Too Large" +
+
[docs]class HTTPUnsupportedMediaType(ClientException): + """ + HTTP 415 - The request entity has a media type which the server or resource does not support. + """ + http_status = 415 + message = "Unsupported Media Type" +
+
[docs]class HTTPRequestedRangeNotSatisfiable(ClientException): + """ + HTTP 416 - The client has asked for a portion of the file, but the server cannot supply that portion. + """ + http_status = 416 + message = "Requested Range Not Satisfiable" +
+
[docs]class HTTPExpectationFailed(ClientException): + """ + HTTP 417 - The server cannot meet the requirements of the Expect request-header field. + """ + http_status = 417 + message = "Expectation Failed" +
+
[docs]class HTTPTeaPot(ClientException): + """ + HTTP 418 - I'm a Tea Pot + """ + http_status = 418 + message = "I'm A Teapot. (RFC 2324)" + + +## +## 500 Errors +## +
+
[docs]class HTTPServerError(ClientException): + """ + HTTP 500 - + """ + http_status = 500 + message = "Error" + +# NotImplemented is a python keyword.
+
[docs]class HTTPNotImplemented(ClientException): + """ + HTTP 501 - Not Implemented: the server does not support this operation. + """ + http_status = 501 + message = "Not Implemented" +
+
[docs]class HTTPBadGateway(ClientException): + """ + HTTP 502 - The server was acting as a gateway or proxy and received an invalid response from the upstream server. + """ + http_status = 502 + message = "Bad Gateway" +
+
[docs]class HTTPServiceUnavailable(ClientException): + """ + HTTP 503 - The server is currently unavailable + """ + http_status = 503 + message = "Service Unavailable" +
+
[docs]class HTTPGatewayTimeout(ClientException): + """ + HTTP 504 - The server was acting as a gateway or proxy and did not receive a timely response from the upstream server. + """ + http_status = 504 + message = "Gateway Timeout" +
+
[docs]class HTTPVersionNotSupported(ClientException): + """ + HTTP 505 - The server does not support the HTTP protocol version used in the request. + """ + http_status = 505 + message = "Version Not Supported" + + +# In Python 2.4 Exception is old-style and thus doesn't have a __subclasses__() +# so we can do this: +# _code_map = dict((c.http_status, c) +# for c in ClientException.__subclasses__()) +# +# Instead, we have to hardcode it:
+_code_map = dict((c.http_status, c) for c in [HTTPBadRequest, HTTPUnauthorized, + HTTPForbidden, HTTPNotFound, HTTPMethodNotAllowed, + HTTPNotAcceptable, HTTPProxyAuthRequired, HTTPRequestTimeout, + HTTPConflict, HTTPGone, HTTPLengthRequired, + HTTPPreconditionFailed, HTTPRequestEntityTooLarge, + HTTPRequestURITooLong, HTTPUnsupportedMediaType, + HTTPRequestedRangeNotSatisfiable, HTTPExpectationFailed, + HTTPTeaPot, HTTPServerError, + HTTPNotImplemented, HTTPBadGateway, + HTTPServiceUnavailable, HTTPGatewayTimeout, + HTTPVersionNotSupported]) + + +
[docs]def from_response(response, body): + """ + Return an instance of an ClientException or subclass + based on an httplib2 response. + + Usage:: + + resp, body = http.request(...) + if resp.status != 200: + raise exception_from_response(resp, body) + + """ + cls = _code_map.get(response.status, ClientException) + return cls(body)
+
+ +
+
+
+
+
+ + +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/hplefthandclient/http.html b/docs/_build/html/_modules/hplefthandclient/http.html new file mode 100644 index 0000000..8004874 --- /dev/null +++ b/docs/_build/html/_modules/hplefthandclient/http.html @@ -0,0 +1,427 @@ + + + + + + + + + + hplefthandclient.http — HP LeftHand REST Client 1.0.0 documentation + + + + + + + + + + + + + + +
+
+
+
+ +

Source code for hplefthandclient.http

+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2013 Hewlett Packard Development Company, L.P.
+# All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+"""
+HPLeftHand HTTP Client
+:Author: Walter A. Boring IV
+:Description: This is the HTTP Client that is used to make the actual calls.
+ It includes the authentication that knows the cookie name for LH.
+
+"""
+
+import logging
+import httplib2
+import time
+import pprint
+
+try:
+    import json
+except ImportError:
+    import simplejson as json
+
+from hplefthandclient import exceptions
+
+
+
[docs]class HTTPJSONRESTClient(httplib2.Http): + """ + An HTTP REST Client that sends and recieves JSON data as the body of the + HTTP request. + + :param api_url: The url to the LH OS REST service + ie. https://<hostname or IP>:<port>/lhos + :type api_url: str + :param insecure: Use https? requires a local certificate + :type insecure: bool + + """ + + USER_AGENT = 'python-hplefthandclient' + SESSION_COOKIE_NAME = 'Authorization' + #API_VERSION = 'X-API-Version' + #CHRP_VERSION = 'X_HP-CHRP-Client-Version' + + def __init__(self, api_url, insecure=False, http_log_debug=False): + super(HTTPJSONRESTClient, + self).__init__(disable_ssl_certificate_validation=True) + self.session_key = None + + #should be http://<Server:Port>/lhos + self.set_url(api_url) + self.set_debug_flag(http_log_debug) + + self.times = [] # [("item", starttime, endtime), ...] + + # httplib2 overrides + self.force_exception_to_status_code = True + #self.disable_ssl_certificate_validation = insecure + + self._logger = logging.getLogger(__name__) + +
[docs] def set_url(self, api_url): + #should be http://<Server:Port>/lhos + self.api_url = api_url.rstrip('/') + self.api_url = self.api_url +
+
[docs] def set_debug_flag(self, flag): + """ + This turns on/off http request/response debugging output to console + + :param flag: Set to True to enable debugging output + :type flag: bool + + """ + self.http_log_debug = flag + if self.http_log_debug: + ch = logging.StreamHandler() + self._logger.setLevel(logging.DEBUG) + self._logger.addHandler(ch) +
+
[docs] def authenticate(self, user, password, optional=None): + """ + This tries to create an authenticated session with the LH OS server + + :param user: The username + :type user: str + :param password: The password + :type password: str + + """ + #this prevens re-auth attempt if auth fails + self.auth_try = 1 + self.session_key = None + + info = {'user': user, 'password': password} + self._auth_optional = None + + if optional: + self._auth_optional = optional + info.update(optional) + + resp, body = self.post('/credentials', body=info) + if body and 'authToken' in body: + self.session_key = body['authToken'] + self.auth_try = 0 + self.user = user + self.password = password +
+ def _reauth(self): + self.authenticate(self.user, self.password, self._auth_optional) + +
[docs] def unauthenticate(self): + """ + This clears the authenticated session with the LH server. It logs out. + + """ + #delete the session on the LH + self.delete('/credentials/%s' % self.session_key) + self.session_key = None +
+
[docs] def get_timings(self): + """ + Ths gives an array of the request timings since last reset_timings call + """ + return self.times +
+
[docs] def reset_timings(self): + """ + This resets the request/response timings array + """ + self.times = [] +
+ def _http_log_req(self, args, kwargs): + if not self.http_log_debug: + return + + string_parts = ['curl -i'] + for element in args: + if element in ('GET', 'POST'): + string_parts.append(' -X %s' % element) + else: + string_parts.append(' %s' % element) + + for element in kwargs['headers']: + header = ' -H "%s: %s"' % (element, kwargs['headers'][element]) + string_parts.append(header) + + self._logger.debug("\nREQ: %s\n" % "".join(string_parts)) + if 'body' in kwargs: + self._logger.debug("REQ BODY: %s\n" % (kwargs['body'])) + + def _http_log_resp(self, resp, body): + if not self.http_log_debug: + return + self._logger.debug("RESP:%s\n", pprint.pformat(resp)) + self._logger.debug("RESP BODY:%s\n", body) + +
[docs] def request(self, *args, **kwargs): + """ + This makes an HTTP Request to the LH server. You should use get, post, + delete instead. + """ + if self.session_key and self.auth_try != 1: + kwargs.setdefault('headers', + {})[self.SESSION_COOKIE_NAME] = self.session_key + + kwargs.setdefault('headers', kwargs.get('headers', {})) + kwargs['headers']['User-Agent'] = self.USER_AGENT + kwargs['headers']['Accept'] = 'application/json' + if 'body' in kwargs: + kwargs['headers']['Content-Type'] = 'application/json' + kwargs['body'] = json.dumps(kwargs['body']) + + self._http_log_req(args, kwargs) + resp, body = super(HTTPJSONRESTClient, self).request(*args, **kwargs) + self._http_log_resp(resp, body) + + # Try and conver the body response to an object + # This assumes the body of the reply is JSON + if body: + try: + body = json.loads(body) + except ValueError: + #pprint.pprint("failed to decode json\n") + pass + else: + body = None + + if resp.status >= 400: + raise exceptions.from_response(resp, body) + + return resp, body +
+ def _time_request(self, url, method, **kwargs): + start_time = time.time() + resp, body = self.request(url, method, **kwargs) + self.times.append(("%s %s" % (method, url), + start_time, time.time())) + return resp, body + + def _do_reauth(self, url, method, ex, **kwargs): + print "_do_reauth called" + try: + if self.auth_try != 1: + self._reauth() + resp, body = self._time_request(self.api_url + url, + method, **kwargs) + return resp, body + else: + raise ex + except exceptions.HTTPUnauthorized: + raise ex + + def _cs_request(self, url, method, **kwargs): + # Perform the request once. If we get a 401 back then it + # might be because the auth token expired, so try to + # re-authenticate and try again. If it still fails, bail. + try: + resp, body = self._time_request(self.api_url + url, method, + **kwargs) + return resp, body + except exceptions.HTTPUnauthorized, ex: + print "_CS_REQUEST HTTPUnauthorized" + resp, body = self._do_reauth(url, method, ex, **kwargs) + return resp, body + except exceptions.HTTPForbidden, ex: + print "_CS_REQUEST HTTPForbidden" + resp, body = self._do_reauth(url, method, ex, **kwargs) + return resp, body + +
[docs] def get(self, url, **kwargs): + """ + Make an HTTP GET request to the server. + + .. code-block:: python + + #example call + try { + headers, body = http.get('/volumes') + } except exceptions.HTTPUnauthorized as ex: + print "Not logged in" + } + + :param url: The relative url from the LH api_url + :type url: str + + :returns: headers - dict of HTTP Response headers + :returns: body - the body of the response. If the body was JSON, + it will be an object + """ + return self._cs_request(url, 'GET', **kwargs) +
+
[docs] def post(self, url, **kwargs): + """ + Make an HTTP POST request to the server. + + .. code-block:: python + + #example call + try { + info = {'name': 'new volume name', 'sizeMiB': 300} + headers, body = http.post('/volumes', body=info) + } except exceptions.HTTPUnauthorized as ex: + print "Not logged in" + } + + :param url: The relative url from the LH api_url + :type url: str + + :returns: headers - dict of HTTP Response headers + :returns: body - the body of the response. If the body was JSON, + it will be an object + """ + return self._cs_request(url, 'POST', **kwargs) +
+
[docs] def put(self, url, **kwargs): + """ + Make an HTTP PUT request to the server. + + .. code-block:: python + + #example call + try { + info = {'name': 'something'} + headers, body = http.put('/volumes', body=info) + } except exceptions.HTTPUnauthorized as ex: + print "Not logged in" + } + + :param url: The relative url from the LH api_url + :type url: str + + :returns: headers - dict of HTTP Response headers + :returns: body - the body of the response. If the body was JSON, + it will be an object + """ + return self._cs_request(url, 'PUT', **kwargs) +
+
[docs] def delete(self, url, **kwargs): + """ + Make an HTTP DELETE request to the server. + + .. code-block:: python + + #example call + try { + headers, body = http.delete('/volumes/%s' % name) + } except exceptions.HTTPUnauthorized as ex: + print "Not logged in" + } + + :param url: The relative url from the LH api_url + :type url: str + + :returns: headers - dict of HTTP Response headers + :returns: body - the body of the response. If the body was JSON, + it will be an object + """ + return self._cs_request(url, 'DELETE', **kwargs)
+
+ +
+
+
+
+
+ + +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/index.html b/docs/_build/html/_modules/index.html new file mode 100644 index 0000000..ff2a877 --- /dev/null +++ b/docs/_build/html/_modules/index.html @@ -0,0 +1,96 @@ + + + + + + + + + + Overview: module code — HP LeftHand REST Client 1.0.0 documentation + + + + + + + + + + + + + +
+ +
+
+ + +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_sources/api/hplefthandclient/client.txt b/docs/_build/html/_sources/api/hplefthandclient/client.txt new file mode 100644 index 0000000..a49aaf9 --- /dev/null +++ b/docs/_build/html/_sources/api/hplefthandclient/client.txt @@ -0,0 +1,34 @@ +:mod:`client` -- HPLeftHandClient +================================= + +.. automodule:: hplefthandclient.client + :synopsis: HP LeftHand REST Web client + + .. autoclass:: hplefthandclient.client.HPLeftHandClient(api_url) + + .. automethod:: debug_rest + .. automethod:: login + .. automethod:: logout + .. automethod:: getClusters + .. automethod:: getCluster + .. automethod:: getClusterByName + .. automethod:: getServers + .. automethod:: getServer + .. automethod:: getServerByName + .. automethod:: createServer + .. automethod:: deleteServer + .. automethod:: getSnapshots + .. automethod:: getSnapshot + .. automethod:: getSnapshotByName + .. automethod:: createSnapshot + .. automethod:: deleteSnapshot + .. automethod:: cloneSnapshot + .. automethod:: getVolumes + .. automethod:: getVolume + .. automethod:: getVolumeByName + .. automethod:: createVolume + .. automethod:: deleteVolume + .. automethod:: modifyVolume + .. automethod:: cloneVolume + .. automethod:: addServerAccess + .. automethod:: removeServerAccess diff --git a/docs/_build/html/_sources/api/hplefthandclient/exceptions.txt b/docs/_build/html/_sources/api/hplefthandclient/exceptions.txt new file mode 100644 index 0000000..0fae7ca --- /dev/null +++ b/docs/_build/html/_sources/api/hplefthandclient/exceptions.txt @@ -0,0 +1,8 @@ +:mod:`exceptions` -- HTTP Exceptions +==================================================== + +.. automodule:: hplefthandclient.exceptions + :synopsis: HTTP Exceptions + + .. autoclass:: hplefthandclient.exceptions.HTTPNotFound + .. autoclass:: hplefthandclient.exceptions.HTTPBadRequest diff --git a/docs/_build/html/_sources/api/hplefthandclient/http.txt b/docs/_build/html/_sources/api/hplefthandclient/http.txt new file mode 100644 index 0000000..e10be95 --- /dev/null +++ b/docs/_build/html/_sources/api/hplefthandclient/http.txt @@ -0,0 +1,24 @@ +:mod:`http` -- HTTP REST Base Class +==================================================== + +.. automodule:: hplefthandclient.http + :synopsis: HTTP REST Base Class + + .. autoclass::hplefthandclient.http(api_url, [insecure=False[,http_log_debug=False]]) + + .. automethod:: authenticate + .. automethod:: unauthenticate + + .. describe:: c[db_name] || c.db_name + + Get the `db_name` :class:`~pymongo.database.Database` on :class:`Connection` `c`. + + Raises :class:`~pymongo.errors.InvalidName` if an invalid database name is used. + + .. autoattribute:: api_url + .. autoattribute:: http_log_debug + .. automethod:: request + .. automethod:: get + .. automethod:: post + .. automethod:: put + .. automethod:: delete diff --git a/docs/_build/html/_sources/api/hplefthandclient/index.txt b/docs/_build/html/_sources/api/hplefthandclient/index.txt new file mode 100644 index 0000000..90c1d36 --- /dev/null +++ b/docs/_build/html/_sources/api/hplefthandclient/index.txt @@ -0,0 +1,18 @@ +:mod:`client` -- HPLeftHandClient +================================= + +.. automodule:: hplefthandclient + :synopsis: HP LeftHand REST Web client + + .. autodata:: version + + +Sub-modules: + +.. toctree:: + :maxdepth: 2 + + client + exceptions + http + diff --git a/docs/_build/html/_sources/api/index.txt b/docs/_build/html/_sources/api/index.txt new file mode 100644 index 0000000..ac5cd35 --- /dev/null +++ b/docs/_build/html/_sources/api/index.txt @@ -0,0 +1,11 @@ +API Documentation +================= + +The HP LeftHand Client package contains a :mod:`hplefthandclient` class which extends a more +generic :mod:`http` class for doing REST calls + +.. toctree:: + :maxdepth: 2 + + hplefthandclient/index + diff --git a/docs/_build/html/_sources/changelog.txt b/docs/_build/html/_sources/changelog.txt new file mode 100644 index 0000000..d9bcd9c --- /dev/null +++ b/docs/_build/html/_sources/changelog.txt @@ -0,0 +1,9 @@ +Changelog +========= + + +Changes in Version 1.0.0 +------------------------ + +- First implementation of the REST API Client + diff --git a/docs/_build/html/_sources/hplefthandclient.txt b/docs/_build/html/_sources/hplefthandclient.txt new file mode 100644 index 0000000..f4c11e6 --- /dev/null +++ b/docs/_build/html/_sources/hplefthandclient.txt @@ -0,0 +1,37 @@ +hplefthandclient Package +======================== + +:mod:`hplefthandclient` Package +------------------------------- + +.. automodule:: hplefthandclient.__init__ + :members: + :undoc-members: + :show-inheritance: + +:mod:`client` Module +-------------------- + +.. automodule:: hplefthandclient.client + :members: + :undoc-members: + :show-inheritance: + +:mod:`exceptions` Module +------------------------ + +.. automodule:: hplefthandclient.exceptions + :members: + :undoc-members: + :show-inheritance: + +:mod:`http` Module +------------------ + +.. automodule:: hplefthandclient.http + :members: + :undoc-members: + :show-inheritance: + + + diff --git a/docs/_build/html/_sources/index.txt b/docs/_build/html/_sources/index.txt new file mode 100644 index 0000000..e52d03c --- /dev/null +++ b/docs/_build/html/_sources/index.txt @@ -0,0 +1,65 @@ +HPLeftHandClient |release| Documentation +======================================== + +Overview +-------- +**HPLeftHandClient** is a Python package containing a class that uses +HTTP REST calls to talk with an HP LeftHand/StoreVirtual drive array. +distribution containing tools for working with +`LeftHand/StoreVirtual Storage Arrays `_. +. This documentation attempts to explain +everything you need to know to use **HPLeftHandClient**. + +:doc:`installation` + Instructions on how to get the distribution. + +:doc:`tutorial` + Start here for a quick overview. + +:doc:`api/index` + The complete API documentation, organized by module. + +Issues +------ +.. todo:: create the open source website +.. todo:: create the bug tracker + +All issues should be reported (and can be tracked / voted for / +commented on) at the main `github issues +`_, in the "LeftHand Python Driver" +project. + +Changes +------- +See the :doc:`changelog` for a full list of changes to HPLeftHandClient. + + +About This Documentation +------------------------ +This documentation is generated using the `Sphinx +`_ documentation generator. The source files +for the documentation are located in the *doc/* directory of the +**HPLeftHandClient** distribution. To generate the docs locally run the +following command from the root directory of the **HPLeftHandClient** + +.. code-block:: bash + + $ python setup.py doc + + +.. toctree:: + :hidden: + + installation + tutorial + changelog + api/index + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` + diff --git a/docs/_build/html/_sources/installation.txt b/docs/_build/html/_sources/installation.txt new file mode 100644 index 0000000..e83124c --- /dev/null +++ b/docs/_build/html/_sources/installation.txt @@ -0,0 +1,43 @@ +Installing / Upgrading +====================== +.. highlight:: bash + +**HPLeftHandClient** is in the `Python Package Index +`_. + +Installing with pip +------------------- + +We prefer `pip `_ +to install hplefthandclient on platforms other than Windows:: + + $ pip install hplefthandclient + +To upgrade using pip:: + + $ pip install --upgrade hplefthandclient + +Installing with easy_install +---------------------------- + +If you must install hplefthandclient using +`setuptools `_ do:: + + $ easy_install hplefthandclient + +To upgrade do:: + + $ easy_install -U hplefthandclient + + +Installing from source +---------------------- + +If you'd rather install directly from the source (i.e. to stay on the +bleeding edge), install the C extension dependencies then check out the +latest source from github and install the driver from the resulting tree:: + + $ git clone git://github.com/WaltHP/python-3parclient.git + $ cd pyton-hplefthandclient/ + $ python setup.py install + diff --git a/docs/_build/html/_sources/tutorial.txt b/docs/_build/html/_sources/tutorial.txt new file mode 100644 index 0000000..8ec3b00 --- /dev/null +++ b/docs/_build/html/_sources/tutorial.txt @@ -0,0 +1,68 @@ +Tutorial +======== + +This tutorial is intended as an introduction to working with +**HPLeftHandClient**. + +Prerequisites +------------- +Before we start, make sure that you have the **HPLeftHandClient** distribution +:doc:`installed `. In the Python shell, the following +should run without raising an exception: + +.. code-block:: bash + + >>> import hplefthandclient + +This tutorial also assumes that a LeftHand array is up and running and the +LeftHand OS is running. + +Create the Client and login +--------------------------- +The first step when working with **HPLeftHandClient** is to create a +:class:`~hplefthandclient.client.HPLeftHandClient` to the LeftHand drive array +and logging in to create the session. You must :meth:`~hplefthandclient.client.HPLeftHandClient.login` prior to calling the other APIs to do work on the LeftHand. +Doing so is easy: + +.. code-block:: python + + from hplefthandclient import client, exceptions + #this creates the client object and sets the url to the + #LeftHand server with IP 10.10.10.10 on port 8008. + cl = client.HPLeftHandClient("https://10.10.10.10:8008/api/v1") + + try: + cl.login(username, password) + print "Login worked!" + except exceptions.HTTPUnauthorized as ex: + print "Login failed." + +When you are done with the the client, it's a good idea to logout from +the LeftHand so there isn't a stale session sitting around. + +.. code-block:: python + + cl.logout() + print "logout worked" + +Getting a list of Volumes +------------------------- +After you have logged in, you can start making calls to the LeftHand APIs. +A simple example is getting a list of existing volumes on the array with +a call to :meth:`~hplefthandclient.client.HPLeftHandClient.getVolumes`. + +.. code-block:: python + + import pprint + try: + volumes = cl.getVolumes() + pprint.pprint(volumes) + except exceptions.HTTPUnauthorized as ex: + print "You must login first" + except Exception as ex: + #something unexpected happened + print ex + + +.. note:: volumes is an array of volumes in the above call + diff --git a/docs/_build/html/_static/ajax-loader.gif b/docs/_build/html/_static/ajax-loader.gif new file mode 100644 index 0000000000000000000000000000000000000000..61faf8cab23993bd3e1560bff0668bd628642330 GIT binary patch literal 673 zcmZ?wbhEHb6krfw_{6~Q|Nno%(3)e{?)x>&1u}A`t?OF7Z|1gRivOgXi&7IyQd1Pl zGfOfQ60;I3a`F>X^fL3(@);C=vM_KlFfb_o=k{|A33hf2a5d61U}gjg=>Rd%XaNQW zW@Cw{|b%Y*pl8F?4B9 zlo4Fz*0kZGJabY|>}Okf0}CCg{u4`zEPY^pV?j2@h+|igy0+Kz6p;@SpM4s6)XEMg z#3Y4GX>Hjlml5ftdH$4x0JGdn8~MX(U~_^d!Hi)=HU{V%g+mi8#UGbE-*ao8f#h+S z2a0-5+vc7MU$e-NhmBjLIC1v|)9+Im8x1yacJ7{^tLX(ZhYi^rpmXm0`@ku9b53aN zEXH@Y3JaztblgpxbJt{AtE1ad1Ca>{v$rwwvK(>{m~Gf_=-Ro7Fk{#;i~+{{>QtvI yb2P8Zac~?~=sRA>$6{!(^3;ZP0TPFR(G_-UDU(8Jl0?(IXu$~#4A!880|o%~Al1tN literal 0 HcmV?d00001 diff --git a/docs/_build/html/_static/basic.css b/docs/_build/html/_static/basic.css new file mode 100644 index 0000000..43e8baf --- /dev/null +++ b/docs/_build/html/_static/basic.css @@ -0,0 +1,540 @@ +/* + * basic.css + * ~~~~~~~~~ + * + * Sphinx stylesheet -- basic theme. + * + * :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +/* -- main layout ----------------------------------------------------------- */ + +div.clearer { + clear: both; +} + +/* -- relbar ---------------------------------------------------------------- */ + +div.related { + width: 100%; + font-size: 90%; +} + +div.related h3 { + display: none; +} + +div.related ul { + margin: 0; + padding: 0 0 0 10px; + list-style: none; +} + +div.related li { + display: inline; +} + +div.related li.right { + float: right; + margin-right: 5px; +} + +/* -- sidebar --------------------------------------------------------------- */ + +div.sphinxsidebarwrapper { + padding: 10px 5px 0 10px; +} + +div.sphinxsidebar { + float: left; + width: 230px; + margin-left: -100%; + font-size: 90%; +} + +div.sphinxsidebar ul { + list-style: none; +} + +div.sphinxsidebar ul ul, +div.sphinxsidebar ul.want-points { + margin-left: 20px; + list-style: square; +} + +div.sphinxsidebar ul ul { + margin-top: 0; + margin-bottom: 0; +} + +div.sphinxsidebar form { + margin-top: 10px; +} + +div.sphinxsidebar input { + border: 1px solid #98dbcc; + font-family: sans-serif; + font-size: 1em; +} + +div.sphinxsidebar #searchbox input[type="text"] { + width: 170px; +} + +div.sphinxsidebar #searchbox input[type="submit"] { + width: 30px; +} + +img { + border: 0; +} + +/* -- search page ----------------------------------------------------------- */ + +ul.search { + margin: 10px 0 0 20px; + padding: 0; +} + +ul.search li { + padding: 5px 0 5px 20px; + background-image: url(file.png); + background-repeat: no-repeat; + background-position: 0 7px; +} + +ul.search li a { + font-weight: bold; +} + +ul.search li div.context { + color: #888; + margin: 2px 0 0 30px; + text-align: left; +} + +ul.keywordmatches li.goodmatch a { + font-weight: bold; +} + +/* -- index page ------------------------------------------------------------ */ + +table.contentstable { + width: 90%; +} + +table.contentstable p.biglink { + line-height: 150%; +} + +a.biglink { + font-size: 1.3em; +} + +span.linkdescr { + font-style: italic; + padding-top: 5px; + font-size: 90%; +} + +/* -- general index --------------------------------------------------------- */ + +table.indextable { + width: 100%; +} + +table.indextable td { + text-align: left; + vertical-align: top; +} + +table.indextable dl, table.indextable dd { + margin-top: 0; + margin-bottom: 0; +} + +table.indextable tr.pcap { + height: 10px; +} + +table.indextable tr.cap { + margin-top: 10px; + background-color: #f2f2f2; +} + +img.toggler { + margin-right: 3px; + margin-top: 3px; + cursor: pointer; +} + +div.modindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +div.genindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +/* -- general body styles --------------------------------------------------- */ + +a.headerlink { + visibility: hidden; +} + +h1:hover > a.headerlink, +h2:hover > a.headerlink, +h3:hover > a.headerlink, +h4:hover > a.headerlink, +h5:hover > a.headerlink, +h6:hover > a.headerlink, +dt:hover > a.headerlink { + visibility: visible; +} + +div.body p.caption { + text-align: inherit; +} + +div.body td { + text-align: left; +} + +.field-list ul { + padding-left: 1em; +} + +.first { + margin-top: 0 !important; +} + +p.rubric { + margin-top: 30px; + font-weight: bold; +} + +img.align-left, .figure.align-left, object.align-left { + clear: left; + float: left; + margin-right: 1em; +} + +img.align-right, .figure.align-right, object.align-right { + clear: right; + float: right; + margin-left: 1em; +} + +img.align-center, .figure.align-center, object.align-center { + display: block; + margin-left: auto; + margin-right: auto; +} + +.align-left { + text-align: left; +} + +.align-center { + text-align: center; +} + +.align-right { + text-align: right; +} + +/* -- sidebars -------------------------------------------------------------- */ + +div.sidebar { + margin: 0 0 0.5em 1em; + border: 1px solid #ddb; + padding: 7px 7px 0 7px; + background-color: #ffe; + width: 40%; + float: right; +} + +p.sidebar-title { + font-weight: bold; +} + +/* -- topics ---------------------------------------------------------------- */ + +div.topic { + border: 1px solid #ccc; + padding: 7px 7px 0 7px; + margin: 10px 0 10px 0; +} + +p.topic-title { + font-size: 1.1em; + font-weight: bold; + margin-top: 10px; +} + +/* -- admonitions ----------------------------------------------------------- */ + +div.admonition { + margin-top: 10px; + margin-bottom: 10px; + padding: 7px; +} + +div.admonition dt { + font-weight: bold; +} + +div.admonition dl { + margin-bottom: 0; +} + +p.admonition-title { + margin: 0px 10px 5px 0px; + font-weight: bold; +} + +div.body p.centered { + text-align: center; + margin-top: 25px; +} + +/* -- tables ---------------------------------------------------------------- */ + +table.docutils { + border: 0; + border-collapse: collapse; +} + +table.docutils td, table.docutils th { + padding: 1px 8px 1px 5px; + border-top: 0; + border-left: 0; + border-right: 0; + border-bottom: 1px solid #aaa; +} + +table.field-list td, table.field-list th { + border: 0 !important; +} + +table.footnote td, table.footnote th { + border: 0 !important; +} + +th { + text-align: left; + padding-right: 5px; +} + +table.citation { + border-left: solid 1px gray; + margin-left: 1px; +} + +table.citation td { + border-bottom: none; +} + +/* -- other body styles ----------------------------------------------------- */ + +ol.arabic { + list-style: decimal; +} + +ol.loweralpha { + list-style: lower-alpha; +} + +ol.upperalpha { + list-style: upper-alpha; +} + +ol.lowerroman { + list-style: lower-roman; +} + +ol.upperroman { + list-style: upper-roman; +} + +dl { + margin-bottom: 15px; +} + +dd p { + margin-top: 0px; +} + +dd ul, dd table { + margin-bottom: 10px; +} + +dd { + margin-top: 3px; + margin-bottom: 10px; + margin-left: 30px; +} + +dt:target, .highlighted { + background-color: #fbe54e; +} + +dl.glossary dt { + font-weight: bold; + font-size: 1.1em; +} + +.field-list ul { + margin: 0; + padding-left: 1em; +} + +.field-list p { + margin: 0; +} + +.refcount { + color: #060; +} + +.optional { + font-size: 1.3em; +} + +.versionmodified { + font-style: italic; +} + +.system-message { + background-color: #fda; + padding: 5px; + border: 3px solid red; +} + +.footnote:target { + background-color: #ffa; +} + +.line-block { + display: block; + margin-top: 1em; + margin-bottom: 1em; +} + +.line-block .line-block { + margin-top: 0; + margin-bottom: 0; + margin-left: 1.5em; +} + +.guilabel, .menuselection { + font-family: sans-serif; +} + +.accelerator { + text-decoration: underline; +} + +.classifier { + font-style: oblique; +} + +abbr, acronym { + border-bottom: dotted 1px; + cursor: help; +} + +/* -- code displays --------------------------------------------------------- */ + +pre { + overflow: auto; + overflow-y: hidden; /* fixes display issues on Chrome browsers */ +} + +td.linenos pre { + padding: 5px 0px; + border: 0; + background-color: transparent; + color: #aaa; +} + +table.highlighttable { + margin-left: 0.5em; +} + +table.highlighttable td { + padding: 0 0.5em 0 0.5em; +} + +tt.descname { + background-color: transparent; + font-weight: bold; + font-size: 1.2em; +} + +tt.descclassname { + background-color: transparent; +} + +tt.xref, a tt { + background-color: transparent; + font-weight: bold; +} + +h1 tt, h2 tt, h3 tt, h4 tt, h5 tt, h6 tt { + background-color: transparent; +} + +.viewcode-link { + float: right; +} + +.viewcode-back { + float: right; + font-family: sans-serif; +} + +div.viewcode-block:target { + margin: -1px -10px; + padding: 0 10px; +} + +/* -- math display ---------------------------------------------------------- */ + +img.math { + vertical-align: middle; +} + +div.body div.math p { + text-align: center; +} + +span.eqno { + float: right; +} + +/* -- printout stylesheet --------------------------------------------------- */ + +@media print { + div.document, + div.documentwrapper, + div.bodywrapper { + margin: 0 !important; + width: 100%; + } + + div.sphinxsidebar, + div.related, + div.footer, + #top-link { + display: none; + } +} \ No newline at end of file diff --git a/docs/_build/html/_static/comment-bright.png b/docs/_build/html/_static/comment-bright.png new file mode 100644 index 0000000000000000000000000000000000000000..551517b8c83b76f734ff791f847829a760ad1903 GIT binary patch literal 3500 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 A2LJ#7 literal 0 HcmV?d00001 diff --git a/docs/_build/html/_static/comment.png b/docs/_build/html/_static/comment.png new file mode 100644 index 0000000000000000000000000000000000000000..92feb52b8824c6b0f59b658b1196c61de9162a95 GIT binary patch literal 3445 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 Xn!ZpXD%P(H00000NkvXXu0mjf= 0 && !jQuery(node.parentNode).hasClass(className)) { + var span = document.createElement("span"); + span.className = className; + span.appendChild(document.createTextNode(val.substr(pos, text.length))); + node.parentNode.insertBefore(span, node.parentNode.insertBefore( + document.createTextNode(val.substr(pos + text.length)), + node.nextSibling)); + node.nodeValue = val.substr(0, pos); + } + } + else if (!jQuery(node).is("button, select, textarea")) { + jQuery.each(node.childNodes, function() { + highlight(this); + }); + } + } + return this.each(function() { + highlight(this); + }); +}; + +/** + * Small JavaScript module for the documentation. + */ +var Documentation = { + + init : function() { + this.fixFirefoxAnchorBug(); + this.highlightSearchWords(); + this.initIndexTable(); + }, + + /** + * i18n support + */ + TRANSLATIONS : {}, + PLURAL_EXPR : function(n) { return n == 1 ? 0 : 1; }, + LOCALE : 'unknown', + + // gettext and ngettext don't access this so that the functions + // can safely bound to a different name (_ = Documentation.gettext) + gettext : function(string) { + var translated = Documentation.TRANSLATIONS[string]; + if (typeof translated == 'undefined') + return string; + return (typeof translated == 'string') ? translated : translated[0]; + }, + + ngettext : function(singular, plural, n) { + var translated = Documentation.TRANSLATIONS[singular]; + if (typeof translated == 'undefined') + return (n == 1) ? singular : plural; + return translated[Documentation.PLURALEXPR(n)]; + }, + + addTranslations : function(catalog) { + for (var key in catalog.messages) + this.TRANSLATIONS[key] = catalog.messages[key]; + this.PLURAL_EXPR = new Function('n', 'return +(' + catalog.plural_expr + ')'); + this.LOCALE = catalog.locale; + }, + + /** + * add context elements like header anchor links + */ + addContextElements : function() { + $('div[id] > :header:first').each(function() { + $('\u00B6'). + attr('href', '#' + this.id). + attr('title', _('Permalink to this headline')). + appendTo(this); + }); + $('dt[id]').each(function() { + $('\u00B6'). + attr('href', '#' + this.id). + attr('title', _('Permalink to this definition')). + appendTo(this); + }); + }, + + /** + * workaround a firefox stupidity + */ + fixFirefoxAnchorBug : function() { + if (document.location.hash && $.browser.mozilla) + window.setTimeout(function() { + document.location.href += ''; + }, 10); + }, + + /** + * highlight the search words provided in the url in the text + */ + highlightSearchWords : function() { + var params = $.getQueryParameters(); + var terms = (params.highlight) ? params.highlight[0].split(/\s+/) : []; + if (terms.length) { + var body = $('div.body'); + window.setTimeout(function() { + $.each(terms, function() { + body.highlightText(this.toLowerCase(), 'highlighted'); + }); + }, 10); + $('') + .appendTo($('#searchbox')); + } + }, + + /** + * init the domain index toggle buttons + */ + initIndexTable : function() { + var togglers = $('img.toggler').click(function() { + var src = $(this).attr('src'); + var idnum = $(this).attr('id').substr(7); + $('tr.cg-' + idnum).toggle(); + if (src.substr(-9) == 'minus.png') + $(this).attr('src', src.substr(0, src.length-9) + 'plus.png'); + else + $(this).attr('src', src.substr(0, src.length-8) + 'minus.png'); + }).css('display', ''); + if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) { + togglers.click(); + } + }, + + /** + * helper function to hide the search marks again + */ + hideSearchWords : function() { + $('#searchbox .highlight-link').fadeOut(300); + $('span.highlighted').removeClass('highlighted'); + }, + + /** + * make the url absolute + */ + makeURL : function(relativeURL) { + return DOCUMENTATION_OPTIONS.URL_ROOT + '/' + relativeURL; + }, + + /** + * get the current relative url + */ + getCurrentURL : function() { + var path = document.location.pathname; + var parts = path.split(/\//); + $.each(DOCUMENTATION_OPTIONS.URL_ROOT.split(/\//), function() { + if (this == '..') + parts.pop(); + }); + var url = parts.join('/'); + return path.substring(url.lastIndexOf('/') + 1, path.length - 1); + } +}; + +// quick alias for translations +_ = Documentation.gettext; + +$(document).ready(function() { + Documentation.init(); +}); diff --git a/docs/_build/html/_static/down-pressed.png b/docs/_build/html/_static/down-pressed.png new file mode 100644 index 0000000000000000000000000000000000000000..6f7ad782782e4f8e39b0c6e15c7344700cdd2527 GIT binary patch literal 368 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|*pj^6U4S$Y z{B+)352QE?JR*yM+OLB!qm#z$3ZNi+iKnkC`z>}Z23@f-Ava~9&<9T!#}JFtXD=!G zGdl{fK6ro2OGiOl+hKvH6i=D3%%Y^j`yIkRn!8O>@bG)IQR0{Kf+mxNd=_WScA8u_ z3;8(7x2){m9`nt+U(Nab&1G)!{`SPVpDX$w8McLTzAJ39wprG3p4XLq$06M`%}2Yk zRPPsbES*dnYm1wkGL;iioAUB*Or2kz6(-M_r_#Me-`{mj$Z%( literal 0 HcmV?d00001 diff --git a/docs/_build/html/_static/down.png b/docs/_build/html/_static/down.png new file mode 100644 index 0000000000000000000000000000000000000000..3003a88770de3977d47a2ba69893436a2860f9e7 GIT binary patch literal 363 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|*pj^6U4S$Y z{B+)352QE?JR*yM+OLB!qm#z$3ZNi+iKnkC`z>}xaV3tUZ$qnrLa#kt978NlpS`ru z&)HFc^}^>{UOEce+71h5nn>6&w6A!ieNbu1wh)UGh{8~et^#oZ1# z>T7oM=FZ~xXWnTo{qnXm$ZLOlqGswI_m2{XwVK)IJmBjW{J3-B3x@C=M{ShWt#fYS9M?R;8K$~YwlIqwf>VA7q=YKcwf2DS4Zj5inDKXXB1zl=(YO3ST6~rDq)&z z*o>z)=hxrfG-cDBW0G$!?6{M<$@{_4{m1o%Ub!naEtn|@^frU1tDnm{r-UW|!^@B8 literal 0 HcmV?d00001 diff --git a/docs/_build/html/_static/empty_dir b/docs/_build/html/_static/empty_dir new file mode 100644 index 0000000..e69de29 diff --git a/docs/_build/html/_static/file.png b/docs/_build/html/_static/file.png new file mode 100644 index 0000000000000000000000000000000000000000..d18082e397e7e54f20721af768c4c2983258f1b4 GIT binary patch literal 392 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`Y)RhkE)4%caKYZ?lYt_f1s;*b z3=G`DAk4@xYmNj^kiEpy*OmP$HyOL$D9)yc9|lc|nKf<9@eUiWd>3GuTC!a5vdfWYEazjncPj5ZQX%+1 zt8B*4=d)!cdDz4wr^#OMYfqGz$1LDFF>|#>*O?AGil(WEs?wLLy{Gj2J_@opDm%`dlax3yA*@*N$G&*ukFv>P8+2CBWO(qz zD0k1@kN>hhb1_6`&wrCswzINE(evt-5C1B^STi2@PmdKI;Vst0PQB6!2kdN literal 0 HcmV?d00001 diff --git a/docs/_build/html/_static/jquery.js b/docs/_build/html/_static/jquery.js new file mode 100644 index 0000000..8ccd0ea --- /dev/null +++ b/docs/_build/html/_static/jquery.js @@ -0,0 +1,9266 @@ +/*! + * jQuery JavaScript Library v1.7.1 + * http://jquery.com/ + * + * Copyright 2011, John Resig + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * Includes Sizzle.js + * http://sizzlejs.com/ + * Copyright 2011, The Dojo Foundation + * Released under the MIT, BSD, and GPL Licenses. + * + * Date: Mon Nov 21 21:11:03 2011 -0500 + */ +(function( window, undefined ) { + +// Use the correct document accordingly with window argument (sandbox) +var document = window.document, + navigator = window.navigator, + location = window.location; +var jQuery = (function() { + +// Define a local copy of jQuery +var jQuery = function( selector, context ) { + // The jQuery object is actually just the init constructor 'enhanced' + return new jQuery.fn.init( selector, context, rootjQuery ); + }, + + // Map over jQuery in case of overwrite + _jQuery = window.jQuery, + + // Map over the $ in case of overwrite + _$ = window.$, + + // A central reference to the root jQuery(document) + rootjQuery, + + // A simple way to check for HTML strings or ID strings + // Prioritize #id over to avoid XSS via location.hash (#9521) + quickExpr = /^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/, + + // Check if a string has a non-whitespace character in it + rnotwhite = /\S/, + + // Used for trimming whitespace + trimLeft = /^\s+/, + trimRight = /\s+$/, + + // Match a standalone tag + rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>)?$/, + + // JSON RegExp + rvalidchars = /^[\],:{}\s]*$/, + rvalidescape = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, + rvalidtokens = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, + rvalidbraces = /(?:^|:|,)(?:\s*\[)+/g, + + // Useragent RegExp + rwebkit = /(webkit)[ \/]([\w.]+)/, + ropera = /(opera)(?:.*version)?[ \/]([\w.]+)/, + rmsie = /(msie) ([\w.]+)/, + rmozilla = /(mozilla)(?:.*? rv:([\w.]+))?/, + + // Matches dashed string for camelizing + rdashAlpha = /-([a-z]|[0-9])/ig, + rmsPrefix = /^-ms-/, + + // Used by jQuery.camelCase as callback to replace() + fcamelCase = function( all, letter ) { + return ( letter + "" ).toUpperCase(); + }, + + // Keep a UserAgent string for use with jQuery.browser + userAgent = navigator.userAgent, + + // For matching the engine and version of the browser + browserMatch, + + // The deferred used on DOM ready + readyList, + + // The ready event handler + DOMContentLoaded, + + // Save a reference to some core methods + toString = Object.prototype.toString, + hasOwn = Object.prototype.hasOwnProperty, + push = Array.prototype.push, + slice = Array.prototype.slice, + trim = String.prototype.trim, + indexOf = Array.prototype.indexOf, + + // [[Class]] -> type pairs + class2type = {}; + +jQuery.fn = jQuery.prototype = { + constructor: jQuery, + init: function( selector, context, rootjQuery ) { + var match, elem, ret, doc; + + // Handle $(""), $(null), or $(undefined) + if ( !selector ) { + return this; + } + + // Handle $(DOMElement) + if ( selector.nodeType ) { + this.context = this[0] = selector; + this.length = 1; + return this; + } + + // The body element only exists once, optimize finding it + if ( selector === "body" && !context && document.body ) { + this.context = document; + this[0] = document.body; + this.selector = selector; + this.length = 1; + return this; + } + + // Handle HTML strings + if ( typeof selector === "string" ) { + // Are we dealing with HTML string or an ID? + if ( selector.charAt(0) === "<" && selector.charAt( selector.length - 1 ) === ">" && selector.length >= 3 ) { + // Assume that strings that start and end with <> are HTML and skip the regex check + match = [ null, selector, null ]; + + } else { + match = quickExpr.exec( selector ); + } + + // Verify a match, and that no context was specified for #id + if ( match && (match[1] || !context) ) { + + // HANDLE: $(html) -> $(array) + if ( match[1] ) { + context = context instanceof jQuery ? context[0] : context; + doc = ( context ? context.ownerDocument || context : document ); + + // If a single string is passed in and it's a single tag + // just do a createElement and skip the rest + ret = rsingleTag.exec( selector ); + + if ( ret ) { + if ( jQuery.isPlainObject( context ) ) { + selector = [ document.createElement( ret[1] ) ]; + jQuery.fn.attr.call( selector, context, true ); + + } else { + selector = [ doc.createElement( ret[1] ) ]; + } + + } else { + ret = jQuery.buildFragment( [ match[1] ], [ doc ] ); + selector = ( ret.cacheable ? jQuery.clone(ret.fragment) : ret.fragment ).childNodes; + } + + return jQuery.merge( this, selector ); + + // HANDLE: $("#id") + } else { + elem = document.getElementById( match[2] ); + + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document #6963 + if ( elem && elem.parentNode ) { + // Handle the case where IE and Opera return items + // by name instead of ID + if ( elem.id !== match[2] ) { + return rootjQuery.find( selector ); + } + + // Otherwise, we inject the element directly into the jQuery object + this.length = 1; + this[0] = elem; + } + + this.context = document; + this.selector = selector; + return this; + } + + // HANDLE: $(expr, $(...)) + } else if ( !context || context.jquery ) { + return ( context || rootjQuery ).find( selector ); + + // HANDLE: $(expr, context) + // (which is just equivalent to: $(context).find(expr) + } else { + return this.constructor( context ).find( selector ); + } + + // HANDLE: $(function) + // Shortcut for document ready + } else if ( jQuery.isFunction( selector ) ) { + return rootjQuery.ready( selector ); + } + + if ( selector.selector !== undefined ) { + this.selector = selector.selector; + this.context = selector.context; + } + + return jQuery.makeArray( selector, this ); + }, + + // Start with an empty selector + selector: "", + + // The current version of jQuery being used + jquery: "1.7.1", + + // The default length of a jQuery object is 0 + length: 0, + + // The number of elements contained in the matched element set + size: function() { + return this.length; + }, + + toArray: function() { + return slice.call( this, 0 ); + }, + + // Get the Nth element in the matched element set OR + // Get the whole matched element set as a clean array + get: function( num ) { + return num == null ? + + // Return a 'clean' array + this.toArray() : + + // Return just the object + ( num < 0 ? this[ this.length + num ] : this[ num ] ); + }, + + // Take an array of elements and push it onto the stack + // (returning the new matched element set) + pushStack: function( elems, name, selector ) { + // Build a new jQuery matched element set + var ret = this.constructor(); + + if ( jQuery.isArray( elems ) ) { + push.apply( ret, elems ); + + } else { + jQuery.merge( ret, elems ); + } + + // Add the old object onto the stack (as a reference) + ret.prevObject = this; + + ret.context = this.context; + + if ( name === "find" ) { + ret.selector = this.selector + ( this.selector ? " " : "" ) + selector; + } else if ( name ) { + ret.selector = this.selector + "." + name + "(" + selector + ")"; + } + + // Return the newly-formed element set + return ret; + }, + + // Execute a callback for every element in the matched set. + // (You can seed the arguments with an array of args, but this is + // only used internally.) + each: function( callback, args ) { + return jQuery.each( this, callback, args ); + }, + + ready: function( fn ) { + // Attach the listeners + jQuery.bindReady(); + + // Add the callback + readyList.add( fn ); + + return this; + }, + + eq: function( i ) { + i = +i; + return i === -1 ? + this.slice( i ) : + this.slice( i, i + 1 ); + }, + + first: function() { + return this.eq( 0 ); + }, + + last: function() { + return this.eq( -1 ); + }, + + slice: function() { + return this.pushStack( slice.apply( this, arguments ), + "slice", slice.call(arguments).join(",") ); + }, + + map: function( callback ) { + return this.pushStack( jQuery.map(this, function( elem, i ) { + return callback.call( elem, i, elem ); + })); + }, + + end: function() { + return this.prevObject || this.constructor(null); + }, + + // For internal use only. + // Behaves like an Array's method, not like a jQuery method. + push: push, + sort: [].sort, + splice: [].splice +}; + +// Give the init function the jQuery prototype for later instantiation +jQuery.fn.init.prototype = jQuery.fn; + +jQuery.extend = jQuery.fn.extend = function() { + var options, name, src, copy, copyIsArray, clone, + target = arguments[0] || {}, + i = 1, + length = arguments.length, + deep = false; + + // Handle a deep copy situation + if ( typeof target === "boolean" ) { + deep = target; + target = arguments[1] || {}; + // skip the boolean and the target + i = 2; + } + + // Handle case when target is a string or something (possible in deep copy) + if ( typeof target !== "object" && !jQuery.isFunction(target) ) { + target = {}; + } + + // extend jQuery itself if only one argument is passed + if ( length === i ) { + target = this; + --i; + } + + for ( ; i < length; i++ ) { + // Only deal with non-null/undefined values + if ( (options = arguments[ i ]) != null ) { + // Extend the base object + for ( name in options ) { + src = target[ name ]; + copy = options[ name ]; + + // Prevent never-ending loop + if ( target === copy ) { + continue; + } + + // Recurse if we're merging plain objects or arrays + if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) { + if ( copyIsArray ) { + copyIsArray = false; + clone = src && jQuery.isArray(src) ? src : []; + + } else { + clone = src && jQuery.isPlainObject(src) ? src : {}; + } + + // Never move original objects, clone them + target[ name ] = jQuery.extend( deep, clone, copy ); + + // Don't bring in undefined values + } else if ( copy !== undefined ) { + target[ name ] = copy; + } + } + } + } + + // Return the modified object + return target; +}; + +jQuery.extend({ + noConflict: function( deep ) { + if ( window.$ === jQuery ) { + window.$ = _$; + } + + if ( deep && window.jQuery === jQuery ) { + window.jQuery = _jQuery; + } + + return jQuery; + }, + + // Is the DOM ready to be used? Set to true once it occurs. + isReady: false, + + // A counter to track how many items to wait for before + // the ready event fires. See #6781 + readyWait: 1, + + // Hold (or release) the ready event + holdReady: function( hold ) { + if ( hold ) { + jQuery.readyWait++; + } else { + jQuery.ready( true ); + } + }, + + // Handle when the DOM is ready + ready: function( wait ) { + // Either a released hold or an DOMready/load event and not yet ready + if ( (wait === true && !--jQuery.readyWait) || (wait !== true && !jQuery.isReady) ) { + // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). + if ( !document.body ) { + return setTimeout( jQuery.ready, 1 ); + } + + // Remember that the DOM is ready + jQuery.isReady = true; + + // If a normal DOM Ready event fired, decrement, and wait if need be + if ( wait !== true && --jQuery.readyWait > 0 ) { + return; + } + + // If there are functions bound, to execute + readyList.fireWith( document, [ jQuery ] ); + + // Trigger any bound ready events + if ( jQuery.fn.trigger ) { + jQuery( document ).trigger( "ready" ).off( "ready" ); + } + } + }, + + bindReady: function() { + if ( readyList ) { + return; + } + + readyList = jQuery.Callbacks( "once memory" ); + + // Catch cases where $(document).ready() is called after the + // browser event has already occurred. + if ( document.readyState === "complete" ) { + // Handle it asynchronously to allow scripts the opportunity to delay ready + return setTimeout( jQuery.ready, 1 ); + } + + // Mozilla, Opera and webkit nightlies currently support this event + if ( document.addEventListener ) { + // Use the handy event callback + document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false ); + + // A fallback to window.onload, that will always work + window.addEventListener( "load", jQuery.ready, false ); + + // If IE event model is used + } else if ( document.attachEvent ) { + // ensure firing before onload, + // maybe late but safe also for iframes + document.attachEvent( "onreadystatechange", DOMContentLoaded ); + + // A fallback to window.onload, that will always work + window.attachEvent( "onload", jQuery.ready ); + + // If IE and not a frame + // continually check to see if the document is ready + var toplevel = false; + + try { + toplevel = window.frameElement == null; + } catch(e) {} + + if ( document.documentElement.doScroll && toplevel ) { + doScrollCheck(); + } + } + }, + + // See test/unit/core.js for details concerning isFunction. + // Since version 1.3, DOM methods and functions like alert + // aren't supported. They return false on IE (#2968). + isFunction: function( obj ) { + return jQuery.type(obj) === "function"; + }, + + isArray: Array.isArray || function( obj ) { + return jQuery.type(obj) === "array"; + }, + + // A crude way of determining if an object is a window + isWindow: function( obj ) { + return obj && typeof obj === "object" && "setInterval" in obj; + }, + + isNumeric: function( obj ) { + return !isNaN( parseFloat(obj) ) && isFinite( obj ); + }, + + type: function( obj ) { + return obj == null ? + String( obj ) : + class2type[ toString.call(obj) ] || "object"; + }, + + isPlainObject: function( obj ) { + // Must be an Object. + // Because of IE, we also have to check the presence of the constructor property. + // Make sure that DOM nodes and window objects don't pass through, as well + if ( !obj || jQuery.type(obj) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) { + return false; + } + + try { + // Not own constructor property must be Object + if ( obj.constructor && + !hasOwn.call(obj, "constructor") && + !hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) { + return false; + } + } catch ( e ) { + // IE8,9 Will throw exceptions on certain host objects #9897 + return false; + } + + // Own properties are enumerated firstly, so to speed up, + // if last one is own, then all properties are own. + + var key; + for ( key in obj ) {} + + return key === undefined || hasOwn.call( obj, key ); + }, + + isEmptyObject: function( obj ) { + for ( var name in obj ) { + return false; + } + return true; + }, + + error: function( msg ) { + throw new Error( msg ); + }, + + parseJSON: function( data ) { + if ( typeof data !== "string" || !data ) { + return null; + } + + // Make sure leading/trailing whitespace is removed (IE can't handle it) + data = jQuery.trim( data ); + + // Attempt to parse using the native JSON parser first + if ( window.JSON && window.JSON.parse ) { + return window.JSON.parse( data ); + } + + // Make sure the incoming data is actual JSON + // Logic borrowed from http://json.org/json2.js + if ( rvalidchars.test( data.replace( rvalidescape, "@" ) + .replace( rvalidtokens, "]" ) + .replace( rvalidbraces, "")) ) { + + return ( new Function( "return " + data ) )(); + + } + jQuery.error( "Invalid JSON: " + data ); + }, + + // Cross-browser xml parsing + parseXML: function( data ) { + var xml, tmp; + try { + if ( window.DOMParser ) { // Standard + tmp = new DOMParser(); + xml = tmp.parseFromString( data , "text/xml" ); + } else { // IE + xml = new ActiveXObject( "Microsoft.XMLDOM" ); + xml.async = "false"; + xml.loadXML( data ); + } + } catch( e ) { + xml = undefined; + } + if ( !xml || !xml.documentElement || xml.getElementsByTagName( "parsererror" ).length ) { + jQuery.error( "Invalid XML: " + data ); + } + return xml; + }, + + noop: function() {}, + + // Evaluates a script in a global context + // Workarounds based on findings by Jim Driscoll + // http://weblogs.java.net/blog/driscoll/archive/2009/09/08/eval-javascript-global-context + globalEval: function( data ) { + if ( data && rnotwhite.test( data ) ) { + // We use execScript on Internet Explorer + // We use an anonymous function so that context is window + // rather than jQuery in Firefox + ( window.execScript || function( data ) { + window[ "eval" ].call( window, data ); + } )( data ); + } + }, + + // Convert dashed to camelCase; used by the css and data modules + // Microsoft forgot to hump their vendor prefix (#9572) + camelCase: function( string ) { + return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); + }, + + nodeName: function( elem, name ) { + return elem.nodeName && elem.nodeName.toUpperCase() === name.toUpperCase(); + }, + + // args is for internal usage only + each: function( object, callback, args ) { + var name, i = 0, + length = object.length, + isObj = length === undefined || jQuery.isFunction( object ); + + if ( args ) { + if ( isObj ) { + for ( name in object ) { + if ( callback.apply( object[ name ], args ) === false ) { + break; + } + } + } else { + for ( ; i < length; ) { + if ( callback.apply( object[ i++ ], args ) === false ) { + break; + } + } + } + + // A special, fast, case for the most common use of each + } else { + if ( isObj ) { + for ( name in object ) { + if ( callback.call( object[ name ], name, object[ name ] ) === false ) { + break; + } + } + } else { + for ( ; i < length; ) { + if ( callback.call( object[ i ], i, object[ i++ ] ) === false ) { + break; + } + } + } + } + + return object; + }, + + // Use native String.trim function wherever possible + trim: trim ? + function( text ) { + return text == null ? + "" : + trim.call( text ); + } : + + // Otherwise use our own trimming functionality + function( text ) { + return text == null ? + "" : + text.toString().replace( trimLeft, "" ).replace( trimRight, "" ); + }, + + // results is for internal usage only + makeArray: function( array, results ) { + var ret = results || []; + + if ( array != null ) { + // The window, strings (and functions) also have 'length' + // Tweaked logic slightly to handle Blackberry 4.7 RegExp issues #6930 + var type = jQuery.type( array ); + + if ( array.length == null || type === "string" || type === "function" || type === "regexp" || jQuery.isWindow( array ) ) { + push.call( ret, array ); + } else { + jQuery.merge( ret, array ); + } + } + + return ret; + }, + + inArray: function( elem, array, i ) { + var len; + + if ( array ) { + if ( indexOf ) { + return indexOf.call( array, elem, i ); + } + + len = array.length; + i = i ? i < 0 ? Math.max( 0, len + i ) : i : 0; + + for ( ; i < len; i++ ) { + // Skip accessing in sparse arrays + if ( i in array && array[ i ] === elem ) { + return i; + } + } + } + + return -1; + }, + + merge: function( first, second ) { + var i = first.length, + j = 0; + + if ( typeof second.length === "number" ) { + for ( var l = second.length; j < l; j++ ) { + first[ i++ ] = second[ j ]; + } + + } else { + while ( second[j] !== undefined ) { + first[ i++ ] = second[ j++ ]; + } + } + + first.length = i; + + return first; + }, + + grep: function( elems, callback, inv ) { + var ret = [], retVal; + inv = !!inv; + + // Go through the array, only saving the items + // that pass the validator function + for ( var i = 0, length = elems.length; i < length; i++ ) { + retVal = !!callback( elems[ i ], i ); + if ( inv !== retVal ) { + ret.push( elems[ i ] ); + } + } + + return ret; + }, + + // arg is for internal usage only + map: function( elems, callback, arg ) { + var value, key, ret = [], + i = 0, + length = elems.length, + // jquery objects are treated as arrays + isArray = elems instanceof jQuery || length !== undefined && typeof length === "number" && ( ( length > 0 && elems[ 0 ] && elems[ length -1 ] ) || length === 0 || jQuery.isArray( elems ) ) ; + + // Go through the array, translating each of the items to their + if ( isArray ) { + for ( ; i < length; i++ ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret[ ret.length ] = value; + } + } + + // Go through every key on the object, + } else { + for ( key in elems ) { + value = callback( elems[ key ], key, arg ); + + if ( value != null ) { + ret[ ret.length ] = value; + } + } + } + + // Flatten any nested arrays + return ret.concat.apply( [], ret ); + }, + + // A global GUID counter for objects + guid: 1, + + // Bind a function to a context, optionally partially applying any + // arguments. + proxy: function( fn, context ) { + if ( typeof context === "string" ) { + var tmp = fn[ context ]; + context = fn; + fn = tmp; + } + + // Quick check to determine if target is callable, in the spec + // this throws a TypeError, but we will just return undefined. + if ( !jQuery.isFunction( fn ) ) { + return undefined; + } + + // Simulated bind + var args = slice.call( arguments, 2 ), + proxy = function() { + return fn.apply( context, args.concat( slice.call( arguments ) ) ); + }; + + // Set the guid of unique handler to the same of original handler, so it can be removed + proxy.guid = fn.guid = fn.guid || proxy.guid || jQuery.guid++; + + return proxy; + }, + + // Mutifunctional method to get and set values to a collection + // The value/s can optionally be executed if it's a function + access: function( elems, key, value, exec, fn, pass ) { + var length = elems.length; + + // Setting many attributes + if ( typeof key === "object" ) { + for ( var k in key ) { + jQuery.access( elems, k, key[k], exec, fn, value ); + } + return elems; + } + + // Setting one attribute + if ( value !== undefined ) { + // Optionally, function values get executed if exec is true + exec = !pass && exec && jQuery.isFunction(value); + + for ( var i = 0; i < length; i++ ) { + fn( elems[i], key, exec ? value.call( elems[i], i, fn( elems[i], key ) ) : value, pass ); + } + + return elems; + } + + // Getting an attribute + return length ? fn( elems[0], key ) : undefined; + }, + + now: function() { + return ( new Date() ).getTime(); + }, + + // Use of jQuery.browser is frowned upon. + // More details: http://docs.jquery.com/Utilities/jQuery.browser + uaMatch: function( ua ) { + ua = ua.toLowerCase(); + + var match = rwebkit.exec( ua ) || + ropera.exec( ua ) || + rmsie.exec( ua ) || + ua.indexOf("compatible") < 0 && rmozilla.exec( ua ) || + []; + + return { browser: match[1] || "", version: match[2] || "0" }; + }, + + sub: function() { + function jQuerySub( selector, context ) { + return new jQuerySub.fn.init( selector, context ); + } + jQuery.extend( true, jQuerySub, this ); + jQuerySub.superclass = this; + jQuerySub.fn = jQuerySub.prototype = this(); + jQuerySub.fn.constructor = jQuerySub; + jQuerySub.sub = this.sub; + jQuerySub.fn.init = function init( selector, context ) { + if ( context && context instanceof jQuery && !(context instanceof jQuerySub) ) { + context = jQuerySub( context ); + } + + return jQuery.fn.init.call( this, selector, context, rootjQuerySub ); + }; + jQuerySub.fn.init.prototype = jQuerySub.fn; + var rootjQuerySub = jQuerySub(document); + return jQuerySub; + }, + + browser: {} +}); + +// Populate the class2type map +jQuery.each("Boolean Number String Function Array Date RegExp Object".split(" "), function(i, name) { + class2type[ "[object " + name + "]" ] = name.toLowerCase(); +}); + +browserMatch = jQuery.uaMatch( userAgent ); +if ( browserMatch.browser ) { + jQuery.browser[ browserMatch.browser ] = true; + jQuery.browser.version = browserMatch.version; +} + +// Deprecated, use jQuery.browser.webkit instead +if ( jQuery.browser.webkit ) { + jQuery.browser.safari = true; +} + +// IE doesn't match non-breaking spaces with \s +if ( rnotwhite.test( "\xA0" ) ) { + trimLeft = /^[\s\xA0]+/; + trimRight = /[\s\xA0]+$/; +} + +// All jQuery objects should point back to these +rootjQuery = jQuery(document); + +// Cleanup functions for the document ready method +if ( document.addEventListener ) { + DOMContentLoaded = function() { + document.removeEventListener( "DOMContentLoaded", DOMContentLoaded, false ); + jQuery.ready(); + }; + +} else if ( document.attachEvent ) { + DOMContentLoaded = function() { + // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). + if ( document.readyState === "complete" ) { + document.detachEvent( "onreadystatechange", DOMContentLoaded ); + jQuery.ready(); + } + }; +} + +// The DOM ready check for Internet Explorer +function doScrollCheck() { + if ( jQuery.isReady ) { + return; + } + + try { + // If IE is used, use the trick by Diego Perini + // http://javascript.nwbox.com/IEContentLoaded/ + document.documentElement.doScroll("left"); + } catch(e) { + setTimeout( doScrollCheck, 1 ); + return; + } + + // and execute any waiting functions + jQuery.ready(); +} + +return jQuery; + +})(); + + +// String to Object flags format cache +var flagsCache = {}; + +// Convert String-formatted flags into Object-formatted ones and store in cache +function createFlags( flags ) { + var object = flagsCache[ flags ] = {}, + i, length; + flags = flags.split( /\s+/ ); + for ( i = 0, length = flags.length; i < length; i++ ) { + object[ flags[i] ] = true; + } + return object; +} + +/* + * Create a callback list using the following parameters: + * + * flags: an optional list of space-separated flags that will change how + * the callback list behaves + * + * By default a callback list will act like an event callback list and can be + * "fired" multiple times. + * + * Possible flags: + * + * once: will ensure the callback list can only be fired once (like a Deferred) + * + * memory: will keep track of previous values and will call any callback added + * after the list has been fired right away with the latest "memorized" + * values (like a Deferred) + * + * unique: will ensure a callback can only be added once (no duplicate in the list) + * + * stopOnFalse: interrupt callings when a callback returns false + * + */ +jQuery.Callbacks = function( flags ) { + + // Convert flags from String-formatted to Object-formatted + // (we check in cache first) + flags = flags ? ( flagsCache[ flags ] || createFlags( flags ) ) : {}; + + var // Actual callback list + list = [], + // Stack of fire calls for repeatable lists + stack = [], + // Last fire value (for non-forgettable lists) + memory, + // Flag to know if list is currently firing + firing, + // First callback to fire (used internally by add and fireWith) + firingStart, + // End of the loop when firing + firingLength, + // Index of currently firing callback (modified by remove if needed) + firingIndex, + // Add one or several callbacks to the list + add = function( args ) { + var i, + length, + elem, + type, + actual; + for ( i = 0, length = args.length; i < length; i++ ) { + elem = args[ i ]; + type = jQuery.type( elem ); + if ( type === "array" ) { + // Inspect recursively + add( elem ); + } else if ( type === "function" ) { + // Add if not in unique mode and callback is not in + if ( !flags.unique || !self.has( elem ) ) { + list.push( elem ); + } + } + } + }, + // Fire callbacks + fire = function( context, args ) { + args = args || []; + memory = !flags.memory || [ context, args ]; + firing = true; + firingIndex = firingStart || 0; + firingStart = 0; + firingLength = list.length; + for ( ; list && firingIndex < firingLength; firingIndex++ ) { + if ( list[ firingIndex ].apply( context, args ) === false && flags.stopOnFalse ) { + memory = true; // Mark as halted + break; + } + } + firing = false; + if ( list ) { + if ( !flags.once ) { + if ( stack && stack.length ) { + memory = stack.shift(); + self.fireWith( memory[ 0 ], memory[ 1 ] ); + } + } else if ( memory === true ) { + self.disable(); + } else { + list = []; + } + } + }, + // Actual Callbacks object + self = { + // Add a callback or a collection of callbacks to the list + add: function() { + if ( list ) { + var length = list.length; + add( arguments ); + // Do we need to add the callbacks to the + // current firing batch? + if ( firing ) { + firingLength = list.length; + // With memory, if we're not firing then + // we should call right away, unless previous + // firing was halted (stopOnFalse) + } else if ( memory && memory !== true ) { + firingStart = length; + fire( memory[ 0 ], memory[ 1 ] ); + } + } + return this; + }, + // Remove a callback from the list + remove: function() { + if ( list ) { + var args = arguments, + argIndex = 0, + argLength = args.length; + for ( ; argIndex < argLength ; argIndex++ ) { + for ( var i = 0; i < list.length; i++ ) { + if ( args[ argIndex ] === list[ i ] ) { + // Handle firingIndex and firingLength + if ( firing ) { + if ( i <= firingLength ) { + firingLength--; + if ( i <= firingIndex ) { + firingIndex--; + } + } + } + // Remove the element + list.splice( i--, 1 ); + // If we have some unicity property then + // we only need to do this once + if ( flags.unique ) { + break; + } + } + } + } + } + return this; + }, + // Control if a given callback is in the list + has: function( fn ) { + if ( list ) { + var i = 0, + length = list.length; + for ( ; i < length; i++ ) { + if ( fn === list[ i ] ) { + return true; + } + } + } + return false; + }, + // Remove all callbacks from the list + empty: function() { + list = []; + return this; + }, + // Have the list do nothing anymore + disable: function() { + list = stack = memory = undefined; + return this; + }, + // Is it disabled? + disabled: function() { + return !list; + }, + // Lock the list in its current state + lock: function() { + stack = undefined; + if ( !memory || memory === true ) { + self.disable(); + } + return this; + }, + // Is it locked? + locked: function() { + return !stack; + }, + // Call all callbacks with the given context and arguments + fireWith: function( context, args ) { + if ( stack ) { + if ( firing ) { + if ( !flags.once ) { + stack.push( [ context, args ] ); + } + } else if ( !( flags.once && memory ) ) { + fire( context, args ); + } + } + return this; + }, + // Call all the callbacks with the given arguments + fire: function() { + self.fireWith( this, arguments ); + return this; + }, + // To know if the callbacks have already been called at least once + fired: function() { + return !!memory; + } + }; + + return self; +}; + + + + +var // Static reference to slice + sliceDeferred = [].slice; + +jQuery.extend({ + + Deferred: function( func ) { + var doneList = jQuery.Callbacks( "once memory" ), + failList = jQuery.Callbacks( "once memory" ), + progressList = jQuery.Callbacks( "memory" ), + state = "pending", + lists = { + resolve: doneList, + reject: failList, + notify: progressList + }, + promise = { + done: doneList.add, + fail: failList.add, + progress: progressList.add, + + state: function() { + return state; + }, + + // Deprecated + isResolved: doneList.fired, + isRejected: failList.fired, + + then: function( doneCallbacks, failCallbacks, progressCallbacks ) { + deferred.done( doneCallbacks ).fail( failCallbacks ).progress( progressCallbacks ); + return this; + }, + always: function() { + deferred.done.apply( deferred, arguments ).fail.apply( deferred, arguments ); + return this; + }, + pipe: function( fnDone, fnFail, fnProgress ) { + return jQuery.Deferred(function( newDefer ) { + jQuery.each( { + done: [ fnDone, "resolve" ], + fail: [ fnFail, "reject" ], + progress: [ fnProgress, "notify" ] + }, function( handler, data ) { + var fn = data[ 0 ], + action = data[ 1 ], + returned; + if ( jQuery.isFunction( fn ) ) { + deferred[ handler ](function() { + returned = fn.apply( this, arguments ); + if ( returned && jQuery.isFunction( returned.promise ) ) { + returned.promise().then( newDefer.resolve, newDefer.reject, newDefer.notify ); + } else { + newDefer[ action + "With" ]( this === deferred ? newDefer : this, [ returned ] ); + } + }); + } else { + deferred[ handler ]( newDefer[ action ] ); + } + }); + }).promise(); + }, + // Get a promise for this deferred + // If obj is provided, the promise aspect is added to the object + promise: function( obj ) { + if ( obj == null ) { + obj = promise; + } else { + for ( var key in promise ) { + obj[ key ] = promise[ key ]; + } + } + return obj; + } + }, + deferred = promise.promise({}), + key; + + for ( key in lists ) { + deferred[ key ] = lists[ key ].fire; + deferred[ key + "With" ] = lists[ key ].fireWith; + } + + // Handle state + deferred.done( function() { + state = "resolved"; + }, failList.disable, progressList.lock ).fail( function() { + state = "rejected"; + }, doneList.disable, progressList.lock ); + + // Call given func if any + if ( func ) { + func.call( deferred, deferred ); + } + + // All done! + return deferred; + }, + + // Deferred helper + when: function( firstParam ) { + var args = sliceDeferred.call( arguments, 0 ), + i = 0, + length = args.length, + pValues = new Array( length ), + count = length, + pCount = length, + deferred = length <= 1 && firstParam && jQuery.isFunction( firstParam.promise ) ? + firstParam : + jQuery.Deferred(), + promise = deferred.promise(); + function resolveFunc( i ) { + return function( value ) { + args[ i ] = arguments.length > 1 ? sliceDeferred.call( arguments, 0 ) : value; + if ( !( --count ) ) { + deferred.resolveWith( deferred, args ); + } + }; + } + function progressFunc( i ) { + return function( value ) { + pValues[ i ] = arguments.length > 1 ? sliceDeferred.call( arguments, 0 ) : value; + deferred.notifyWith( promise, pValues ); + }; + } + if ( length > 1 ) { + for ( ; i < length; i++ ) { + if ( args[ i ] && args[ i ].promise && jQuery.isFunction( args[ i ].promise ) ) { + args[ i ].promise().then( resolveFunc(i), deferred.reject, progressFunc(i) ); + } else { + --count; + } + } + if ( !count ) { + deferred.resolveWith( deferred, args ); + } + } else if ( deferred !== firstParam ) { + deferred.resolveWith( deferred, length ? [ firstParam ] : [] ); + } + return promise; + } +}); + + + + +jQuery.support = (function() { + + var support, + all, + a, + select, + opt, + input, + marginDiv, + fragment, + tds, + events, + eventName, + i, + isSupported, + div = document.createElement( "div" ), + documentElement = document.documentElement; + + // Preliminary tests + div.setAttribute("className", "t"); + div.innerHTML = "
a"; + + all = div.getElementsByTagName( "*" ); + a = div.getElementsByTagName( "a" )[ 0 ]; + + // Can't get basic test support + if ( !all || !all.length || !a ) { + return {}; + } + + // First batch of supports tests + select = document.createElement( "select" ); + opt = select.appendChild( document.createElement("option") ); + input = div.getElementsByTagName( "input" )[ 0 ]; + + support = { + // IE strips leading whitespace when .innerHTML is used + leadingWhitespace: ( div.firstChild.nodeType === 3 ), + + // Make sure that tbody elements aren't automatically inserted + // IE will insert them into empty tables + tbody: !div.getElementsByTagName("tbody").length, + + // Make sure that link elements get serialized correctly by innerHTML + // This requires a wrapper element in IE + htmlSerialize: !!div.getElementsByTagName("link").length, + + // Get the style information from getAttribute + // (IE uses .cssText instead) + style: /top/.test( a.getAttribute("style") ), + + // Make sure that URLs aren't manipulated + // (IE normalizes it by default) + hrefNormalized: ( a.getAttribute("href") === "/a" ), + + // Make sure that element opacity exists + // (IE uses filter instead) + // Use a regex to work around a WebKit issue. See #5145 + opacity: /^0.55/.test( a.style.opacity ), + + // Verify style float existence + // (IE uses styleFloat instead of cssFloat) + cssFloat: !!a.style.cssFloat, + + // Make sure that if no value is specified for a checkbox + // that it defaults to "on". + // (WebKit defaults to "" instead) + checkOn: ( input.value === "on" ), + + // Make sure that a selected-by-default option has a working selected property. + // (WebKit defaults to false instead of true, IE too, if it's in an optgroup) + optSelected: opt.selected, + + // Test setAttribute on camelCase class. If it works, we need attrFixes when doing get/setAttribute (ie6/7) + getSetAttribute: div.className !== "t", + + // Tests for enctype support on a form(#6743) + enctype: !!document.createElement("form").enctype, + + // Makes sure cloning an html5 element does not cause problems + // Where outerHTML is undefined, this still works + html5Clone: document.createElement("nav").cloneNode( true ).outerHTML !== "<:nav>", + + // Will be defined later + submitBubbles: true, + changeBubbles: true, + focusinBubbles: false, + deleteExpando: true, + noCloneEvent: true, + inlineBlockNeedsLayout: false, + shrinkWrapBlocks: false, + reliableMarginRight: true + }; + + // Make sure checked status is properly cloned + input.checked = true; + support.noCloneChecked = input.cloneNode( true ).checked; + + // Make sure that the options inside disabled selects aren't marked as disabled + // (WebKit marks them as disabled) + select.disabled = true; + support.optDisabled = !opt.disabled; + + // Test to see if it's possible to delete an expando from an element + // Fails in Internet Explorer + try { + delete div.test; + } catch( e ) { + support.deleteExpando = false; + } + + if ( !div.addEventListener && div.attachEvent && div.fireEvent ) { + div.attachEvent( "onclick", function() { + // Cloning a node shouldn't copy over any + // bound event handlers (IE does this) + support.noCloneEvent = false; + }); + div.cloneNode( true ).fireEvent( "onclick" ); + } + + // Check if a radio maintains its value + // after being appended to the DOM + input = document.createElement("input"); + input.value = "t"; + input.setAttribute("type", "radio"); + support.radioValue = input.value === "t"; + + input.setAttribute("checked", "checked"); + div.appendChild( input ); + fragment = document.createDocumentFragment(); + fragment.appendChild( div.lastChild ); + + // WebKit doesn't clone checked state correctly in fragments + support.checkClone = fragment.cloneNode( true ).cloneNode( true ).lastChild.checked; + + // Check if a disconnected checkbox will retain its checked + // value of true after appended to the DOM (IE6/7) + support.appendChecked = input.checked; + + fragment.removeChild( input ); + fragment.appendChild( div ); + + div.innerHTML = ""; + + // Check if div with explicit width and no margin-right incorrectly + // gets computed margin-right based on width of container. For more + // info see bug #3333 + // Fails in WebKit before Feb 2011 nightlies + // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right + if ( window.getComputedStyle ) { + marginDiv = document.createElement( "div" ); + marginDiv.style.width = "0"; + marginDiv.style.marginRight = "0"; + div.style.width = "2px"; + div.appendChild( marginDiv ); + support.reliableMarginRight = + ( parseInt( ( window.getComputedStyle( marginDiv, null ) || { marginRight: 0 } ).marginRight, 10 ) || 0 ) === 0; + } + + // Technique from Juriy Zaytsev + // http://perfectionkills.com/detecting-event-support-without-browser-sniffing/ + // We only care about the case where non-standard event systems + // are used, namely in IE. Short-circuiting here helps us to + // avoid an eval call (in setAttribute) which can cause CSP + // to go haywire. See: https://developer.mozilla.org/en/Security/CSP + if ( div.attachEvent ) { + for( i in { + submit: 1, + change: 1, + focusin: 1 + }) { + eventName = "on" + i; + isSupported = ( eventName in div ); + if ( !isSupported ) { + div.setAttribute( eventName, "return;" ); + isSupported = ( typeof div[ eventName ] === "function" ); + } + support[ i + "Bubbles" ] = isSupported; + } + } + + fragment.removeChild( div ); + + // Null elements to avoid leaks in IE + fragment = select = opt = marginDiv = div = input = null; + + // Run tests that need a body at doc ready + jQuery(function() { + var container, outer, inner, table, td, offsetSupport, + conMarginTop, ptlm, vb, style, html, + body = document.getElementsByTagName("body")[0]; + + if ( !body ) { + // Return for frameset docs that don't have a body + return; + } + + conMarginTop = 1; + ptlm = "position:absolute;top:0;left:0;width:1px;height:1px;margin:0;"; + vb = "visibility:hidden;border:0;"; + style = "style='" + ptlm + "border:5px solid #000;padding:0;'"; + html = "
" + + "" + + "
"; + + container = document.createElement("div"); + container.style.cssText = vb + "width:0;height:0;position:static;top:0;margin-top:" + conMarginTop + "px"; + body.insertBefore( container, body.firstChild ); + + // Construct the test element + div = document.createElement("div"); + container.appendChild( div ); + + // Check if table cells still have offsetWidth/Height when they are set + // to display:none and there are still other visible table cells in a + // table row; if so, offsetWidth/Height are not reliable for use when + // determining if an element has been hidden directly using + // display:none (it is still safe to use offsets if a parent element is + // hidden; don safety goggles and see bug #4512 for more information). + // (only IE 8 fails this test) + div.innerHTML = "
t
"; + tds = div.getElementsByTagName( "td" ); + isSupported = ( tds[ 0 ].offsetHeight === 0 ); + + tds[ 0 ].style.display = ""; + tds[ 1 ].style.display = "none"; + + // Check if empty table cells still have offsetWidth/Height + // (IE <= 8 fail this test) + support.reliableHiddenOffsets = isSupported && ( tds[ 0 ].offsetHeight === 0 ); + + // Figure out if the W3C box model works as expected + div.innerHTML = ""; + div.style.width = div.style.paddingLeft = "1px"; + jQuery.boxModel = support.boxModel = div.offsetWidth === 2; + + if ( typeof div.style.zoom !== "undefined" ) { + // Check if natively block-level elements act like inline-block + // elements when setting their display to 'inline' and giving + // them layout + // (IE < 8 does this) + div.style.display = "inline"; + div.style.zoom = 1; + support.inlineBlockNeedsLayout = ( div.offsetWidth === 2 ); + + // Check if elements with layout shrink-wrap their children + // (IE 6 does this) + div.style.display = ""; + div.innerHTML = "
"; + support.shrinkWrapBlocks = ( div.offsetWidth !== 2 ); + } + + div.style.cssText = ptlm + vb; + div.innerHTML = html; + + outer = div.firstChild; + inner = outer.firstChild; + td = outer.nextSibling.firstChild.firstChild; + + offsetSupport = { + doesNotAddBorder: ( inner.offsetTop !== 5 ), + doesAddBorderForTableAndCells: ( td.offsetTop === 5 ) + }; + + inner.style.position = "fixed"; + inner.style.top = "20px"; + + // safari subtracts parent border width here which is 5px + offsetSupport.fixedPosition = ( inner.offsetTop === 20 || inner.offsetTop === 15 ); + inner.style.position = inner.style.top = ""; + + outer.style.overflow = "hidden"; + outer.style.position = "relative"; + + offsetSupport.subtractsBorderForOverflowNotVisible = ( inner.offsetTop === -5 ); + offsetSupport.doesNotIncludeMarginInBodyOffset = ( body.offsetTop !== conMarginTop ); + + body.removeChild( container ); + div = container = null; + + jQuery.extend( support, offsetSupport ); + }); + + return support; +})(); + + + + +var rbrace = /^(?:\{.*\}|\[.*\])$/, + rmultiDash = /([A-Z])/g; + +jQuery.extend({ + cache: {}, + + // Please use with caution + uuid: 0, + + // Unique for each copy of jQuery on the page + // Non-digits removed to match rinlinejQuery + expando: "jQuery" + ( jQuery.fn.jquery + Math.random() ).replace( /\D/g, "" ), + + // The following elements throw uncatchable exceptions if you + // attempt to add expando properties to them. + noData: { + "embed": true, + // Ban all objects except for Flash (which handle expandos) + "object": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000", + "applet": true + }, + + hasData: function( elem ) { + elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ]; + return !!elem && !isEmptyDataObject( elem ); + }, + + data: function( elem, name, data, pvt /* Internal Use Only */ ) { + if ( !jQuery.acceptData( elem ) ) { + return; + } + + var privateCache, thisCache, ret, + internalKey = jQuery.expando, + getByName = typeof name === "string", + + // We have to handle DOM nodes and JS objects differently because IE6-7 + // can't GC object references properly across the DOM-JS boundary + isNode = elem.nodeType, + + // Only DOM nodes need the global jQuery cache; JS object data is + // attached directly to the object so GC can occur automatically + cache = isNode ? jQuery.cache : elem, + + // Only defining an ID for JS objects if its cache already exists allows + // the code to shortcut on the same path as a DOM node with no cache + id = isNode ? elem[ internalKey ] : elem[ internalKey ] && internalKey, + isEvents = name === "events"; + + // Avoid doing any more work than we need to when trying to get data on an + // object that has no data at all + if ( (!id || !cache[id] || (!isEvents && !pvt && !cache[id].data)) && getByName && data === undefined ) { + return; + } + + if ( !id ) { + // Only DOM nodes need a new unique ID for each element since their data + // ends up in the global cache + if ( isNode ) { + elem[ internalKey ] = id = ++jQuery.uuid; + } else { + id = internalKey; + } + } + + if ( !cache[ id ] ) { + cache[ id ] = {}; + + // Avoids exposing jQuery metadata on plain JS objects when the object + // is serialized using JSON.stringify + if ( !isNode ) { + cache[ id ].toJSON = jQuery.noop; + } + } + + // An object can be passed to jQuery.data instead of a key/value pair; this gets + // shallow copied over onto the existing cache + if ( typeof name === "object" || typeof name === "function" ) { + if ( pvt ) { + cache[ id ] = jQuery.extend( cache[ id ], name ); + } else { + cache[ id ].data = jQuery.extend( cache[ id ].data, name ); + } + } + + privateCache = thisCache = cache[ id ]; + + // jQuery data() is stored in a separate object inside the object's internal data + // cache in order to avoid key collisions between internal data and user-defined + // data. + if ( !pvt ) { + if ( !thisCache.data ) { + thisCache.data = {}; + } + + thisCache = thisCache.data; + } + + if ( data !== undefined ) { + thisCache[ jQuery.camelCase( name ) ] = data; + } + + // Users should not attempt to inspect the internal events object using jQuery.data, + // it is undocumented and subject to change. But does anyone listen? No. + if ( isEvents && !thisCache[ name ] ) { + return privateCache.events; + } + + // Check for both converted-to-camel and non-converted data property names + // If a data property was specified + if ( getByName ) { + + // First Try to find as-is property data + ret = thisCache[ name ]; + + // Test for null|undefined property data + if ( ret == null ) { + + // Try to find the camelCased property + ret = thisCache[ jQuery.camelCase( name ) ]; + } + } else { + ret = thisCache; + } + + return ret; + }, + + removeData: function( elem, name, pvt /* Internal Use Only */ ) { + if ( !jQuery.acceptData( elem ) ) { + return; + } + + var thisCache, i, l, + + // Reference to internal data cache key + internalKey = jQuery.expando, + + isNode = elem.nodeType, + + // See jQuery.data for more information + cache = isNode ? jQuery.cache : elem, + + // See jQuery.data for more information + id = isNode ? elem[ internalKey ] : internalKey; + + // If there is already no cache entry for this object, there is no + // purpose in continuing + if ( !cache[ id ] ) { + return; + } + + if ( name ) { + + thisCache = pvt ? cache[ id ] : cache[ id ].data; + + if ( thisCache ) { + + // Support array or space separated string names for data keys + if ( !jQuery.isArray( name ) ) { + + // try the string as a key before any manipulation + if ( name in thisCache ) { + name = [ name ]; + } else { + + // split the camel cased version by spaces unless a key with the spaces exists + name = jQuery.camelCase( name ); + if ( name in thisCache ) { + name = [ name ]; + } else { + name = name.split( " " ); + } + } + } + + for ( i = 0, l = name.length; i < l; i++ ) { + delete thisCache[ name[i] ]; + } + + // If there is no data left in the cache, we want to continue + // and let the cache object itself get destroyed + if ( !( pvt ? isEmptyDataObject : jQuery.isEmptyObject )( thisCache ) ) { + return; + } + } + } + + // See jQuery.data for more information + if ( !pvt ) { + delete cache[ id ].data; + + // Don't destroy the parent cache unless the internal data object + // had been the only thing left in it + if ( !isEmptyDataObject(cache[ id ]) ) { + return; + } + } + + // Browsers that fail expando deletion also refuse to delete expandos on + // the window, but it will allow it on all other JS objects; other browsers + // don't care + // Ensure that `cache` is not a window object #10080 + if ( jQuery.support.deleteExpando || !cache.setInterval ) { + delete cache[ id ]; + } else { + cache[ id ] = null; + } + + // We destroyed the cache and need to eliminate the expando on the node to avoid + // false lookups in the cache for entries that no longer exist + if ( isNode ) { + // IE does not allow us to delete expando properties from nodes, + // nor does it have a removeAttribute function on Document nodes; + // we must handle all of these cases + if ( jQuery.support.deleteExpando ) { + delete elem[ internalKey ]; + } else if ( elem.removeAttribute ) { + elem.removeAttribute( internalKey ); + } else { + elem[ internalKey ] = null; + } + } + }, + + // For internal use only. + _data: function( elem, name, data ) { + return jQuery.data( elem, name, data, true ); + }, + + // A method for determining if a DOM node can handle the data expando + acceptData: function( elem ) { + if ( elem.nodeName ) { + var match = jQuery.noData[ elem.nodeName.toLowerCase() ]; + + if ( match ) { + return !(match === true || elem.getAttribute("classid") !== match); + } + } + + return true; + } +}); + +jQuery.fn.extend({ + data: function( key, value ) { + var parts, attr, name, + data = null; + + if ( typeof key === "undefined" ) { + if ( this.length ) { + data = jQuery.data( this[0] ); + + if ( this[0].nodeType === 1 && !jQuery._data( this[0], "parsedAttrs" ) ) { + attr = this[0].attributes; + for ( var i = 0, l = attr.length; i < l; i++ ) { + name = attr[i].name; + + if ( name.indexOf( "data-" ) === 0 ) { + name = jQuery.camelCase( name.substring(5) ); + + dataAttr( this[0], name, data[ name ] ); + } + } + jQuery._data( this[0], "parsedAttrs", true ); + } + } + + return data; + + } else if ( typeof key === "object" ) { + return this.each(function() { + jQuery.data( this, key ); + }); + } + + parts = key.split("."); + parts[1] = parts[1] ? "." + parts[1] : ""; + + if ( value === undefined ) { + data = this.triggerHandler("getData" + parts[1] + "!", [parts[0]]); + + // Try to fetch any internally stored data first + if ( data === undefined && this.length ) { + data = jQuery.data( this[0], key ); + data = dataAttr( this[0], key, data ); + } + + return data === undefined && parts[1] ? + this.data( parts[0] ) : + data; + + } else { + return this.each(function() { + var self = jQuery( this ), + args = [ parts[0], value ]; + + self.triggerHandler( "setData" + parts[1] + "!", args ); + jQuery.data( this, key, value ); + self.triggerHandler( "changeData" + parts[1] + "!", args ); + }); + } + }, + + removeData: function( key ) { + return this.each(function() { + jQuery.removeData( this, key ); + }); + } +}); + +function dataAttr( elem, key, data ) { + // If nothing was found internally, try to fetch any + // data from the HTML5 data-* attribute + if ( data === undefined && elem.nodeType === 1 ) { + + var name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase(); + + data = elem.getAttribute( name ); + + if ( typeof data === "string" ) { + try { + data = data === "true" ? true : + data === "false" ? false : + data === "null" ? null : + jQuery.isNumeric( data ) ? parseFloat( data ) : + rbrace.test( data ) ? jQuery.parseJSON( data ) : + data; + } catch( e ) {} + + // Make sure we set the data so it isn't changed later + jQuery.data( elem, key, data ); + + } else { + data = undefined; + } + } + + return data; +} + +// checks a cache object for emptiness +function isEmptyDataObject( obj ) { + for ( var name in obj ) { + + // if the public data object is empty, the private is still empty + if ( name === "data" && jQuery.isEmptyObject( obj[name] ) ) { + continue; + } + if ( name !== "toJSON" ) { + return false; + } + } + + return true; +} + + + + +function handleQueueMarkDefer( elem, type, src ) { + var deferDataKey = type + "defer", + queueDataKey = type + "queue", + markDataKey = type + "mark", + defer = jQuery._data( elem, deferDataKey ); + if ( defer && + ( src === "queue" || !jQuery._data(elem, queueDataKey) ) && + ( src === "mark" || !jQuery._data(elem, markDataKey) ) ) { + // Give room for hard-coded callbacks to fire first + // and eventually mark/queue something else on the element + setTimeout( function() { + if ( !jQuery._data( elem, queueDataKey ) && + !jQuery._data( elem, markDataKey ) ) { + jQuery.removeData( elem, deferDataKey, true ); + defer.fire(); + } + }, 0 ); + } +} + +jQuery.extend({ + + _mark: function( elem, type ) { + if ( elem ) { + type = ( type || "fx" ) + "mark"; + jQuery._data( elem, type, (jQuery._data( elem, type ) || 0) + 1 ); + } + }, + + _unmark: function( force, elem, type ) { + if ( force !== true ) { + type = elem; + elem = force; + force = false; + } + if ( elem ) { + type = type || "fx"; + var key = type + "mark", + count = force ? 0 : ( (jQuery._data( elem, key ) || 1) - 1 ); + if ( count ) { + jQuery._data( elem, key, count ); + } else { + jQuery.removeData( elem, key, true ); + handleQueueMarkDefer( elem, type, "mark" ); + } + } + }, + + queue: function( elem, type, data ) { + var q; + if ( elem ) { + type = ( type || "fx" ) + "queue"; + q = jQuery._data( elem, type ); + + // Speed up dequeue by getting out quickly if this is just a lookup + if ( data ) { + if ( !q || jQuery.isArray(data) ) { + q = jQuery._data( elem, type, jQuery.makeArray(data) ); + } else { + q.push( data ); + } + } + return q || []; + } + }, + + dequeue: function( elem, type ) { + type = type || "fx"; + + var queue = jQuery.queue( elem, type ), + fn = queue.shift(), + hooks = {}; + + // If the fx queue is dequeued, always remove the progress sentinel + if ( fn === "inprogress" ) { + fn = queue.shift(); + } + + if ( fn ) { + // Add a progress sentinel to prevent the fx queue from being + // automatically dequeued + if ( type === "fx" ) { + queue.unshift( "inprogress" ); + } + + jQuery._data( elem, type + ".run", hooks ); + fn.call( elem, function() { + jQuery.dequeue( elem, type ); + }, hooks ); + } + + if ( !queue.length ) { + jQuery.removeData( elem, type + "queue " + type + ".run", true ); + handleQueueMarkDefer( elem, type, "queue" ); + } + } +}); + +jQuery.fn.extend({ + queue: function( type, data ) { + if ( typeof type !== "string" ) { + data = type; + type = "fx"; + } + + if ( data === undefined ) { + return jQuery.queue( this[0], type ); + } + return this.each(function() { + var queue = jQuery.queue( this, type, data ); + + if ( type === "fx" && queue[0] !== "inprogress" ) { + jQuery.dequeue( this, type ); + } + }); + }, + dequeue: function( type ) { + return this.each(function() { + jQuery.dequeue( this, type ); + }); + }, + // Based off of the plugin by Clint Helfers, with permission. + // http://blindsignals.com/index.php/2009/07/jquery-delay/ + delay: function( time, type ) { + time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time; + type = type || "fx"; + + return this.queue( type, function( next, hooks ) { + var timeout = setTimeout( next, time ); + hooks.stop = function() { + clearTimeout( timeout ); + }; + }); + }, + clearQueue: function( type ) { + return this.queue( type || "fx", [] ); + }, + // Get a promise resolved when queues of a certain type + // are emptied (fx is the type by default) + promise: function( type, object ) { + if ( typeof type !== "string" ) { + object = type; + type = undefined; + } + type = type || "fx"; + var defer = jQuery.Deferred(), + elements = this, + i = elements.length, + count = 1, + deferDataKey = type + "defer", + queueDataKey = type + "queue", + markDataKey = type + "mark", + tmp; + function resolve() { + if ( !( --count ) ) { + defer.resolveWith( elements, [ elements ] ); + } + } + while( i-- ) { + if (( tmp = jQuery.data( elements[ i ], deferDataKey, undefined, true ) || + ( jQuery.data( elements[ i ], queueDataKey, undefined, true ) || + jQuery.data( elements[ i ], markDataKey, undefined, true ) ) && + jQuery.data( elements[ i ], deferDataKey, jQuery.Callbacks( "once memory" ), true ) )) { + count++; + tmp.add( resolve ); + } + } + resolve(); + return defer.promise(); + } +}); + + + + +var rclass = /[\n\t\r]/g, + rspace = /\s+/, + rreturn = /\r/g, + rtype = /^(?:button|input)$/i, + rfocusable = /^(?:button|input|object|select|textarea)$/i, + rclickable = /^a(?:rea)?$/i, + rboolean = /^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i, + getSetAttribute = jQuery.support.getSetAttribute, + nodeHook, boolHook, fixSpecified; + +jQuery.fn.extend({ + attr: function( name, value ) { + return jQuery.access( this, name, value, true, jQuery.attr ); + }, + + removeAttr: function( name ) { + return this.each(function() { + jQuery.removeAttr( this, name ); + }); + }, + + prop: function( name, value ) { + return jQuery.access( this, name, value, true, jQuery.prop ); + }, + + removeProp: function( name ) { + name = jQuery.propFix[ name ] || name; + return this.each(function() { + // try/catch handles cases where IE balks (such as removing a property on window) + try { + this[ name ] = undefined; + delete this[ name ]; + } catch( e ) {} + }); + }, + + addClass: function( value ) { + var classNames, i, l, elem, + setClass, c, cl; + + if ( jQuery.isFunction( value ) ) { + return this.each(function( j ) { + jQuery( this ).addClass( value.call(this, j, this.className) ); + }); + } + + if ( value && typeof value === "string" ) { + classNames = value.split( rspace ); + + for ( i = 0, l = this.length; i < l; i++ ) { + elem = this[ i ]; + + if ( elem.nodeType === 1 ) { + if ( !elem.className && classNames.length === 1 ) { + elem.className = value; + + } else { + setClass = " " + elem.className + " "; + + for ( c = 0, cl = classNames.length; c < cl; c++ ) { + if ( !~setClass.indexOf( " " + classNames[ c ] + " " ) ) { + setClass += classNames[ c ] + " "; + } + } + elem.className = jQuery.trim( setClass ); + } + } + } + } + + return this; + }, + + removeClass: function( value ) { + var classNames, i, l, elem, className, c, cl; + + if ( jQuery.isFunction( value ) ) { + return this.each(function( j ) { + jQuery( this ).removeClass( value.call(this, j, this.className) ); + }); + } + + if ( (value && typeof value === "string") || value === undefined ) { + classNames = ( value || "" ).split( rspace ); + + for ( i = 0, l = this.length; i < l; i++ ) { + elem = this[ i ]; + + if ( elem.nodeType === 1 && elem.className ) { + if ( value ) { + className = (" " + elem.className + " ").replace( rclass, " " ); + for ( c = 0, cl = classNames.length; c < cl; c++ ) { + className = className.replace(" " + classNames[ c ] + " ", " "); + } + elem.className = jQuery.trim( className ); + + } else { + elem.className = ""; + } + } + } + } + + return this; + }, + + toggleClass: function( value, stateVal ) { + var type = typeof value, + isBool = typeof stateVal === "boolean"; + + if ( jQuery.isFunction( value ) ) { + return this.each(function( i ) { + jQuery( this ).toggleClass( value.call(this, i, this.className, stateVal), stateVal ); + }); + } + + return this.each(function() { + if ( type === "string" ) { + // toggle individual class names + var className, + i = 0, + self = jQuery( this ), + state = stateVal, + classNames = value.split( rspace ); + + while ( (className = classNames[ i++ ]) ) { + // check each className given, space seperated list + state = isBool ? state : !self.hasClass( className ); + self[ state ? "addClass" : "removeClass" ]( className ); + } + + } else if ( type === "undefined" || type === "boolean" ) { + if ( this.className ) { + // store className if set + jQuery._data( this, "__className__", this.className ); + } + + // toggle whole className + this.className = this.className || value === false ? "" : jQuery._data( this, "__className__" ) || ""; + } + }); + }, + + hasClass: function( selector ) { + var className = " " + selector + " ", + i = 0, + l = this.length; + for ( ; i < l; i++ ) { + if ( this[i].nodeType === 1 && (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) > -1 ) { + return true; + } + } + + return false; + }, + + val: function( value ) { + var hooks, ret, isFunction, + elem = this[0]; + + if ( !arguments.length ) { + if ( elem ) { + hooks = jQuery.valHooks[ elem.nodeName.toLowerCase() ] || jQuery.valHooks[ elem.type ]; + + if ( hooks && "get" in hooks && (ret = hooks.get( elem, "value" )) !== undefined ) { + return ret; + } + + ret = elem.value; + + return typeof ret === "string" ? + // handle most common string cases + ret.replace(rreturn, "") : + // handle cases where value is null/undef or number + ret == null ? "" : ret; + } + + return; + } + + isFunction = jQuery.isFunction( value ); + + return this.each(function( i ) { + var self = jQuery(this), val; + + if ( this.nodeType !== 1 ) { + return; + } + + if ( isFunction ) { + val = value.call( this, i, self.val() ); + } else { + val = value; + } + + // Treat null/undefined as ""; convert numbers to string + if ( val == null ) { + val = ""; + } else if ( typeof val === "number" ) { + val += ""; + } else if ( jQuery.isArray( val ) ) { + val = jQuery.map(val, function ( value ) { + return value == null ? "" : value + ""; + }); + } + + hooks = jQuery.valHooks[ this.nodeName.toLowerCase() ] || jQuery.valHooks[ this.type ]; + + // If set returns undefined, fall back to normal setting + if ( !hooks || !("set" in hooks) || hooks.set( this, val, "value" ) === undefined ) { + this.value = val; + } + }); + } +}); + +jQuery.extend({ + valHooks: { + option: { + get: function( elem ) { + // attributes.value is undefined in Blackberry 4.7 but + // uses .value. See #6932 + var val = elem.attributes.value; + return !val || val.specified ? elem.value : elem.text; + } + }, + select: { + get: function( elem ) { + var value, i, max, option, + index = elem.selectedIndex, + values = [], + options = elem.options, + one = elem.type === "select-one"; + + // Nothing was selected + if ( index < 0 ) { + return null; + } + + // Loop through all the selected options + i = one ? index : 0; + max = one ? index + 1 : options.length; + for ( ; i < max; i++ ) { + option = options[ i ]; + + // Don't return options that are disabled or in a disabled optgroup + if ( option.selected && (jQuery.support.optDisabled ? !option.disabled : option.getAttribute("disabled") === null) && + (!option.parentNode.disabled || !jQuery.nodeName( option.parentNode, "optgroup" )) ) { + + // Get the specific value for the option + value = jQuery( option ).val(); + + // We don't need an array for one selects + if ( one ) { + return value; + } + + // Multi-Selects return an array + values.push( value ); + } + } + + // Fixes Bug #2551 -- select.val() broken in IE after form.reset() + if ( one && !values.length && options.length ) { + return jQuery( options[ index ] ).val(); + } + + return values; + }, + + set: function( elem, value ) { + var values = jQuery.makeArray( value ); + + jQuery(elem).find("option").each(function() { + this.selected = jQuery.inArray( jQuery(this).val(), values ) >= 0; + }); + + if ( !values.length ) { + elem.selectedIndex = -1; + } + return values; + } + } + }, + + attrFn: { + val: true, + css: true, + html: true, + text: true, + data: true, + width: true, + height: true, + offset: true + }, + + attr: function( elem, name, value, pass ) { + var ret, hooks, notxml, + nType = elem.nodeType; + + // don't get/set attributes on text, comment and attribute nodes + if ( !elem || nType === 3 || nType === 8 || nType === 2 ) { + return; + } + + if ( pass && name in jQuery.attrFn ) { + return jQuery( elem )[ name ]( value ); + } + + // Fallback to prop when attributes are not supported + if ( typeof elem.getAttribute === "undefined" ) { + return jQuery.prop( elem, name, value ); + } + + notxml = nType !== 1 || !jQuery.isXMLDoc( elem ); + + // All attributes are lowercase + // Grab necessary hook if one is defined + if ( notxml ) { + name = name.toLowerCase(); + hooks = jQuery.attrHooks[ name ] || ( rboolean.test( name ) ? boolHook : nodeHook ); + } + + if ( value !== undefined ) { + + if ( value === null ) { + jQuery.removeAttr( elem, name ); + return; + + } else if ( hooks && "set" in hooks && notxml && (ret = hooks.set( elem, value, name )) !== undefined ) { + return ret; + + } else { + elem.setAttribute( name, "" + value ); + return value; + } + + } else if ( hooks && "get" in hooks && notxml && (ret = hooks.get( elem, name )) !== null ) { + return ret; + + } else { + + ret = elem.getAttribute( name ); + + // Non-existent attributes return null, we normalize to undefined + return ret === null ? + undefined : + ret; + } + }, + + removeAttr: function( elem, value ) { + var propName, attrNames, name, l, + i = 0; + + if ( value && elem.nodeType === 1 ) { + attrNames = value.toLowerCase().split( rspace ); + l = attrNames.length; + + for ( ; i < l; i++ ) { + name = attrNames[ i ]; + + if ( name ) { + propName = jQuery.propFix[ name ] || name; + + // See #9699 for explanation of this approach (setting first, then removal) + jQuery.attr( elem, name, "" ); + elem.removeAttribute( getSetAttribute ? name : propName ); + + // Set corresponding property to false for boolean attributes + if ( rboolean.test( name ) && propName in elem ) { + elem[ propName ] = false; + } + } + } + } + }, + + attrHooks: { + type: { + set: function( elem, value ) { + // We can't allow the type property to be changed (since it causes problems in IE) + if ( rtype.test( elem.nodeName ) && elem.parentNode ) { + jQuery.error( "type property can't be changed" ); + } else if ( !jQuery.support.radioValue && value === "radio" && jQuery.nodeName(elem, "input") ) { + // Setting the type on a radio button after the value resets the value in IE6-9 + // Reset value to it's default in case type is set after value + // This is for element creation + var val = elem.value; + elem.setAttribute( "type", value ); + if ( val ) { + elem.value = val; + } + return value; + } + } + }, + // Use the value property for back compat + // Use the nodeHook for button elements in IE6/7 (#1954) + value: { + get: function( elem, name ) { + if ( nodeHook && jQuery.nodeName( elem, "button" ) ) { + return nodeHook.get( elem, name ); + } + return name in elem ? + elem.value : + null; + }, + set: function( elem, value, name ) { + if ( nodeHook && jQuery.nodeName( elem, "button" ) ) { + return nodeHook.set( elem, value, name ); + } + // Does not return so that setAttribute is also used + elem.value = value; + } + } + }, + + propFix: { + tabindex: "tabIndex", + readonly: "readOnly", + "for": "htmlFor", + "class": "className", + maxlength: "maxLength", + cellspacing: "cellSpacing", + cellpadding: "cellPadding", + rowspan: "rowSpan", + colspan: "colSpan", + usemap: "useMap", + frameborder: "frameBorder", + contenteditable: "contentEditable" + }, + + prop: function( elem, name, value ) { + var ret, hooks, notxml, + nType = elem.nodeType; + + // don't get/set properties on text, comment and attribute nodes + if ( !elem || nType === 3 || nType === 8 || nType === 2 ) { + return; + } + + notxml = nType !== 1 || !jQuery.isXMLDoc( elem ); + + if ( notxml ) { + // Fix name and attach hooks + name = jQuery.propFix[ name ] || name; + hooks = jQuery.propHooks[ name ]; + } + + if ( value !== undefined ) { + if ( hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) { + return ret; + + } else { + return ( elem[ name ] = value ); + } + + } else { + if ( hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ) { + return ret; + + } else { + return elem[ name ]; + } + } + }, + + propHooks: { + tabIndex: { + get: function( elem ) { + // elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set + // http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ + var attributeNode = elem.getAttributeNode("tabindex"); + + return attributeNode && attributeNode.specified ? + parseInt( attributeNode.value, 10 ) : + rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ? + 0 : + undefined; + } + } + } +}); + +// Add the tabIndex propHook to attrHooks for back-compat (different case is intentional) +jQuery.attrHooks.tabindex = jQuery.propHooks.tabIndex; + +// Hook for boolean attributes +boolHook = { + get: function( elem, name ) { + // Align boolean attributes with corresponding properties + // Fall back to attribute presence where some booleans are not supported + var attrNode, + property = jQuery.prop( elem, name ); + return property === true || typeof property !== "boolean" && ( attrNode = elem.getAttributeNode(name) ) && attrNode.nodeValue !== false ? + name.toLowerCase() : + undefined; + }, + set: function( elem, value, name ) { + var propName; + if ( value === false ) { + // Remove boolean attributes when set to false + jQuery.removeAttr( elem, name ); + } else { + // value is true since we know at this point it's type boolean and not false + // Set boolean attributes to the same name and set the DOM property + propName = jQuery.propFix[ name ] || name; + if ( propName in elem ) { + // Only set the IDL specifically if it already exists on the element + elem[ propName ] = true; + } + + elem.setAttribute( name, name.toLowerCase() ); + } + return name; + } +}; + +// IE6/7 do not support getting/setting some attributes with get/setAttribute +if ( !getSetAttribute ) { + + fixSpecified = { + name: true, + id: true + }; + + // Use this for any attribute in IE6/7 + // This fixes almost every IE6/7 issue + nodeHook = jQuery.valHooks.button = { + get: function( elem, name ) { + var ret; + ret = elem.getAttributeNode( name ); + return ret && ( fixSpecified[ name ] ? ret.nodeValue !== "" : ret.specified ) ? + ret.nodeValue : + undefined; + }, + set: function( elem, value, name ) { + // Set the existing or create a new attribute node + var ret = elem.getAttributeNode( name ); + if ( !ret ) { + ret = document.createAttribute( name ); + elem.setAttributeNode( ret ); + } + return ( ret.nodeValue = value + "" ); + } + }; + + // Apply the nodeHook to tabindex + jQuery.attrHooks.tabindex.set = nodeHook.set; + + // Set width and height to auto instead of 0 on empty string( Bug #8150 ) + // This is for removals + jQuery.each([ "width", "height" ], function( i, name ) { + jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], { + set: function( elem, value ) { + if ( value === "" ) { + elem.setAttribute( name, "auto" ); + return value; + } + } + }); + }); + + // Set contenteditable to false on removals(#10429) + // Setting to empty string throws an error as an invalid value + jQuery.attrHooks.contenteditable = { + get: nodeHook.get, + set: function( elem, value, name ) { + if ( value === "" ) { + value = "false"; + } + nodeHook.set( elem, value, name ); + } + }; +} + + +// Some attributes require a special call on IE +if ( !jQuery.support.hrefNormalized ) { + jQuery.each([ "href", "src", "width", "height" ], function( i, name ) { + jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], { + get: function( elem ) { + var ret = elem.getAttribute( name, 2 ); + return ret === null ? undefined : ret; + } + }); + }); +} + +if ( !jQuery.support.style ) { + jQuery.attrHooks.style = { + get: function( elem ) { + // Return undefined in the case of empty string + // Normalize to lowercase since IE uppercases css property names + return elem.style.cssText.toLowerCase() || undefined; + }, + set: function( elem, value ) { + return ( elem.style.cssText = "" + value ); + } + }; +} + +// Safari mis-reports the default selected property of an option +// Accessing the parent's selectedIndex property fixes it +if ( !jQuery.support.optSelected ) { + jQuery.propHooks.selected = jQuery.extend( jQuery.propHooks.selected, { + get: function( elem ) { + var parent = elem.parentNode; + + if ( parent ) { + parent.selectedIndex; + + // Make sure that it also works with optgroups, see #5701 + if ( parent.parentNode ) { + parent.parentNode.selectedIndex; + } + } + return null; + } + }); +} + +// IE6/7 call enctype encoding +if ( !jQuery.support.enctype ) { + jQuery.propFix.enctype = "encoding"; +} + +// Radios and checkboxes getter/setter +if ( !jQuery.support.checkOn ) { + jQuery.each([ "radio", "checkbox" ], function() { + jQuery.valHooks[ this ] = { + get: function( elem ) { + // Handle the case where in Webkit "" is returned instead of "on" if a value isn't specified + return elem.getAttribute("value") === null ? "on" : elem.value; + } + }; + }); +} +jQuery.each([ "radio", "checkbox" ], function() { + jQuery.valHooks[ this ] = jQuery.extend( jQuery.valHooks[ this ], { + set: function( elem, value ) { + if ( jQuery.isArray( value ) ) { + return ( elem.checked = jQuery.inArray( jQuery(elem).val(), value ) >= 0 ); + } + } + }); +}); + + + + +var rformElems = /^(?:textarea|input|select)$/i, + rtypenamespace = /^([^\.]*)?(?:\.(.+))?$/, + rhoverHack = /\bhover(\.\S+)?\b/, + rkeyEvent = /^key/, + rmouseEvent = /^(?:mouse|contextmenu)|click/, + rfocusMorph = /^(?:focusinfocus|focusoutblur)$/, + rquickIs = /^(\w*)(?:#([\w\-]+))?(?:\.([\w\-]+))?$/, + quickParse = function( selector ) { + var quick = rquickIs.exec( selector ); + if ( quick ) { + // 0 1 2 3 + // [ _, tag, id, class ] + quick[1] = ( quick[1] || "" ).toLowerCase(); + quick[3] = quick[3] && new RegExp( "(?:^|\\s)" + quick[3] + "(?:\\s|$)" ); + } + return quick; + }, + quickIs = function( elem, m ) { + var attrs = elem.attributes || {}; + return ( + (!m[1] || elem.nodeName.toLowerCase() === m[1]) && + (!m[2] || (attrs.id || {}).value === m[2]) && + (!m[3] || m[3].test( (attrs[ "class" ] || {}).value )) + ); + }, + hoverHack = function( events ) { + return jQuery.event.special.hover ? events : events.replace( rhoverHack, "mouseenter$1 mouseleave$1" ); + }; + +/* + * Helper functions for managing events -- not part of the public interface. + * Props to Dean Edwards' addEvent library for many of the ideas. + */ +jQuery.event = { + + add: function( elem, types, handler, data, selector ) { + + var elemData, eventHandle, events, + t, tns, type, namespaces, handleObj, + handleObjIn, quick, handlers, special; + + // Don't attach events to noData or text/comment nodes (allow plain objects tho) + if ( elem.nodeType === 3 || elem.nodeType === 8 || !types || !handler || !(elemData = jQuery._data( elem )) ) { + return; + } + + // Caller can pass in an object of custom data in lieu of the handler + if ( handler.handler ) { + handleObjIn = handler; + handler = handleObjIn.handler; + } + + // Make sure that the handler has a unique ID, used to find/remove it later + if ( !handler.guid ) { + handler.guid = jQuery.guid++; + } + + // Init the element's event structure and main handler, if this is the first + events = elemData.events; + if ( !events ) { + elemData.events = events = {}; + } + eventHandle = elemData.handle; + if ( !eventHandle ) { + elemData.handle = eventHandle = function( e ) { + // Discard the second event of a jQuery.event.trigger() and + // when an event is called after a page has unloaded + return typeof jQuery !== "undefined" && (!e || jQuery.event.triggered !== e.type) ? + jQuery.event.dispatch.apply( eventHandle.elem, arguments ) : + undefined; + }; + // Add elem as a property of the handle fn to prevent a memory leak with IE non-native events + eventHandle.elem = elem; + } + + // Handle multiple events separated by a space + // jQuery(...).bind("mouseover mouseout", fn); + types = jQuery.trim( hoverHack(types) ).split( " " ); + for ( t = 0; t < types.length; t++ ) { + + tns = rtypenamespace.exec( types[t] ) || []; + type = tns[1]; + namespaces = ( tns[2] || "" ).split( "." ).sort(); + + // If event changes its type, use the special event handlers for the changed type + special = jQuery.event.special[ type ] || {}; + + // If selector defined, determine special event api type, otherwise given type + type = ( selector ? special.delegateType : special.bindType ) || type; + + // Update special based on newly reset type + special = jQuery.event.special[ type ] || {}; + + // handleObj is passed to all event handlers + handleObj = jQuery.extend({ + type: type, + origType: tns[1], + data: data, + handler: handler, + guid: handler.guid, + selector: selector, + quick: quickParse( selector ), + namespace: namespaces.join(".") + }, handleObjIn ); + + // Init the event handler queue if we're the first + handlers = events[ type ]; + if ( !handlers ) { + handlers = events[ type ] = []; + handlers.delegateCount = 0; + + // Only use addEventListener/attachEvent if the special events handler returns false + if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) { + // Bind the global event handler to the element + if ( elem.addEventListener ) { + elem.addEventListener( type, eventHandle, false ); + + } else if ( elem.attachEvent ) { + elem.attachEvent( "on" + type, eventHandle ); + } + } + } + + if ( special.add ) { + special.add.call( elem, handleObj ); + + if ( !handleObj.handler.guid ) { + handleObj.handler.guid = handler.guid; + } + } + + // Add to the element's handler list, delegates in front + if ( selector ) { + handlers.splice( handlers.delegateCount++, 0, handleObj ); + } else { + handlers.push( handleObj ); + } + + // Keep track of which events have ever been used, for event optimization + jQuery.event.global[ type ] = true; + } + + // Nullify elem to prevent memory leaks in IE + elem = null; + }, + + global: {}, + + // Detach an event or set of events from an element + remove: function( elem, types, handler, selector, mappedTypes ) { + + var elemData = jQuery.hasData( elem ) && jQuery._data( elem ), + t, tns, type, origType, namespaces, origCount, + j, events, special, handle, eventType, handleObj; + + if ( !elemData || !(events = elemData.events) ) { + return; + } + + // Once for each type.namespace in types; type may be omitted + types = jQuery.trim( hoverHack( types || "" ) ).split(" "); + for ( t = 0; t < types.length; t++ ) { + tns = rtypenamespace.exec( types[t] ) || []; + type = origType = tns[1]; + namespaces = tns[2]; + + // Unbind all events (on this namespace, if provided) for the element + if ( !type ) { + for ( type in events ) { + jQuery.event.remove( elem, type + types[ t ], handler, selector, true ); + } + continue; + } + + special = jQuery.event.special[ type ] || {}; + type = ( selector? special.delegateType : special.bindType ) || type; + eventType = events[ type ] || []; + origCount = eventType.length; + namespaces = namespaces ? new RegExp("(^|\\.)" + namespaces.split(".").sort().join("\\.(?:.*\\.)?") + "(\\.|$)") : null; + + // Remove matching events + for ( j = 0; j < eventType.length; j++ ) { + handleObj = eventType[ j ]; + + if ( ( mappedTypes || origType === handleObj.origType ) && + ( !handler || handler.guid === handleObj.guid ) && + ( !namespaces || namespaces.test( handleObj.namespace ) ) && + ( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) { + eventType.splice( j--, 1 ); + + if ( handleObj.selector ) { + eventType.delegateCount--; + } + if ( special.remove ) { + special.remove.call( elem, handleObj ); + } + } + } + + // Remove generic event handler if we removed something and no more handlers exist + // (avoids potential for endless recursion during removal of special event handlers) + if ( eventType.length === 0 && origCount !== eventType.length ) { + if ( !special.teardown || special.teardown.call( elem, namespaces ) === false ) { + jQuery.removeEvent( elem, type, elemData.handle ); + } + + delete events[ type ]; + } + } + + // Remove the expando if it's no longer used + if ( jQuery.isEmptyObject( events ) ) { + handle = elemData.handle; + if ( handle ) { + handle.elem = null; + } + + // removeData also checks for emptiness and clears the expando if empty + // so use it instead of delete + jQuery.removeData( elem, [ "events", "handle" ], true ); + } + }, + + // Events that are safe to short-circuit if no handlers are attached. + // Native DOM events should not be added, they may have inline handlers. + customEvent: { + "getData": true, + "setData": true, + "changeData": true + }, + + trigger: function( event, data, elem, onlyHandlers ) { + // Don't do events on text and comment nodes + if ( elem && (elem.nodeType === 3 || elem.nodeType === 8) ) { + return; + } + + // Event object or event type + var type = event.type || event, + namespaces = [], + cache, exclusive, i, cur, old, ontype, special, handle, eventPath, bubbleType; + + // focus/blur morphs to focusin/out; ensure we're not firing them right now + if ( rfocusMorph.test( type + jQuery.event.triggered ) ) { + return; + } + + if ( type.indexOf( "!" ) >= 0 ) { + // Exclusive events trigger only for the exact event (no namespaces) + type = type.slice(0, -1); + exclusive = true; + } + + if ( type.indexOf( "." ) >= 0 ) { + // Namespaced trigger; create a regexp to match event type in handle() + namespaces = type.split("."); + type = namespaces.shift(); + namespaces.sort(); + } + + if ( (!elem || jQuery.event.customEvent[ type ]) && !jQuery.event.global[ type ] ) { + // No jQuery handlers for this event type, and it can't have inline handlers + return; + } + + // Caller can pass in an Event, Object, or just an event type string + event = typeof event === "object" ? + // jQuery.Event object + event[ jQuery.expando ] ? event : + // Object literal + new jQuery.Event( type, event ) : + // Just the event type (string) + new jQuery.Event( type ); + + event.type = type; + event.isTrigger = true; + event.exclusive = exclusive; + event.namespace = namespaces.join( "." ); + event.namespace_re = event.namespace? new RegExp("(^|\\.)" + namespaces.join("\\.(?:.*\\.)?") + "(\\.|$)") : null; + ontype = type.indexOf( ":" ) < 0 ? "on" + type : ""; + + // Handle a global trigger + if ( !elem ) { + + // TODO: Stop taunting the data cache; remove global events and always attach to document + cache = jQuery.cache; + for ( i in cache ) { + if ( cache[ i ].events && cache[ i ].events[ type ] ) { + jQuery.event.trigger( event, data, cache[ i ].handle.elem, true ); + } + } + return; + } + + // Clean up the event in case it is being reused + event.result = undefined; + if ( !event.target ) { + event.target = elem; + } + + // Clone any incoming data and prepend the event, creating the handler arg list + data = data != null ? jQuery.makeArray( data ) : []; + data.unshift( event ); + + // Allow special events to draw outside the lines + special = jQuery.event.special[ type ] || {}; + if ( special.trigger && special.trigger.apply( elem, data ) === false ) { + return; + } + + // Determine event propagation path in advance, per W3C events spec (#9951) + // Bubble up to document, then to window; watch for a global ownerDocument var (#9724) + eventPath = [[ elem, special.bindType || type ]]; + if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) { + + bubbleType = special.delegateType || type; + cur = rfocusMorph.test( bubbleType + type ) ? elem : elem.parentNode; + old = null; + for ( ; cur; cur = cur.parentNode ) { + eventPath.push([ cur, bubbleType ]); + old = cur; + } + + // Only add window if we got to document (e.g., not plain obj or detached DOM) + if ( old && old === elem.ownerDocument ) { + eventPath.push([ old.defaultView || old.parentWindow || window, bubbleType ]); + } + } + + // Fire handlers on the event path + for ( i = 0; i < eventPath.length && !event.isPropagationStopped(); i++ ) { + + cur = eventPath[i][0]; + event.type = eventPath[i][1]; + + handle = ( jQuery._data( cur, "events" ) || {} )[ event.type ] && jQuery._data( cur, "handle" ); + if ( handle ) { + handle.apply( cur, data ); + } + // Note that this is a bare JS function and not a jQuery handler + handle = ontype && cur[ ontype ]; + if ( handle && jQuery.acceptData( cur ) && handle.apply( cur, data ) === false ) { + event.preventDefault(); + } + } + event.type = type; + + // If nobody prevented the default action, do it now + if ( !onlyHandlers && !event.isDefaultPrevented() ) { + + if ( (!special._default || special._default.apply( elem.ownerDocument, data ) === false) && + !(type === "click" && jQuery.nodeName( elem, "a" )) && jQuery.acceptData( elem ) ) { + + // Call a native DOM method on the target with the same name name as the event. + // Can't use an .isFunction() check here because IE6/7 fails that test. + // Don't do default actions on window, that's where global variables be (#6170) + // IE<9 dies on focus/blur to hidden element (#1486) + if ( ontype && elem[ type ] && ((type !== "focus" && type !== "blur") || event.target.offsetWidth !== 0) && !jQuery.isWindow( elem ) ) { + + // Don't re-trigger an onFOO event when we call its FOO() method + old = elem[ ontype ]; + + if ( old ) { + elem[ ontype ] = null; + } + + // Prevent re-triggering of the same event, since we already bubbled it above + jQuery.event.triggered = type; + elem[ type ](); + jQuery.event.triggered = undefined; + + if ( old ) { + elem[ ontype ] = old; + } + } + } + } + + return event.result; + }, + + dispatch: function( event ) { + + // Make a writable jQuery.Event from the native event object + event = jQuery.event.fix( event || window.event ); + + var handlers = ( (jQuery._data( this, "events" ) || {} )[ event.type ] || []), + delegateCount = handlers.delegateCount, + args = [].slice.call( arguments, 0 ), + run_all = !event.exclusive && !event.namespace, + handlerQueue = [], + i, j, cur, jqcur, ret, selMatch, matched, matches, handleObj, sel, related; + + // Use the fix-ed jQuery.Event rather than the (read-only) native event + args[0] = event; + event.delegateTarget = this; + + // Determine handlers that should run if there are delegated events + // Avoid disabled elements in IE (#6911) and non-left-click bubbling in Firefox (#3861) + if ( delegateCount && !event.target.disabled && !(event.button && event.type === "click") ) { + + // Pregenerate a single jQuery object for reuse with .is() + jqcur = jQuery(this); + jqcur.context = this.ownerDocument || this; + + for ( cur = event.target; cur != this; cur = cur.parentNode || this ) { + selMatch = {}; + matches = []; + jqcur[0] = cur; + for ( i = 0; i < delegateCount; i++ ) { + handleObj = handlers[ i ]; + sel = handleObj.selector; + + if ( selMatch[ sel ] === undefined ) { + selMatch[ sel ] = ( + handleObj.quick ? quickIs( cur, handleObj.quick ) : jqcur.is( sel ) + ); + } + if ( selMatch[ sel ] ) { + matches.push( handleObj ); + } + } + if ( matches.length ) { + handlerQueue.push({ elem: cur, matches: matches }); + } + } + } + + // Add the remaining (directly-bound) handlers + if ( handlers.length > delegateCount ) { + handlerQueue.push({ elem: this, matches: handlers.slice( delegateCount ) }); + } + + // Run delegates first; they may want to stop propagation beneath us + for ( i = 0; i < handlerQueue.length && !event.isPropagationStopped(); i++ ) { + matched = handlerQueue[ i ]; + event.currentTarget = matched.elem; + + for ( j = 0; j < matched.matches.length && !event.isImmediatePropagationStopped(); j++ ) { + handleObj = matched.matches[ j ]; + + // Triggered event must either 1) be non-exclusive and have no namespace, or + // 2) have namespace(s) a subset or equal to those in the bound event (both can have no namespace). + if ( run_all || (!event.namespace && !handleObj.namespace) || event.namespace_re && event.namespace_re.test( handleObj.namespace ) ) { + + event.data = handleObj.data; + event.handleObj = handleObj; + + ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler ) + .apply( matched.elem, args ); + + if ( ret !== undefined ) { + event.result = ret; + if ( ret === false ) { + event.preventDefault(); + event.stopPropagation(); + } + } + } + } + } + + return event.result; + }, + + // Includes some event props shared by KeyEvent and MouseEvent + // *** attrChange attrName relatedNode srcElement are not normalized, non-W3C, deprecated, will be removed in 1.8 *** + props: "attrChange attrName relatedNode srcElement altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "), + + fixHooks: {}, + + keyHooks: { + props: "char charCode key keyCode".split(" "), + filter: function( event, original ) { + + // Add which for key events + if ( event.which == null ) { + event.which = original.charCode != null ? original.charCode : original.keyCode; + } + + return event; + } + }, + + mouseHooks: { + props: "button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "), + filter: function( event, original ) { + var eventDoc, doc, body, + button = original.button, + fromElement = original.fromElement; + + // Calculate pageX/Y if missing and clientX/Y available + if ( event.pageX == null && original.clientX != null ) { + eventDoc = event.target.ownerDocument || document; + doc = eventDoc.documentElement; + body = eventDoc.body; + + event.pageX = original.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && body.clientLeft || 0 ); + event.pageY = original.clientY + ( doc && doc.scrollTop || body && body.scrollTop || 0 ) - ( doc && doc.clientTop || body && body.clientTop || 0 ); + } + + // Add relatedTarget, if necessary + if ( !event.relatedTarget && fromElement ) { + event.relatedTarget = fromElement === event.target ? original.toElement : fromElement; + } + + // Add which for click: 1 === left; 2 === middle; 3 === right + // Note: button is not normalized, so don't use it + if ( !event.which && button !== undefined ) { + event.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) ); + } + + return event; + } + }, + + fix: function( event ) { + if ( event[ jQuery.expando ] ) { + return event; + } + + // Create a writable copy of the event object and normalize some properties + var i, prop, + originalEvent = event, + fixHook = jQuery.event.fixHooks[ event.type ] || {}, + copy = fixHook.props ? this.props.concat( fixHook.props ) : this.props; + + event = jQuery.Event( originalEvent ); + + for ( i = copy.length; i; ) { + prop = copy[ --i ]; + event[ prop ] = originalEvent[ prop ]; + } + + // Fix target property, if necessary (#1925, IE 6/7/8 & Safari2) + if ( !event.target ) { + event.target = originalEvent.srcElement || document; + } + + // Target should not be a text node (#504, Safari) + if ( event.target.nodeType === 3 ) { + event.target = event.target.parentNode; + } + + // For mouse/key events; add metaKey if it's not there (#3368, IE6/7/8) + if ( event.metaKey === undefined ) { + event.metaKey = event.ctrlKey; + } + + return fixHook.filter? fixHook.filter( event, originalEvent ) : event; + }, + + special: { + ready: { + // Make sure the ready event is setup + setup: jQuery.bindReady + }, + + load: { + // Prevent triggered image.load events from bubbling to window.load + noBubble: true + }, + + focus: { + delegateType: "focusin" + }, + blur: { + delegateType: "focusout" + }, + + beforeunload: { + setup: function( data, namespaces, eventHandle ) { + // We only want to do this special case on windows + if ( jQuery.isWindow( this ) ) { + this.onbeforeunload = eventHandle; + } + }, + + teardown: function( namespaces, eventHandle ) { + if ( this.onbeforeunload === eventHandle ) { + this.onbeforeunload = null; + } + } + } + }, + + simulate: function( type, elem, event, bubble ) { + // Piggyback on a donor event to simulate a different one. + // Fake originalEvent to avoid donor's stopPropagation, but if the + // simulated event prevents default then we do the same on the donor. + var e = jQuery.extend( + new jQuery.Event(), + event, + { type: type, + isSimulated: true, + originalEvent: {} + } + ); + if ( bubble ) { + jQuery.event.trigger( e, null, elem ); + } else { + jQuery.event.dispatch.call( elem, e ); + } + if ( e.isDefaultPrevented() ) { + event.preventDefault(); + } + } +}; + +// Some plugins are using, but it's undocumented/deprecated and will be removed. +// The 1.7 special event interface should provide all the hooks needed now. +jQuery.event.handle = jQuery.event.dispatch; + +jQuery.removeEvent = document.removeEventListener ? + function( elem, type, handle ) { + if ( elem.removeEventListener ) { + elem.removeEventListener( type, handle, false ); + } + } : + function( elem, type, handle ) { + if ( elem.detachEvent ) { + elem.detachEvent( "on" + type, handle ); + } + }; + +jQuery.Event = function( src, props ) { + // Allow instantiation without the 'new' keyword + if ( !(this instanceof jQuery.Event) ) { + return new jQuery.Event( src, props ); + } + + // Event object + if ( src && src.type ) { + this.originalEvent = src; + this.type = src.type; + + // Events bubbling up the document may have been marked as prevented + // by a handler lower down the tree; reflect the correct value. + this.isDefaultPrevented = ( src.defaultPrevented || src.returnValue === false || + src.getPreventDefault && src.getPreventDefault() ) ? returnTrue : returnFalse; + + // Event type + } else { + this.type = src; + } + + // Put explicitly provided properties onto the event object + if ( props ) { + jQuery.extend( this, props ); + } + + // Create a timestamp if incoming event doesn't have one + this.timeStamp = src && src.timeStamp || jQuery.now(); + + // Mark it as fixed + this[ jQuery.expando ] = true; +}; + +function returnFalse() { + return false; +} +function returnTrue() { + return true; +} + +// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding +// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html +jQuery.Event.prototype = { + preventDefault: function() { + this.isDefaultPrevented = returnTrue; + + var e = this.originalEvent; + if ( !e ) { + return; + } + + // if preventDefault exists run it on the original event + if ( e.preventDefault ) { + e.preventDefault(); + + // otherwise set the returnValue property of the original event to false (IE) + } else { + e.returnValue = false; + } + }, + stopPropagation: function() { + this.isPropagationStopped = returnTrue; + + var e = this.originalEvent; + if ( !e ) { + return; + } + // if stopPropagation exists run it on the original event + if ( e.stopPropagation ) { + e.stopPropagation(); + } + // otherwise set the cancelBubble property of the original event to true (IE) + e.cancelBubble = true; + }, + stopImmediatePropagation: function() { + this.isImmediatePropagationStopped = returnTrue; + this.stopPropagation(); + }, + isDefaultPrevented: returnFalse, + isPropagationStopped: returnFalse, + isImmediatePropagationStopped: returnFalse +}; + +// Create mouseenter/leave events using mouseover/out and event-time checks +jQuery.each({ + mouseenter: "mouseover", + mouseleave: "mouseout" +}, function( orig, fix ) { + jQuery.event.special[ orig ] = { + delegateType: fix, + bindType: fix, + + handle: function( event ) { + var target = this, + related = event.relatedTarget, + handleObj = event.handleObj, + selector = handleObj.selector, + ret; + + // For mousenter/leave call the handler if related is outside the target. + // NB: No relatedTarget if the mouse left/entered the browser window + if ( !related || (related !== target && !jQuery.contains( target, related )) ) { + event.type = handleObj.origType; + ret = handleObj.handler.apply( this, arguments ); + event.type = fix; + } + return ret; + } + }; +}); + +// IE submit delegation +if ( !jQuery.support.submitBubbles ) { + + jQuery.event.special.submit = { + setup: function() { + // Only need this for delegated form submit events + if ( jQuery.nodeName( this, "form" ) ) { + return false; + } + + // Lazy-add a submit handler when a descendant form may potentially be submitted + jQuery.event.add( this, "click._submit keypress._submit", function( e ) { + // Node name check avoids a VML-related crash in IE (#9807) + var elem = e.target, + form = jQuery.nodeName( elem, "input" ) || jQuery.nodeName( elem, "button" ) ? elem.form : undefined; + if ( form && !form._submit_attached ) { + jQuery.event.add( form, "submit._submit", function( event ) { + // If form was submitted by the user, bubble the event up the tree + if ( this.parentNode && !event.isTrigger ) { + jQuery.event.simulate( "submit", this.parentNode, event, true ); + } + }); + form._submit_attached = true; + } + }); + // return undefined since we don't need an event listener + }, + + teardown: function() { + // Only need this for delegated form submit events + if ( jQuery.nodeName( this, "form" ) ) { + return false; + } + + // Remove delegated handlers; cleanData eventually reaps submit handlers attached above + jQuery.event.remove( this, "._submit" ); + } + }; +} + +// IE change delegation and checkbox/radio fix +if ( !jQuery.support.changeBubbles ) { + + jQuery.event.special.change = { + + setup: function() { + + if ( rformElems.test( this.nodeName ) ) { + // IE doesn't fire change on a check/radio until blur; trigger it on click + // after a propertychange. Eat the blur-change in special.change.handle. + // This still fires onchange a second time for check/radio after blur. + if ( this.type === "checkbox" || this.type === "radio" ) { + jQuery.event.add( this, "propertychange._change", function( event ) { + if ( event.originalEvent.propertyName === "checked" ) { + this._just_changed = true; + } + }); + jQuery.event.add( this, "click._change", function( event ) { + if ( this._just_changed && !event.isTrigger ) { + this._just_changed = false; + jQuery.event.simulate( "change", this, event, true ); + } + }); + } + return false; + } + // Delegated event; lazy-add a change handler on descendant inputs + jQuery.event.add( this, "beforeactivate._change", function( e ) { + var elem = e.target; + + if ( rformElems.test( elem.nodeName ) && !elem._change_attached ) { + jQuery.event.add( elem, "change._change", function( event ) { + if ( this.parentNode && !event.isSimulated && !event.isTrigger ) { + jQuery.event.simulate( "change", this.parentNode, event, true ); + } + }); + elem._change_attached = true; + } + }); + }, + + handle: function( event ) { + var elem = event.target; + + // Swallow native change events from checkbox/radio, we already triggered them above + if ( this !== elem || event.isSimulated || event.isTrigger || (elem.type !== "radio" && elem.type !== "checkbox") ) { + return event.handleObj.handler.apply( this, arguments ); + } + }, + + teardown: function() { + jQuery.event.remove( this, "._change" ); + + return rformElems.test( this.nodeName ); + } + }; +} + +// Create "bubbling" focus and blur events +if ( !jQuery.support.focusinBubbles ) { + jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) { + + // Attach a single capturing handler while someone wants focusin/focusout + var attaches = 0, + handler = function( event ) { + jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ), true ); + }; + + jQuery.event.special[ fix ] = { + setup: function() { + if ( attaches++ === 0 ) { + document.addEventListener( orig, handler, true ); + } + }, + teardown: function() { + if ( --attaches === 0 ) { + document.removeEventListener( orig, handler, true ); + } + } + }; + }); +} + +jQuery.fn.extend({ + + on: function( types, selector, data, fn, /*INTERNAL*/ one ) { + var origFn, type; + + // Types can be a map of types/handlers + if ( typeof types === "object" ) { + // ( types-Object, selector, data ) + if ( typeof selector !== "string" ) { + // ( types-Object, data ) + data = selector; + selector = undefined; + } + for ( type in types ) { + this.on( type, selector, data, types[ type ], one ); + } + return this; + } + + if ( data == null && fn == null ) { + // ( types, fn ) + fn = selector; + data = selector = undefined; + } else if ( fn == null ) { + if ( typeof selector === "string" ) { + // ( types, selector, fn ) + fn = data; + data = undefined; + } else { + // ( types, data, fn ) + fn = data; + data = selector; + selector = undefined; + } + } + if ( fn === false ) { + fn = returnFalse; + } else if ( !fn ) { + return this; + } + + if ( one === 1 ) { + origFn = fn; + fn = function( event ) { + // Can use an empty set, since event contains the info + jQuery().off( event ); + return origFn.apply( this, arguments ); + }; + // Use same guid so caller can remove using origFn + fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); + } + return this.each( function() { + jQuery.event.add( this, types, fn, data, selector ); + }); + }, + one: function( types, selector, data, fn ) { + return this.on.call( this, types, selector, data, fn, 1 ); + }, + off: function( types, selector, fn ) { + if ( types && types.preventDefault && types.handleObj ) { + // ( event ) dispatched jQuery.Event + var handleObj = types.handleObj; + jQuery( types.delegateTarget ).off( + handleObj.namespace? handleObj.type + "." + handleObj.namespace : handleObj.type, + handleObj.selector, + handleObj.handler + ); + return this; + } + if ( typeof types === "object" ) { + // ( types-object [, selector] ) + for ( var type in types ) { + this.off( type, selector, types[ type ] ); + } + return this; + } + if ( selector === false || typeof selector === "function" ) { + // ( types [, fn] ) + fn = selector; + selector = undefined; + } + if ( fn === false ) { + fn = returnFalse; + } + return this.each(function() { + jQuery.event.remove( this, types, fn, selector ); + }); + }, + + bind: function( types, data, fn ) { + return this.on( types, null, data, fn ); + }, + unbind: function( types, fn ) { + return this.off( types, null, fn ); + }, + + live: function( types, data, fn ) { + jQuery( this.context ).on( types, this.selector, data, fn ); + return this; + }, + die: function( types, fn ) { + jQuery( this.context ).off( types, this.selector || "**", fn ); + return this; + }, + + delegate: function( selector, types, data, fn ) { + return this.on( types, selector, data, fn ); + }, + undelegate: function( selector, types, fn ) { + // ( namespace ) or ( selector, types [, fn] ) + return arguments.length == 1? this.off( selector, "**" ) : this.off( types, selector, fn ); + }, + + trigger: function( type, data ) { + return this.each(function() { + jQuery.event.trigger( type, data, this ); + }); + }, + triggerHandler: function( type, data ) { + if ( this[0] ) { + return jQuery.event.trigger( type, data, this[0], true ); + } + }, + + toggle: function( fn ) { + // Save reference to arguments for access in closure + var args = arguments, + guid = fn.guid || jQuery.guid++, + i = 0, + toggler = function( event ) { + // Figure out which function to execute + var lastToggle = ( jQuery._data( this, "lastToggle" + fn.guid ) || 0 ) % i; + jQuery._data( this, "lastToggle" + fn.guid, lastToggle + 1 ); + + // Make sure that clicks stop + event.preventDefault(); + + // and execute the function + return args[ lastToggle ].apply( this, arguments ) || false; + }; + + // link all the functions, so any of them can unbind this click handler + toggler.guid = guid; + while ( i < args.length ) { + args[ i++ ].guid = guid; + } + + return this.click( toggler ); + }, + + hover: function( fnOver, fnOut ) { + return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver ); + } +}); + +jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblclick " + + "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " + + "change select submit keydown keypress keyup error contextmenu").split(" "), function( i, name ) { + + // Handle event binding + jQuery.fn[ name ] = function( data, fn ) { + if ( fn == null ) { + fn = data; + data = null; + } + + return arguments.length > 0 ? + this.on( name, null, data, fn ) : + this.trigger( name ); + }; + + if ( jQuery.attrFn ) { + jQuery.attrFn[ name ] = true; + } + + if ( rkeyEvent.test( name ) ) { + jQuery.event.fixHooks[ name ] = jQuery.event.keyHooks; + } + + if ( rmouseEvent.test( name ) ) { + jQuery.event.fixHooks[ name ] = jQuery.event.mouseHooks; + } +}); + + + +/*! + * Sizzle CSS Selector Engine + * Copyright 2011, The Dojo Foundation + * Released under the MIT, BSD, and GPL Licenses. + * More information: http://sizzlejs.com/ + */ +(function(){ + +var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g, + expando = "sizcache" + (Math.random() + '').replace('.', ''), + done = 0, + toString = Object.prototype.toString, + hasDuplicate = false, + baseHasDuplicate = true, + rBackslash = /\\/g, + rReturn = /\r\n/g, + rNonWord = /\W/; + +// Here we check if the JavaScript engine is using some sort of +// optimization where it does not always call our comparision +// function. If that is the case, discard the hasDuplicate value. +// Thus far that includes Google Chrome. +[0, 0].sort(function() { + baseHasDuplicate = false; + return 0; +}); + +var Sizzle = function( selector, context, results, seed ) { + results = results || []; + context = context || document; + + var origContext = context; + + if ( context.nodeType !== 1 && context.nodeType !== 9 ) { + return []; + } + + if ( !selector || typeof selector !== "string" ) { + return results; + } + + var m, set, checkSet, extra, ret, cur, pop, i, + prune = true, + contextXML = Sizzle.isXML( context ), + parts = [], + soFar = selector; + + // Reset the position of the chunker regexp (start from head) + do { + chunker.exec( "" ); + m = chunker.exec( soFar ); + + if ( m ) { + soFar = m[3]; + + parts.push( m[1] ); + + if ( m[2] ) { + extra = m[3]; + break; + } + } + } while ( m ); + + if ( parts.length > 1 && origPOS.exec( selector ) ) { + + if ( parts.length === 2 && Expr.relative[ parts[0] ] ) { + set = posProcess( parts[0] + parts[1], context, seed ); + + } else { + set = Expr.relative[ parts[0] ] ? + [ context ] : + Sizzle( parts.shift(), context ); + + while ( parts.length ) { + selector = parts.shift(); + + if ( Expr.relative[ selector ] ) { + selector += parts.shift(); + } + + set = posProcess( selector, set, seed ); + } + } + + } else { + // Take a shortcut and set the context if the root selector is an ID + // (but not if it'll be faster if the inner selector is an ID) + if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML && + Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) { + + ret = Sizzle.find( parts.shift(), context, contextXML ); + context = ret.expr ? + Sizzle.filter( ret.expr, ret.set )[0] : + ret.set[0]; + } + + if ( context ) { + ret = seed ? + { expr: parts.pop(), set: makeArray(seed) } : + Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML ); + + set = ret.expr ? + Sizzle.filter( ret.expr, ret.set ) : + ret.set; + + if ( parts.length > 0 ) { + checkSet = makeArray( set ); + + } else { + prune = false; + } + + while ( parts.length ) { + cur = parts.pop(); + pop = cur; + + if ( !Expr.relative[ cur ] ) { + cur = ""; + } else { + pop = parts.pop(); + } + + if ( pop == null ) { + pop = context; + } + + Expr.relative[ cur ]( checkSet, pop, contextXML ); + } + + } else { + checkSet = parts = []; + } + } + + if ( !checkSet ) { + checkSet = set; + } + + if ( !checkSet ) { + Sizzle.error( cur || selector ); + } + + if ( toString.call(checkSet) === "[object Array]" ) { + if ( !prune ) { + results.push.apply( results, checkSet ); + + } else if ( context && context.nodeType === 1 ) { + for ( i = 0; checkSet[i] != null; i++ ) { + if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i])) ) { + results.push( set[i] ); + } + } + + } else { + for ( i = 0; checkSet[i] != null; i++ ) { + if ( checkSet[i] && checkSet[i].nodeType === 1 ) { + results.push( set[i] ); + } + } + } + + } else { + makeArray( checkSet, results ); + } + + if ( extra ) { + Sizzle( extra, origContext, results, seed ); + Sizzle.uniqueSort( results ); + } + + return results; +}; + +Sizzle.uniqueSort = function( results ) { + if ( sortOrder ) { + hasDuplicate = baseHasDuplicate; + results.sort( sortOrder ); + + if ( hasDuplicate ) { + for ( var i = 1; i < results.length; i++ ) { + if ( results[i] === results[ i - 1 ] ) { + results.splice( i--, 1 ); + } + } + } + } + + return results; +}; + +Sizzle.matches = function( expr, set ) { + return Sizzle( expr, null, null, set ); +}; + +Sizzle.matchesSelector = function( node, expr ) { + return Sizzle( expr, null, null, [node] ).length > 0; +}; + +Sizzle.find = function( expr, context, isXML ) { + var set, i, len, match, type, left; + + if ( !expr ) { + return []; + } + + for ( i = 0, len = Expr.order.length; i < len; i++ ) { + type = Expr.order[i]; + + if ( (match = Expr.leftMatch[ type ].exec( expr )) ) { + left = match[1]; + match.splice( 1, 1 ); + + if ( left.substr( left.length - 1 ) !== "\\" ) { + match[1] = (match[1] || "").replace( rBackslash, "" ); + set = Expr.find[ type ]( match, context, isXML ); + + if ( set != null ) { + expr = expr.replace( Expr.match[ type ], "" ); + break; + } + } + } + } + + if ( !set ) { + set = typeof context.getElementsByTagName !== "undefined" ? + context.getElementsByTagName( "*" ) : + []; + } + + return { set: set, expr: expr }; +}; + +Sizzle.filter = function( expr, set, inplace, not ) { + var match, anyFound, + type, found, item, filter, left, + i, pass, + old = expr, + result = [], + curLoop = set, + isXMLFilter = set && set[0] && Sizzle.isXML( set[0] ); + + while ( expr && set.length ) { + for ( type in Expr.filter ) { + if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) { + filter = Expr.filter[ type ]; + left = match[1]; + + anyFound = false; + + match.splice(1,1); + + if ( left.substr( left.length - 1 ) === "\\" ) { + continue; + } + + if ( curLoop === result ) { + result = []; + } + + if ( Expr.preFilter[ type ] ) { + match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter ); + + if ( !match ) { + anyFound = found = true; + + } else if ( match === true ) { + continue; + } + } + + if ( match ) { + for ( i = 0; (item = curLoop[i]) != null; i++ ) { + if ( item ) { + found = filter( item, match, i, curLoop ); + pass = not ^ found; + + if ( inplace && found != null ) { + if ( pass ) { + anyFound = true; + + } else { + curLoop[i] = false; + } + + } else if ( pass ) { + result.push( item ); + anyFound = true; + } + } + } + } + + if ( found !== undefined ) { + if ( !inplace ) { + curLoop = result; + } + + expr = expr.replace( Expr.match[ type ], "" ); + + if ( !anyFound ) { + return []; + } + + break; + } + } + } + + // Improper expression + if ( expr === old ) { + if ( anyFound == null ) { + Sizzle.error( expr ); + + } else { + break; + } + } + + old = expr; + } + + return curLoop; +}; + +Sizzle.error = function( msg ) { + throw new Error( "Syntax error, unrecognized expression: " + msg ); +}; + +/** + * Utility function for retreiving the text value of an array of DOM nodes + * @param {Array|Element} elem + */ +var getText = Sizzle.getText = function( elem ) { + var i, node, + nodeType = elem.nodeType, + ret = ""; + + if ( nodeType ) { + if ( nodeType === 1 || nodeType === 9 ) { + // Use textContent || innerText for elements + if ( typeof elem.textContent === 'string' ) { + return elem.textContent; + } else if ( typeof elem.innerText === 'string' ) { + // Replace IE's carriage returns + return elem.innerText.replace( rReturn, '' ); + } else { + // Traverse it's children + for ( elem = elem.firstChild; elem; elem = elem.nextSibling) { + ret += getText( elem ); + } + } + } else if ( nodeType === 3 || nodeType === 4 ) { + return elem.nodeValue; + } + } else { + + // If no nodeType, this is expected to be an array + for ( i = 0; (node = elem[i]); i++ ) { + // Do not traverse comment nodes + if ( node.nodeType !== 8 ) { + ret += getText( node ); + } + } + } + return ret; +}; + +var Expr = Sizzle.selectors = { + order: [ "ID", "NAME", "TAG" ], + + match: { + ID: /#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/, + CLASS: /\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/, + NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/, + ATTR: /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/, + TAG: /^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/, + CHILD: /:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/, + POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/, + PSEUDO: /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/ + }, + + leftMatch: {}, + + attrMap: { + "class": "className", + "for": "htmlFor" + }, + + attrHandle: { + href: function( elem ) { + return elem.getAttribute( "href" ); + }, + type: function( elem ) { + return elem.getAttribute( "type" ); + } + }, + + relative: { + "+": function(checkSet, part){ + var isPartStr = typeof part === "string", + isTag = isPartStr && !rNonWord.test( part ), + isPartStrNotTag = isPartStr && !isTag; + + if ( isTag ) { + part = part.toLowerCase(); + } + + for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) { + if ( (elem = checkSet[i]) ) { + while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {} + + checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ? + elem || false : + elem === part; + } + } + + if ( isPartStrNotTag ) { + Sizzle.filter( part, checkSet, true ); + } + }, + + ">": function( checkSet, part ) { + var elem, + isPartStr = typeof part === "string", + i = 0, + l = checkSet.length; + + if ( isPartStr && !rNonWord.test( part ) ) { + part = part.toLowerCase(); + + for ( ; i < l; i++ ) { + elem = checkSet[i]; + + if ( elem ) { + var parent = elem.parentNode; + checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false; + } + } + + } else { + for ( ; i < l; i++ ) { + elem = checkSet[i]; + + if ( elem ) { + checkSet[i] = isPartStr ? + elem.parentNode : + elem.parentNode === part; + } + } + + if ( isPartStr ) { + Sizzle.filter( part, checkSet, true ); + } + } + }, + + "": function(checkSet, part, isXML){ + var nodeCheck, + doneName = done++, + checkFn = dirCheck; + + if ( typeof part === "string" && !rNonWord.test( part ) ) { + part = part.toLowerCase(); + nodeCheck = part; + checkFn = dirNodeCheck; + } + + checkFn( "parentNode", part, doneName, checkSet, nodeCheck, isXML ); + }, + + "~": function( checkSet, part, isXML ) { + var nodeCheck, + doneName = done++, + checkFn = dirCheck; + + if ( typeof part === "string" && !rNonWord.test( part ) ) { + part = part.toLowerCase(); + nodeCheck = part; + checkFn = dirNodeCheck; + } + + checkFn( "previousSibling", part, doneName, checkSet, nodeCheck, isXML ); + } + }, + + find: { + ID: function( match, context, isXML ) { + if ( typeof context.getElementById !== "undefined" && !isXML ) { + var m = context.getElementById(match[1]); + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document #6963 + return m && m.parentNode ? [m] : []; + } + }, + + NAME: function( match, context ) { + if ( typeof context.getElementsByName !== "undefined" ) { + var ret = [], + results = context.getElementsByName( match[1] ); + + for ( var i = 0, l = results.length; i < l; i++ ) { + if ( results[i].getAttribute("name") === match[1] ) { + ret.push( results[i] ); + } + } + + return ret.length === 0 ? null : ret; + } + }, + + TAG: function( match, context ) { + if ( typeof context.getElementsByTagName !== "undefined" ) { + return context.getElementsByTagName( match[1] ); + } + } + }, + preFilter: { + CLASS: function( match, curLoop, inplace, result, not, isXML ) { + match = " " + match[1].replace( rBackslash, "" ) + " "; + + if ( isXML ) { + return match; + } + + for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) { + if ( elem ) { + if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n\r]/g, " ").indexOf(match) >= 0) ) { + if ( !inplace ) { + result.push( elem ); + } + + } else if ( inplace ) { + curLoop[i] = false; + } + } + } + + return false; + }, + + ID: function( match ) { + return match[1].replace( rBackslash, "" ); + }, + + TAG: function( match, curLoop ) { + return match[1].replace( rBackslash, "" ).toLowerCase(); + }, + + CHILD: function( match ) { + if ( match[1] === "nth" ) { + if ( !match[2] ) { + Sizzle.error( match[0] ); + } + + match[2] = match[2].replace(/^\+|\s*/g, ''); + + // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6' + var test = /(-?)(\d*)(?:n([+\-]?\d*))?/.exec( + match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" || + !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]); + + // calculate the numbers (first)n+(last) including if they are negative + match[2] = (test[1] + (test[2] || 1)) - 0; + match[3] = test[3] - 0; + } + else if ( match[2] ) { + Sizzle.error( match[0] ); + } + + // TODO: Move to normal caching system + match[0] = done++; + + return match; + }, + + ATTR: function( match, curLoop, inplace, result, not, isXML ) { + var name = match[1] = match[1].replace( rBackslash, "" ); + + if ( !isXML && Expr.attrMap[name] ) { + match[1] = Expr.attrMap[name]; + } + + // Handle if an un-quoted value was used + match[4] = ( match[4] || match[5] || "" ).replace( rBackslash, "" ); + + if ( match[2] === "~=" ) { + match[4] = " " + match[4] + " "; + } + + return match; + }, + + PSEUDO: function( match, curLoop, inplace, result, not ) { + if ( match[1] === "not" ) { + // If we're dealing with a complex expression, or a simple one + if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) { + match[3] = Sizzle(match[3], null, null, curLoop); + + } else { + var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not); + + if ( !inplace ) { + result.push.apply( result, ret ); + } + + return false; + } + + } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) { + return true; + } + + return match; + }, + + POS: function( match ) { + match.unshift( true ); + + return match; + } + }, + + filters: { + enabled: function( elem ) { + return elem.disabled === false && elem.type !== "hidden"; + }, + + disabled: function( elem ) { + return elem.disabled === true; + }, + + checked: function( elem ) { + return elem.checked === true; + }, + + selected: function( elem ) { + // Accessing this property makes selected-by-default + // options in Safari work properly + if ( elem.parentNode ) { + elem.parentNode.selectedIndex; + } + + return elem.selected === true; + }, + + parent: function( elem ) { + return !!elem.firstChild; + }, + + empty: function( elem ) { + return !elem.firstChild; + }, + + has: function( elem, i, match ) { + return !!Sizzle( match[3], elem ).length; + }, + + header: function( elem ) { + return (/h\d/i).test( elem.nodeName ); + }, + + text: function( elem ) { + var attr = elem.getAttribute( "type" ), type = elem.type; + // IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc) + // use getAttribute instead to test this case + return elem.nodeName.toLowerCase() === "input" && "text" === type && ( attr === type || attr === null ); + }, + + radio: function( elem ) { + return elem.nodeName.toLowerCase() === "input" && "radio" === elem.type; + }, + + checkbox: function( elem ) { + return elem.nodeName.toLowerCase() === "input" && "checkbox" === elem.type; + }, + + file: function( elem ) { + return elem.nodeName.toLowerCase() === "input" && "file" === elem.type; + }, + + password: function( elem ) { + return elem.nodeName.toLowerCase() === "input" && "password" === elem.type; + }, + + submit: function( elem ) { + var name = elem.nodeName.toLowerCase(); + return (name === "input" || name === "button") && "submit" === elem.type; + }, + + image: function( elem ) { + return elem.nodeName.toLowerCase() === "input" && "image" === elem.type; + }, + + reset: function( elem ) { + var name = elem.nodeName.toLowerCase(); + return (name === "input" || name === "button") && "reset" === elem.type; + }, + + button: function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && "button" === elem.type || name === "button"; + }, + + input: function( elem ) { + return (/input|select|textarea|button/i).test( elem.nodeName ); + }, + + focus: function( elem ) { + return elem === elem.ownerDocument.activeElement; + } + }, + setFilters: { + first: function( elem, i ) { + return i === 0; + }, + + last: function( elem, i, match, array ) { + return i === array.length - 1; + }, + + even: function( elem, i ) { + return i % 2 === 0; + }, + + odd: function( elem, i ) { + return i % 2 === 1; + }, + + lt: function( elem, i, match ) { + return i < match[3] - 0; + }, + + gt: function( elem, i, match ) { + return i > match[3] - 0; + }, + + nth: function( elem, i, match ) { + return match[3] - 0 === i; + }, + + eq: function( elem, i, match ) { + return match[3] - 0 === i; + } + }, + filter: { + PSEUDO: function( elem, match, i, array ) { + var name = match[1], + filter = Expr.filters[ name ]; + + if ( filter ) { + return filter( elem, i, match, array ); + + } else if ( name === "contains" ) { + return (elem.textContent || elem.innerText || getText([ elem ]) || "").indexOf(match[3]) >= 0; + + } else if ( name === "not" ) { + var not = match[3]; + + for ( var j = 0, l = not.length; j < l; j++ ) { + if ( not[j] === elem ) { + return false; + } + } + + return true; + + } else { + Sizzle.error( name ); + } + }, + + CHILD: function( elem, match ) { + var first, last, + doneName, parent, cache, + count, diff, + type = match[1], + node = elem; + + switch ( type ) { + case "only": + case "first": + while ( (node = node.previousSibling) ) { + if ( node.nodeType === 1 ) { + return false; + } + } + + if ( type === "first" ) { + return true; + } + + node = elem; + + case "last": + while ( (node = node.nextSibling) ) { + if ( node.nodeType === 1 ) { + return false; + } + } + + return true; + + case "nth": + first = match[2]; + last = match[3]; + + if ( first === 1 && last === 0 ) { + return true; + } + + doneName = match[0]; + parent = elem.parentNode; + + if ( parent && (parent[ expando ] !== doneName || !elem.nodeIndex) ) { + count = 0; + + for ( node = parent.firstChild; node; node = node.nextSibling ) { + if ( node.nodeType === 1 ) { + node.nodeIndex = ++count; + } + } + + parent[ expando ] = doneName; + } + + diff = elem.nodeIndex - last; + + if ( first === 0 ) { + return diff === 0; + + } else { + return ( diff % first === 0 && diff / first >= 0 ); + } + } + }, + + ID: function( elem, match ) { + return elem.nodeType === 1 && elem.getAttribute("id") === match; + }, + + TAG: function( elem, match ) { + return (match === "*" && elem.nodeType === 1) || !!elem.nodeName && elem.nodeName.toLowerCase() === match; + }, + + CLASS: function( elem, match ) { + return (" " + (elem.className || elem.getAttribute("class")) + " ") + .indexOf( match ) > -1; + }, + + ATTR: function( elem, match ) { + var name = match[1], + result = Sizzle.attr ? + Sizzle.attr( elem, name ) : + Expr.attrHandle[ name ] ? + Expr.attrHandle[ name ]( elem ) : + elem[ name ] != null ? + elem[ name ] : + elem.getAttribute( name ), + value = result + "", + type = match[2], + check = match[4]; + + return result == null ? + type === "!=" : + !type && Sizzle.attr ? + result != null : + type === "=" ? + value === check : + type === "*=" ? + value.indexOf(check) >= 0 : + type === "~=" ? + (" " + value + " ").indexOf(check) >= 0 : + !check ? + value && result !== false : + type === "!=" ? + value !== check : + type === "^=" ? + value.indexOf(check) === 0 : + type === "$=" ? + value.substr(value.length - check.length) === check : + type === "|=" ? + value === check || value.substr(0, check.length + 1) === check + "-" : + false; + }, + + POS: function( elem, match, i, array ) { + var name = match[2], + filter = Expr.setFilters[ name ]; + + if ( filter ) { + return filter( elem, i, match, array ); + } + } + } +}; + +var origPOS = Expr.match.POS, + fescape = function(all, num){ + return "\\" + (num - 0 + 1); + }; + +for ( var type in Expr.match ) { + Expr.match[ type ] = new RegExp( Expr.match[ type ].source + (/(?![^\[]*\])(?![^\(]*\))/.source) ); + Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, fescape) ); +} + +var makeArray = function( array, results ) { + array = Array.prototype.slice.call( array, 0 ); + + if ( results ) { + results.push.apply( results, array ); + return results; + } + + return array; +}; + +// Perform a simple check to determine if the browser is capable of +// converting a NodeList to an array using builtin methods. +// Also verifies that the returned array holds DOM nodes +// (which is not the case in the Blackberry browser) +try { + Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType; + +// Provide a fallback method if it does not work +} catch( e ) { + makeArray = function( array, results ) { + var i = 0, + ret = results || []; + + if ( toString.call(array) === "[object Array]" ) { + Array.prototype.push.apply( ret, array ); + + } else { + if ( typeof array.length === "number" ) { + for ( var l = array.length; i < l; i++ ) { + ret.push( array[i] ); + } + + } else { + for ( ; array[i]; i++ ) { + ret.push( array[i] ); + } + } + } + + return ret; + }; +} + +var sortOrder, siblingCheck; + +if ( document.documentElement.compareDocumentPosition ) { + sortOrder = function( a, b ) { + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) { + return a.compareDocumentPosition ? -1 : 1; + } + + return a.compareDocumentPosition(b) & 4 ? -1 : 1; + }; + +} else { + sortOrder = function( a, b ) { + // The nodes are identical, we can exit early + if ( a === b ) { + hasDuplicate = true; + return 0; + + // Fallback to using sourceIndex (in IE) if it's available on both nodes + } else if ( a.sourceIndex && b.sourceIndex ) { + return a.sourceIndex - b.sourceIndex; + } + + var al, bl, + ap = [], + bp = [], + aup = a.parentNode, + bup = b.parentNode, + cur = aup; + + // If the nodes are siblings (or identical) we can do a quick check + if ( aup === bup ) { + return siblingCheck( a, b ); + + // If no parents were found then the nodes are disconnected + } else if ( !aup ) { + return -1; + + } else if ( !bup ) { + return 1; + } + + // Otherwise they're somewhere else in the tree so we need + // to build up a full list of the parentNodes for comparison + while ( cur ) { + ap.unshift( cur ); + cur = cur.parentNode; + } + + cur = bup; + + while ( cur ) { + bp.unshift( cur ); + cur = cur.parentNode; + } + + al = ap.length; + bl = bp.length; + + // Start walking down the tree looking for a discrepancy + for ( var i = 0; i < al && i < bl; i++ ) { + if ( ap[i] !== bp[i] ) { + return siblingCheck( ap[i], bp[i] ); + } + } + + // We ended someplace up the tree so do a sibling check + return i === al ? + siblingCheck( a, bp[i], -1 ) : + siblingCheck( ap[i], b, 1 ); + }; + + siblingCheck = function( a, b, ret ) { + if ( a === b ) { + return ret; + } + + var cur = a.nextSibling; + + while ( cur ) { + if ( cur === b ) { + return -1; + } + + cur = cur.nextSibling; + } + + return 1; + }; +} + +// Check to see if the browser returns elements by name when +// querying by getElementById (and provide a workaround) +(function(){ + // We're going to inject a fake input element with a specified name + var form = document.createElement("div"), + id = "script" + (new Date()).getTime(), + root = document.documentElement; + + form.innerHTML = ""; + + // Inject it into the root element, check its status, and remove it quickly + root.insertBefore( form, root.firstChild ); + + // The workaround has to do additional checks after a getElementById + // Which slows things down for other browsers (hence the branching) + if ( document.getElementById( id ) ) { + Expr.find.ID = function( match, context, isXML ) { + if ( typeof context.getElementById !== "undefined" && !isXML ) { + var m = context.getElementById(match[1]); + + return m ? + m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? + [m] : + undefined : + []; + } + }; + + Expr.filter.ID = function( elem, match ) { + var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id"); + + return elem.nodeType === 1 && node && node.nodeValue === match; + }; + } + + root.removeChild( form ); + + // release memory in IE + root = form = null; +})(); + +(function(){ + // Check to see if the browser returns only elements + // when doing getElementsByTagName("*") + + // Create a fake element + var div = document.createElement("div"); + div.appendChild( document.createComment("") ); + + // Make sure no comments are found + if ( div.getElementsByTagName("*").length > 0 ) { + Expr.find.TAG = function( match, context ) { + var results = context.getElementsByTagName( match[1] ); + + // Filter out possible comments + if ( match[1] === "*" ) { + var tmp = []; + + for ( var i = 0; results[i]; i++ ) { + if ( results[i].nodeType === 1 ) { + tmp.push( results[i] ); + } + } + + results = tmp; + } + + return results; + }; + } + + // Check to see if an attribute returns normalized href attributes + div.innerHTML = ""; + + if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" && + div.firstChild.getAttribute("href") !== "#" ) { + + Expr.attrHandle.href = function( elem ) { + return elem.getAttribute( "href", 2 ); + }; + } + + // release memory in IE + div = null; +})(); + +if ( document.querySelectorAll ) { + (function(){ + var oldSizzle = Sizzle, + div = document.createElement("div"), + id = "__sizzle__"; + + div.innerHTML = "

"; + + // Safari can't handle uppercase or unicode characters when + // in quirks mode. + if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) { + return; + } + + Sizzle = function( query, context, extra, seed ) { + context = context || document; + + // Only use querySelectorAll on non-XML documents + // (ID selectors don't work in non-HTML documents) + if ( !seed && !Sizzle.isXML(context) ) { + // See if we find a selector to speed up + var match = /^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec( query ); + + if ( match && (context.nodeType === 1 || context.nodeType === 9) ) { + // Speed-up: Sizzle("TAG") + if ( match[1] ) { + return makeArray( context.getElementsByTagName( query ), extra ); + + // Speed-up: Sizzle(".CLASS") + } else if ( match[2] && Expr.find.CLASS && context.getElementsByClassName ) { + return makeArray( context.getElementsByClassName( match[2] ), extra ); + } + } + + if ( context.nodeType === 9 ) { + // Speed-up: Sizzle("body") + // The body element only exists once, optimize finding it + if ( query === "body" && context.body ) { + return makeArray( [ context.body ], extra ); + + // Speed-up: Sizzle("#ID") + } else if ( match && match[3] ) { + var elem = context.getElementById( match[3] ); + + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document #6963 + if ( elem && elem.parentNode ) { + // Handle the case where IE and Opera return items + // by name instead of ID + if ( elem.id === match[3] ) { + return makeArray( [ elem ], extra ); + } + + } else { + return makeArray( [], extra ); + } + } + + try { + return makeArray( context.querySelectorAll(query), extra ); + } catch(qsaError) {} + + // qSA works strangely on Element-rooted queries + // We can work around this by specifying an extra ID on the root + // and working up from there (Thanks to Andrew Dupont for the technique) + // IE 8 doesn't work on object elements + } else if ( context.nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) { + var oldContext = context, + old = context.getAttribute( "id" ), + nid = old || id, + hasParent = context.parentNode, + relativeHierarchySelector = /^\s*[+~]/.test( query ); + + if ( !old ) { + context.setAttribute( "id", nid ); + } else { + nid = nid.replace( /'/g, "\\$&" ); + } + if ( relativeHierarchySelector && hasParent ) { + context = context.parentNode; + } + + try { + if ( !relativeHierarchySelector || hasParent ) { + return makeArray( context.querySelectorAll( "[id='" + nid + "'] " + query ), extra ); + } + + } catch(pseudoError) { + } finally { + if ( !old ) { + oldContext.removeAttribute( "id" ); + } + } + } + } + + return oldSizzle(query, context, extra, seed); + }; + + for ( var prop in oldSizzle ) { + Sizzle[ prop ] = oldSizzle[ prop ]; + } + + // release memory in IE + div = null; + })(); +} + +(function(){ + var html = document.documentElement, + matches = html.matchesSelector || html.mozMatchesSelector || html.webkitMatchesSelector || html.msMatchesSelector; + + if ( matches ) { + // Check to see if it's possible to do matchesSelector + // on a disconnected node (IE 9 fails this) + var disconnectedMatch = !matches.call( document.createElement( "div" ), "div" ), + pseudoWorks = false; + + try { + // This should fail with an exception + // Gecko does not error, returns false instead + matches.call( document.documentElement, "[test!='']:sizzle" ); + + } catch( pseudoError ) { + pseudoWorks = true; + } + + Sizzle.matchesSelector = function( node, expr ) { + // Make sure that attribute selectors are quoted + expr = expr.replace(/\=\s*([^'"\]]*)\s*\]/g, "='$1']"); + + if ( !Sizzle.isXML( node ) ) { + try { + if ( pseudoWorks || !Expr.match.PSEUDO.test( expr ) && !/!=/.test( expr ) ) { + var ret = matches.call( node, expr ); + + // IE 9's matchesSelector returns false on disconnected nodes + if ( ret || !disconnectedMatch || + // As well, disconnected nodes are said to be in a document + // fragment in IE 9, so check for that + node.document && node.document.nodeType !== 11 ) { + return ret; + } + } + } catch(e) {} + } + + return Sizzle(expr, null, null, [node]).length > 0; + }; + } +})(); + +(function(){ + var div = document.createElement("div"); + + div.innerHTML = "
"; + + // Opera can't find a second classname (in 9.6) + // Also, make sure that getElementsByClassName actually exists + if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) { + return; + } + + // Safari caches class attributes, doesn't catch changes (in 3.2) + div.lastChild.className = "e"; + + if ( div.getElementsByClassName("e").length === 1 ) { + return; + } + + Expr.order.splice(1, 0, "CLASS"); + Expr.find.CLASS = function( match, context, isXML ) { + if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) { + return context.getElementsByClassName(match[1]); + } + }; + + // release memory in IE + div = null; +})(); + +function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + + if ( elem ) { + var match = false; + + elem = elem[dir]; + + while ( elem ) { + if ( elem[ expando ] === doneName ) { + match = checkSet[elem.sizset]; + break; + } + + if ( elem.nodeType === 1 && !isXML ){ + elem[ expando ] = doneName; + elem.sizset = i; + } + + if ( elem.nodeName.toLowerCase() === cur ) { + match = elem; + break; + } + + elem = elem[dir]; + } + + checkSet[i] = match; + } + } +} + +function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + + if ( elem ) { + var match = false; + + elem = elem[dir]; + + while ( elem ) { + if ( elem[ expando ] === doneName ) { + match = checkSet[elem.sizset]; + break; + } + + if ( elem.nodeType === 1 ) { + if ( !isXML ) { + elem[ expando ] = doneName; + elem.sizset = i; + } + + if ( typeof cur !== "string" ) { + if ( elem === cur ) { + match = true; + break; + } + + } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) { + match = elem; + break; + } + } + + elem = elem[dir]; + } + + checkSet[i] = match; + } + } +} + +if ( document.documentElement.contains ) { + Sizzle.contains = function( a, b ) { + return a !== b && (a.contains ? a.contains(b) : true); + }; + +} else if ( document.documentElement.compareDocumentPosition ) { + Sizzle.contains = function( a, b ) { + return !!(a.compareDocumentPosition(b) & 16); + }; + +} else { + Sizzle.contains = function() { + return false; + }; +} + +Sizzle.isXML = function( elem ) { + // documentElement is verified for cases where it doesn't yet exist + // (such as loading iframes in IE - #4833) + var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement; + + return documentElement ? documentElement.nodeName !== "HTML" : false; +}; + +var posProcess = function( selector, context, seed ) { + var match, + tmpSet = [], + later = "", + root = context.nodeType ? [context] : context; + + // Position selectors must be done after the filter + // And so must :not(positional) so we move all PSEUDOs to the end + while ( (match = Expr.match.PSEUDO.exec( selector )) ) { + later += match[0]; + selector = selector.replace( Expr.match.PSEUDO, "" ); + } + + selector = Expr.relative[selector] ? selector + "*" : selector; + + for ( var i = 0, l = root.length; i < l; i++ ) { + Sizzle( selector, root[i], tmpSet, seed ); + } + + return Sizzle.filter( later, tmpSet ); +}; + +// EXPOSE +// Override sizzle attribute retrieval +Sizzle.attr = jQuery.attr; +Sizzle.selectors.attrMap = {}; +jQuery.find = Sizzle; +jQuery.expr = Sizzle.selectors; +jQuery.expr[":"] = jQuery.expr.filters; +jQuery.unique = Sizzle.uniqueSort; +jQuery.text = Sizzle.getText; +jQuery.isXMLDoc = Sizzle.isXML; +jQuery.contains = Sizzle.contains; + + +})(); + + +var runtil = /Until$/, + rparentsprev = /^(?:parents|prevUntil|prevAll)/, + // Note: This RegExp should be improved, or likely pulled from Sizzle + rmultiselector = /,/, + isSimple = /^.[^:#\[\.,]*$/, + slice = Array.prototype.slice, + POS = jQuery.expr.match.POS, + // methods guaranteed to produce a unique set when starting from a unique set + guaranteedUnique = { + children: true, + contents: true, + next: true, + prev: true + }; + +jQuery.fn.extend({ + find: function( selector ) { + var self = this, + i, l; + + if ( typeof selector !== "string" ) { + return jQuery( selector ).filter(function() { + for ( i = 0, l = self.length; i < l; i++ ) { + if ( jQuery.contains( self[ i ], this ) ) { + return true; + } + } + }); + } + + var ret = this.pushStack( "", "find", selector ), + length, n, r; + + for ( i = 0, l = this.length; i < l; i++ ) { + length = ret.length; + jQuery.find( selector, this[i], ret ); + + if ( i > 0 ) { + // Make sure that the results are unique + for ( n = length; n < ret.length; n++ ) { + for ( r = 0; r < length; r++ ) { + if ( ret[r] === ret[n] ) { + ret.splice(n--, 1); + break; + } + } + } + } + } + + return ret; + }, + + has: function( target ) { + var targets = jQuery( target ); + return this.filter(function() { + for ( var i = 0, l = targets.length; i < l; i++ ) { + if ( jQuery.contains( this, targets[i] ) ) { + return true; + } + } + }); + }, + + not: function( selector ) { + return this.pushStack( winnow(this, selector, false), "not", selector); + }, + + filter: function( selector ) { + return this.pushStack( winnow(this, selector, true), "filter", selector ); + }, + + is: function( selector ) { + return !!selector && ( + typeof selector === "string" ? + // If this is a positional selector, check membership in the returned set + // so $("p:first").is("p:last") won't return true for a doc with two "p". + POS.test( selector ) ? + jQuery( selector, this.context ).index( this[0] ) >= 0 : + jQuery.filter( selector, this ).length > 0 : + this.filter( selector ).length > 0 ); + }, + + closest: function( selectors, context ) { + var ret = [], i, l, cur = this[0]; + + // Array (deprecated as of jQuery 1.7) + if ( jQuery.isArray( selectors ) ) { + var level = 1; + + while ( cur && cur.ownerDocument && cur !== context ) { + for ( i = 0; i < selectors.length; i++ ) { + + if ( jQuery( cur ).is( selectors[ i ] ) ) { + ret.push({ selector: selectors[ i ], elem: cur, level: level }); + } + } + + cur = cur.parentNode; + level++; + } + + return ret; + } + + // String + var pos = POS.test( selectors ) || typeof selectors !== "string" ? + jQuery( selectors, context || this.context ) : + 0; + + for ( i = 0, l = this.length; i < l; i++ ) { + cur = this[i]; + + while ( cur ) { + if ( pos ? pos.index(cur) > -1 : jQuery.find.matchesSelector(cur, selectors) ) { + ret.push( cur ); + break; + + } else { + cur = cur.parentNode; + if ( !cur || !cur.ownerDocument || cur === context || cur.nodeType === 11 ) { + break; + } + } + } + } + + ret = ret.length > 1 ? jQuery.unique( ret ) : ret; + + return this.pushStack( ret, "closest", selectors ); + }, + + // Determine the position of an element within + // the matched set of elements + index: function( elem ) { + + // No argument, return index in parent + if ( !elem ) { + return ( this[0] && this[0].parentNode ) ? this.prevAll().length : -1; + } + + // index in selector + if ( typeof elem === "string" ) { + return jQuery.inArray( this[0], jQuery( elem ) ); + } + + // Locate the position of the desired element + return jQuery.inArray( + // If it receives a jQuery object, the first element is used + elem.jquery ? elem[0] : elem, this ); + }, + + add: function( selector, context ) { + var set = typeof selector === "string" ? + jQuery( selector, context ) : + jQuery.makeArray( selector && selector.nodeType ? [ selector ] : selector ), + all = jQuery.merge( this.get(), set ); + + return this.pushStack( isDisconnected( set[0] ) || isDisconnected( all[0] ) ? + all : + jQuery.unique( all ) ); + }, + + andSelf: function() { + return this.add( this.prevObject ); + } +}); + +// A painfully simple check to see if an element is disconnected +// from a document (should be improved, where feasible). +function isDisconnected( node ) { + return !node || !node.parentNode || node.parentNode.nodeType === 11; +} + +jQuery.each({ + parent: function( elem ) { + var parent = elem.parentNode; + return parent && parent.nodeType !== 11 ? parent : null; + }, + parents: function( elem ) { + return jQuery.dir( elem, "parentNode" ); + }, + parentsUntil: function( elem, i, until ) { + return jQuery.dir( elem, "parentNode", until ); + }, + next: function( elem ) { + return jQuery.nth( elem, 2, "nextSibling" ); + }, + prev: function( elem ) { + return jQuery.nth( elem, 2, "previousSibling" ); + }, + nextAll: function( elem ) { + return jQuery.dir( elem, "nextSibling" ); + }, + prevAll: function( elem ) { + return jQuery.dir( elem, "previousSibling" ); + }, + nextUntil: function( elem, i, until ) { + return jQuery.dir( elem, "nextSibling", until ); + }, + prevUntil: function( elem, i, until ) { + return jQuery.dir( elem, "previousSibling", until ); + }, + siblings: function( elem ) { + return jQuery.sibling( elem.parentNode.firstChild, elem ); + }, + children: function( elem ) { + return jQuery.sibling( elem.firstChild ); + }, + contents: function( elem ) { + return jQuery.nodeName( elem, "iframe" ) ? + elem.contentDocument || elem.contentWindow.document : + jQuery.makeArray( elem.childNodes ); + } +}, function( name, fn ) { + jQuery.fn[ name ] = function( until, selector ) { + var ret = jQuery.map( this, fn, until ); + + if ( !runtil.test( name ) ) { + selector = until; + } + + if ( selector && typeof selector === "string" ) { + ret = jQuery.filter( selector, ret ); + } + + ret = this.length > 1 && !guaranteedUnique[ name ] ? jQuery.unique( ret ) : ret; + + if ( (this.length > 1 || rmultiselector.test( selector )) && rparentsprev.test( name ) ) { + ret = ret.reverse(); + } + + return this.pushStack( ret, name, slice.call( arguments ).join(",") ); + }; +}); + +jQuery.extend({ + filter: function( expr, elems, not ) { + if ( not ) { + expr = ":not(" + expr + ")"; + } + + return elems.length === 1 ? + jQuery.find.matchesSelector(elems[0], expr) ? [ elems[0] ] : [] : + jQuery.find.matches(expr, elems); + }, + + dir: function( elem, dir, until ) { + var matched = [], + cur = elem[ dir ]; + + while ( cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !jQuery( cur ).is( until )) ) { + if ( cur.nodeType === 1 ) { + matched.push( cur ); + } + cur = cur[dir]; + } + return matched; + }, + + nth: function( cur, result, dir, elem ) { + result = result || 1; + var num = 0; + + for ( ; cur; cur = cur[dir] ) { + if ( cur.nodeType === 1 && ++num === result ) { + break; + } + } + + return cur; + }, + + sibling: function( n, elem ) { + var r = []; + + for ( ; n; n = n.nextSibling ) { + if ( n.nodeType === 1 && n !== elem ) { + r.push( n ); + } + } + + return r; + } +}); + +// Implement the identical functionality for filter and not +function winnow( elements, qualifier, keep ) { + + // Can't pass null or undefined to indexOf in Firefox 4 + // Set to 0 to skip string check + qualifier = qualifier || 0; + + if ( jQuery.isFunction( qualifier ) ) { + return jQuery.grep(elements, function( elem, i ) { + var retVal = !!qualifier.call( elem, i, elem ); + return retVal === keep; + }); + + } else if ( qualifier.nodeType ) { + return jQuery.grep(elements, function( elem, i ) { + return ( elem === qualifier ) === keep; + }); + + } else if ( typeof qualifier === "string" ) { + var filtered = jQuery.grep(elements, function( elem ) { + return elem.nodeType === 1; + }); + + if ( isSimple.test( qualifier ) ) { + return jQuery.filter(qualifier, filtered, !keep); + } else { + qualifier = jQuery.filter( qualifier, filtered ); + } + } + + return jQuery.grep(elements, function( elem, i ) { + return ( jQuery.inArray( elem, qualifier ) >= 0 ) === keep; + }); +} + + + + +function createSafeFragment( document ) { + var list = nodeNames.split( "|" ), + safeFrag = document.createDocumentFragment(); + + if ( safeFrag.createElement ) { + while ( list.length ) { + safeFrag.createElement( + list.pop() + ); + } + } + return safeFrag; +} + +var nodeNames = "abbr|article|aside|audio|canvas|datalist|details|figcaption|figure|footer|" + + "header|hgroup|mark|meter|nav|output|progress|section|summary|time|video", + rinlinejQuery = / jQuery\d+="(?:\d+|null)"/g, + rleadingWhitespace = /^\s+/, + rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig, + rtagName = /<([\w:]+)/, + rtbody = /", "" ], + legend: [ 1, "
", "
" ], + thead: [ 1, "", "
" ], + tr: [ 2, "", "
" ], + td: [ 3, "", "
" ], + col: [ 2, "", "
" ], + area: [ 1, "", "" ], + _default: [ 0, "", "" ] + }, + safeFragment = createSafeFragment( document ); + +wrapMap.optgroup = wrapMap.option; +wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; +wrapMap.th = wrapMap.td; + +// IE can't serialize and + + + + + + + + + + + +
+
+
+
+ +
+

client – HPLeftHandClient¶

+

HPLeftHand REST Client

+ +++ + + + + + +
Author:Kurt Martin
Description:This is the LeftHand/StoreVirtual Client that talks to the
+

LeftHand OS REST Service.

+

This client requires and works with version 11.5 of the LeftHand firmware

+
+
+class hplefthandclient.client.HPLeftHandClient(api_url)[source]¶
+
+
+debug_rest(flag)[source]¶
+

This is useful for debugging requests to LeftHand

+ +++ + + + +
Parameters:flag (bool) – set to True to enable debugging
+
+ +
+
+login(username, password)[source]¶
+

This authenticates against the LH OS REST server and creates a session.

+ +++ + + + + + +
Parameters:
    +
  • username (str) – The username
  • +
  • password (str) – The password
  • +
+
Returns:

None

+
+
+ +
+
+logout()[source]¶
+

This destroys the session and logs out from the LH OS server

+ +++ + + + +
Returns:None
+
+ +
+
+getClusters()[source]¶
+

Get the list of Clusters

+ +++ + + + +
Returns:list of Clusters
+
+ +
+
+getCluster(cluster_id)[source]¶
+

Get information about a Cluster

+ +++ + + + + + +
Parameters:cluster_id (str) – The id of the cluster to find
Returns:cluster
+
+ +
+
+getClusterByName(name)[source]¶
+

Get information about a cluster by name

+ +++ + + + + + + + +
Parameters:name (str) – The name of the cluster to find
Returns:cluster
Raises :HTTPNotFound -
+

NON_EXISTENT_CLUSTER - cluster doesn’t exist

+
+ +
+
+getServers()[source]¶
+

Get the list of Servers

+ +++ + + + +
Returns:list of Servers
+
+ +
+
+getServer(server_id)[source]¶
+

Get information about a server

+ +++ + + + + + + + +
Parameters:server_id (str) – The id of the server to find
Returns:server
Raises :HTTPServerError
+
+ +
+
+getServerByName(name)[source]¶
+

Get information about a server by name

+ +++ + + + + + + + +
Parameters:name (str) – The name of the server to find
Returns:server
Raises :HTTPNotFound -
+

NON_EXISTENT_SERVER - server doesn’t exist

+
+ +
+
+createServer(name, iqn, optional=None)[source]¶
+

Create a server by name

+ +++ + + + +
Parameters:
    +
  • name (str) – The name of the server to create
  • +
  • iqn – The iSCSI qualified name
  • +
  • optional (dict) – Dictionary of optional params
  • +
+
+
optional = {
+    'description' : "some comment",
+    'iscsiEnabled' : True,
+    'chapName': "some chap name",
+    'chapAuthenticationRequired': False,
+    'chapInitiatorSecret': "initiator secret",
+    'chapTargetSecret': "target secret",
+    'iscsiLoadBalancingEnabled': True,
+    'controllingServerName': "server name",
+    'fibreChannelEnabled': False,
+    'inServerCluster": True
+}
+
+
+ +++ + + + + + +
Returns:server
Raises :HTTPNotFound -
+

NON_EXISTENT_SERVER - server doesn’t exist

+
+ +
+
+deleteServer(server_id)[source]¶
+

Delete a Server

+ +++ + + + + + +
Parameters:server_id – the server ID to delete
Raises :HTTPNotFound -
+

NON_EXISTENT_SERVER - The server does not exist

+
+ +
+
+getSnapshots()[source]¶
+

Get the list of Snapshots

+ +++ + + + +
Returns:list of Snapshots
+
+ +
+
+getSnapshot(snapshot_id)[source]¶
+

Get information about a Snapshot

+ +++ + + + + + +
Returns:snapshot
Raises :HTTPServerError
+
+ +
+
+getSnapshotByName(name)[source]¶
+

Get information about a snapshot by name

+ +++ + + + + + + + +
Parameters:name – The name of the snapshot to find
Returns:volume
Raises :HTTPNotFound -
+

NON_EXISTENT_SNAP - shapshot doesn’t exist

+
+ +
+
+createSnapshot(name, source_volume_id, optional=None)[source]¶
+

Create a snapshot of an existing Volume

+ +++ + + + +
Parameters:
    +
  • name (str) – Name of the Snapshot
  • +
  • source_volume_id (int) – The volume you want to snapshot
  • +
  • optional (dict) – Dictionary of optional params
  • +
+
+
optional = {
+    'description' : "some comment",
+    'inheritAccess' : false
+}
+
+
+
+ +
+
+deleteSnapshot(snapshot_id)[source]¶
+

Delete a Snapshot

+ +++ + + + + + +
Parameters:snapshot_id – the snapshot ID to delete
Raises :HTTPNotFound -
+

NON_EXISTENT_SNAPSHOT - The snapshot does not exist

+
+ +
+
+cloneSnapshot(name, source_snapshot_id, optional=None)[source]¶
+

Create a clone of an existing Shapshot

+ +++ + + + +
Parameters:
    +
  • name (str) – Name of the Snapshot clone
  • +
  • source_snapshot_id (int) – The snapshot you want to clone
  • +
  • optional (dict) – Dictionary of optional params
  • +
+
+
optional = {
+    'description' : "some comment"
+}
+
+
+
+ +
+
+getVolumes()[source]¶
+

Get the list of Volumes

+ +++ + + + +
Returns:list of Volumes
+
+ +
+
+getVolume(volume_id)[source]¶
+

Get information about a volume

+ +++ + + + + + + + +
Parameters:volume_id (str) – The id of the volume to find
Returns:volume
Raises :HTTPNotFound -
+

NON_EXISTENT_VOL - volume doesn’t exist

+
+ +
+
+getVolumeByName(name)[source]¶
+

Get information about a volume by name

+ +++ + + + + + + + +
Parameters:name – The name of the volume to find
Returns:volume
Raises :HTTPNotFound -
+

NON_EXISTENT_VOL - volume doesn’t exist

+
+ +
+
+createVolume(name, cluster_id, size, optional=None)[source]¶
+

Create a new volume

+ +++ + + + +
Parameters:
    +
  • name (str) – the name of the volume
  • +
  • cluster_id (int) – the cluster Id
  • +
  • sizeKB (int) – size in KB for the volume
  • +
  • optional (dict) – dict of other optional items
  • +
+
+
optional = {
+ 'description': 'some comment',
+ 'isThinProvisioned': 'true',
+ 'autogrowSeconds': 200,
+ 'clusterName': 'somename',
+ 'isAdaptiveOptimizationEnabled': 'true',
+ 'dataProtectionLevel': 2,
+}
+
+
+ +++ + + + + + +
Returns:List of Volumes
Raises :HTTPConflict -
+

EXISTENT_SV - Volume Exists already

+
+ +
+
+deleteVolume(volume_id)[source]¶
+

Delete a volume

+ +++ + + + + + +
Parameters:name (str) – the name of the volume
Raises :HTTPNotFound -
+

NON_EXISTENT_VOL - The volume does not exist

+
+ +
+
+modifyVolume(volume_id, optional)[source]¶
+

Modify an existing volume.

+ +++ + + + + + + + +
Parameters:volume_id (str) – The id of the volume to find
Returns:volume
Raises :HTTPNotFound -
+

NON_EXISTENT_VOL - volume doesn’t exist

+
+ +
+
+cloneVolume(name, source_volume_id, optional=None)[source]¶
+

Create a clone of an existing Volume

+ +++ + + + +
Parameters:
    +
  • name (str) – Name of the Volume clone
  • +
  • source_volume_id (int) – The Volume you want to clone
  • +
  • optional (dict) – Dictionary of optional params
  • +
+
+
optional = {
+    'description' : "some comment"
+}
+
+
+
+ +
+
+addServerAccess(volume_id, server_id, optional=None)[source]¶
+

Assign a Volume to a Server

+ +++ + + + +
Parameters:
    +
  • volume_id – Volume ID of the volume
  • +
  • server_id – Server ID of the server to add the volume to
  • +
  • optional (dict) – Dictionary of optional params
  • +
+
+
optional = {
+    'Transport' : 0,
+    'Lun' : 1,
+}
+
+
+
+ +
+
+removeServerAccess(volume_id, server_id)[source]¶
+

Unassign a Volume from a Server

+ +++ + + + +
Parameters:
    +
  • volume_id – Volume ID of the volume
  • +
  • server_id – Server ID of the server to remove the volume fom
  • +
+
+
+ +
+ +
+ + +
+
+
+
+
+

Previous topic

+

client – HPLeftHandClient

+

Next topic

+

exceptions – HTTP Exceptions

+

This Page

+ + + +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/api/hplefthandclient/exceptions.html b/docs/_build/html/api/hplefthandclient/exceptions.html new file mode 100644 index 0000000..d14374c --- /dev/null +++ b/docs/_build/html/api/hplefthandclient/exceptions.html @@ -0,0 +1,146 @@ + + + + + + + + + + exceptions – HTTP Exceptions — HP LeftHand REST Client 1.0.0 documentation + + + + + + + + + + + + + + + + +
+
+
+
+ +
+

exceptions – HTTP Exceptions¶

+

Exceptions for the client

+ +++ + + + + + +
Author:Walter A. Boring IV
Description:This contains the HTTP exceptions that can come back from the REST calls
+
+
+class hplefthandclient.exceptions.HTTPNotFound(error=None)[source]¶
+

HTTP 404 - Not found

+
+ +
+
+class hplefthandclient.exceptions.HTTPBadRequest(error=None)[source]¶
+

HTTP 400 - Bad request: you sent some malformed data.

+
+ +
+ + +
+
+
+
+
+

Previous topic

+

client – HPLeftHandClient

+

Next topic

+

http – HTTP REST Base Class

+

This Page

+ + + +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/api/hplefthandclient/http.html b/docs/_build/html/api/hplefthandclient/http.html new file mode 100644 index 0000000..bb73e48 --- /dev/null +++ b/docs/_build/html/api/hplefthandclient/http.html @@ -0,0 +1,118 @@ + + + + + + + + + + http – HTTP REST Base Class — HP LeftHand REST Client 1.0.0 documentation + + + + + + + + + + + + + + + +
+
+
+
+ +
+

http – HTTP REST Base Class¶

+

HPLeftHand HTTP Client +:Author: Walter A. Boring IV +:Description: This is the HTTP Client that is used to make the actual calls.

+
+
It includes the authentication that knows the cookie name for LH.
+
+ + +
+
+
+
+
+

Previous topic

+

exceptions – HTTP Exceptions

+

This Page

+ + + +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/api/hplefthandclient/index.html b/docs/_build/html/api/hplefthandclient/index.html new file mode 100644 index 0000000..a5a5336 --- /dev/null +++ b/docs/_build/html/api/hplefthandclient/index.html @@ -0,0 +1,150 @@ + + + + + + + + + + client – HPLeftHandClient — HP LeftHand REST Client 1.0.0 documentation + + + + + + + + + + + + + + + + +
+
+
+
+ +
+

client – HPLeftHandClient¶

+

HP LeftHand REST Client

+ +++ + + + + + + + + + +
Author:Kurt Martin
Author:Walter A. Boring IV
Copyright:Copyright 2013, Hewlett Packard Development Company, L.P.
License:Apache v2.0
+
+
+hplefthandclient.version = '1.0.0'¶
+

Current version of HPLeftHandClient.

+
+ +

Sub-modules:

+ +
+ + +
+
+
+
+
+

Previous topic

+

API Documentation

+

Next topic

+

client – HPLeftHandClient

+

This Page

+ + + +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/api/index.html b/docs/_build/html/api/index.html new file mode 100644 index 0000000..3639bca --- /dev/null +++ b/docs/_build/html/api/index.html @@ -0,0 +1,130 @@ + + + + + + + + + + API Documentation — HP LeftHand REST Client 1.0.0 documentation + + + + + + + + + + + + + + + +
+
+
+
+ +
+

API Documentation¶

+

The HP LeftHand Client package contains a hplefthandclient class which extends a more +generic http class for doing REST calls

+ +
+ + +
+
+
+
+
+

Previous topic

+

Changelog

+

Next topic

+

client – HPLeftHandClient

+

This Page

+ + + +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/changelog.html b/docs/_build/html/changelog.html new file mode 100644 index 0000000..5b35327 --- /dev/null +++ b/docs/_build/html/changelog.html @@ -0,0 +1,132 @@ + + + + + + + + + + Changelog — HP LeftHand REST Client 1.0.0 documentation + + + + + + + + + + + + + + + +
+
+
+
+ +
+

Changelog¶

+
+

Changes in Version 1.0.0¶

+
    +
  • First implementation of the REST API Client
  • +
+
+
+ + +
+
+
+
+
+

Table Of Contents

+ + +

Previous topic

+

Tutorial

+

Next topic

+

API Documentation

+

This Page

+ + + +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/genindex.html b/docs/_build/html/genindex.html new file mode 100644 index 0000000..b1a98b9 --- /dev/null +++ b/docs/_build/html/genindex.html @@ -0,0 +1,755 @@ + + + + + + + + + + + + Index — HP LeftHand REST Client 1.0.0 documentation + + + + + + + + + + + + + +
+
+
+
+ + +

Index

+ +
+ A + | C + | D + | F + | G + | H + | L + | M + | N + | P + | R + | S + | U + | V + +
+

A

+ + + +
+ +
addServerAccess() (hplefthandclient.client.HPLeftHandClient method), [1] +
+ + +
authenticate() (hplefthandclient.http.HTTPJSONRESTClient method) +
+ +
+ +
AuthorizationFailure +
+ +
+ +

C

+ + + +
+ +
ClientException +
+ + +
cloneSnapshot() (hplefthandclient.client.HPLeftHandClient method), [1] +
+ + +
cloneVolume() (hplefthandclient.client.HPLeftHandClient method), [1] +
+ + +
CommandError +
+ +
+ +
createServer() (hplefthandclient.client.HPLeftHandClient method), [1] +
+ + +
createSnapshot() (hplefthandclient.client.HPLeftHandClient method), [1] +
+ + +
createVolume() (hplefthandclient.client.HPLeftHandClient method), [1] +
+ +
+ +

D

+ + + +
+ +
debug_rest() (hplefthandclient.client.HPLeftHandClient method), [1] +
+ + +
delete() (hplefthandclient.http.HTTPJSONRESTClient method) +
+ + +
deleteServer() (hplefthandclient.client.HPLeftHandClient method), [1] +
+ +
+ +
deleteSnapshot() (hplefthandclient.client.HPLeftHandClient method), [1] +
+ + +
deleteVolume() (hplefthandclient.client.HPLeftHandClient method), [1] +
+ +
+ +

F

+ + +
+ +
from_response() (in module hplefthandclient.exceptions) +
+ +
+ +

G

+ + + +
+ +
get() (hplefthandclient.http.HTTPJSONRESTClient method) +
+ + +
get_code() (hplefthandclient.exceptions.ClientException method) +
+ + +
get_description() (hplefthandclient.exceptions.ClientException method) +
+ + +
get_timings() (hplefthandclient.http.HTTPJSONRESTClient method) +
+ + +
get_version_string() (in module hplefthandclient.__init__) +
+ + +
getCluster() (hplefthandclient.client.HPLeftHandClient method), [1] +
+ + +
getClusterByName() (hplefthandclient.client.HPLeftHandClient method), [1] +
+ + +
getClusters() (hplefthandclient.client.HPLeftHandClient method), [1] +
+ + +
getServer() (hplefthandclient.client.HPLeftHandClient method), [1] +
+ +
+ +
getServerByName() (hplefthandclient.client.HPLeftHandClient method), [1] +
+ + +
getServers() (hplefthandclient.client.HPLeftHandClient method), [1] +
+ + +
getSnapshot() (hplefthandclient.client.HPLeftHandClient method), [1] +
+ + +
getSnapshotByName() (hplefthandclient.client.HPLeftHandClient method), [1] +
+ + +
getSnapshots() (hplefthandclient.client.HPLeftHandClient method), [1] +
+ + +
getVolume() (hplefthandclient.client.HPLeftHandClient method), [1] +
+ + +
getVolumeByName() (hplefthandclient.client.HPLeftHandClient method), [1] +
+ + +
getVolumes() (hplefthandclient.client.HPLeftHandClient method), [1] +
+ +
+ +

H

+ + + +
+ +
HPLeftHandClient (class in hplefthandclient.client), [1] +
+ + +
hplefthandclient (module) +
+ + +
hplefthandclient.__init__ (module) +
+ + +
hplefthandclient.client (module), [1] +
+ + +
hplefthandclient.exceptions (module), [1] +
+ + +
hplefthandclient.http (module), [1] +
+ + +
http_status (hplefthandclient.exceptions.HTTPBadGateway attribute) +
+ +
+ +
(hplefthandclient.exceptions.HTTPBadRequest attribute) +
+ + +
(hplefthandclient.exceptions.HTTPConflict attribute) +
+ + +
(hplefthandclient.exceptions.HTTPExpectationFailed attribute) +
+ + +
(hplefthandclient.exceptions.HTTPForbidden attribute) +
+ + +
(hplefthandclient.exceptions.HTTPGatewayTimeout attribute) +
+ + +
(hplefthandclient.exceptions.HTTPGone attribute) +
+ + +
(hplefthandclient.exceptions.HTTPLengthRequired attribute) +
+ + +
(hplefthandclient.exceptions.HTTPMethodNotAllowed attribute) +
+ + +
(hplefthandclient.exceptions.HTTPNotAcceptable attribute) +
+ + +
(hplefthandclient.exceptions.HTTPNotFound attribute) +
+ + +
(hplefthandclient.exceptions.HTTPNotImplemented attribute) +
+ + +
(hplefthandclient.exceptions.HTTPPreconditionFailed attribute) +
+ + +
(hplefthandclient.exceptions.HTTPProxyAuthRequired attribute) +
+ + +
(hplefthandclient.exceptions.HTTPRequestEntityTooLarge attribute) +
+ + +
(hplefthandclient.exceptions.HTTPRequestTimeout attribute) +
+ + +
(hplefthandclient.exceptions.HTTPRequestURITooLong attribute) +
+ + +
(hplefthandclient.exceptions.HTTPRequestedRangeNotSatisfiable attribute) +
+ + +
(hplefthandclient.exceptions.HTTPServerError attribute) +
+ + +
(hplefthandclient.exceptions.HTTPServiceUnavailable attribute) +
+ + +
(hplefthandclient.exceptions.HTTPTeaPot attribute) +
+ + +
(hplefthandclient.exceptions.HTTPUnauthorized attribute) +
+ + +
(hplefthandclient.exceptions.HTTPUnsupportedMediaType attribute) +
+ + +
(hplefthandclient.exceptions.HTTPVersionNotSupported attribute) +
+ +
+ +
HTTPBadGateway +
+ + +
HTTPBadRequest +
+ +
+ +
(class in hplefthandclient.exceptions) +
+ +
+ +
HTTPConflict +
+ + +
HTTPExpectationFailed +
+ + +
HTTPForbidden +
+ + +
HTTPGatewayTimeout +
+ + +
HTTPGone +
+ + +
HTTPJSONRESTClient (class in hplefthandclient.http) +
+ + +
HTTPLengthRequired +
+ +
+ +
HTTPMethodNotAllowed +
+ + +
HTTPNotAcceptable +
+ + +
HTTPNotFound +
+ +
+ +
(class in hplefthandclient.exceptions) +
+ +
+ +
HTTPNotImplemented +
+ + +
HTTPPreconditionFailed +
+ + +
HTTPProxyAuthRequired +
+ + +
HTTPRequestedRangeNotSatisfiable +
+ + +
HTTPRequestEntityTooLarge +
+ + +
HTTPRequestTimeout +
+ + +
HTTPRequestURITooLong +
+ + +
HTTPServerError +
+ + +
HTTPServiceUnavailable +
+ + +
HTTPTeaPot +
+ + +
HTTPUnauthorized +
+ + +
HTTPUnsupportedMediaType +
+ + +
HTTPVersionNotSupported +
+ +
+ +

L

+ + + +
+ +
login() (hplefthandclient.client.HPLeftHandClient method), [1] +
+ +
+ +
logout() (hplefthandclient.client.HPLeftHandClient method), [1] +
+ +
+ +

M

+ + + +
+ +
message (hplefthandclient.exceptions.HTTPBadGateway attribute) +
+ +
+ +
(hplefthandclient.exceptions.HTTPBadRequest attribute) +
+ + +
(hplefthandclient.exceptions.HTTPConflict attribute) +
+ + +
(hplefthandclient.exceptions.HTTPExpectationFailed attribute) +
+ + +
(hplefthandclient.exceptions.HTTPForbidden attribute) +
+ + +
(hplefthandclient.exceptions.HTTPGatewayTimeout attribute) +
+ + +
(hplefthandclient.exceptions.HTTPGone attribute) +
+ + +
(hplefthandclient.exceptions.HTTPLengthRequired attribute) +
+ + +
(hplefthandclient.exceptions.HTTPMethodNotAllowed attribute) +
+ + +
(hplefthandclient.exceptions.HTTPNotAcceptable attribute) +
+ + +
(hplefthandclient.exceptions.HTTPNotFound attribute) +
+ + +
(hplefthandclient.exceptions.HTTPNotImplemented attribute) +
+ + +
(hplefthandclient.exceptions.HTTPPreconditionFailed attribute) +
+ + +
(hplefthandclient.exceptions.HTTPProxyAuthRequired attribute) +
+ + +
(hplefthandclient.exceptions.HTTPRequestEntityTooLarge attribute) +
+ + +
(hplefthandclient.exceptions.HTTPRequestTimeout attribute) +
+ + +
(hplefthandclient.exceptions.HTTPRequestURITooLong attribute) +
+ + +
(hplefthandclient.exceptions.HTTPRequestedRangeNotSatisfiable attribute) +
+ + +
(hplefthandclient.exceptions.HTTPServerError attribute) +
+ + +
(hplefthandclient.exceptions.HTTPServiceUnavailable attribute) +
+ + +
(hplefthandclient.exceptions.HTTPTeaPot attribute) +
+ + +
(hplefthandclient.exceptions.HTTPUnauthorized attribute) +
+ + +
(hplefthandclient.exceptions.HTTPUnsupportedMediaType attribute) +
+ + +
(hplefthandclient.exceptions.HTTPVersionNotSupported attribute) +
+ +
+
+ +
modifyVolume() (hplefthandclient.client.HPLeftHandClient method), [1] +
+ +
+ +

N

+ + +
+ +
NoUniqueMatch +
+ +
+ +

P

+ + + +
+ +
post() (hplefthandclient.http.HTTPJSONRESTClient method) +
+ +
+ +
put() (hplefthandclient.http.HTTPJSONRESTClient method) +
+ +
+ +

R

+ + + +
+ +
removeServerAccess() (hplefthandclient.client.HPLeftHandClient method), [1] +
+ + +
request() (hplefthandclient.http.HTTPJSONRESTClient method) +
+ +
+ +
reset_timings() (hplefthandclient.http.HTTPJSONRESTClient method) +
+ +
+ +

S

+ + + +
+ +
SESSION_COOKIE_NAME (hplefthandclient.http.HTTPJSONRESTClient attribute) +
+ + +
set_debug_flag() (hplefthandclient.http.HTTPJSONRESTClient method) +
+ +
+ +
set_url() (hplefthandclient.http.HTTPJSONRESTClient method) +
+ +
+ +

U

+ + + +
+ +
unauthenticate() (hplefthandclient.http.HTTPJSONRESTClient method) +
+ + +
UnsupportedVersion +
+ +
+ +
USER_AGENT (hplefthandclient.http.HTTPJSONRESTClient attribute) +
+ +
+ +

V

+ + +
+ +
version (in module hplefthandclient) +
+ +
+ +
(in module hplefthandclient.__init__) +
+ +
+
+ + + +
+
+
+
+
+ + + + + +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/hplefthandclient.html b/docs/_build/html/hplefthandclient.html new file mode 100644 index 0000000..8769de6 --- /dev/null +++ b/docs/_build/html/hplefthandclient.html @@ -0,0 +1,1377 @@ + + + + + + + + + + hplefthandclient Package — HP LeftHand REST Client 1.0.0 documentation + + + + + + + + + + + + + +
+
+
+
+ +
+

hplefthandclient Package¶

+
+

hplefthandclient Package¶

+

HP LeftHand REST Client

+ +++ + + + + + + + + + +
Author:Kurt Martin
Author:Walter A. Boring IV
Copyright:Copyright 2013, Hewlett Packard Development Company, L.P.
License:Apache v2.0
+
+
+hplefthandclient.__init__.get_version_string()[source]¶
+
+ +
+
+hplefthandclient.__init__.version = '1.0.0'¶
+

Current version of HPLeftHandClient.

+
+ +
+
+

client Module¶

+

HPLeftHand REST Client

+ +++ + + + + + +
Author:Kurt Martin
Description:This is the LeftHand/StoreVirtual Client that talks to the
+

LeftHand OS REST Service.

+

This client requires and works with version 11.5 of the LeftHand firmware

+
+
+class hplefthandclient.client.HPLeftHandClient(api_url)[source]¶
+
+
+addServerAccess(volume_id, server_id, optional=None)[source]¶
+

Assign a Volume to a Server

+ +++ + + + +
Parameters:
    +
  • volume_id – Volume ID of the volume
  • +
  • server_id – Server ID of the server to add the volume to
  • +
  • optional (dict) – Dictionary of optional params
  • +
+
+
optional = {
+    'Transport' : 0,
+    'Lun' : 1,
+}
+
+
+
+ +
+
+cloneSnapshot(name, source_snapshot_id, optional=None)[source]¶
+

Create a clone of an existing Shapshot

+ +++ + + + +
Parameters:
    +
  • name (str) – Name of the Snapshot clone
  • +
  • source_snapshot_id (int) – The snapshot you want to clone
  • +
  • optional (dict) – Dictionary of optional params
  • +
+
+
optional = {
+    'description' : "some comment"
+}
+
+
+
+ +
+
+cloneVolume(name, source_volume_id, optional=None)[source]¶
+

Create a clone of an existing Volume

+ +++ + + + +
Parameters:
    +
  • name (str) – Name of the Volume clone
  • +
  • source_volume_id (int) – The Volume you want to clone
  • +
  • optional (dict) – Dictionary of optional params
  • +
+
+
optional = {
+    'description' : "some comment"
+}
+
+
+
+ +
+
+createServer(name, iqn, optional=None)[source]¶
+

Create a server by name

+ +++ + + + +
Parameters:
    +
  • name (str) – The name of the server to create
  • +
  • iqn – The iSCSI qualified name
  • +
  • optional (dict) – Dictionary of optional params
  • +
+
+
optional = {
+    'description' : "some comment",
+    'iscsiEnabled' : True,
+    'chapName': "some chap name",
+    'chapAuthenticationRequired': False,
+    'chapInitiatorSecret': "initiator secret",
+    'chapTargetSecret': "target secret",
+    'iscsiLoadBalancingEnabled': True,
+    'controllingServerName': "server name",
+    'fibreChannelEnabled': False,
+    'inServerCluster": True
+}
+
+
+ +++ + + + + + +
Returns:server
Raises :HTTPNotFound -
+

NON_EXISTENT_SERVER - server doesn’t exist

+
+ +
+
+createSnapshot(name, source_volume_id, optional=None)[source]¶
+

Create a snapshot of an existing Volume

+ +++ + + + +
Parameters:
    +
  • name (str) – Name of the Snapshot
  • +
  • source_volume_id (int) – The volume you want to snapshot
  • +
  • optional (dict) – Dictionary of optional params
  • +
+
+
optional = {
+    'description' : "some comment",
+    'inheritAccess' : false
+}
+
+
+
+ +
+
+createVolume(name, cluster_id, size, optional=None)[source]¶
+

Create a new volume

+ +++ + + + +
Parameters:
    +
  • name (str) – the name of the volume
  • +
  • cluster_id (int) – the cluster Id
  • +
  • sizeKB (int) – size in KB for the volume
  • +
  • optional (dict) – dict of other optional items
  • +
+
+
optional = {
+ 'description': 'some comment',
+ 'isThinProvisioned': 'true',
+ 'autogrowSeconds': 200,
+ 'clusterName': 'somename',
+ 'isAdaptiveOptimizationEnabled': 'true',
+ 'dataProtectionLevel': 2,
+}
+
+
+ +++ + + + + + +
Returns:List of Volumes
Raises :HTTPConflict -
+

EXISTENT_SV - Volume Exists already

+
+ +
+
+debug_rest(flag)[source]¶
+

This is useful for debugging requests to LeftHand

+ +++ + + + +
Parameters:flag (bool) – set to True to enable debugging
+
+ +
+
+deleteServer(server_id)[source]¶
+

Delete a Server

+ +++ + + + + + +
Parameters:server_id – the server ID to delete
Raises :HTTPNotFound -
+

NON_EXISTENT_SERVER - The server does not exist

+
+ +
+
+deleteSnapshot(snapshot_id)[source]¶
+

Delete a Snapshot

+ +++ + + + + + +
Parameters:snapshot_id – the snapshot ID to delete
Raises :HTTPNotFound -
+

NON_EXISTENT_SNAPSHOT - The snapshot does not exist

+
+ +
+
+deleteVolume(volume_id)[source]¶
+

Delete a volume

+ +++ + + + + + +
Parameters:name (str) – the name of the volume
Raises :HTTPNotFound -
+

NON_EXISTENT_VOL - The volume does not exist

+
+ +
+
+getCluster(cluster_id)[source]¶
+

Get information about a Cluster

+ +++ + + + + + +
Parameters:cluster_id (str) – The id of the cluster to find
Returns:cluster
+
+ +
+
+getClusterByName(name)[source]¶
+

Get information about a cluster by name

+ +++ + + + + + + + +
Parameters:name (str) – The name of the cluster to find
Returns:cluster
Raises :HTTPNotFound -
+

NON_EXISTENT_CLUSTER - cluster doesn’t exist

+
+ +
+
+getClusters()[source]¶
+

Get the list of Clusters

+ +++ + + + +
Returns:list of Clusters
+
+ +
+
+getServer(server_id)[source]¶
+

Get information about a server

+ +++ + + + + + + + +
Parameters:server_id (str) – The id of the server to find
Returns:server
Raises :HTTPServerError
+
+ +
+
+getServerByName(name)[source]¶
+

Get information about a server by name

+ +++ + + + + + + + +
Parameters:name (str) – The name of the server to find
Returns:server
Raises :HTTPNotFound -
+

NON_EXISTENT_SERVER - server doesn’t exist

+
+ +
+
+getServers()[source]¶
+

Get the list of Servers

+ +++ + + + +
Returns:list of Servers
+
+ +
+
+getSnapshot(snapshot_id)[source]¶
+

Get information about a Snapshot

+ +++ + + + + + +
Returns:snapshot
Raises :HTTPServerError
+
+ +
+
+getSnapshotByName(name)[source]¶
+

Get information about a snapshot by name

+ +++ + + + + + + + +
Parameters:name – The name of the snapshot to find
Returns:volume
Raises :HTTPNotFound -
+

NON_EXISTENT_SNAP - shapshot doesn’t exist

+
+ +
+
+getSnapshots()[source]¶
+

Get the list of Snapshots

+ +++ + + + +
Returns:list of Snapshots
+
+ +
+
+getVolume(volume_id)[source]¶
+

Get information about a volume

+ +++ + + + + + + + +
Parameters:volume_id (str) – The id of the volume to find
Returns:volume
Raises :HTTPNotFound -
+

NON_EXISTENT_VOL - volume doesn’t exist

+
+ +
+
+getVolumeByName(name)[source]¶
+

Get information about a volume by name

+ +++ + + + + + + + +
Parameters:name – The name of the volume to find
Returns:volume
Raises :HTTPNotFound -
+

NON_EXISTENT_VOL - volume doesn’t exist

+
+ +
+
+getVolumes()[source]¶
+

Get the list of Volumes

+ +++ + + + +
Returns:list of Volumes
+
+ +
+
+login(username, password)[source]¶
+

This authenticates against the LH OS REST server and creates a session.

+ +++ + + + + + +
Parameters:
    +
  • username (str) – The username
  • +
  • password (str) – The password
  • +
+
Returns:

None

+
+
+ +
+
+logout()[source]¶
+

This destroys the session and logs out from the LH OS server

+ +++ + + + +
Returns:None
+
+ +
+
+modifyVolume(volume_id, optional)[source]¶
+

Modify an existing volume.

+ +++ + + + + + + + +
Parameters:volume_id (str) – The id of the volume to find
Returns:volume
Raises :HTTPNotFound -
+

NON_EXISTENT_VOL - volume doesn’t exist

+
+ +
+
+removeServerAccess(volume_id, server_id)[source]¶
+

Unassign a Volume from a Server

+ +++ + + + +
Parameters:
    +
  • volume_id – Volume ID of the volume
  • +
  • server_id – Server ID of the server to remove the volume fom
  • +
+
+
+ +
+ +
+
+

exceptions Module¶

+

Exceptions for the client

+ +++ + + + + + +
Author:Walter A. Boring IV
Description:This contains the HTTP exceptions that can come back from the REST calls
+
+
+exception hplefthandclient.exceptions.AuthorizationFailure[source]¶
+

Bases: exceptions.Exception

+
+ +
+
+exception hplefthandclient.exceptions.ClientException(error=None)[source]¶
+

Bases: exceptions.Exception

+

The base exception class for all exceptions this library raises.

+ +++ + + + +
Parameters:error (array) – The error array
+
+
+get_code()[source]¶
+
+ +
+
+get_description()[source]¶
+
+ +
+ +
+
+exception hplefthandclient.exceptions.CommandError[source]¶
+

Bases: exceptions.Exception

+
+ +
+
+exception hplefthandclient.exceptions.HTTPBadGateway(error=None)[source]¶
+

Bases: hplefthandclient.exceptions.ClientException

+

HTTP 502 - The server was acting as a gateway or proxy and received an invalid response from the upstream server.

+
+
+http_status = 502¶
+
+ +
+
+message = 'Bad Gateway'¶
+
+ +
+ +
+
+exception hplefthandclient.exceptions.HTTPBadRequest(error=None)[source]¶
+

Bases: hplefthandclient.exceptions.ClientException

+

HTTP 400 - Bad request: you sent some malformed data.

+
+
+http_status = 400¶
+
+ +
+
+message = 'Bad request'¶
+
+ +
+ +
+
+exception hplefthandclient.exceptions.HTTPConflict(error=None)[source]¶
+

Bases: hplefthandclient.exceptions.ClientException

+

HTTP 409 - Conflict: A Conflict happened on the server

+
+
+http_status = 409¶
+
+ +
+
+message = 'Conflict'¶
+
+ +
+ +
+
+exception hplefthandclient.exceptions.HTTPExpectationFailed(error=None)[source]¶
+

Bases: hplefthandclient.exceptions.ClientException

+

HTTP 417 - The server cannot meet the requirements of the Expect request-header field.

+
+
+http_status = 417¶
+
+ +
+
+message = 'Expectation Failed'¶
+
+ +
+ +
+
+exception hplefthandclient.exceptions.HTTPForbidden(error=None)[source]¶
+

Bases: hplefthandclient.exceptions.ClientException

+

HTTP 403 - Forbidden: your credentials don’t give you access to this +resource.

+
+
+http_status = 403¶
+
+ +
+
+message = 'Forbidden'¶
+
+ +
+ +
+
+exception hplefthandclient.exceptions.HTTPGatewayTimeout(error=None)[source]¶
+

Bases: hplefthandclient.exceptions.ClientException

+

HTTP 504 - The server was acting as a gateway or proxy and did not receive a timely response from the upstream server.

+
+
+http_status = 504¶
+
+ +
+
+message = 'Gateway Timeout'¶
+
+ +
+ +
+
+exception hplefthandclient.exceptions.HTTPGone(error=None)[source]¶
+

Bases: hplefthandclient.exceptions.ClientException

+

HTTP 410 - Indicates that the resource requested is no longer available and will not be available again.

+
+
+http_status = 410¶
+
+ +
+
+message = 'Gone'¶
+
+ +
+ +
+
+exception hplefthandclient.exceptions.HTTPLengthRequired(error=None)[source]¶
+

Bases: hplefthandclient.exceptions.ClientException

+

HTTP 411 - The request did not specify the length of its content, which is required by the requested resource.

+
+
+http_status = 411¶
+
+ +
+
+message = 'Length Required'¶
+
+ +
+ +
+
+exception hplefthandclient.exceptions.HTTPMethodNotAllowed(error=None)[source]¶
+

Bases: hplefthandclient.exceptions.ClientException

+

HTTP 405 - Method not Allowed

+
+
+http_status = 405¶
+
+ +
+
+message = 'Method Not Allowed'¶
+
+ +
+ +
+
+exception hplefthandclient.exceptions.HTTPNotAcceptable(error=None)[source]¶
+

Bases: hplefthandclient.exceptions.ClientException

+

HTTP 406 - Method not Acceptable

+
+
+http_status = 406¶
+
+ +
+
+message = 'Method Not Acceptable'¶
+
+ +
+ +
+
+exception hplefthandclient.exceptions.HTTPNotFound(error=None)[source]¶
+

Bases: hplefthandclient.exceptions.ClientException

+

HTTP 404 - Not found

+
+
+http_status = 404¶
+
+ +
+
+message = 'Not found'¶
+
+ +
+ +
+
+exception hplefthandclient.exceptions.HTTPNotImplemented(error=None)[source]¶
+

Bases: hplefthandclient.exceptions.ClientException

+

HTTP 501 - Not Implemented: the server does not support this operation.

+
+
+http_status = 501¶
+
+ +
+
+message = 'Not Implemented'¶
+
+ +
+ +
+
+exception hplefthandclient.exceptions.HTTPPreconditionFailed(error=None)[source]¶
+

Bases: hplefthandclient.exceptions.ClientException

+

HTTP 412 - The server does not meet one of the preconditions that the requester put on the request.

+
+
+http_status = 412¶
+
+ +
+
+message = 'Over limit'¶
+
+ +
+ +
+
+exception hplefthandclient.exceptions.HTTPProxyAuthRequired(error=None)[source]¶
+

Bases: hplefthandclient.exceptions.ClientException

+

HTTP 407 - The client must first authenticate itself with the proxy.

+
+
+http_status = 407¶
+
+ +
+
+message = 'Proxy Authentication Required'¶
+
+ +
+ +
+
+exception hplefthandclient.exceptions.HTTPRequestEntityTooLarge(error=None)[source]¶
+

Bases: hplefthandclient.exceptions.ClientException

+

HTTP 413 - The request is larger than the server is willing or able to process

+
+
+http_status = 413¶
+
+ +
+
+message = 'Request Entity Too Large'¶
+
+ +
+ +
+
+exception hplefthandclient.exceptions.HTTPRequestTimeout(error=None)[source]¶
+

Bases: hplefthandclient.exceptions.ClientException

+

HTTP 408 - The server timed out waiting for the request.

+
+
+http_status = 408¶
+
+ +
+
+message = 'Request Timeout'¶
+
+ +
+ +
+
+exception hplefthandclient.exceptions.HTTPRequestURITooLong(error=None)[source]¶
+

Bases: hplefthandclient.exceptions.ClientException

+

HTTP 414 - The URI provided was too long for the server to process.

+
+
+http_status = 414¶
+
+ +
+
+message = 'Request URI Too Large'¶
+
+ +
+ +
+
+exception hplefthandclient.exceptions.HTTPRequestedRangeNotSatisfiable(error=None)[source]¶
+

Bases: hplefthandclient.exceptions.ClientException

+

HTTP 416 - The client has asked for a portion of the file, but the server cannot supply that portion.

+
+
+http_status = 416¶
+
+ +
+
+message = 'Requested Range Not Satisfiable'¶
+
+ +
+ +
+
+exception hplefthandclient.exceptions.HTTPServerError(error=None)[source]¶
+

Bases: hplefthandclient.exceptions.ClientException

+

HTTP 500 -

+
+
+http_status = 500¶
+
+ +
+
+message = 'Error'¶
+
+ +
+ +
+
+exception hplefthandclient.exceptions.HTTPServiceUnavailable(error=None)[source]¶
+

Bases: hplefthandclient.exceptions.ClientException

+

HTTP 503 - The server is currently unavailable

+
+
+http_status = 503¶
+
+ +
+
+message = 'Service Unavailable'¶
+
+ +
+ +
+
+exception hplefthandclient.exceptions.HTTPTeaPot(error=None)[source]¶
+

Bases: hplefthandclient.exceptions.ClientException

+

HTTP 418 - I’m a Tea Pot

+
+
+http_status = 418¶
+
+ +
+
+message = "I'm A Teapot. (RFC 2324)"¶
+
+ +
+ +
+
+exception hplefthandclient.exceptions.HTTPUnauthorized(error=None)[source]¶
+

Bases: hplefthandclient.exceptions.ClientException

+

HTTP 401 - Unauthorized: bad credentials.

+
+
+http_status = 401¶
+
+ +
+
+message = 'Unauthorized'¶
+
+ +
+ +
+
+exception hplefthandclient.exceptions.HTTPUnsupportedMediaType(error=None)[source]¶
+

Bases: hplefthandclient.exceptions.ClientException

+

HTTP 415 - The request entity has a media type which the server or resource does not support.

+
+
+http_status = 415¶
+
+ +
+
+message = 'Unsupported Media Type'¶
+
+ +
+ +
+
+exception hplefthandclient.exceptions.HTTPVersionNotSupported(error=None)[source]¶
+

Bases: hplefthandclient.exceptions.ClientException

+

HTTP 505 - The server does not support the HTTP protocol version used in the request.

+
+
+http_status = 505¶
+
+ +
+
+message = 'Version Not Supported'¶
+
+ +
+ +
+
+exception hplefthandclient.exceptions.NoUniqueMatch[source]¶
+

Bases: exceptions.Exception

+
+ +
+
+exception hplefthandclient.exceptions.UnsupportedVersion[source]¶
+

Bases: exceptions.Exception

+

Indicates that the user is trying to use an unsupported version of the API

+
+ +
+
+hplefthandclient.exceptions.from_response(response, body)[source]¶
+

Return an instance of an ClientException or subclass +based on an httplib2 response.

+

Usage:

+
resp, body = http.request(...)
+if resp.status != 200:
+    raise exception_from_response(resp, body)
+
+
+
+ +
+
+

http Module¶

+

HPLeftHand HTTP Client +:Author: Walter A. Boring IV +:Description: This is the HTTP Client that is used to make the actual calls.

+
+
It includes the authentication that knows the cookie name for LH.
+
+
+class hplefthandclient.http.HTTPJSONRESTClient(api_url, insecure=False, http_log_debug=False)[source]¶
+

Bases: httplib2.Http

+

An HTTP REST Client that sends and recieves JSON data as the body of the +HTTP request.

+ +++ + + + +
Parameters:
    +
  • api_url (str) – The url to the LH OS REST service +ie. https://<hostname or IP>:<port>/lhos
  • +
  • insecure (bool) – Use https? requires a local certificate
  • +
+
+
+ +
+ +
+
+USER_AGENT = 'python-hplefthandclient'¶
+
+ +
+
+authenticate(user, password, optional=None)[source]¶
+

This tries to create an authenticated session with the LH OS server

+ +++ + + + +
Parameters:
    +
  • user (str) – The username
  • +
  • password (str) – The password
  • +
+
+
+ +
+
+delete(url, **kwargs)[source]¶
+

Make an HTTP DELETE request to the server.

+
#example call
+try {
+    headers, body = http.delete('/volumes/%s' % name)
+} except exceptions.HTTPUnauthorized as ex:
+    print "Not logged in"
+}
+
+
+ +++ + + + + + + + +
Parameters:url (str) – The relative url from the LH api_url
Returns:headers - dict of HTTP Response headers
Returns:body - the body of the response. If the body was JSON,
+

it will be an object

+
+ +
+
+get(url, **kwargs)[source]¶
+

Make an HTTP GET request to the server.

+
#example call
+try {
+    headers, body = http.get('/volumes')
+} except exceptions.HTTPUnauthorized as ex:
+    print "Not logged in"
+}
+
+
+ +++ + + + + + + + +
Parameters:url (str) – The relative url from the LH api_url
Returns:headers - dict of HTTP Response headers
Returns:body - the body of the response. If the body was JSON,
+

it will be an object

+
+ +
+
+get_timings()[source]¶
+

Ths gives an array of the request timings since last reset_timings call

+
+ +
+
+post(url, **kwargs)[source]¶
+

Make an HTTP POST request to the server.

+
#example call
+try {
+    info = {'name': 'new volume name', 'sizeMiB': 300}
+    headers, body = http.post('/volumes', body=info)
+} except exceptions.HTTPUnauthorized as ex:
+    print "Not logged in"
+}
+
+
+ +++ + + + + + + + +
Parameters:url (str) – The relative url from the LH api_url
Returns:headers - dict of HTTP Response headers
Returns:body - the body of the response. If the body was JSON,
+

it will be an object

+
+ +
+
+put(url, **kwargs)[source]¶
+

Make an HTTP PUT request to the server.

+
#example call
+try {
+    info = {'name': 'something'}
+    headers, body = http.put('/volumes', body=info)
+} except exceptions.HTTPUnauthorized as ex:
+    print "Not logged in"
+}
+
+
+ +++ + + + + + + + +
Parameters:url (str) – The relative url from the LH api_url
Returns:headers - dict of HTTP Response headers
Returns:body - the body of the response. If the body was JSON,
+

it will be an object

+
+ +
+
+request(*args, **kwargs)[source]¶
+

This makes an HTTP Request to the LH server. You should use get, post, +delete instead.

+
+ +
+
+reset_timings()[source]¶
+

This resets the request/response timings array

+
+ +
+
+set_debug_flag(flag)[source]¶
+

This turns on/off http request/response debugging output to console

+ +++ + + + +
Parameters:flag (bool) – Set to True to enable debugging output
+
+ +
+
+set_url(api_url)[source]¶
+
+ +
+
+unauthenticate()[source]¶
+

This clears the authenticated session with the LH server. It logs out.

+
+ +
+ +
+
+ + +
+
+
+
+
+

Table Of Contents

+ + +

This Page

+ + + +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/index.html b/docs/_build/html/index.html new file mode 100644 index 0000000..3d565e2 --- /dev/null +++ b/docs/_build/html/index.html @@ -0,0 +1,169 @@ + + + + + + + + + + HPLeftHandClient 1.0.0 Documentation — HP LeftHand REST Client 1.0.0 documentation + + + + + + + + + + + + + + +
+
+
+
+ +
+

HPLeftHandClient 1.0.0 Documentation¶

+
+

Overview¶

+

HPLeftHandClient is a Python package containing a class that uses +HTTP REST calls to talk with an HP LeftHand/StoreVirtual drive array. +distribution containing tools for working with +LeftHand/StoreVirtual Storage Arrays. +. This documentation attempts to explain +everything you need to know to use HPLeftHandClient.

+
+
Installing / Upgrading
+
Instructions on how to get the distribution.
+
Tutorial
+
Start here for a quick overview.
+
API Documentation
+
The complete API documentation, organized by module.
+
+
+
+

Issues¶

+

All issues should be reported (and can be tracked / voted for / +commented on) at the main github issues, in the “LeftHand Python Driver” +project.

+
+
+

Changes¶

+

See the Changelog for a full list of changes to HPLeftHandClient.

+
+
+

About This Documentation¶

+

This documentation is generated using the Sphinx documentation generator. The source files +for the documentation are located in the doc/ directory of the +HPLeftHandClient distribution. To generate the docs locally run the +following command from the root directory of the HPLeftHandClient

+
$ python setup.py doc
+
+
+
+
+
+
+
+

Indices and tables¶

+ +
+ + +
+
+
+
+
+

Table Of Contents

+ + +

Next topic

+

Installing / Upgrading

+

This Page

+ + + +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/installation.html b/docs/_build/html/installation.html new file mode 100644 index 0000000..0ee7ee1 --- /dev/null +++ b/docs/_build/html/installation.html @@ -0,0 +1,164 @@ + + + + + + + + + + Installing / Upgrading — HP LeftHand REST Client 1.0.0 documentation + + + + + + + + + + + + + + + +
+
+
+
+ +
+

Installing / Upgrading¶

+

HPLeftHandClient is in the Python Package Index.

+
+

Installing with pip¶

+

We prefer pip +to install hplefthandclient on platforms other than Windows:

+
$ pip install hplefthandclient
+
+
+

To upgrade using pip:

+
$ pip install --upgrade hplefthandclient
+
+
+
+
+

Installing with easy_install¶

+

If you must install hplefthandclient using +setuptools do:

+
$ easy_install hplefthandclient
+
+
+

To upgrade do:

+
$ easy_install -U hplefthandclient
+
+
+
+
+

Installing from source¶

+

If you’d rather install directly from the source (i.e. to stay on the +bleeding edge), install the C extension dependencies then check out the +latest source from github and install the driver from the resulting tree:

+
$ git clone git://github.com/WaltHP/python-3parclient.git
+$ cd pyton-hplefthandclient/
+$ python setup.py install
+
+
+
+
+ + +
+
+
+
+
+

Table Of Contents

+ + +

Previous topic

+

HPLeftHandClient 1.0.0 Documentation

+

Next topic

+

Tutorial

+

This Page

+ + + +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/objects.inv b/docs/_build/html/objects.inv new file mode 100644 index 0000000000000000000000000000000000000000..a553771d1609bffa326c3d34f881e5d4b8ce6a7a GIT binary patch literal 1291 zcmV+m1@!tOAX9K?X>NERX>N99Zgg*Qc_4OWa&u{KZXhxWBOp+6Z)#;@bUGkNP#{cY zW^_nlZe$=*MN?EDLu_efZgdJGAXa5^b7^mGIv_DFFfK3(BOp|0Wgv28ZDDC{WMy(7 zZ)PBLXlZjGW@&6?AZc?TV{dJ6a%FRKWn>_Ab7^j8AbMAwynfJM`&@nQjg2T3^hR;%QwF->N4wSf7u8k=%<}c=#%itJHm^2J zSq7ihg@Tn|GcCB1pnV@+ITT(R?Se|*@=AxVy^DQYQPty{e|k3;f=+(T3i;)H!mp-({Ad74Zi3oqT$2-UdZlq`Y&KzFmn_qm}kA)7GaH24qoa%EEd!6Ox!aM zzu2aqgLA>LQ7rS`%IDI<-^s53JWLRR>N#1>au0^6!j&mO_BW56z`^Wl|C&rn-{3EZNPbOHTE00G8%pwRiY(Y?TCfUq3%K59b74!>=t5; zNCh}XfnC0w3G;zC+&h#>uI&-)o*jNh) z8X^+{BSqh9STs=KKIA`feGF(+YIHPb@XPHU**HY#3^uXWSVFWP)z++VYvVB#ADTQ5 z54KtpWDBR|K;IzyR?_H;>X=rv^$nu-mLJxX1bnq#5- zQ#TSCJ@S034Jtlf0Gk?ux&I6!cW~q--E|e`(bBo2Sgu&WNtm@0zhX4dUWBtYRl!@% zMlnr{&S>Ccb3$Uj>V$y9Ds_Zqqry0-Wg~_xs{y8s2Af~l^|j5RI>(}sE9SouFi zL#u!Xp3jbkTN0tfv{N*7_$xW{=xM&3C$n^LpG+2=vm2@Poz7Qy{;GNW$g%CHeUc-O zL+|ULdz-sTjS&p*5FQ=5gZ9IRSlYVLDev-OM1%)1@3=kN=xX5b%Uik6<#TpksS!p@ez1gb9CQ{q}=V9S4w(gmzyN-Xn;_t=HJ~kLXVa+ zI)%hImq$Cqpy6<`;mA&>kS1LuNOH<75n+=3#h)<<$rhUesWDH?Qp*tEW9Io9k&ie% zj{2y)CmwkNhC{z=(d1h4OK;Toxgv + + + + + + + Python Module Index — HP LeftHand REST Client 1.0.0 documentation + + + + + + + + + + + + + + + + +
+
+
+
+ + +

Python Module Index

+ +
+ h +
+ + + + + + + + + + + + + + + + + + + +
 
+ h
+ hplefthandclient + HP LeftHand REST Web client
    + hplefthandclient.__init__ +
    + hplefthandclient.client +
    + hplefthandclient.exceptions +
    + hplefthandclient.http +
+ + +
+
+
+
+
+ + +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/search.html b/docs/_build/html/search.html new file mode 100644 index 0000000..ce99911 --- /dev/null +++ b/docs/_build/html/search.html @@ -0,0 +1,105 @@ + + + + + + + + + + Search — HP LeftHand REST Client 1.0.0 documentation + + + + + + + + + + + + + + + + + +
+
+
+
+ +

Search

+
+ +

+ Please activate JavaScript to enable the search + functionality. +

+
+

+ From here you can search these documents. Enter your search + words into the box below and click "search". Note that the search + function will automatically search for all of the words. Pages + containing fewer words won't appear in the result list. +

+
+ + + +
+ +
+ +
+ +
+
+
+
+
+
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/searchindex.js b/docs/_build/html/searchindex.js new file mode 100644 index 0000000..bdb316a --- /dev/null +++ b/docs/_build/html/searchindex.js @@ -0,0 +1 @@ +Search.setIndex({objects:{"":{hplefthandclient:[8,0,1,""]},"hplefthandclient.client.HPLeftHandClient":{getVolume:[5,3,1,""],deleteSnapshot:[5,3,1,""],deleteServer:[5,3,1,""],deleteVolume:[5,3,1,""],getClusters:[5,3,1,""],removeServerAccess:[5,3,1,""],getVolumeByName:[5,3,1,""],getCluster:[5,3,1,""],modifyVolume:[5,3,1,""],addServerAccess:[5,3,1,""],getClusterByName:[5,3,1,""],getServers:[5,3,1,""],createServer:[5,3,1,""],getSnapshot:[5,3,1,""],logout:[5,3,1,""],cloneVolume:[5,3,1,""],getServer:[5,3,1,""],cloneSnapshot:[5,3,1,""],createSnapshot:[5,3,1,""],getSnapshotByName:[5,3,1,""],getVolumes:[5,3,1,""],createVolume:[5,3,1,""],getServerByName:[5,3,1,""],getSnapshots:[5,3,1,""],debug_rest:[5,3,1,""],login:[5,3,1,""]},"hplefthandclient.exceptions.HTTPExpectationFailed":{message:[5,2,1,""],http_status:[5,2,1,""]},"hplefthandclient.exceptions.HTTPForbidden":{message:[5,2,1,""],http_status:[5,2,1,""]},"hplefthandclient.exceptions.HTTPServiceUnavailable":{message:[5,2,1,""],http_status:[5,2,1,""]},hplefthandclient:{exceptions:[5,0,1,""],client:[5,0,1,""],http:[5,0,1,""],version:[8,1,1,""],"__init__":[5,0,1,""]},"hplefthandclient.exceptions.HTTPBadGateway":{message:[5,2,1,""],http_status:[5,2,1,""]},"hplefthandclient.exceptions.HTTPNotImplemented":{message:[5,2,1,""],http_status:[5,2,1,""]},"hplefthandclient.exceptions.HTTPProxyAuthRequired":{message:[5,2,1,""],http_status:[5,2,1,""]},"hplefthandclient.exceptions.HTTPVersionNotSupported":{message:[5,2,1,""],http_status:[5,2,1,""]},"hplefthandclient.exceptions.HTTPRequestEntityTooLarge":{message:[5,2,1,""],http_status:[5,2,1,""]},"hplefthandclient.exceptions.HTTPServerError":{message:[5,2,1,""],http_status:[5,2,1,""]},"hplefthandclient.http":{HTTPJSONRESTClient:[5,6,1,""]},"hplefthandclient.exceptions.HTTPPreconditionFailed":{message:[5,2,1,""],http_status:[5,2,1,""]},"hplefthandclient.exceptions.HTTPLengthRequired":{message:[5,2,1,""],http_status:[5,2,1,""]},"hplefthandclient.client":{HPLeftHandClient:[5,6,1,""]},"hplefthandclient.exceptions.HTTPUnsupportedMediaType":{message:[5,2,1,""],http_status:[5,2,1,""]},"hplefthandclient.exceptions.HTTPGatewayTimeout":{message:[5,2,1,""],http_status:[5,2,1,""]},"hplefthandclient.exceptions.HTTPNotAcceptable":{message:[5,2,1,""],http_status:[5,2,1,""]},"hplefthandclient.exceptions.HTTPRequestTimeout":{message:[5,2,1,""],http_status:[5,2,1,""]},"hplefthandclient.exceptions.HTTPRequestedRangeNotSatisfiable":{message:[5,2,1,""],http_status:[5,2,1,""]},"hplefthandclient.exceptions.HTTPConflict":{message:[5,2,1,""],http_status:[5,2,1,""]},"hplefthandclient.exceptions.ClientException":{get_description:[5,3,1,""],get_code:[5,3,1,""]},"hplefthandclient.http.HTTPJSONRESTClient":{get_timings:[5,3,1,""],get:[5,3,1,""],request:[5,3,1,""],set_debug_flag:[5,3,1,""],USER_AGENT:[5,2,1,""],SESSION_COOKIE_NAME:[5,2,1,""],unauthenticate:[5,3,1,""],authenticate:[5,3,1,""],reset_timings:[5,3,1,""],set_url:[5,3,1,""],put:[5,3,1,""],post:[5,3,1,""],"delete":[5,3,1,""]},"hplefthandclient.exceptions.HTTPTeaPot":{message:[5,2,1,""],http_status:[5,2,1,""]},"hplefthandclient.__init__":{version:[5,1,1,""],get_version_string:[5,5,1,""]},"hplefthandclient.exceptions":{HTTPRequestEntityTooLarge:[5,4,1,""],HTTPRequestedRangeNotSatisfiable:[5,4,1,""],UnsupportedVersion:[5,4,1,""],HTTPServiceUnavailable:[5,4,1,""],HTTPMethodNotAllowed:[5,4,1,""],HTTPRequestTimeout:[5,4,1,""],HTTPBadRequest:[5,4,1,""],HTTPUnsupportedMediaType:[5,4,1,""],HTTPConflict:[5,4,1,""],HTTPBadGateway:[5,4,1,""],HTTPNotImplemented:[5,4,1,""],HTTPExpectationFailed:[5,4,1,""],HTTPVersionNotSupported:[5,4,1,""],HTTPServerError:[5,4,1,""],HTTPNotAcceptable:[5,4,1,""],NoUniqueMatch:[5,4,1,""],from_response:[5,5,1,""],HTTPPreconditionFailed:[5,4,1,""],HTTPTeaPot:[5,4,1,""],CommandError:[5,4,1,""],HTTPProxyAuthRequired:[5,4,1,""],HTTPUnauthorized:[5,4,1,""],HTTPGatewayTimeout:[5,4,1,""],HTTPNotFound:[5,4,1,""],AuthorizationFailure:[5,4,1,""],HTTPGone:[5,4,1,""],HTTPForbidden:[5,4,1,""],HTTPLengthRequired:[5,4,1,""],ClientException:[5,4,1,""],HTTPRequestURITooLong:[5,4,1,""]},"hplefthandclient.exceptions.HTTPNotFound":{message:[5,2,1,""],http_status:[5,2,1,""]},"hplefthandclient.exceptions.HTTPUnauthorized":{message:[5,2,1,""],http_status:[5,2,1,""]},"hplefthandclient.exceptions.HTTPRequestURITooLong":{message:[5,2,1,""],http_status:[5,2,1,""]},"hplefthandclient.exceptions.HTTPBadRequest":{message:[5,2,1,""],http_status:[5,2,1,""]},"hplefthandclient.exceptions.HTTPMethodNotAllowed":{message:[5,2,1,""],http_status:[5,2,1,""]},"hplefthandclient.exceptions.HTTPGone":{message:[5,2,1,""],http_status:[5,2,1,""]}},terms:{all:[0,5],edg:1,forbidden:5,follow:[0,9],session_cookie_nam:5,depend:1,pprint:9,send:5,"3parclient":1,sent:[5,4],sourc:[0,6,1,4,5],fals:[5,6],volum:[5,6,9],httprequesttimeout:5,upstream:5,lefthand:[0,3,5,6,8,9],tri:5,did:5,set_debug_flag:5,list:[0,6,9,5],"try":[5,9],item:[5,6],unsupport:5,cooki:[5,7],clusternam:[5,6],tea:5,httprequestentitytoolarg:5,port:[5,9],index:[0,1],sub:8,abl:5,access:5,delet:[5,6],version:[5,6,8,2],"new":[5,6],method:5,full:0,httpnotaccept:5,unauthor:5,here:0,satisfi:5,logout:[5,6,9],modifi:[5,6],sinc:5,wait:5,search:0,larger:5,step:9,hplefthandcli:[0,1,3,4,5,6,8,9],prior:9,implement:[5,2],non_existent_serv:[5,6],httpunauthor:[5,9],modul:[0,8,5],prefer:1,gatewai:5,ask:5,api:[3,0,5,9,2],instal:[0,1,9],httpbadrequest:[5,4],httpgatewaytimeout:5,dataprotectionlevel:[5,6],from:[0,1,4,5,6,9],iscsiloadbalancingen:[5,6],upgrad:[0,1],call:[0,3,4,5,7,9],type:5,more:3,modifyvolum:[5,6],flag:[5,6],indic:[0,5],compani:[5,8],must:[5,1,9],none:[5,6,4],setup:[0,1],work:[0,6,9,5],getclust:[5,6],kwarg:5,can:[0,4,9,5],meet:5,root:0,malform:[5,4],want:[5,6],give:5,process:5,walthp:1,httpmethodnotallow:5,getvolum:[5,6,9],accept:5,iscsien:[5,6],liter:[7,6,4,8],unavail:5,unassign:[5,6],sit:9,rather:1,getsnapshotbynam:[5,6],from_respons:5,instead:5,httpserviceunavail:5,simpl:9,get_tim:5,resourc:5,clone:[5,6,1],after:9,befor:9,httplengthrequir:5,get_version_str:5,github:[0,1],attempt:0,lho:5,non_existent_vol:[5,6],credenti:5,controllingservernam:[5,6],issu:0,inform:[5,6],allow:5,kurt:[5,6,8],make:[5,7,9],talk:[0,6,5],hewlett:[5,8],over:5,httpnotimpl:5,paramet:[5,6],how:0,precondit:5,platform:1,window:1,requir:[5,6],main:0,exception_from_respons:5,good:9,"return":[5,6],python:[0,1,9,5],initi:[5,6],httplib2:5,introduct:9,name:[5,6,7],non_existent_snapshot:[5,6],changelog:[0,2],authent:[5,6,7],timeout:5,debug:[5,6],found:[5,4],nouniquematch:5,reset:5,idea:9,expect:5,happen:[5,9],out:[5,6,1],existent_sv:[5,6],content:5,rel:5,print:[5,9],qualifi:[5,6],proxi:5,quick:0,base:[3,5,7,8],dictionari:[5,6],put:5,bleed:1,httpnotfound:[5,6,4],deleteserv:[5,6],getserverbynam:[5,6],turn:5,length:5,isn:9,assign:[5,6],first:[5,9,2],oper:5,rang:5,directli:1,arrai:[0,9,5],instruct:0,alreadi:[5,6],cluster_id:[5,6],vote:0,size:[5,6],data:[5,4],licens:[5,8],messag:5,too:5,shell:9,consol:5,option:[5,6],tool:0,setuptool:1,specifi:5,user_ag:5,than:[5,1],target:[5,6],deletesnapshot:[5,6],provid:5,createserv:[5,6],tree:1,project:0,str:[5,6],http_log_debug:5,stale:9,pre:[7,6,4,8],respons:5,storevirtu:[0,6,5],packag:[3,0,1,5],have:9,snapshot_id:[5,6],need:0,hplefthnadcli:[],httpversionnotsupport:5,destroi:[5,6],client:[2,3,4,5,6,7,8,9],note:9,also:9,exampl:[5,9],which:[3,5],addserveraccess:[5,6],sure:9,distribut:[0,9],usernam:[5,6,9],object:[5,9],authorizationfailur:5,commanderror:5,"class":[0,3,4,5,6,7,8],don:5,url:[5,9],doc:0,clear:5,request:[5,6,4],uri:5,doe:[5,6],httpproxyauthrequir:5,getsnapshot:[5,6],snapshot:[5,6],set_url:5,source_volume_id:[5,6],deletevolum:[5,6],httpteapot:5,session:[5,6,9],find:[5,6],clientexcept:5,current:[5,8],locat:0,copyright:[5,8],non_existent_clust:[5,6],explain:0,get_descript:5,apach:[5,8],pyton:1,should:[0,9,5],dict:[5,6],local:[0,5],chaptargetsecret:[5,6],clonevolum:[5,6],get:[0,6,9,5],httppreconditionfail:5,cannot:5,report:0,fibrechannelen:[5,6],enabl:[5,6],organ:0,rfc:5,bad:[5,4],contain:[3,0,4,5],debug_rest:[5,6],remov:[5,6],createsnapshot:[5,6],certif:5,set:[5,6,9],see:0,result:1,arg:5,fail:[5,9],statu:5,someth:[5,9],autogrowsecond:[5,6],"import":9,entiti:5,gener:[3,0],lun:[5,6],httprequestedrangenotsatisfi:5,extend:3,extens:1,come:[5,4],bodi:5,non_existent_snap:[5,6],last:5,getvolumebynam:[5,6],against:[5,6],instanc:5,login:[5,6,9],com:1,comment:[0,6,5],overview:0,pot:5,header:5,suppli:5,assum:9,reciev:5,server_id:[5,6],hplefthand:[5,6,7],httpservererror:[5,6],json:5,walter:[5,7,4,8],get_cod:5,getclusterbynam:[5,6],teapot:5,servic:[5,6],abov:9,error:[5,4],sizekb:[5,6],tabl:0,cluster:[5,6],itself:5,"__init__":5,develop:[5,8],author:[7,5,6,4,8],receiv:5,media:5,iscsi:[5,6],tutori:[0,9],document:[3,0],conflict:5,complet:0,http:[0,3,4,5,7,8,9],hostnam:5,driver:[0,1],httpbadgatewai:5,rais:[5,6,9],user:5,volume_id:[5,6],chang:[0,2],off:5,reset_tim:5,without:9,command:0,thi:[0,4,5,6,7,9],"3par":[],everyth:0,latest:1,protocol:5,createvolum:[5,6],resp:5,rest:[0,2,3,4,5,6,7,8],httpunsupportedmediatyp:5,httpexpectationfail:5,easi:9,drive:[0,9],except:[3,5,4,8,9],param:[5,6],add:[5,6],around:9,know:[0,7,5],password:[5,6,9],isthinprovis:[5,6],docutil:[7,6,4,8],server:[5,6,9],unsupportedvers:5,martin:[5,6,8],output:5,page:0,chapnam:[5,6],inheritaccess:[5,6],some:[5,6,4],back:[5,4],bore:[5,7,4,8],transport:[5,6],subclass:5,track:0,larg:5,fom:[5,6],chapinitiatorsecret:[5,6],run:[0,9],insecur:5,usag:5,httpgone:5,http_statu:5,chapauthenticationrequir:[5,6],prerequisit:9,post:5,about:[0,6,5],actual:[5,7],done:9,act:5,easy_instal:1,httprequesturitoolong:5,storag:0,your:5,git:1,span:[7,6,4,8],log:[5,6,9],support:5,secret:[5,6],"long":5,avail:5,start:[0,9],includ:[5,7],stai:1,unexpect:9,httpjsonrestcli:5,clonesnapshot:[5,6],"true":[5,6],info:5,packard:[5,8],iqn:[5,6],limit:5,shapshot:[5,6],gone:5,creat:[5,6,9],"int":[5,6],doesn:[5,6],unauthent:5,chap:[5,6],exist:[5,6,9],file:[0,5],pip:1,source_snapshot_id:[5,6],check:1,again:5,somenam:[5,6],api_url:[5,6],when:9,portion:5,invalid:5,field:5,other:[5,6,1,9],bool:[5,6],librari:5,you:[0,1,4,5,6,9],intend:9,firmwar:[5,6],httpforbidden:5,getserv:[5,6],removeserveraccess:[5,6],inserverclust:[5,6],sphinx:0,longer:5,directori:0,descript:[7,5,6,4],sizemib:5,time:5,httpconflict:[5,6],isadaptiveoptimizationen:[5,6]},objtypes:{"0":"py:module","1":"py:data","2":"py:attribute","3":"py:method","4":"py:exception","5":"py:function","6":"py:class"},titles:["HPLeftHandClient 1.0.0 Documentation","Installing / Upgrading","Changelog","API Documentation","exceptions – HTTP Exceptions","hplefthandclient Package","client – HPLeftHandClient","http – HTTP REST Base Class","client – HPLeftHandClient","Tutorial"],objnames:{"0":["py","module","Python module"],"1":["py","data","Python data"],"2":["py","attribute","Python attribute"],"3":["py","method","Python method"],"4":["py","exception","Python exception"],"5":["py","function","Python function"],"6":["py","class","Python class"]},filenames:["index","installation","changelog","api/index","api/hplefthandclient/exceptions","hplefthandclient","api/hplefthandclient/client","api/hplefthandclient/http","api/hplefthandclient/index","tutorial"]}) \ No newline at end of file diff --git a/docs/_build/html/tutorial.html b/docs/_build/html/tutorial.html new file mode 100644 index 0000000..f89c0da --- /dev/null +++ b/docs/_build/html/tutorial.html @@ -0,0 +1,187 @@ + + + + + + + + + + Tutorial — HP LeftHand REST Client 1.0.0 documentation + + + + + + + + + + + + + + + +
+
+
+
+ +
+

Tutorial¶

+

This tutorial is intended as an introduction to working with +HPLeftHandClient.

+
+

Prerequisites¶

+

Before we start, make sure that you have the HPLeftHandClient distribution +installed. In the Python shell, the following +should run without raising an exception:

+
>>> import hplefthandclient
+
+
+

This tutorial also assumes that a LeftHand array is up and running and the +LeftHand OS is running.

+
+
+

Create the Client and login¶

+

The first step when working with HPLeftHandClient is to create a +HPLeftHandClient to the LeftHand drive array +and logging in to create the session. You must login() prior to calling the other APIs to do work on the LeftHand. +Doing so is easy:

+
from hplefthandclient import client, exceptions
+#this creates the client object and sets the url to the
+#LeftHand server with IP 10.10.10.10 on port 8008.
+cl = client.HPLeftHandClient("https://10.10.10.10:8008/api/v1")
+
+try:
+    cl.login(username, password)
+    print "Login worked!"
+except exceptions.HTTPUnauthorized as ex:
+    print "Login failed."
+
+
+

When you are done with the the client, it’s a good idea to logout from +the LeftHand so there isn’t a stale session sitting around.

+
cl.logout()
+print "logout worked"
+
+
+
+
+

Getting a list of Volumes¶

+

After you have logged in, you can start making calls to the LeftHand APIs. +A simple example is getting a list of existing volumes on the array with +a call to getVolumes().

+
import pprint
+try:
+   volumes = cl.getVolumes()
+   pprint.pprint(volumes)
+except exceptions.HTTPUnauthorized as ex:
+   print "You must login first"
+except Exception as ex:
+   #something unexpected happened
+   print ex
+
+
+
+

Note

+

volumes is an array of volumes in the above call

+
+
+
+ + +
+
+
+
+
+

Table Of Contents

+ + +

Previous topic

+

Installing / Upgrading

+

Next topic

+

Changelog

+

This Page

+ + + +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_static/empty_dir b/docs/_static/empty_dir new file mode 100644 index 0000000..e69de29 diff --git a/docs/api/hplefthandclient/client.rst b/docs/api/hplefthandclient/client.rst new file mode 100644 index 0000000..a49aaf9 --- /dev/null +++ b/docs/api/hplefthandclient/client.rst @@ -0,0 +1,34 @@ +:mod:`client` -- HPLeftHandClient +================================= + +.. automodule:: hplefthandclient.client + :synopsis: HP LeftHand REST Web client + + .. autoclass:: hplefthandclient.client.HPLeftHandClient(api_url) + + .. automethod:: debug_rest + .. automethod:: login + .. automethod:: logout + .. automethod:: getClusters + .. automethod:: getCluster + .. automethod:: getClusterByName + .. automethod:: getServers + .. automethod:: getServer + .. automethod:: getServerByName + .. automethod:: createServer + .. automethod:: deleteServer + .. automethod:: getSnapshots + .. automethod:: getSnapshot + .. automethod:: getSnapshotByName + .. automethod:: createSnapshot + .. automethod:: deleteSnapshot + .. automethod:: cloneSnapshot + .. automethod:: getVolumes + .. automethod:: getVolume + .. automethod:: getVolumeByName + .. automethod:: createVolume + .. automethod:: deleteVolume + .. automethod:: modifyVolume + .. automethod:: cloneVolume + .. automethod:: addServerAccess + .. automethod:: removeServerAccess diff --git a/docs/api/hplefthandclient/exceptions.rst b/docs/api/hplefthandclient/exceptions.rst new file mode 100644 index 0000000..0fae7ca --- /dev/null +++ b/docs/api/hplefthandclient/exceptions.rst @@ -0,0 +1,8 @@ +:mod:`exceptions` -- HTTP Exceptions +==================================================== + +.. automodule:: hplefthandclient.exceptions + :synopsis: HTTP Exceptions + + .. autoclass:: hplefthandclient.exceptions.HTTPNotFound + .. autoclass:: hplefthandclient.exceptions.HTTPBadRequest diff --git a/docs/api/hplefthandclient/http.rst b/docs/api/hplefthandclient/http.rst new file mode 100644 index 0000000..e10be95 --- /dev/null +++ b/docs/api/hplefthandclient/http.rst @@ -0,0 +1,24 @@ +:mod:`http` -- HTTP REST Base Class +==================================================== + +.. automodule:: hplefthandclient.http + :synopsis: HTTP REST Base Class + + .. autoclass::hplefthandclient.http(api_url, [insecure=False[,http_log_debug=False]]) + + .. automethod:: authenticate + .. automethod:: unauthenticate + + .. describe:: c[db_name] || c.db_name + + Get the `db_name` :class:`~pymongo.database.Database` on :class:`Connection` `c`. + + Raises :class:`~pymongo.errors.InvalidName` if an invalid database name is used. + + .. autoattribute:: api_url + .. autoattribute:: http_log_debug + .. automethod:: request + .. automethod:: get + .. automethod:: post + .. automethod:: put + .. automethod:: delete diff --git a/docs/api/hplefthandclient/index.rst b/docs/api/hplefthandclient/index.rst new file mode 100644 index 0000000..90c1d36 --- /dev/null +++ b/docs/api/hplefthandclient/index.rst @@ -0,0 +1,18 @@ +:mod:`client` -- HPLeftHandClient +================================= + +.. automodule:: hplefthandclient + :synopsis: HP LeftHand REST Web client + + .. autodata:: version + + +Sub-modules: + +.. toctree:: + :maxdepth: 2 + + client + exceptions + http + diff --git a/docs/api/index.rst b/docs/api/index.rst new file mode 100644 index 0000000..ac5cd35 --- /dev/null +++ b/docs/api/index.rst @@ -0,0 +1,11 @@ +API Documentation +================= + +The HP LeftHand Client package contains a :mod:`hplefthandclient` class which extends a more +generic :mod:`http` class for doing REST calls + +.. toctree:: + :maxdepth: 2 + + hplefthandclient/index + diff --git a/docs/changelog.rst b/docs/changelog.rst new file mode 100644 index 0000000..d9bcd9c --- /dev/null +++ b/docs/changelog.rst @@ -0,0 +1,9 @@ +Changelog +========= + + +Changes in Version 1.0.0 +------------------------ + +- First implementation of the REST API Client + diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 0000000..becc753 --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,289 @@ +# -*- coding: utf-8 -*- +# +# hplefthandclient documentation build configuration file, created by +# sphinx-quickstart on Mon Nov 25 13:33:35 2013. +# +# This file is execfile()d with the current directory set to its containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys, os + +sys.path.insert(0,os.path.realpath(os.path.abspath('../'))) + +import hplefthandclient + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +#sys.path.insert(0, os.path.abspath('.')) + +# -- General configuration ----------------------------------------------------- + +# If your documentation needs a minimal Sphinx version, state it here. +#needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be extensions +# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. +extensions = ['sphinx.ext.autodoc', 'sphinx.ext.todo', 'sphinx.ext.coverage', 'sphinx.ext.viewcode'] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix of source filenames. +source_suffix = '.rst' + +# The encoding of source files. +#source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'HP LeftHand REST Client' +copyright = u'2013 Hewlett Packard Development Company, L.P.' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = hplefthandclient.version +# The full version, including alpha/beta/rc tags. +release = hplefthandclient.version + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +#language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = ['_build'] + +# The reST default role (used for this markup: `text`) to use for all documents. +#default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +#modindex_common_prefix = [] + + +# -- Options for HTML output --------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +html_theme = 'default' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +#html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +#html_theme_path = [] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +#html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_domain_indices = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +#html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +#html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +#html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = None + +# Output file base name for HTML help builder. +htmlhelp_basename = 'HPLeftHandClient' + release.replace('.','_') + + +# -- Options for LaTeX output -------------------------------------------------- + +latex_elements = { +# The paper size ('letterpaper' or 'a4paper'). +#'papersize': 'letterpaper', + +# The font size ('10pt', '11pt' or '12pt'). +#'pointsize': '10pt', + +# Additional stuff for the LaTeX preamble. +#'preamble': '', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, author, documentclass [howto/manual]). +latex_documents = [ + ('index', 'hplefthandclient.tex', u'hplefthandclient Documentation', + u'Author', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# If true, show page references after internal links. +#latex_show_pagerefs = False + +# If true, show URL addresses after external links. +#latex_show_urls = False + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_domain_indices = True + + +# -- Options for manual page output -------------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + ('index', 'hplefthandclient', u'hplefthandclient Documentation', + [u'Author'], 1) +] + +# If true, show URL addresses after external links. +#man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------------ + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + ('index', 'hplefthandclient', u'hplefthandclient Documentation', + u'Author', 'hplefthandclient', 'One line description of project.', + 'Miscellaneous'), +] + +# Documents to append as an appendix to all manuals. +#texinfo_appendices = [] + +# If false, no module index is generated. +#texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +#texinfo_show_urls = 'footnote' + + +# -- Options for Epub output --------------------------------------------------- + +# Bibliographic Dublin Core info. +epub_title = u'hplefthandclient' +epub_author = u'Author' +epub_publisher = u'Author' +epub_copyright = u'2013, Author' + +# The language of the text. It defaults to the language option +# or en if the language is not set. +#epub_language = '' + +# The scheme of the identifier. Typical schemes are ISBN or URL. +#epub_scheme = '' + +# The unique identifier of the text. This can be a ISBN number +# or the project homepage. +#epub_identifier = '' + +# A unique identification for the text. +#epub_uid = '' + +# A tuple containing the cover image and cover page html template filenames. +#epub_cover = () + +# HTML files that should be inserted before the pages created by sphinx. +# The format is a list of tuples containing the path and title. +#epub_pre_files = [] + +# HTML files shat should be inserted after the pages created by sphinx. +# The format is a list of tuples containing the path and title. +#epub_post_files = [] + +# A list of files that should not be packed into the epub file. +#epub_exclude_files = [] + +# The depth of the table of contents in toc.ncx. +#epub_tocdepth = 3 + +# Allow duplicate toc entries. +#epub_tocdup = True diff --git a/docs/hplefthandclient.rst b/docs/hplefthandclient.rst new file mode 100644 index 0000000..f4c11e6 --- /dev/null +++ b/docs/hplefthandclient.rst @@ -0,0 +1,37 @@ +hplefthandclient Package +======================== + +:mod:`hplefthandclient` Package +------------------------------- + +.. automodule:: hplefthandclient.__init__ + :members: + :undoc-members: + :show-inheritance: + +:mod:`client` Module +-------------------- + +.. automodule:: hplefthandclient.client + :members: + :undoc-members: + :show-inheritance: + +:mod:`exceptions` Module +------------------------ + +.. automodule:: hplefthandclient.exceptions + :members: + :undoc-members: + :show-inheritance: + +:mod:`http` Module +------------------ + +.. automodule:: hplefthandclient.http + :members: + :undoc-members: + :show-inheritance: + + + diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 0000000..e52d03c --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,65 @@ +HPLeftHandClient |release| Documentation +======================================== + +Overview +-------- +**HPLeftHandClient** is a Python package containing a class that uses +HTTP REST calls to talk with an HP LeftHand/StoreVirtual drive array. +distribution containing tools for working with +`LeftHand/StoreVirtual Storage Arrays `_. +. This documentation attempts to explain +everything you need to know to use **HPLeftHandClient**. + +:doc:`installation` + Instructions on how to get the distribution. + +:doc:`tutorial` + Start here for a quick overview. + +:doc:`api/index` + The complete API documentation, organized by module. + +Issues +------ +.. todo:: create the open source website +.. todo:: create the bug tracker + +All issues should be reported (and can be tracked / voted for / +commented on) at the main `github issues +`_, in the "LeftHand Python Driver" +project. + +Changes +------- +See the :doc:`changelog` for a full list of changes to HPLeftHandClient. + + +About This Documentation +------------------------ +This documentation is generated using the `Sphinx +`_ documentation generator. The source files +for the documentation are located in the *doc/* directory of the +**HPLeftHandClient** distribution. To generate the docs locally run the +following command from the root directory of the **HPLeftHandClient** + +.. code-block:: bash + + $ python setup.py doc + + +.. toctree:: + :hidden: + + installation + tutorial + changelog + api/index + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` + diff --git a/docs/installation.rst b/docs/installation.rst new file mode 100644 index 0000000..e83124c --- /dev/null +++ b/docs/installation.rst @@ -0,0 +1,43 @@ +Installing / Upgrading +====================== +.. highlight:: bash + +**HPLeftHandClient** is in the `Python Package Index +`_. + +Installing with pip +------------------- + +We prefer `pip `_ +to install hplefthandclient on platforms other than Windows:: + + $ pip install hplefthandclient + +To upgrade using pip:: + + $ pip install --upgrade hplefthandclient + +Installing with easy_install +---------------------------- + +If you must install hplefthandclient using +`setuptools `_ do:: + + $ easy_install hplefthandclient + +To upgrade do:: + + $ easy_install -U hplefthandclient + + +Installing from source +---------------------- + +If you'd rather install directly from the source (i.e. to stay on the +bleeding edge), install the C extension dependencies then check out the +latest source from github and install the driver from the resulting tree:: + + $ git clone git://github.com/WaltHP/python-3parclient.git + $ cd pyton-hplefthandclient/ + $ python setup.py install + diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 0000000..dbad203 --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,190 @@ +@ECHO OFF + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set BUILDDIR=_build +set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . +set I18NSPHINXOPTS=%SPHINXOPTS% . +if NOT "%PAPER%" == "" ( + set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% + set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% +) + +if "%1" == "" goto help + +if "%1" == "help" ( + :help + echo.Please use `make ^` where ^ is one of + echo. html to make standalone HTML files + echo. dirhtml to make HTML files named index.html in directories + echo. singlehtml to make a single large HTML file + echo. pickle to make pickle files + echo. json to make JSON files + echo. htmlhelp to make HTML files and a HTML help project + echo. qthelp to make HTML files and a qthelp project + echo. devhelp to make HTML files and a Devhelp project + echo. epub to make an epub + echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter + echo. text to make text files + echo. man to make manual pages + echo. texinfo to make Texinfo files + echo. gettext to make PO message catalogs + echo. changes to make an overview over all changed/added/deprecated items + echo. linkcheck to check all external links for integrity + echo. doctest to run all doctests embedded in the documentation if enabled + goto end +) + +if "%1" == "clean" ( + for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i + del /q /s %BUILDDIR%\* + goto end +) + +if "%1" == "html" ( + %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/html. + goto end +) + +if "%1" == "dirhtml" ( + %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. + goto end +) + +if "%1" == "singlehtml" ( + %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. + goto end +) + +if "%1" == "pickle" ( + %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the pickle files. + goto end +) + +if "%1" == "json" ( + %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the JSON files. + goto end +) + +if "%1" == "htmlhelp" ( + %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run HTML Help Workshop with the ^ +.hhp project file in %BUILDDIR%/htmlhelp. + goto end +) + +if "%1" == "qthelp" ( + %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run "qcollectiongenerator" with the ^ +.qhcp project file in %BUILDDIR%/qthelp, like this: + echo.^> qcollectiongenerator %BUILDDIR%\qthelp\hplefthandclient.qhcp + echo.To view the help file: + echo.^> assistant -collectionFile %BUILDDIR%\qthelp\hplefthandclient.ghc + goto end +) + +if "%1" == "devhelp" ( + %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. + goto end +) + +if "%1" == "epub" ( + %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The epub file is in %BUILDDIR%/epub. + goto end +) + +if "%1" == "latex" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "text" ( + %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The text files are in %BUILDDIR%/text. + goto end +) + +if "%1" == "man" ( + %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The manual pages are in %BUILDDIR%/man. + goto end +) + +if "%1" == "texinfo" ( + %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. + goto end +) + +if "%1" == "gettext" ( + %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The message catalogs are in %BUILDDIR%/locale. + goto end +) + +if "%1" == "changes" ( + %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes + if errorlevel 1 exit /b 1 + echo. + echo.The overview file is in %BUILDDIR%/changes. + goto end +) + +if "%1" == "linkcheck" ( + %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck + if errorlevel 1 exit /b 1 + echo. + echo.Link check complete; look for any errors in the above output ^ +or in %BUILDDIR%/linkcheck/output.txt. + goto end +) + +if "%1" == "doctest" ( + %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest + if errorlevel 1 exit /b 1 + echo. + echo.Testing of doctests in the sources finished, look at the ^ +results in %BUILDDIR%/doctest/output.txt. + goto end +) + +:end diff --git a/docs/tutorial.rst b/docs/tutorial.rst new file mode 100644 index 0000000..8ec3b00 --- /dev/null +++ b/docs/tutorial.rst @@ -0,0 +1,68 @@ +Tutorial +======== + +This tutorial is intended as an introduction to working with +**HPLeftHandClient**. + +Prerequisites +------------- +Before we start, make sure that you have the **HPLeftHandClient** distribution +:doc:`installed `. In the Python shell, the following +should run without raising an exception: + +.. code-block:: bash + + >>> import hplefthandclient + +This tutorial also assumes that a LeftHand array is up and running and the +LeftHand OS is running. + +Create the Client and login +--------------------------- +The first step when working with **HPLeftHandClient** is to create a +:class:`~hplefthandclient.client.HPLeftHandClient` to the LeftHand drive array +and logging in to create the session. You must :meth:`~hplefthandclient.client.HPLeftHandClient.login` prior to calling the other APIs to do work on the LeftHand. +Doing so is easy: + +.. code-block:: python + + from hplefthandclient import client, exceptions + #this creates the client object and sets the url to the + #LeftHand server with IP 10.10.10.10 on port 8008. + cl = client.HPLeftHandClient("https://10.10.10.10:8008/api/v1") + + try: + cl.login(username, password) + print "Login worked!" + except exceptions.HTTPUnauthorized as ex: + print "Login failed." + +When you are done with the the client, it's a good idea to logout from +the LeftHand so there isn't a stale session sitting around. + +.. code-block:: python + + cl.logout() + print "logout worked" + +Getting a list of Volumes +------------------------- +After you have logged in, you can start making calls to the LeftHand APIs. +A simple example is getting a list of existing volumes on the array with +a call to :meth:`~hplefthandclient.client.HPLeftHandClient.getVolumes`. + +.. code-block:: python + + import pprint + try: + volumes = cl.getVolumes() + pprint.pprint(volumes) + except exceptions.HTTPUnauthorized as ex: + print "You must login first" + except Exception as ex: + #something unexpected happened + print ex + + +.. note:: volumes is an array of volumes in the above call + diff --git a/hplefthandclient/__init__.py b/hplefthandclient/__init__.py new file mode 100644 index 0000000..9648d50 --- /dev/null +++ b/hplefthandclient/__init__.py @@ -0,0 +1,36 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# Copyright 2013 Hewlett Packard Development Company, L.P. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +""" +HP LeftHand REST Client + +:Author: Kurt Martin +:Author: Walter A. Boring IV +:Copyright: Copyright 2013, Hewlett Packard Development Company, L.P. +:License: Apache v2.0 + +""" + +version_tuple = (1, 0, 0) + + +def get_version_string(): + if isinstance(version_tuple[-1], basestring): + return '.'.join(map(str, version_tuple[:-1])) + version_tuple[-1] + return '.'.join(map(str, version_tuple)) + +version = get_version_string() +"""Current version of HPLeftHandClient.""" diff --git a/hplefthandclient/client.py b/hplefthandclient/client.py new file mode 100644 index 0000000..ee24517 --- /dev/null +++ b/hplefthandclient/client.py @@ -0,0 +1,497 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# Copyright 2012 Hewlett Packard Development Company, L.P. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +""" +HPLeftHand REST Client + +.. module: HPLeftHandClient +.. moduleauthor: Kurt Martin + +:Author: Kurt Martin +:Description: This is the LeftHand/StoreVirtual Client that talks to the +LeftHand OS REST Service. + +This client requires and works with version 11.5 of the LeftHand firmware + +""" + +from hplefthandclient import http + + +class HPLeftHandClient: + + def __init__(self, api_url): + self.api_url = api_url + self.http = http.HTTPJSONRESTClient(self.api_url) + + def debug_rest(self, flag): + """ + This is useful for debugging requests to LeftHand + + :param flag: set to True to enable debugging + :type flag: bool + + """ + self.http.set_debug_flag(flag) + + def login(self, username, password): + """ + This authenticates against the LH OS REST server and creates a session. + + :param username: The username + :type username: str + :param password: The password + :type password: str + + :returns: None + + """ + self.http.authenticate(username, password) + + def logout(self): + """ + This destroys the session and logs out from the LH OS server + + :returns: None + + """ + self.http.unauthenticate() + + def getClusters(self): + """ + Get the list of Clusters + + :returns: list of Clusters + """ + response, body = self.http.get('/clusters') + return body + + def getCluster(self, cluster_id): + """ + Get information about a Cluster + + :param cluster_id: The id of the cluster to find + :type cluster_id: str + + :returns: cluster + """ + response, body = self.http.get('/clusters/%s' % cluster_id) + return body + + def getClusterByName(self, name): + """ + Get information about a cluster by name + + :param name: The name of the cluster to find + :type name: str + + :returns: cluster + :raises: :class:`~hplefthandclient.exceptions.HTTPNotFound` - + NON_EXISTENT_CLUSTER - cluster doesn't exist + """ + response, body = self.http.get('/clusters?name=%s' % name) + return body + + def getServers(self): + """ + Get the list of Servers + + :returns: list of Servers + """ + response, body = self.http.get('/servers') + return body + + def getServer(self, server_id): + """ + Get information about a server + + :param server_id: The id of the server to find + :type server_id: str + + :returns: server + :raises: :class:`~hplefthandclient.exceptions.HTTPServerError` + """ + response, body = self.http.get('/servers/%s' % server_id) + return body + + def getServerByName(self, name): + """ + Get information about a server by name + + :param name: The name of the server to find + :type name: str + + :returns: server + :raises: :class:`~hplefthandclient.exceptions.HTTPNotFound` - + NON_EXISTENT_SERVER - server doesn't exist + """ + response, body = self.http.get('/servers?name=%s' % name) + return body + + def createServer(self, name, iqn, optional=None): + """ + Create a server by name + + :param name: The name of the server to create + :type name: str + :param iqn: The iSCSI qualified name + :type name: str + :param optional: Dictionary of optional params + :type optional: dict + + .. code-block:: python + + optional = { + 'description' : "some comment", + 'iscsiEnabled' : True, + 'chapName': "some chap name", + 'chapAuthenticationRequired': False, + 'chapInitiatorSecret': "initiator secret", + 'chapTargetSecret': "target secret", + 'iscsiLoadBalancingEnabled': True, + 'controllingServerName': "server name", + 'fibreChannelEnabled': False, + 'inServerCluster": True + } + + :returns: server + :raises: :class:`~hplefthandclient.exceptions.HTTPNotFound` - + NON_EXISTENT_SERVER - server doesn't exist + """ + info = {'name': name, 'iscsiIQN': iqn} + if optional: + info = self._mergeDict(info, optional) + + response, body = self.http.post('/servers', body=info) + return body + + def deleteServer(self, server_id): + """ + Delete a Server + + :param server_id: the server ID to delete + + :raises: :class:`~hplefthandclient.exceptions.HTTPNotFound` - + NON_EXISTENT_SERVER - The server does not exist + """ + response, body = self.http.delete('/servers/%s' % server_id) + return body + + def getSnapshots(self): + """ + Get the list of Snapshots + + :returns: list of Snapshots + """ + response, body = self.http.get('/snapshots') + return body + + def getSnapshot(self, snapshot_id): + """ + Get information about a Snapshot + + :returns: snapshot + :raises: :class:`~hplefthandclient.exceptions.HTTPServerError` + """ + response, body = self.http.get('/snapshots/%s' % snapshot_id) + return body + + def getSnapshotByName(self, name): + """ + Get information about a snapshot by name + + :param name: The name of the snapshot to find + + :returns: volume + :raises: :class:`~hplefthandclient.exceptions.HTTPNotFound` - + NON_EXISTENT_SNAP - shapshot doesn't exist + """ + response, body = self.http.get('/snapshots?name=%s' % name) + return body + + def createSnapshot(self, name, source_volume_id, optional=None): + """ + Create a snapshot of an existing Volume + + :param name: Name of the Snapshot + :type name: str + :param source_volume_id: The volume you want to snapshot + :type source_volume_id: int + :param optional: Dictionary of optional params + :type optional: dict + + .. code-block:: python + + optional = { + 'description' : "some comment", + 'inheritAccess' : false + } + + """ + parameters = {'name': name} + if optional: + parameters = self._mergeDict(parameters, optional) + + info = {'action': 'createSnapshot', + 'parameters': parameters} + + response, body = self.http.post('/volumes/%s' % source_volume_id, + body=info) + return body + + def deleteSnapshot(self, snapshot_id): + """ + Delete a Snapshot + + :param snapshot_id: the snapshot ID to delete + + :raises: :class:`~hplefthandclient.exceptions.HTTPNotFound` - + NON_EXISTENT_SNAPSHOT - The snapshot does not exist + """ + response, body = self.http.delete('/snapshots/%s' % snapshot_id) + return body + + def cloneSnapshot(self, name, source_snapshot_id, optional=None): + """ + Create a clone of an existing Shapshot + + :param name: Name of the Snapshot clone + :type name: str + :param source_snapshot_id: The snapshot you want to clone + :type source_snapshot_id: int + :param optional: Dictionary of optional params + :type optional: dict + + .. code-block:: python + + optional = { + 'description' : "some comment" + } + + """ + parameters = {'name': name} + if optional: + parameters = self._mergeDict(parameters, optional) + + info = {'action': 'createSmartClone', + 'parameters': parameters} + + response, body = self.http.post('/snapshots/%s' % source_snapshot_id, + body=info) + return body + + def getVolumes(self): + """ + Get the list of Volumes + + :returns: list of Volumes + """ + response, body = self.http.get('/volumes') + return body + + def getVolume(self, volume_id): + """ + Get information about a volume + + :param volume_id: The id of the volume to find + :type volume_id: str + + :returns: volume + :raises: :class:`~hplefthandclient.exceptions.HTTPNotFound` - + NON_EXISTENT_VOL - volume doesn't exist + """ + response, body = self.http.get('/volumes/%s' % volume_id) + return body + + def getVolumeByName(self, name): + """ + Get information about a volume by name + + :param name: The name of the volume to find + :type volume_id: str + + :returns: volume + :raises: :class:`~hplefthandclient.exceptions.HTTPNotFound` - + NON_EXISTENT_VOL - volume doesn't exist + """ + response, body = self.http.get('/volumes?name=%s' % name) + return body + + def createVolume(self, name, cluster_id, size, optional=None): + """ Create a new volume + + :param name: the name of the volume + :type name: str + :param cluster_id: the cluster Id + :type cluster_id: int + :param sizeKB: size in KB for the volume + :type sizeKB: int + :param optional: dict of other optional items + :type optional: dict + + .. code-block:: python + + optional = { + 'description': 'some comment', + 'isThinProvisioned': 'true', + 'autogrowSeconds': 200, + 'clusterName': 'somename', + 'isAdaptiveOptimizationEnabled': 'true', + 'dataProtectionLevel': 2, + } + + :returns: List of Volumes + + :raises: :class:`~hplefthandclient.exceptions.HTTPConflict` - + EXISTENT_SV - Volume Exists already + """ + info = {'name': name, 'clusterID': cluster_id, 'size': size} + if optional: + info = self._mergeDict(info, optional) + + response, body = self.http.post('/volumes', body=info) + return body + + def deleteVolume(self, volume_id): + """ + Delete a volume + + :param name: the name of the volume + :type name: str + + :raises: :class:`~hplefthandclient.exceptions.HTTPNotFound` - + NON_EXISTENT_VOL - The volume does not exist + """ + response, body = self.http.delete('/volumes/%s' % volume_id) + return body + + def modifyVolume(self, volume_id, optional): + """Modify an existing volume. + + :param volume_id: The id of the volume to find + :type volume_id: str + + :returns: volume + :raises: :class:`~hplefthandclient.exceptions.HTTPNotFound` - + NON_EXISTENT_VOL - volume doesn't exist + """ + info = {'volume_id': volume_id} + info = self._mergeDict(info, optional) + response, body = self.http.put('/volumes/%s' % volume_id, body=info) + return body + + def cloneVolume(self, name, source_volume_id, optional=None): + """ + Create a clone of an existing Volume + + :param name: Name of the Volume clone + :type name: str + :param source_volume_id: The Volume you want to clone + :type source_volume_id: int + :param optional: Dictionary of optional params + :type optional: dict + + .. code-block:: python + + optional = { + 'description' : "some comment" + } + + """ + parameters = {'name': name} + if optional: + parameters = self._mergeDict(parameters, optional) + + info = {'action': 'createSmartClone', + 'parameters': parameters} + + response, body = self.http.post('/volumes/%s' % source_volume_id, + body=info) + return body + + def addServerAccess(self, volume_id, server_id, optional=None): + """ + Assign a Volume to a Server + + :param volume_id: Volume ID of the volume + :type name: int + :param server_id: Server ID of the server to add the volume to + :type source_volume_id: int + :param optional: Dictionary of optional params + :type optional: dict + + .. code-block:: python + + optional = { + 'Transport' : 0, + 'Lun' : 1, + } + + """ + parameters = {'serverID': server_id, + 'exclusiveAccess': True, + 'readAccess': True, + 'writeAccess': True} + if optional: + parameters = self._mergeDict(parameters, optional) + + info = {'action': 'addServerAccess', + 'parameters': parameters} + + response, body = self.http.post('/volumes/%s' % volume_id, + body=info) + return body + + def removeServerAccess(self, volume_id, server_id): + """ + Unassign a Volume from a Server + + :param volume_id: Volume ID of the volume + :type name: int + :param server_id: Server ID of the server to remove the volume fom + :type source_volume_id: int + + """ + parameters = {'serverID': server_id} + + info = {'action': 'removeServerAccess', + 'parameters': parameters} + + response, body = self.http.post('/volumes/%s' % volume_id, + body=info) + return body + + def _mergeDict(self, dict1, dict2): + """ + Safely merge 2 dictionaries together + + :param dict1: The first dictionary + :type dict1: dict + :param dict2: The second dictionary + :type dict2: dict + + :returns: dict + + :raises Exception: dict1, dict2 is not a dictionary + """ + if type(dict1) is not dict: + raise Exception("dict1 is not a dictionary") + if type(dict2) is not dict: + raise Exception("dict2 is not a dictionary") + + dict3 = dict1.copy() + dict3.update(dict2) + return dict3 diff --git a/hplefthandclient/exceptions.py b/hplefthandclient/exceptions.py new file mode 100644 index 0000000..678844d --- /dev/null +++ b/hplefthandclient/exceptions.py @@ -0,0 +1,308 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# Copyright 2012 Hewlett Packard Development Company, L.P. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +""" +Exceptions for the client + +.. module: Exceptions + +:Author: Walter A. Boring IV +:Description: This contains the HTTP exceptions that can come back from the REST calls +""" + + +class UnsupportedVersion(Exception): + """ + Indicates that the user is trying to use an unsupported version of the API + """ + pass + +class CommandError(Exception): + pass + +class AuthorizationFailure(Exception): + pass + +class NoUniqueMatch(Exception): + pass + +class ClientException(Exception): + """ + The base exception class for all exceptions this library raises. + + :param error: The error array + :type error: array + + """ + _error_code = None + _error_desc = None + + _debug1 = None + _debug2 = None + + def __init__(self, error=None): + if error: + if 'messageID' in error: + self._error_code = error['messageID'] + if 'message' in error: + self._error_desc = error['message'] + + if 'debug1' in error: + self._debug1 = error['debug1'] + if 'debug2' in error: + self._debug2 = error['debug2'] + + def get_code(self): + return self._error_code + + def get_description(self): + return self._error_desc + + + def __str__(self): + formatted_string = "%s (HTTP %s)" % (self.message, self.http_status) + if self._error_code: + formatted_string += " %s" % self._error_code + if self._error_desc: + formatted_string += " - %s" % self._error_desc + + if self._debug1: + formatted_string += " (1: '%s')" % self._debug1 + + if self._debug2: + formatted_string += " (2: '%s')" % self._debug2 + + return formatted_string + + +## +## 400 Errors +## + + +class HTTPBadRequest(ClientException): + """ + HTTP 400 - Bad request: you sent some malformed data. + """ + http_status = 400 + message = "Bad request" + + +class HTTPUnauthorized(ClientException): + """ + HTTP 401 - Unauthorized: bad credentials. + """ + http_status = 401 + message = "Unauthorized" + + +class HTTPForbidden(ClientException): + """ + HTTP 403 - Forbidden: your credentials don't give you access to this + resource. + """ + http_status = 403 + message = "Forbidden" + + +class HTTPNotFound(ClientException): + """ + HTTP 404 - Not found + """ + http_status = 404 + message = "Not found" + +class HTTPMethodNotAllowed(ClientException): + """ + HTTP 405 - Method not Allowed + """ + http_status = 405 + message = "Method Not Allowed" + +class HTTPNotAcceptable(ClientException): + """ + HTTP 406 - Method not Acceptable + """ + http_status = 406 + message = "Method Not Acceptable" + +class HTTPProxyAuthRequired(ClientException): + """ + HTTP 407 - The client must first authenticate itself with the proxy. + """ + http_status = 407 + message = "Proxy Authentication Required" + +class HTTPRequestTimeout(ClientException): + """ + HTTP 408 - The server timed out waiting for the request. + """ + http_status = 408 + message = "Request Timeout" + + +class HTTPConflict(ClientException): + """ + HTTP 409 - Conflict: A Conflict happened on the server + """ + http_status = 409 + message = "Conflict" + +class HTTPGone(ClientException): + """ + HTTP 410 - Indicates that the resource requested is no longer available and will not be available again. + """ + http_status = 410 + message = "Gone" + +class HTTPLengthRequired(ClientException): + """ + HTTP 411 - The request did not specify the length of its content, which is required by the requested resource. + """ + http_status = 411 + message = "Length Required" + +class HTTPPreconditionFailed(ClientException): + """ + HTTP 412 - The server does not meet one of the preconditions that the requester put on the request. + """ + http_status = 412 + message = "Over limit" + +class HTTPRequestEntityTooLarge(ClientException): + """ + HTTP 413 - The request is larger than the server is willing or able to process + """ + http_status = 413 + message = "Request Entity Too Large" + +class HTTPRequestURITooLong(ClientException): + """ + HTTP 414 - The URI provided was too long for the server to process. + """ + http_status = 414 + message = "Request URI Too Large" + +class HTTPUnsupportedMediaType(ClientException): + """ + HTTP 415 - The request entity has a media type which the server or resource does not support. + """ + http_status = 415 + message = "Unsupported Media Type" + +class HTTPRequestedRangeNotSatisfiable(ClientException): + """ + HTTP 416 - The client has asked for a portion of the file, but the server cannot supply that portion. + """ + http_status = 416 + message = "Requested Range Not Satisfiable" + +class HTTPExpectationFailed(ClientException): + """ + HTTP 417 - The server cannot meet the requirements of the Expect request-header field. + """ + http_status = 417 + message = "Expectation Failed" + +class HTTPTeaPot(ClientException): + """ + HTTP 418 - I'm a Tea Pot + """ + http_status = 418 + message = "I'm A Teapot. (RFC 2324)" + + +## +## 500 Errors +## + +class HTTPServerError(ClientException): + """ + HTTP 500 - + """ + http_status = 500 + message = "Error" + +# NotImplemented is a python keyword. +class HTTPNotImplemented(ClientException): + """ + HTTP 501 - Not Implemented: the server does not support this operation. + """ + http_status = 501 + message = "Not Implemented" + +class HTTPBadGateway(ClientException): + """ + HTTP 502 - The server was acting as a gateway or proxy and received an invalid response from the upstream server. + """ + http_status = 502 + message = "Bad Gateway" + +class HTTPServiceUnavailable(ClientException): + """ + HTTP 503 - The server is currently unavailable + """ + http_status = 503 + message = "Service Unavailable" + +class HTTPGatewayTimeout(ClientException): + """ + HTTP 504 - The server was acting as a gateway or proxy and did not receive a timely response from the upstream server. + """ + http_status = 504 + message = "Gateway Timeout" + +class HTTPVersionNotSupported(ClientException): + """ + HTTP 505 - The server does not support the HTTP protocol version used in the request. + """ + http_status = 505 + message = "Version Not Supported" + + +# In Python 2.4 Exception is old-style and thus doesn't have a __subclasses__() +# so we can do this: +# _code_map = dict((c.http_status, c) +# for c in ClientException.__subclasses__()) +# +# Instead, we have to hardcode it: +_code_map = dict((c.http_status, c) for c in [HTTPBadRequest, HTTPUnauthorized, + HTTPForbidden, HTTPNotFound, HTTPMethodNotAllowed, + HTTPNotAcceptable, HTTPProxyAuthRequired, HTTPRequestTimeout, + HTTPConflict, HTTPGone, HTTPLengthRequired, + HTTPPreconditionFailed, HTTPRequestEntityTooLarge, + HTTPRequestURITooLong, HTTPUnsupportedMediaType, + HTTPRequestedRangeNotSatisfiable, HTTPExpectationFailed, + HTTPTeaPot, HTTPServerError, + HTTPNotImplemented, HTTPBadGateway, + HTTPServiceUnavailable, HTTPGatewayTimeout, + HTTPVersionNotSupported]) + + +def from_response(response, body): + """ + Return an instance of an ClientException or subclass + based on an httplib2 response. + + Usage:: + + resp, body = http.request(...) + if resp.status != 200: + raise exception_from_response(resp, body) + + """ + cls = _code_map.get(response.status, ClientException) + return cls(body) diff --git a/hplefthandclient/http.py b/hplefthandclient/http.py new file mode 100644 index 0000000..79d5f14 --- /dev/null +++ b/hplefthandclient/http.py @@ -0,0 +1,331 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# Copyright 2013 Hewlett Packard Development Company, L.P. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +""" +HPLeftHand HTTP Client +:Author: Walter A. Boring IV +:Description: This is the HTTP Client that is used to make the actual calls. + It includes the authentication that knows the cookie name for LH. + +""" + +import logging +import httplib2 +import time +import pprint + +try: + import json +except ImportError: + import simplejson as json + +from hplefthandclient import exceptions + + +class HTTPJSONRESTClient(httplib2.Http): + """ + An HTTP REST Client that sends and recieves JSON data as the body of the + HTTP request. + + :param api_url: The url to the LH OS REST service + ie. https://:/lhos + :type api_url: str + :param insecure: Use https? requires a local certificate + :type insecure: bool + + """ + + USER_AGENT = 'python-hplefthandclient' + SESSION_COOKIE_NAME = 'Authorization' + #API_VERSION = 'X-API-Version' + #CHRP_VERSION = 'X_HP-CHRP-Client-Version' + + def __init__(self, api_url, insecure=False, http_log_debug=False): + super(HTTPJSONRESTClient, + self).__init__(disable_ssl_certificate_validation=True) + self.session_key = None + + #should be http:///lhos + self.set_url(api_url) + self.set_debug_flag(http_log_debug) + + self.times = [] # [("item", starttime, endtime), ...] + + # httplib2 overrides + self.force_exception_to_status_code = True + #self.disable_ssl_certificate_validation = insecure + + self._logger = logging.getLogger(__name__) + + def set_url(self, api_url): + #should be http:///lhos + self.api_url = api_url.rstrip('/') + self.api_url = self.api_url + + def set_debug_flag(self, flag): + """ + This turns on/off http request/response debugging output to console + + :param flag: Set to True to enable debugging output + :type flag: bool + + """ + self.http_log_debug = flag + if self.http_log_debug: + ch = logging.StreamHandler() + self._logger.setLevel(logging.DEBUG) + self._logger.addHandler(ch) + + def authenticate(self, user, password, optional=None): + """ + This tries to create an authenticated session with the LH OS server + + :param user: The username + :type user: str + :param password: The password + :type password: str + + """ + #this prevens re-auth attempt if auth fails + self.auth_try = 1 + self.session_key = None + + info = {'user': user, 'password': password} + self._auth_optional = None + + if optional: + self._auth_optional = optional + info.update(optional) + + resp, body = self.post('/credentials', body=info) + if body and 'authToken' in body: + self.session_key = body['authToken'] + self.auth_try = 0 + self.user = user + self.password = password + + def _reauth(self): + self.authenticate(self.user, self.password, self._auth_optional) + + def unauthenticate(self): + """ + This clears the authenticated session with the LH server. It logs out. + + """ + #delete the session on the LH + self.delete('/credentials/%s' % self.session_key) + self.session_key = None + + def get_timings(self): + """ + Ths gives an array of the request timings since last reset_timings call + """ + return self.times + + def reset_timings(self): + """ + This resets the request/response timings array + """ + self.times = [] + + def _http_log_req(self, args, kwargs): + if not self.http_log_debug: + return + + string_parts = ['curl -i'] + for element in args: + if element in ('GET', 'POST'): + string_parts.append(' -X %s' % element) + else: + string_parts.append(' %s' % element) + + for element in kwargs['headers']: + header = ' -H "%s: %s"' % (element, kwargs['headers'][element]) + string_parts.append(header) + + self._logger.debug("\nREQ: %s\n" % "".join(string_parts)) + if 'body' in kwargs: + self._logger.debug("REQ BODY: %s\n" % (kwargs['body'])) + + def _http_log_resp(self, resp, body): + if not self.http_log_debug: + return + self._logger.debug("RESP:%s\n", pprint.pformat(resp)) + self._logger.debug("RESP BODY:%s\n", body) + + def request(self, *args, **kwargs): + """ + This makes an HTTP Request to the LH server. You should use get, post, + delete instead. + """ + if self.session_key and self.auth_try != 1: + kwargs.setdefault('headers', + {})[self.SESSION_COOKIE_NAME] = self.session_key + + kwargs.setdefault('headers', kwargs.get('headers', {})) + kwargs['headers']['User-Agent'] = self.USER_AGENT + kwargs['headers']['Accept'] = 'application/json' + if 'body' in kwargs: + kwargs['headers']['Content-Type'] = 'application/json' + kwargs['body'] = json.dumps(kwargs['body']) + + self._http_log_req(args, kwargs) + resp, body = super(HTTPJSONRESTClient, self).request(*args, **kwargs) + self._http_log_resp(resp, body) + + # Try and conver the body response to an object + # This assumes the body of the reply is JSON + if body: + try: + body = json.loads(body) + except ValueError: + #pprint.pprint("failed to decode json\n") + pass + else: + body = None + + if resp.status >= 400: + raise exceptions.from_response(resp, body) + + return resp, body + + def _time_request(self, url, method, **kwargs): + start_time = time.time() + resp, body = self.request(url, method, **kwargs) + self.times.append(("%s %s" % (method, url), + start_time, time.time())) + return resp, body + + def _do_reauth(self, url, method, ex, **kwargs): + print "_do_reauth called" + try: + if self.auth_try != 1: + self._reauth() + resp, body = self._time_request(self.api_url + url, + method, **kwargs) + return resp, body + else: + raise ex + except exceptions.HTTPUnauthorized: + raise ex + + def _cs_request(self, url, method, **kwargs): + # Perform the request once. If we get a 401 back then it + # might be because the auth token expired, so try to + # re-authenticate and try again. If it still fails, bail. + try: + resp, body = self._time_request(self.api_url + url, method, + **kwargs) + return resp, body + except exceptions.HTTPUnauthorized, ex: + print "_CS_REQUEST HTTPUnauthorized" + resp, body = self._do_reauth(url, method, ex, **kwargs) + return resp, body + except exceptions.HTTPForbidden, ex: + print "_CS_REQUEST HTTPForbidden" + resp, body = self._do_reauth(url, method, ex, **kwargs) + return resp, body + + def get(self, url, **kwargs): + """ + Make an HTTP GET request to the server. + + .. code-block:: python + + #example call + try { + headers, body = http.get('/volumes') + } except exceptions.HTTPUnauthorized as ex: + print "Not logged in" + } + + :param url: The relative url from the LH api_url + :type url: str + + :returns: headers - dict of HTTP Response headers + :returns: body - the body of the response. If the body was JSON, + it will be an object + """ + return self._cs_request(url, 'GET', **kwargs) + + def post(self, url, **kwargs): + """ + Make an HTTP POST request to the server. + + .. code-block:: python + + #example call + try { + info = {'name': 'new volume name', 'sizeMiB': 300} + headers, body = http.post('/volumes', body=info) + } except exceptions.HTTPUnauthorized as ex: + print "Not logged in" + } + + :param url: The relative url from the LH api_url + :type url: str + + :returns: headers - dict of HTTP Response headers + :returns: body - the body of the response. If the body was JSON, + it will be an object + """ + return self._cs_request(url, 'POST', **kwargs) + + def put(self, url, **kwargs): + """ + Make an HTTP PUT request to the server. + + .. code-block:: python + + #example call + try { + info = {'name': 'something'} + headers, body = http.put('/volumes', body=info) + } except exceptions.HTTPUnauthorized as ex: + print "Not logged in" + } + + :param url: The relative url from the LH api_url + :type url: str + + :returns: headers - dict of HTTP Response headers + :returns: body - the body of the response. If the body was JSON, + it will be an object + """ + return self._cs_request(url, 'PUT', **kwargs) + + def delete(self, url, **kwargs): + """ + Make an HTTP DELETE request to the server. + + .. code-block:: python + + #example call + try { + headers, body = http.delete('/volumes/%s' % name) + } except exceptions.HTTPUnauthorized as ex: + print "Not logged in" + } + + :param url: The relative url from the LH api_url + :type url: str + + :returns: headers - dict of HTTP Response headers + :returns: body - the body of the response. If the body was JSON, + it will be an object + """ + return self._cs_request(url, 'DELETE', **kwargs) diff --git a/samples/README.rst b/samples/README.rst new file mode 100644 index 0000000..86eb0f4 --- /dev/null +++ b/samples/README.rst @@ -0,0 +1,20 @@ +This directory is going away..... +The unit tests are in 'test' + +This directory contains unit tests + +flask_server.py -- is a sample server that acts like a LeftHand OS API server. + This requires python-flask + +How to run the tests +* First run the api server + python flask_server.py + +* Now run the test client. + python test_client.py + + + +for overriding error content in flask +http://flask.pocoo.org/snippets/83/ +http://flask.pocoo.org/docs/api/?highlight=abort#flask.Flask.errorhandler diff --git a/samples/test_client.py b/samples/test_client.py new file mode 100644 index 0000000..8eef35b --- /dev/null +++ b/samples/test_client.py @@ -0,0 +1,233 @@ +import argparse +from os import sys +import os +import pprint + +# this is a hack to get the hp driver module +# and it's utils module on the search path. +cmd_folder = os.path.realpath(os.path.abspath("..")) +if cmd_folder not in sys.path: + sys.path.insert(0, cmd_folder) + +from hplefthandclient import client, exceptions + +parser = argparse.ArgumentParser() +parser.add_argument("-debug", help="Turn on http debugging", default=False, + action="store_true") +args = parser.parse_args() + + +cl = client.HPLeftHandClient("http://10.10.22.7:8080/lhos") +# This is the local flask server url +#cl = client.HPLeftHandClient("http://127.0.0.1:5000/lhos") +if "debug" in args and args.debug == True: + cl.debug_rest(True) + + +def test_login(): + print "Test Login" + try: + cl.login("administrator", "hpinvent") + pprint.pprint("Login worked") + except exceptions.HTTPUnauthorized: + pprint.pprint("Login Failed") + + +def test_logout(): + print "Test Logout" + try: + cl.login("administrator", "hpinvent") + pprint.pprint("Login worked") + except exceptions.HTTPUnauthorized: + pprint.pprint("Login Failed") + + try: + cl.logout() + pprint.pprint("Logout worked") + except exceptions.HTTPUnauthorized: + pprint.pprint("Logout Failed") + + +def test_get_snapshot(snap_id): + print "Get Snapshot" + try: + cl.login("administrator", "hpinvent") + snap = cl.getSnapshot(snap_id) + pprint.pprint(snap) + except exceptions.HTTPUnauthorized as ex: + pprint.pprint("You must login first") + except Exception as ex: + pprint.pprint(ex) + + +def test_get_snapshot_by_name(name): + print "Get Snapshot By Name" + try: + cl.login("administrator", "hpinvent") + snap = cl.getSnapshotByName(name) + pprint.pprint(snap) + except exceptions.HTTPUnauthorized as ex: + pprint.pprint("You must login first") + except Exception as ex: + pprint.pprint(ex) + + +def test_create_snapshot(): + print "Create Snapshot" + try: + cl.login("administrator", "hpinvent") + vol = cl.createVolume("VolumeSource", 20, 1048576) + snapshot = cl.createSnapshot('VolumeSnapshot', vol['id']) + pprint.pprint(snapshot) + cl.deleteSnapshot(snapshot['id']) + cl.deleteVolume(vol['id']) + except exceptions.HTTPUnauthorized: + pprint.pprint("You must login first") + + +def test_get_snapshots(): + print "Get Snapshots" + try: + cl.login("administrator", "hpinvent") + snapshots = cl.getSnapshots() + pprint.pprint(snapshots) + except exceptions.HTTPUnauthorized: + pprint.pprint("You must login first") + + +def test_get_servers(): + print "Get Servers" + try: + cl.login("administrator", "hpinvent") + servers = cl.getServers() + pprint.pprint(servers) + except exceptions.HTTPUnauthorized: + pprint.pprint("You must login first") + + +def test_get_server(server_id): + print "Get Server" + try: + cl.login("administrator", "hpinvent") + server = cl.getServer(server_id) + pprint.pprint(server) + except exceptions.HTTPUnauthorized: + pprint.pprint("You must login first") + + +def test_get_server_by_name(name): + print "Get Server By Name" + try: + cl.login("administrator", "hpinvent") + server = cl.getServerByName(name) + pprint.pprint(server) + except exceptions.HTTPUnauthorized: + pprint.pprint("You must login first") + + +def test_get_volume(volume_id): + print "Get Volumes" + try: + cl.login("administrator", "hpinvent") + volume = cl.getVolume(volume_id) + return volume + except exceptions.HTTPUnauthorized: + pprint.pprint("You must login first") + + +def test_get_volume_by_name(name): + print "Get Volume By Name" + try: + cl.login("administrator", "hpinvent") + volume = cl.getVolumeByName(name) + return volume + except exceptions.HTTPUnauthorized: + pprint.pprint("You must login first") + + +def test_get_volumes(): + print "Get Volumes" + try: + cl.login("administrator", "hpinvent") + volumes = cl.getVolumes() + pprint.pprint(volumes) + except exceptions.HTTPUnauthorized: + pprint.pprint("You must login first") + + +def test_get_clusters(): + print "Get Clusters" + try: + cl.login("administrator", "hpinvent") + volumes = cl.getClusters() + pprint.pprint(volumes) + except exceptions.HTTPUnauthorized: + pprint.pprint("You must login first") + + +def test_get_cluster_by_name(name): + print "Get Cluster By Name" + try: + cl.login("administrator", "hpinvent") + volumes = cl.getClusterByName(name) + pprint.pprint(volumes) + except exceptions.HTTPUnauthorized: + pprint.pprint("You must login first") + + +def test_create_volume(): + print "Create Volumes" + try: + cl.login("administrator", "hpinvent") + vol = cl.createVolume("Volume1", 20, 1048576) + return vol + + except exceptions.HTTPUnauthorized: + pprint.pprint("You must login first") + + +def test_delete_volume(volume_id): + print "Delete a Volume" + try: + cl.login("administrator", "hpinvent") + vol = cl.deleteVolume(volume_id) + return vol + except exceptions.HTTPUnauthorized: + pprint.pprint("You must login first") + + +def test_error(): + print "test Error" + try: + cl.login("administrator", "hpinvent") + resp, body = cl.http.get('/throwerror') + pprint.pprint(resp) + pprint.pprint(body) + except Exception as ex: + print ex + +#test_login() +#test_logout() + +#vols = test_get_volumes() +#vol = test_get_volume_by_name("vol1_test") +#vols = test_get_clusters() + +#snap = test_get_snapshot("25") +#snap = test_get_snapshot_by_name("vol1_test_SS_1") +snaps = test_get_snapshots() + +#clusters = test_get_clusters() +#cluster = test_get_cluster_by_name("ClusterVSA309") + +#servers = test_get_servers() +#server = test_get_server_by_name("jim-devstack") + +#vol = test_get_volume(23) +#pprint.pprint(vol) + +#test_delete_volume(400) + +#test_create_snapshot() + +#test_error() diff --git a/samples/utils.py b/samples/utils.py new file mode 100644 index 0000000..0ebede9 --- /dev/null +++ b/samples/utils.py @@ -0,0 +1,148 @@ +import argparse +from os import sys +import random +from sys import path +from os import getcwd +import os, sys, inspect, pprint + +# this is a hack to get the hp driver module +# and it's utils module on the search path. +cmd_folder = os.path.realpath(os.path.abspath("..") ) +if cmd_folder not in sys.path: + sys.path.insert(0, cmd_folder) + +from hplefthandclient import client, exceptions + + +def get_volumes(cl): + print "Get Volumes" + try: + volumes = cl.getVolumes() + if volumes: + for volume in volumes['members']: + print "Found '%s'" % volume['name'] + except exceptions.HTTPUnauthorized as ex: + print "You must login first" + except Exception as ex: + print ex + print "Complete\n" + +def get_volume(cl, name): + print "Get Volume %s" % name + try: + vol = cl.getVolume(name) + pprint.pprint(vol) + except exceptions.HTTPUnauthorized as ex: + print "You must login first" + except Exception as ex: + print ex + print "Complete\n" + +def get_hosts(cl): + print "Get Hosts" + try: + hosts = cl.getHosts() + if hosts: + for host in hosts['members']: + pprint.pprint(host) +# print "Found '%s'" % host['name'] + except exceptions.HTTPUnauthorized as ex: + print "You must login first" + except Exception as ex: + print ex + +def get_host(cl,hostname): + try: + host = cl.getHost(hostname) + pprint.pprint(host) + except exceptions.HTTPUnauthorized as ex: + print "You must login first" + except Exception as ex: + print ex + +def delete_host(cl,hostname): + try: + host = cl.deleteHost(hostname) + except exceptions.HTTPUnauthorized as ex: + print "You must login first" + except Exception as ex: + print ex + +def get_host_vluns(cl,hostname): + try: + host = cl.getHostVLUNs(hostname) + pprint.pprint(host) + except exceptions.HTTPUnauthorized as ex: + print "You must login first" + except Exception as ex: + print ex + +def delete_host_vluns(cl, hostName): + try: + vluns = cl.getHostVLUNs(hostName) + if vluns: + for vlun in vluns: + print "Deleting VLUN %s " % vlun['volumeName'] + cl.deleteVLUN(vlun['volumeName'], vlun['lun'], + vlun['hostname'], vlun['portPos']) + + except exceptions.HTTPUnauthorized as ex: + print "You must login" + except Exception as ex: + print ex + +def get_ports(cl): + try: + ports = cl.getPorts() + pprint.pprint(ports) + except exceptions.HTTPUnauthorized as ex: + print "You must login first" + except Exception as ex: + print ex + + +def get_vluns(cl): + print "Get VLUNs" + try: + vluns = cl.getVLUNs() + if vluns: + pprint.pprint(vluns) + for vlun in vluns['members']: + pprint.pprint(vlun) + print "Found VLUN '%s'" % vlun['volumeName'] + except exceptions.HTTPUnauthorized as ex: + print "You must login first" + except Exception as ex: + print ex + + +def get_vlun(cl,vlunname): + try: + vlun = cl.getVLUN(vlunname) + pprint.pprint(vlun) + except exceptions.HTTPUnauthorized as ex: + print "You must login first" + except Exception as ex: + print ex + + +def get_cpgs(cl): + print "Get CPGs" + try: + cpgs = cl.getCPGs() + if cpgs: + for cpg in cpgs['members']: + print "Found CPG '%s'" % cpg['name'] + except exceptions.HTTPUnauthorized as ex: + print "You must login first" + except Exception as ex: + print ex + +def get_cpg(cl, name): + try: + cpg = cl.getCPG(name) + pprint.pprint(cpg) + except exceptions.HTTPUnauthorized as ex: + print "You must login first" + except Exception as ex: + print ex diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..be32cea --- /dev/null +++ b/setup.py @@ -0,0 +1,36 @@ +import hplefthandclient + +try: + from setuptools import setup, find_packages +except ImportError: + from distutils.core import setup, find_packages +import sys + + +setup( + name='hplefthandclient', + version=hplefthandclient.version, + description="HP LeftHand/StoreVirtual HTTP REST Client", + author="Kurt Martin", + author_email="kurt.f.martin@hp.com", + maintainer="Kurt Martin", + keywords=["hp", "lefthand", "storevirtual", "rest"], + requires=['httplib2(>=0.6.0)'], + install_requires=['httplib2 >= 0.6.0'], + tests_require=["nose", "werkzeug", "nose-testconfig"], + license="Apache License, Version 2.0", + packages=find_packages(), + provides=['hplefthandclient'], + url="http://packages.python.org/hplefthandclient", + classifiers=[ + 'Development Status :: 3 - Alpha', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: Apache Software License', + 'Environment :: Web Environment', + 'Programming Language :: Python', + 'Programming Language :: Python :: 2.6', + 'Programming Language :: Python :: 2.7', + 'Topic :: Internet :: WWW/HTTP', + + ] + ) diff --git a/test/README.rst b/test/README.rst new file mode 100644 index 0000000..85ab9be --- /dev/null +++ b/test/README.rst @@ -0,0 +1,7 @@ +Unit tests +========== + +1. pip install nose +2. pip install nose-testconfig +3. use config.ini to configure unit tests +3. run tests with nosetests --tc-file config.ini diff --git a/test/config.ini b/test/config.ini new file mode 100644 index 0000000..8e40364 --- /dev/null +++ b/test/config.ini @@ -0,0 +1,9 @@ +[TEST] +flask_url=http://localhost:5001/lhos +user=administrator +pass=hpinvent +unit=false +debug=false +start_flask_server=true +cluster=ClusterVSA309 +lhos_url=http://10.10.22.7:8080/lhos diff --git a/test/test_HPLeftHandClient_base.py b/test/test_HPLeftHandClient_base.py new file mode 100644 index 0000000..6c5fe3b --- /dev/null +++ b/test/test_HPLeftHandClient_base.py @@ -0,0 +1,102 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# Copyright 2009-2012 10gen, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Test base class of LeftHand Client""" + +import sys +import os +sys.path.insert(0, os.path.realpath(os.path.abspath('../'))) + +from hplefthandclient import client +import unittest +import subprocess +import time +import inspect +from testconfig import config +from urlparse import urlparse + +# pip install nose-testconfig + +# e.g. +# nosetests test_HPLeftHandClient_volume.py -v --tc-file config.ini + + +class HPLeftHandClientBaseTestCase(unittest.TestCase): + + user = config['TEST']['user'] + password = config['TEST']['pass'] + cluster = config['TEST']['cluster'] + flask_url = config['TEST']['flask_url'] + url_lhos = config['TEST']['lhos_url'] + debug = config['TEST']['debug'].lower() == 'true' + unitTest = config['TEST']['unit'].lower() == 'true' + startFlask = config['TEST']['start_flask_server'].lower() == 'true' + + def setUp(self): + + cwd = os.path.dirname(os.path.abspath( + inspect.getfile(inspect.currentframe()))) + + if self.unitTest: + self.printHeader('Using flask ' + self.flask_url) + self.cl = client.HPLeftHandClient(self.flask_url) + parsed_url = urlparse(self.flask_url) + userArg = '-user=%s' % self.user + passwordArg = '-password=%s' % self.password + portArg = '-port=%s' % parsed_url.port + + script = 'test_HPLeftHandMockServer_flask.py' + path = "%s/%s" % (cwd, script) + try: + if self.startFlask: + self.mockServer = subprocess.Popen([sys.executable, + path, + userArg, + passwordArg, + portArg], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + stdin=subprocess.PIPE) + else: + pass + except Exception: + pass + time.sleep(1) + else: + self.printHeader('Using LeftHand ' + self.url_lhos) + self.cl = client.HPLeftHandClient(self.url_lhos) + + if self.debug: + self.cl.debug_rest(True) + + self.cl.login(self.user, self.password) + + def tearDown(self): + self.cl.logout() + if self.unitTest and self.startFlask: + #TODO: it seems to kill all the process except the last one... + #don't know why + self.mockServer.kill() + + def printHeader(self, name): + print "\n##Start testing '%s'" % name + + def printFooter(self, name): + print "##Compeleted testing '%s\n" % name + + def findInDict(self, dic, key, value): + for i in dic: + if key in i and i[key] == value: + return True diff --git a/test/test_HPLeftHandClient_volume.py b/test/test_HPLeftHandClient_volume.py new file mode 100644 index 0000000..990d27a --- /dev/null +++ b/test/test_HPLeftHandClient_volume.py @@ -0,0 +1,259 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# Copyright 2009-2012 10gen, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Test class of LeftHand Client handling volumes & snapshots """ + +import sys +import os +sys.path.insert(0, os.path.realpath(os.path.abspath('../'))) + +from hplefthandclient import exceptions +import test_HPLeftHandClient_base + +VOLUME_NAME1 = 'VOLUME1_UNIT_TEST' +VOLUME_NAME2 = 'VOLUME2_UNIT_TEST' +VOLUME_NAME3 = 'VOLUME3_UNIT_TEST' +SNAP_NAME1 = 'SNAP_UNIT_TEST' + + +class HPLeftHandClientVolumeTestCase(test_HPLeftHandClient_base. + HPLeftHandClientBaseTestCase): + + cluster_id = 0 + GB_TO_BYTES = 1073741824 + + def setUp(self): + super(HPLeftHandClientVolumeTestCase, self).setUp() + + try: + cluster_info = self.cl.getClusterByName( + test_HPLeftHandClient_base. + HPLeftHandClientBaseTestCase.cluster) + self.cluster_id = cluster_info['id'] + except Exception: + pass + + def tearDown(self): + + try: + volume_info = self.cl.getVolumeByName(VOLUME_NAME1) + self.cl.deleteVolume(volume_info['id']) + except Exception as ex: + print ex + pass + try: + volume_info = self.cl.getVolumeByName(VOLUME_NAME2) + self.cl.deleteVolume(volume_info['id']) + except Exception as ex: + print ex + pass + + super(HPLeftHandClientVolumeTestCase, self).tearDown() + + def test_1_create_volume(self): + self.printHeader('create_volume') + + try: + #add one + optional = {'description': 'test volume', + 'isThinProvisioned': True} + self.cl.createVolume(VOLUME_NAME1, self.cluster_id, + 1 * self.GB_TO_BYTES, optional) + except Exception as ex: + print ex + self.fail('Failed to create volume') + return + + try: + #check + vol1 = self.cl.getVolumeByName(VOLUME_NAME1) + self.assertIsNotNone(vol1) + volName = vol1['name'] + self.assertEqual(VOLUME_NAME1, volName) + + except Exception as ex: + print ex + self.fail('Failed to get volume') + return + + try: + #add another + optional = {'description': 'test volume2', + 'isThinProvisioned': True} + self.cl.createVolume(VOLUME_NAME2, self.cluster_id, + 1 * self.GB_TO_BYTES, optional) + except Exception as ex: + print ex + self.fail('Failed to create volume') + return + + try: + #check + vol2 = self.cl.getVolumeByName(VOLUME_NAME2) + self.assertIsNotNone(vol2) + volName = vol2['name'] + self.assertEqual(VOLUME_NAME2, volName) + except Exception as ex: + print ex + self.fail("Failed to get volume") + + self.printFooter('create_volume') + + def test_1_create_volume_duplicate_name(self): + self.printHeader('create_volume_duplicate_name') + + #add one and check + try: + optional = {'description': 'test volume', + 'isThinProvisioned': True} + self.cl.createVolume(VOLUME_NAME1, self.cluster_id, + 2 * self.GB_TO_BYTES, optional) + except Exception as ex: + print ex + self.fail("Failed to create volume") + + self.assertRaises(exceptions.HTTPServerError, + self.cl.createVolume, + VOLUME_NAME1, + self.cluster_id, + 2 * self.GB_TO_BYTES, + optional) + self.printFooter('create_volume_duplicate_name') + + def test_1_create_volume_tooLarge(self): + self.printHeader('create_volume_tooLarge') + optional = {'description': 'test volume', + 'isThinProvisioned': False} + + self.assertRaises(exceptions.HTTPServerError, + self.cl.createVolume, + VOLUME_NAME1, + self.cluster_id, + 16777218 * self.GB_TO_BYTES, + optional) + + self.printFooter('create_volume_tooLarge') + + def test_2_get_volume_bad(self): + self.printHeader('get_volume_bad') + + self.assertRaises(exceptions.HTTPNotFound, + self.cl.getVolumeByName, + 'NoSuchVolume') + + self.printFooter('get_volume_bad') + + def test_2_get_volumes(self): + self.printHeader('get_volumes') + + self.cl.createVolume(VOLUME_NAME1, self.cluster_id, + 3 * self.GB_TO_BYTES) + self.cl.createVolume(VOLUME_NAME2, self.cluster_id, + 3 * self.GB_TO_BYTES) + + vol1 = self.cl.getVolumeByName(VOLUME_NAME1) + vol2 = self.cl.getVolumeByName(VOLUME_NAME2) + + vols = self.cl.getVolumes() + + self.assertTrue(self.findInDict(vols['members'], 'name', vol1['name'])) + self.assertTrue(self.findInDict(vols['members'], 'name', vol2['name'])) + + self.printFooter('get_volumes') + + def test_3_delete_volume_nonExist(self): + self.printHeader('delete_volume_nonExist') + volume_id = -1 + + self.assertRaises(exceptions.HTTPServerError, + self.cl.deleteVolume, + volume_id) + self.printFooter('delete_volume_nonExist') + + def test_3_delete_volumes(self): + self.printHeader('delete_volumes') + + try: + optional = {'description': 'Made by flask.'} + self.cl.createVolume(VOLUME_NAME1, self.cluster_id, + 1 * self.GB_TO_BYTES, optional) + vol1 = self.cl.getVolumeByName(VOLUME_NAME1) + self.printHeader('vol1 id %s' % vol1['id']) + self.printHeader('members %s' % vol1) + except Exception as ex: + print ex + self.fail('Failed to create volume %s' % VOLUME_NAME1) + + try: + optional = {'description': 'Made by flask.'} + self.cl.createVolume(VOLUME_NAME2, self.cluster_id, + 1 * self.GB_TO_BYTES, optional) + vol2 = self.cl.getVolumeByName(VOLUME_NAME2) + self.printHeader('vol2 id %s' % vol2['id']) + except Exception as ex: + print ex + self.fail('Failed to create volume %s' % VOLUME_NAME2) + + try: + self.cl.deleteVolume(vol1['id']) + except Exception as ex: + print ex + self.fail('Failed to delete %s' % vol1['id']) + + self.assertRaises(exceptions.HTTPNotFound, + self.cl.getVolumeByName, + VOLUME_NAME1) + + try: + self.cl.deleteVolume(vol2['id']) + except Exception as ex: + print ex + self.fail('Failed to delete %s' % vol2['id']) + + self.assertRaises(exceptions.HTTPNotFound, + self.cl.getVolumeByName, + VOLUME_NAME2) + self.printFooter('delete_volumes') + + def test_4_create_snapshot(self): + self.printHeader('create_snapshot') + + try: + self.cl.createVolume(VOLUME_NAME1, self.cluster_id, + 1 * self.GB_TO_BYTES) + volume_info = self.cl.getVolumeByName(VOLUME_NAME1) + option = {'inheritAccess': True} + self.cl.createSnapshot(SNAP_NAME1, volume_info['id'], option) + except Exception as ex: + print ex + self.fail("Failed with unexpected exception") + + snap_info = self.cl.getSnapshotByName(SNAP_NAME1) + self.cl.deleteSnapshot(snap_info['id']) + self.printFooter('create_snapshot') + + def test_4_create_snapshot_nonExistVolume(self): + self.printHeader('create_snapshot_nonExistVolume') + + optional = {'description': 'test snapshot'} + self.assertRaises(exceptions.HTTPServerError, + self.cl.createSnapshot, + 'UnitTestSnapshot', + -1, + optional) + self.printFooter('create_snapshot_nonExistVolume') +#testing +#suite = unittest.TestLoader().loadTestsFromTestCase(HPLeftHandClientVolumeTestCase) +#unittest.TextTestRunner(verbosity=2).run(suite) diff --git a/test/test_HPLeftHandMockServer_flask.py b/test/test_HPLeftHandMockServer_flask.py new file mode 100755 index 0000000..49b48ca --- /dev/null +++ b/test/test_HPLeftHandMockServer_flask.py @@ -0,0 +1,391 @@ +import pprint +import json +import random +import string +import argparse +from werkzeug.exceptions import default_exceptions +from werkzeug.exceptions import HTTPException + +parser = argparse.ArgumentParser() +parser.add_argument("-debug", help="Turn on http debugging", + default=False, action="store_true") +parser.add_argument("-user", help="User name", default='administrator') +parser.add_argument("-password", help="User password", default='hpinvent') +parser.add_argument("-port", help="Port to listen on", type=int, default=5001) +args = parser.parse_args() +user_name = args.user +user_pass = args.password +debugRequests = False +if "debug" in args and args.debug is True: + debugRequests = True + +#__all__ = ['make_json_app'] + + +def id_generator(size=6, chars=string.ascii_uppercase + string.digits): + return ''.join(random.choice(chars) for x in range(size)) + + +def make_json_app(import_name, **kwargs): + """ + Creates a JSON-oriented Flask app. + + All error responses that you don't specifically + manage yourself will have application/json content + type, and will contain JSON like this (just an example): + + { "message": "405: Method Not Allowed" } + """ + def make_json_error(ex): + pprint.pprint(ex) + pprint.pprint(ex.code) + #response = jsonify(message=str(ex)) + response = jsonify(ex) + response.status_code = (ex.code + if isinstance(ex, HTTPException) + else 500) + return response + + app = Flask(import_name, **kwargs) + #app.debug = True + app.secret_key = id_generator(24) + + for code in default_exceptions.iterkeys(): + app.error_handler_spec[None][code] = make_json_error + + return app + +app = make_json_app(__name__) + +session_key = id_generator(24) + + +def debugRequest(request): + if debugRequests: + print "\n" + pprint.pprint(request) + pprint.pprint(request.headers) + pprint.pprint(request.data) + + +def throw_error(http_code, error_code=None, + desc=None, debug1=None, debug2=None): + if error_code: + info = {'code': error_code, 'desc': desc} + if debug1: + info['debug1'] = debug1 + if debug2: + info['debug2'] = debug2 + abort(Response(json.dumps(info), status=http_code)) + else: + abort(http_code) + + +@app.route('/') +def index(): + debugRequest(request) + if 'username' in session: + return 'Logged in as %s' % escape(session['username']) + abort(401) + + +@app.route('/lhos/throwerror') +def errtest(): + debugRequest(request) + throw_error(405, 'ERR_TEST', 'testing throwing an error', + 'debug1 message', 'debug2 message') + + +@app.errorhandler(404) +def not_found(error): + debugRequest(request) + return Response("%s has not been implemented" % request.path, status=501) + + +@app.route('/lhos/credentials', methods=['GET', 'POST']) +def credentials(): + debugRequest(request) + + if request.method == 'GET': + return 'GET credentials called' + + elif request.method == 'POST': + data = json.loads(request.data) + + if data['user'] == user_name and data['password'] == user_pass: + #do something good here + try: + resp = make_response(json.dumps({'key': session_key}), 201) + resp.headers['Location'] = '/lhos/credentials/%s' % session_key + session['username'] = data['user'] + session['password'] = data['password'] + session['session_key'] = session_key + return resp + except Exception as ex: + pprint.pprint(ex) + + else: + #authentication failed! + throw_error(401, "HTTP_AUTH_FAIL", + "Username and or Password was incorrect") + + +@app.route('/lhos/credentials/', methods=['DELETE']) +def logout_credentials(session_key): + debugRequest(request) + session.clear() + return 'DELETE credentials called' + +#### CLUSTER INFO #### + + +@app.route('/lhos/clusters', methods=['GET']) +def get_cluster_by_name(): + debugRequest(request) + cluster_name = request.args.get('name') + + for cluster in clusters['members']: + if cluster['name'] == cluster_name: + resp = make_response(json.dumps(cluster), 200) + return resp + + throw_error(404, 'NON_EXISTENT_CLUSTER', "cluster doesn't exist") + +### VOLUMES & SNAPSHOTS #### + + +@app.route('/lhos/volumes', methods=['GET']) +def get_volume(): + debugRequest(request) + volume_name = None + volume_name = request.args.get('name') + + if volume_name is not None: + for volume in volumes['members']: + if volume['name'] == volume_name: + resp = make_response(json.dumps(volume), 200) + return resp + throw_error(404, 'NON_EXISTENT_VOLUME', "volume doesn't exist") + else: + resp = make_response(json.dumps(volumes), 200) + return resp + + +@app.route('/lhos/snapshots', methods=['GET']) +def get_snapshot(): + debugRequest(request) + snapshot_name = None + snapshot_name = request.args.get('name') + + if snapshot_name is not None: + pprint.pprint('snapshot name %s' % snapshot_name) + for snapshot in snapshots['members']: + if snapshot['name'] == snapshot_name: + pprint.pprint('snapshot namei inside %s' % snapshot['name']) + resp = make_response(json.dumps(snapshot), 200) + return resp + throw_error(404, 'NON_EXISTENT_SNAPSHOT', "snapshot doesn't exist") + else: + resp = make_response(json.dumps(snapshots), 200) + return resp + + +@app.route('/lhos/volumes/', methods=['POST']) +def create_snapshot(volume_id): + debugRequest(request) + data = json.loads(request.data) + + valid_keys = {'action': None, 'parameters': None} + + ## do some fake errors here depending on data + for key in data.keys(): + if key not in valid_keys.keys(): + throw_error(400, 'INV_INPUT', "Invalid Parameter '%s'" % key) + + for volume in volumes['members']: + if volume['id'] == int(volume_id): + snapshots['members'].append({'name': data['parameters'].get('name'), + 'id': random.randint(1, 2000)}) + pprint.pprint(snapshots) + return make_response("", 200) + + throw_error(500, 'SERVER_ERROR', "volume doesn't exist") + + +@app.route('/lhos/volumes', methods=['POST']) +def create_volumes(): + debugRequest(request) + data = json.loads(request.data) + + valid_keys = {'name': None, 'isThinProvisioned': None, 'size': None, + 'description': None, 'clusterID': None} + + for key in data.keys(): + if key not in valid_keys.keys(): + throw_error(500, 'SERVER_ERROR', "Invalid Parameter '%s'" % key) + + if 'name' in data.keys(): + for vol in volumes['members']: + if vol['name'] == data['name']: + throw_error(500, 'SERVER_ERROR', + 'The volume already exists.') + else: + throw_error(500, 'SERVER_ERROR', + 'No volume name provided.') + + if 'size' in data.keys(): + if data['size'] > 17592188141567: + throw_error(500, 'SERVER_ERROR', 'Volume to larger') + + data['id'] = random.randint(1, 2000) + + volumes['members'].append(data) + return make_response("", 200) + + +@app.route('/lhos/volumes/', methods=['DELETE']) +def delete_volumes(volume_id): + debugRequest(request) + for volume in volumes['members']: + if volume['id'] == int(volume_id): + volumes['members'].remove(volume) + return make_response("", 200) + + throw_error(500, 'SERVER_ERROR', + "The volume id '%s' does not exists." % volume_id) + + +@app.route('/lhos/snapshots/', methods=['DELETE']) +def delete_snapshots(snapshot_id): + debugRequest(request) + for snapshot in snapshots['members']: + if snapshot['id'] == int(snapshot_id): + snapshots['members'].remove(snapshot) + return make_response("", 200) + + throw_error(404, 'NON_EXISTENT_SNAPSHOT', + "The snapshot id '%s' does not exists." % snapshot_id) + + +if __name__ == "__main__": + + #fake volumes + global volumes + volumes = {'members': [{ + 'autogrowSeconds': 2, + 'bytesWritten': 0, + 'clusterId': 21, + 'clusterName': 'ClusterVSA309', + 'created': '2013-10-23T16:58:58Z', + 'dataProtectionLevel': 0, + 'dataWritten': 0, + 'description': 'test volume', + 'fcTransportStatus': 0, + 'fibreChannelPaths': None, + 'friendlyName': '', + 'hasUnrecoverableIOErrors': False, + 'id': 24, + 'isAdaptiveOptimizationEnabled': True, + 'isAvailable': True, + 'isDeleting': False, + 'isLicensed': True, + 'isMigrating': False, + 'isPrimary': True, + 'isThinProvisioned': False, + 'isVIPRebalancing': False, + 'iscsiIqn': 'iqn.2003-10.com.lefthandnetworks:mgvsa309:24:vol1', + 'iscsiSessions': None, + 'migrationStatus': 'none', + 'modified': '', + 'name': 'VOLUME0_UNIT_TEST', + 'numberOfReplicas': 1, + 'provisionedSpace': 4194304, + 'replicationStatus': 'normal', + 'restripePendingStatus': 'none', + 'resynchronizationStatus': 'none', + 'scsiLUNStatus': 'available', + 'serialNumber': '27d18c785f81e91f36a5073fff9233720000000000000018', + 'size': 1048576, + 'snapshots': {'name': 'snapshots', + 'resource': None, + 'type': 'snapshot', + 'uri': '/snapshots?volumeName=VOLUME1_UNIT_TEST'}, + 'transport': 0, + 'transportServerId': 0, + 'type': 'volume', + 'uri': '/lhos/volumes/24', + 'volumeACL': None}], + 'total': 4} + + #fake snapshots + global snapshots + snapshots = {'members': [{ + 'autogrowSeconds': 2, + 'bytesWritten': 0, + 'clusterId': 21, + 'clusterName': 'ClusterVSA309', + 'created': '2013-10-23T16:59:19Z', + 'dataWritten': 0, + 'description': '', + 'fcTransportStatus': 0, + 'fibreChannelPaths': None, + 'hasUnrecoverableIOErrors': False, + 'id': 26, + 'isAutomatic': False, + 'isAvailable': True, + 'isDeleting': False, + 'isLicensed': True, + 'isMigrating': False, + 'isPrimary': True, + 'isThinProvisioned': True, + 'iscsiIqn': 'iqn.2003-10.com.lefthandnetworks:mgvsa309:26:vol1-ss-1', + 'managedBy': 0, + 'migrationStatus': 'none', + 'modified': '', + 'name': 'vol1_SS_1', + 'provisionedSpace': 528384, + 'replicationStatus': 'normal', + 'restripePendingStatus': 'none', + 'resynchronizationStatus': 'none', + 'scsiLUNStatus': 'available', + 'serialNumber': '27d18c785f81e91f36a5073fff923372000000000000001a', + 'sessions': None, + 'size': 4194304, + 'snapshotACL': None, + 'transport': 0, + 'transportServerId': 0, + 'type': 'snapshot', + 'uri': '/lhos/snapshots/26', + 'writableSpaceUsed': 0}], + 'total': 4} + + #fake clusters + global clusters + clusters = {'members': [{'adaptiveOptimizationCapable': False, + 'created': 'N/A', + 'description': '', + 'id': 21, + 'modified': 'N/A', + 'moduleCount': 1, + 'name': 'ClusterVSA309', + 'spaceAvailable': 13457408, + 'spaceTotal': 40728576, + 'storageModuleIPAddresses': ['10.10.30.165'], + 'supportedFeatures': [''], + 'type': 'cluster', + 'uri': '/lhos/clusters/21', + 'virtualIPAddresses': [{'ipV4Address': '10.10.22.7', + 'ipV4NetMask': '255.255.224.0'}], + 'virtualIPEnabled': True, + 'volumeCreationSpace': [{'availableSpace': 13457408, + 'replicationLevel': 1}], + 'volumes': {'name': 'volumes', + 'resource': None, + 'type': 'volume', + 'uri': '/volumes?clusterName=ClusterVSA309'}}], + 'name': 'Clusters Collection', + 'total': 1, + 'type': 'cluster', + 'uri': '/lhos/clusters'} + + app.run(port=args.port, debug=debugRequests)