Retire the python-searchlightclient project
As announced in openstack-discuss ML[1], Searchlight project is retiring in Wallaby cycle. This commit retires python-searchlightclient repository as per process deinfed in project-guide[2]. Anyone would like to maintain it again, please revert back this commit and propose the re-adding it to governance. The community wishes to express our thanks and appreciation to all of those who have contributed to the python-searchlightclient project over the years. Depends-On: https://review.opendev.org/c/openstack/project-config/+/764519 Needed-By: https://review.opendev.org/c/openstack/governance/+/764530 [1] http://lists.openstack.org/pipermail/openstack-discuss/2020-November/018637.html [2] https://docs.openstack.org/project-team-guide/repository.html#retiring-a-repository Change-Id: If02fcc1c21abc9040a3b81cfa4973c7c6382ea15
This commit is contained in:
parent
75bb786669
commit
31410355d4
|
@ -1,20 +0,0 @@
|
|||
.coverage*
|
||||
.venv
|
||||
*,cover
|
||||
cover
|
||||
*.pyc
|
||||
AUTHORS
|
||||
build
|
||||
dist
|
||||
ChangeLog
|
||||
run_tests.err.log
|
||||
.tox
|
||||
doc/source/api
|
||||
doc/build
|
||||
*.egg
|
||||
*.eggs
|
||||
searchlightclient/versioninfo
|
||||
*.egg-info
|
||||
*.log
|
||||
.stestr/
|
||||
*.swp
|
|
@ -1,3 +0,0 @@
|
|||
[DEFAULT]
|
||||
test_path=./searchlightclient/tests
|
||||
top_dir=./
|
|
@ -1,8 +0,0 @@
|
|||
- project:
|
||||
templates:
|
||||
- openstack-python3-wallaby-jobs
|
||||
- openstack-cover-jobs
|
||||
- publish-openstack-docs-pti
|
||||
- check-requirements
|
||||
- openstackclient-plugin-jobs
|
||||
- openstack-lower-constraints-jobs
|
|
@ -1,16 +0,0 @@
|
|||
If you would like to contribute to the development of OpenStack,
|
||||
you must follow the steps documented at:
|
||||
|
||||
https://docs.openstack.org/infra/manual/developers.html#development-workflow
|
||||
|
||||
Once those steps have been completed, changes to OpenStack
|
||||
should be submitted for review via the Gerrit tool, following
|
||||
the workflow documented at:
|
||||
|
||||
https://docs.openstack.org/infra/manual/developers.html#development-workflow
|
||||
|
||||
Pull requests submitted through GitHub will be ignored.
|
||||
|
||||
Bugs should be filed on Storyboard, not GitHub:
|
||||
|
||||
https://storyboard.openstack.org/#!/project_group/searchlight
|
175
LICENSE
175
LICENSE
|
@ -1,175 +0,0 @@
|
|||
|
||||
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.
|
158
README.rst
158
README.rst
|
@ -1,152 +1,10 @@
|
|||
========================
|
||||
Team and repository tags
|
||||
========================
|
||||
This project is no longer maintained.
|
||||
|
||||
.. image:: https://governance.openstack.org/tc/badges/python-searchlightclient.svg
|
||||
:target: https://governance.openstack.org/tc/reference/tags/index.html
|
||||
The contents of this repository are still available in the Git
|
||||
source code management system. To see the contents of this
|
||||
repository before it reached its end of life, please check out the
|
||||
previous commit with "git checkout HEAD^1".
|
||||
|
||||
.. Change things from this point on
|
||||
|
||||
========================
|
||||
python-searchlightclient
|
||||
========================
|
||||
|
||||
OpenStack Indexing and Search API Client Library
|
||||
|
||||
This is a client library for Searchlight built on the Searchlight API. It
|
||||
provides a Python API (the ``searchlightclient`` module) and a command-line
|
||||
tool (``searchlight``).
|
||||
|
||||
The project is hosted on `Storyboard`_, where bugs can be filed. The code is
|
||||
hosted on `OpenStack git repository`_. Patches must be submitted using
|
||||
`Gerrit`_, *not* git repo
|
||||
pull requests.
|
||||
|
||||
.. _OpenStack git repository: https://opendev.org/openstack/python-searchlightclient
|
||||
.. _Storyboard: https://storyboard.openstack.org/#!/project_group/searchlight
|
||||
.. _Gerrit: https://docs.openstack.org/infra/manual/developers.html#development-workflow
|
||||
|
||||
python-searchlightclient is licensed under the Apache License like the rest of
|
||||
OpenStack.
|
||||
|
||||
.. contents:: Contents:
|
||||
:local:
|
||||
|
||||
Install the client from PyPI
|
||||
----------------------------
|
||||
The program `python-searchlightclient`_ package is published on `PyPI`_ and
|
||||
so can be installed using the pip tool, which will manage installing all
|
||||
python dependencies::
|
||||
|
||||
$ pip install python-searchlightclient
|
||||
|
||||
.. note::
|
||||
The packages on PyPI may lag behind the git repo in functionality.
|
||||
|
||||
.. _PyPI: https://pypi.python.org/pypi/python-searchlightclient/
|
||||
|
||||
Setup the client from source
|
||||
----------------------------
|
||||
|
||||
* Clone repository for python-searchlightclient::
|
||||
|
||||
$ git clone https://opendev.org/openstack/python-searchlightclient.git
|
||||
$ cd python-searchlightclient
|
||||
|
||||
* Setup a virtualenv
|
||||
|
||||
.. note::
|
||||
This is an optional step, but will allow Searchlightclient's dependencies
|
||||
to be installed in a contained environment that can be easily deleted
|
||||
if you choose to start over or uninstall Searchlightclient.
|
||||
|
||||
::
|
||||
|
||||
$ tox -evenv --notest
|
||||
|
||||
Activate the virtual environment whenever you want to work in it.
|
||||
All further commands in this section should be run with the venv active:
|
||||
|
||||
::
|
||||
|
||||
$ source .tox/venv/bin/activate
|
||||
|
||||
.. note::
|
||||
When ALL steps are complete, deactivate the virtualenv: $ deactivate
|
||||
|
||||
* Install Searchlightclient and its dependencies::
|
||||
|
||||
(venv) $ python setup.py develop
|
||||
|
||||
Command-line API
|
||||
----------------
|
||||
|
||||
Set Keystone environment variables to execute CLI commands against searchlight.
|
||||
|
||||
* To execute CLI commands::
|
||||
|
||||
$ export OS_USERNAME=<user>
|
||||
$ export OS_PASSWORD=<password>
|
||||
$ export OS_TENANT_NAME=<project>
|
||||
$ export OS_AUTH_URL='http://localhost:5000/v2.0/'
|
||||
|
||||
.. note::
|
||||
With devstack you just need to $ source openrc <user> <project>. And you can
|
||||
work with a local installation by passing --os-token <TOKEN> and --os-url
|
||||
http://localhost:9393. You can also set up a `Openstackclient`_ config file
|
||||
to work with the CLI.
|
||||
|
||||
.. _Openstackclient: https://docs.openstack.org/developer/python-openstackclient/configuration.html#clouds-yaml
|
||||
|
||||
::
|
||||
|
||||
$ openstack
|
||||
(openstack) search resource type list
|
||||
+--------------------------+--------------------------+
|
||||
| Name | Type |
|
||||
+--------------------------+--------------------------+
|
||||
| OS::Designate::RecordSet | OS::Designate::RecordSet |
|
||||
| OS::Designate::Zone | OS::Designate::Zone |
|
||||
| OS::Glance::Image | OS::Glance::Image |
|
||||
| OS::Glance::Metadef | OS::Glance::Metadef |
|
||||
| OS::Nova::Server | OS::Nova::Server |
|
||||
+--------------------------+--------------------------+
|
||||
|
||||
Here are the full list of subcommands, Use -h to see options:
|
||||
|
||||
============================= =======================================
|
||||
Subcommand Description
|
||||
============================= =======================================
|
||||
search facet list List Searchlight Facet
|
||||
search resource type list List Searchlight Resource Type (Plugin)
|
||||
search query Search Searchlight resource
|
||||
============================= =======================================
|
||||
|
||||
Python API
|
||||
----------
|
||||
|
||||
To use with keystone as the authentication system::
|
||||
|
||||
>>> from keystoneclient.auth.identity import generic
|
||||
>>> from keystoneclient import session
|
||||
>>> from searchlightclient import client
|
||||
>>> auth = generic.Password(auth_url=OS_AUTH_URL, username=OS_USERNAME, password=OS_PASSWORD, tenant_name=OS_TENANT_NAME)
|
||||
>>> keystone_session = session.Session(auth=auth)
|
||||
>>> sc = client.Client('1', session=keystone_session)
|
||||
>>> sc.resource_types.list()
|
||||
[...]
|
||||
|
||||
|
||||
* License: Apache License, Version 2.0
|
||||
* Documentation: https://docs.openstack.org/developer/python-searchlightclient
|
||||
* Source: https://opendev.org/openstack/python-searchlightclient
|
||||
* Bugs: https://storyboard.openstack.org/#!/project_group/searchlight
|
||||
|
||||
Testing
|
||||
-------
|
||||
|
||||
There are multiple test targets that can be run to validate the code.
|
||||
|
||||
* tox -e pep8 - style guidelines enforcement
|
||||
* tox -e py36 - traditional unit testing with python 3.6
|
||||
* tox -e py37 - traditional unit testing with python 3.7
|
||||
For any further questions, please email
|
||||
openstack-discuss@lists.openstack.org or join #openstack-dev on
|
||||
Freenode.
|
||||
|
|
90
doc/Makefile
90
doc/Makefile
|
@ -1,90 +0,0 @@
|
|||
# Makefile for Sphinx documentation
|
||||
#
|
||||
|
||||
# You can set these variables from the command line.
|
||||
SPHINXOPTS =
|
||||
SPHINXBUILD = sphinx-build
|
||||
SPHINXSOURCE = source
|
||||
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) $(SPHINXSOURCE)
|
||||
|
||||
.PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest
|
||||
|
||||
help:
|
||||
@echo "Please use \`make <target>' where <target> is one of"
|
||||
@echo " html to make standalone HTML files"
|
||||
@echo " dirhtml to make HTML files named index.html in directories"
|
||||
@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 " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
|
||||
@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."
|
||||
|
||||
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/python-searchlightclient.qhcp"
|
||||
@echo "To view the help file:"
|
||||
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/python-searchlightclient.qhc"
|
||||
|
||||
latex:
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||
@echo
|
||||
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
|
||||
@echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \
|
||||
"run these through (pdf)latex."
|
||||
|
||||
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."
|
|
@ -1,7 +0,0 @@
|
|||
# The order of packages is significant, because pip processes them in the order
|
||||
# of appearance. Changing the order has an impact on the overall integration
|
||||
# process, which may cause wedges in the gate later.
|
||||
|
||||
openstackdocstheme>=2.2.0 # Apache-2.0
|
||||
sphinxcontrib-svg2pdfconverter>=0.1.0 # BSD
|
||||
sphinx>=2.0.0,!=2.1.0 # BSD
|
|
@ -1,220 +0,0 @@
|
|||
# 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.
|
||||
|
||||
# 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.
|
||||
|
||||
# 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.append(os.path.abspath('.'))
|
||||
|
||||
# -- General configuration ----------------------------------------------------
|
||||
|
||||
# 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',
|
||||
'openstackdocstheme',
|
||||
'sphinxcontrib.rsvgconverter']
|
||||
|
||||
autoclass_content = 'both'
|
||||
|
||||
# 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'
|
||||
|
||||
# The master toctree document.
|
||||
master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
copyright = 'OpenStack Contributors'
|
||||
|
||||
# openstackdocstheme options
|
||||
openstackdocs_repo_name = 'openstack/python-searchlightclient'
|
||||
openstackdocs_pdf_link = True
|
||||
openstackdocs_bug_project = 'python-searchlightclient'
|
||||
openstackdocs_bug_tag = ''
|
||||
|
||||
# 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 documents that shouldn't be included in the build.
|
||||
#unused_docs = []
|
||||
|
||||
# List of directories, relative to source directory, that shouldn't be searched
|
||||
# for source files.
|
||||
exclude_trees = []
|
||||
|
||||
# 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 = []
|
||||
|
||||
# Grouping the document tree for man pages.
|
||||
# List of tuples 'sourcefile', 'target', u'title', u'Authors name', 'manual'
|
||||
|
||||
man_pages = [
|
||||
('man/openstack', 'openstack', 'OpenStack Search command line client',
|
||||
['OpenStack Contributors'], 1),
|
||||
]
|
||||
|
||||
# -- Options for HTML output --------------------------------------------------
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. Major themes that come with
|
||||
# Sphinx are currently 'default' and 'sphinxdoc'.
|
||||
html_theme = 'openstackdocs'
|
||||
|
||||
# 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
|
||||
# "<project> v<release> 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 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_use_modindex = 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, an OpenSearch description file will be output, and all pages will
|
||||
# contain a <link> tag referring to it. The value of this option must be the
|
||||
# base URL from which the finished HTML is served.
|
||||
#html_use_opensearch = ''
|
||||
|
||||
# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml").
|
||||
#html_file_suffix = ''
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = 'python-searchlightclientdoc'
|
||||
|
||||
|
||||
# -- Options for LaTeX output -------------------------------------------------
|
||||
|
||||
# The paper size ('letter' or 'a4').
|
||||
#latex_paper_size = 'letter'
|
||||
|
||||
# The font size ('10pt', '11pt' or '12pt').
|
||||
#latex_font_size = '10pt'
|
||||
|
||||
latex_use_xindy = False
|
||||
|
||||
latex_domain_indices = False
|
||||
|
||||
latex_elements = {
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
# openany: Skip blank pages in generated PDFs
|
||||
'extraclassoptions': 'openany,oneside',
|
||||
'makeindex': '',
|
||||
'printindex': '',
|
||||
'preamble': r'\setcounter{tocdepth}{3}',
|
||||
}
|
||||
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title, author, documentclass [howto/manual])
|
||||
# .
|
||||
latex_documents = [
|
||||
('index', 'doc-python-searchlightclient.tex',
|
||||
'python-searchlightclient Documentation',
|
||||
'OpenStack Foundation', 'manual', True),
|
||||
]
|
||||
|
||||
# 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
|
||||
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
#latex_preamble = ''
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
#latex_appendices = []
|
||||
|
||||
# If false, no module index is generated.
|
||||
#latex_use_modindex = True
|
|
@ -1,20 +0,0 @@
|
|||
============
|
||||
Contributing
|
||||
============
|
||||
|
||||
Code is hosted at `opendev.org`_. Submit bugs to the searchlight project
|
||||
on `Storyboard`_. Submit code to the openstack/python-searchlightclient project
|
||||
using `Gerrit`_.
|
||||
|
||||
.. _opendev.org: https://opendev.org/openstack/python-searchlightclient
|
||||
.. _Storyboard: https://storyboard.openstack.org/#!/project_group/93
|
||||
.. _Gerrit: https://docs.openstack.org/infra/manual/developers.html#development-workflow
|
||||
|
||||
Testing
|
||||
-------
|
||||
|
||||
The preferred way to run the unit tests is using ``tox``.
|
||||
|
||||
See `Consistent Testing Interface`_ for more details.
|
||||
|
||||
.. _Consistent Testing Interface: https://opendev.org/openstack/governance/tree/reference/project-testing-interface.rst
|
|
@ -1,24 +0,0 @@
|
|||
Python bindings to the OpenStack Searchlight API
|
||||
================================================
|
||||
|
||||
This is a client for OpenStack Searchlight API. There's a Python API
|
||||
(the :mod:`searchlightclient` module), and a command-line script
|
||||
(installed as :program:`openstack`).
|
||||
|
||||
Contents
|
||||
--------
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
contribute/index
|
||||
|
||||
|
||||
Man Page
|
||||
--------
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
man/openstack
|
||||
man/search
|
|
@ -1,7 +0,0 @@
|
|||
=========
|
||||
openstack
|
||||
=========
|
||||
|
||||
Please refer to OpenStack command-line interface `OpenStackClient`_.
|
||||
|
||||
.. _OpenStackClient: https://docs.openstack.org/python-openstackclient/latest/
|
|
@ -1,78 +0,0 @@
|
|||
======
|
||||
Search
|
||||
======
|
||||
|
||||
search facet list
|
||||
-----------------
|
||||
|
||||
List Searchlight Facets.
|
||||
|
||||
.. program:: search facet list
|
||||
.. code:: bash
|
||||
|
||||
openstack search facet list
|
||||
[--type <resource-type>]
|
||||
[--limit-terms <limit>]
|
||||
[--all-projects]
|
||||
|
||||
.. option:: --type <resource-type>
|
||||
|
||||
Get facets for a particular resource type.
|
||||
|
||||
.. option:: --limit-terms <limit>
|
||||
|
||||
Restricts the number of options returned for fields that
|
||||
support facet terms.
|
||||
|
||||
.. option:: --all-projects
|
||||
|
||||
Request facet terms for all projects (admin only).
|
||||
|
||||
search resource type list
|
||||
-------------------------
|
||||
|
||||
List Searchlight Resource Type (Plugin).
|
||||
|
||||
.. program:: search resource type list
|
||||
.. code:: bash
|
||||
|
||||
openstack search resource type list
|
||||
|
||||
search query
|
||||
------------
|
||||
|
||||
Search Searchlight resource.
|
||||
|
||||
.. program:: search query
|
||||
.. code:: bash
|
||||
|
||||
openstack search query
|
||||
--type [<resource-type> [<resource-type> ...]]
|
||||
--all-projects
|
||||
--source [[<field>,...]]
|
||||
--json
|
||||
<query>
|
||||
|
||||
.. option:: --type [<resource-type> [<resource-type> ...]]
|
||||
|
||||
One or more types to search. Uniquely identifies
|
||||
resource types. Example: ``--type OS::Glance::Image
|
||||
OS::Nova::Server``
|
||||
|
||||
.. option:: --all-projects
|
||||
|
||||
By default searches are restricted to the current project
|
||||
unless all_projects is set.
|
||||
|
||||
.. option:: --source [[<field>,...]]
|
||||
|
||||
Whether to display the json source. If not specified,
|
||||
it will not be displayed. If specified with no argument,
|
||||
the full source will be displayed. Otherwise, specify
|
||||
the fields combined with ',' to return the fields you want.
|
||||
It is recommended that you use the ``--max-width`` argument
|
||||
with this option.
|
||||
|
||||
.. option:: --json
|
||||
|
||||
Treat the query argument as a JSON formatted DSL query.
|
|
@ -1,71 +0,0 @@
|
|||
alabaster==0.7.10
|
||||
appdirs==1.3.0
|
||||
asn1crypto==0.23.0
|
||||
Babel==2.3.4
|
||||
cffi==1.14.0
|
||||
cliff==2.8.0
|
||||
cmd2==0.8.0
|
||||
coverage==4.0
|
||||
cryptography==2.7
|
||||
debtcollector==1.2.0
|
||||
decorator==3.4.0
|
||||
deprecation==1.0
|
||||
docutils==0.11
|
||||
dogpile.cache==0.6.2
|
||||
dulwich==0.15.0
|
||||
extras==1.0.0
|
||||
fixtures==3.0.0
|
||||
idna==2.6
|
||||
imagesize==0.7.1
|
||||
iso8601==0.1.11
|
||||
Jinja2==2.10
|
||||
jmespath==0.9.0
|
||||
jsonpatch==1.16
|
||||
jsonpointer==1.13
|
||||
jsonschema==2.6.0
|
||||
keystoneauth1==3.4.0
|
||||
linecache2==1.0.0
|
||||
MarkupSafe==1.0
|
||||
monotonic==0.6
|
||||
msgpack-python==0.4.0
|
||||
munch==2.1.0
|
||||
netaddr==0.7.18
|
||||
netifaces==0.10.4
|
||||
openstacksdk==0.11.2
|
||||
os-client-config==1.28.0
|
||||
os-service-types==1.2.0
|
||||
osc-lib==1.8.0
|
||||
oslo.config==5.2.0
|
||||
oslo.i18n==3.15.3
|
||||
oslo.serialization==2.18.0
|
||||
oslo.utils==3.33.0
|
||||
pbr==2.0.0
|
||||
positional==1.2.1
|
||||
prettytable==0.7.1
|
||||
pycparser==2.18
|
||||
Pygments==2.2.0
|
||||
pyOpenSSL==17.1.0
|
||||
pyparsing==2.1.0
|
||||
pyperclip==1.5.27
|
||||
python-cinderclient==3.3.0
|
||||
python-glanceclient==2.8.0
|
||||
python-keystoneclient==3.8.0
|
||||
python-mimeparse==1.6.0
|
||||
python-novaclient==9.1.0
|
||||
python-openstackclient==3.12.0
|
||||
python-subunit==1.0.0
|
||||
pytz==2013.6
|
||||
PyYAML==3.13
|
||||
requests==2.14.2
|
||||
requestsexceptions==1.2.0
|
||||
rfc3986==0.3.1
|
||||
simplejson==3.5.1
|
||||
snowballstemmer==1.2.1
|
||||
stevedore==1.20.0
|
||||
stestr==2.0.0
|
||||
testrepository==0.0.18
|
||||
testtools==2.2.0
|
||||
traceback2==1.4.0
|
||||
unittest2==1.1.0
|
||||
warlock==1.2.0
|
||||
wrapt==1.7.0
|
|
@ -1,6 +0,0 @@
|
|||
---
|
||||
upgrade:
|
||||
- |
|
||||
Python 2.7 support has been dropped. Last release of python-searchlightclient
|
||||
to support python 2.7 is OpenStack Train. The minimum version of Python now
|
||||
supported is Python 3.6.
|
|
@ -1,15 +0,0 @@
|
|||
# The order of packages is significant, because pip processes them in the order
|
||||
# of appearance. Changing the order has an impact on the overall integration
|
||||
# process, which may cause wedges in the gate later.
|
||||
|
||||
Babel!=2.4.0,>=2.3.4 # BSD
|
||||
pbr!=2.1.0,>=2.0.0 # Apache-2.0
|
||||
osc-lib>=1.8.0 # Apache-2.0
|
||||
PrettyTable<0.8,>=0.7.1 # BSD
|
||||
oslo.i18n>=3.15.3 # Apache-2.0
|
||||
oslo.serialization!=2.19.1,>=2.18.0 # Apache-2.0
|
||||
oslo.utils>=3.33.0 # Apache-2.0
|
||||
python-keystoneclient>=3.8.0 # Apache-2.0
|
||||
python-openstackclient>=3.12.0 # Apache-2.0
|
||||
PyYAML>=3.13 # MIT
|
||||
requests>=2.14.2 # Apache-2.0
|
|
@ -1,60 +0,0 @@
|
|||
# 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.
|
||||
|
||||
from keystoneclient import adapter
|
||||
|
||||
from searchlightclient.common import utils
|
||||
from searchlightclient import exc
|
||||
|
||||
|
||||
def Client(version, *args, **kwargs):
|
||||
module = utils.import_versioned_module(version, 'client')
|
||||
client_class = getattr(module, 'Client')
|
||||
return client_class(*args, **kwargs)
|
||||
|
||||
|
||||
def _construct_http_client(**kwargs):
|
||||
kwargs = kwargs.copy()
|
||||
if kwargs.get('session') is None:
|
||||
raise ValueError("A session instance is required")
|
||||
|
||||
return SessionClient(
|
||||
session=kwargs.get('session'),
|
||||
auth=kwargs.get('auth'),
|
||||
region_name=kwargs.get('region_name'),
|
||||
service_type=kwargs.get('service_type', 'search'),
|
||||
interface=kwargs.get('endpoint_type', 'public').rstrip('URL'),
|
||||
user_agent=kwargs.get('user_agent', 'python-searchlightclient'),
|
||||
endpoint_override=kwargs.get('endpoint_override'),
|
||||
timeout=kwargs.get('timeout')
|
||||
)
|
||||
|
||||
|
||||
class SessionClient(adapter.LegacyJsonAdapter):
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.timeout = kwargs.pop('timeout', None)
|
||||
super(SessionClient, self).__init__(*args, **kwargs)
|
||||
|
||||
def request(self, *args, **kwargs):
|
||||
kwargs.setdefault('raise_exc', True)
|
||||
|
||||
if self.timeout is not None:
|
||||
kwargs.setdefault('timeout', self.timeout)
|
||||
|
||||
kwargs.setdefault('headers', {}).setdefault(
|
||||
'Content-Type', 'application/json')
|
||||
|
||||
resp, body = super(SessionClient, self).request(*args, **kwargs)
|
||||
|
||||
if kwargs.get('raise_exc') and resp.status_code >= 400:
|
||||
raise exc.from_response(resp, body)
|
||||
return resp
|
|
@ -1,530 +0,0 @@
|
|||
# Copyright 2010 Jacob Kaplan-Moss
|
||||
# Copyright 2011 OpenStack Foundation
|
||||
# Copyright 2012 Grid Dynamics
|
||||
# Copyright 2013 OpenStack Foundation
|
||||
# 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.
|
||||
|
||||
"""
|
||||
Base utilities to build API operation managers and objects on top of.
|
||||
"""
|
||||
|
||||
########################################################################
|
||||
#
|
||||
# THIS MODULE IS DEPRECATED
|
||||
#
|
||||
# Please refer to
|
||||
# https://etherpad.openstack.org/p/kilo-oslo-library-proposals for
|
||||
# the discussion leading to this deprecation.
|
||||
#
|
||||
# We recommend checking out the python-openstacksdk project
|
||||
# (https://launchpad.net/python-openstacksdk) instead.
|
||||
#
|
||||
########################################################################
|
||||
|
||||
|
||||
# E1102: %s is not callable
|
||||
# pylint: disable=E1102
|
||||
|
||||
import abc
|
||||
import copy
|
||||
|
||||
from oslo_utils import strutils
|
||||
from urllib import parse
|
||||
|
||||
from searchlightclient.common import exceptions
|
||||
from searchlightclient.i18n import _
|
||||
|
||||
|
||||
def getid(obj):
|
||||
"""Return id if argument is a Resource.
|
||||
|
||||
Abstracts the common pattern of allowing both an object or an object's ID
|
||||
(UUID) as a parameter when dealing with relationships.
|
||||
"""
|
||||
try:
|
||||
if obj.uuid:
|
||||
return obj.uuid
|
||||
except AttributeError:
|
||||
pass
|
||||
try:
|
||||
return obj.id
|
||||
except AttributeError:
|
||||
return obj
|
||||
|
||||
|
||||
# TODO(aababilov): call run_hooks() in HookableMixin's child classes
|
||||
class HookableMixin(object):
|
||||
"""Mixin so classes can register and run hooks."""
|
||||
_hooks_map = {}
|
||||
|
||||
@classmethod
|
||||
def add_hook(cls, hook_type, hook_func):
|
||||
"""Add a new hook of specified type.
|
||||
|
||||
:param cls: class that registers hooks
|
||||
:param hook_type: hook type, e.g., '__pre_parse_args__'
|
||||
:param hook_func: hook function
|
||||
"""
|
||||
if hook_type not in cls._hooks_map:
|
||||
cls._hooks_map[hook_type] = []
|
||||
|
||||
cls._hooks_map[hook_type].append(hook_func)
|
||||
|
||||
@classmethod
|
||||
def run_hooks(cls, hook_type, *args, **kwargs):
|
||||
"""Run all hooks of specified type.
|
||||
|
||||
:param cls: class that registers hooks
|
||||
:param hook_type: hook type, e.g., '__pre_parse_args__'
|
||||
:param args: args to be passed to every hook function
|
||||
:param kwargs: kwargs to be passed to every hook function
|
||||
"""
|
||||
hook_funcs = cls._hooks_map.get(hook_type) or []
|
||||
for hook_func in hook_funcs:
|
||||
hook_func(*args, **kwargs)
|
||||
|
||||
|
||||
class BaseManager(HookableMixin):
|
||||
"""Basic manager type providing common operations.
|
||||
|
||||
Managers interact with a particular type of API (servers, flavors, images,
|
||||
etc.) and provide CRUD operations for them.
|
||||
"""
|
||||
resource_class = None
|
||||
|
||||
def __init__(self, client):
|
||||
"""Initializes BaseManager with `client`.
|
||||
|
||||
:param client: instance of BaseClient descendant for HTTP requests
|
||||
"""
|
||||
super(BaseManager, self).__init__()
|
||||
self.client = client
|
||||
|
||||
def _list(self, url, response_key=None, obj_class=None, json=None):
|
||||
"""List the collection.
|
||||
|
||||
:param url: a partial URL, e.g., '/servers'
|
||||
:param response_key: the key to be looked up in response dictionary,
|
||||
e.g., 'servers'. If response_key is None - all response body
|
||||
will be used.
|
||||
:param obj_class: class for constructing the returned objects
|
||||
(self.resource_class will be used by default)
|
||||
:param json: data that will be encoded as JSON and passed in POST
|
||||
request (GET will be sent by default)
|
||||
"""
|
||||
if json:
|
||||
body = self.client.post(url, json=json).json()
|
||||
else:
|
||||
body = self.client.get(url).json()
|
||||
|
||||
if obj_class is None:
|
||||
obj_class = self.resource_class
|
||||
|
||||
data = body[response_key] if response_key is not None else body
|
||||
# NOTE(ja): keystone returns values as list as {'values': [ ... ]}
|
||||
# unlike other services which just return the list...
|
||||
try:
|
||||
data = data['values']
|
||||
except (KeyError, TypeError):
|
||||
pass
|
||||
|
||||
return [obj_class(self, res, loaded=True) for res in data if res]
|
||||
|
||||
def _get(self, url, response_key=None):
|
||||
"""Get an object from collection.
|
||||
|
||||
:param url: a partial URL, e.g., '/servers'
|
||||
:param response_key: the key to be looked up in response dictionary,
|
||||
e.g., 'server'. If response_key is None - all response body
|
||||
will be used.
|
||||
"""
|
||||
body = self.client.get(url).json()
|
||||
data = body[response_key] if response_key is not None else body
|
||||
return self.resource_class(self, data, loaded=True)
|
||||
|
||||
def _head(self, url):
|
||||
"""Retrieve request headers for an object.
|
||||
|
||||
:param url: a partial URL, e.g., '/servers'
|
||||
"""
|
||||
resp = self.client.head(url)
|
||||
return resp.status_code == 204
|
||||
|
||||
def _post(self, url, json, response_key=None, return_raw=False):
|
||||
"""Create an object.
|
||||
|
||||
:param url: a partial URL, e.g., '/servers'
|
||||
:param json: data that will be encoded as JSON and passed in POST
|
||||
request (GET will be sent by default)
|
||||
:param response_key: the key to be looked up in response dictionary,
|
||||
e.g., 'server'. If response_key is None - all response body
|
||||
will be used.
|
||||
:param return_raw: flag to force returning raw JSON instead of
|
||||
Python object of self.resource_class
|
||||
"""
|
||||
body = self.client.post(url, json=json).json()
|
||||
data = body[response_key] if response_key is not None else body
|
||||
if return_raw:
|
||||
return data
|
||||
return self.resource_class(self, data)
|
||||
|
||||
def _put(self, url, json=None, response_key=None):
|
||||
"""Update an object with PUT method.
|
||||
|
||||
:param url: a partial URL, e.g., '/servers'
|
||||
:param json: data that will be encoded as JSON and passed in POST
|
||||
request (GET will be sent by default)
|
||||
:param response_key: the key to be looked up in response dictionary,
|
||||
e.g., 'servers'. If response_key is None - all response body
|
||||
will be used.
|
||||
"""
|
||||
resp = self.client.put(url, json=json)
|
||||
# PUT requests may not return a body
|
||||
if resp.content:
|
||||
body = resp.json()
|
||||
if response_key is not None:
|
||||
return self.resource_class(self, body[response_key])
|
||||
else:
|
||||
return self.resource_class(self, body)
|
||||
|
||||
def _patch(self, url, json=None, response_key=None):
|
||||
"""Update an object with PATCH method.
|
||||
|
||||
:param url: a partial URL, e.g., '/servers'
|
||||
:param json: data that will be encoded as JSON and passed in POST
|
||||
request (GET will be sent by default)
|
||||
:param response_key: the key to be looked up in response dictionary,
|
||||
e.g., 'servers'. If response_key is None - all response body
|
||||
will be used.
|
||||
"""
|
||||
body = self.client.patch(url, json=json).json()
|
||||
if response_key is not None:
|
||||
return self.resource_class(self, body[response_key])
|
||||
else:
|
||||
return self.resource_class(self, body)
|
||||
|
||||
def _delete(self, url):
|
||||
"""Delete an object.
|
||||
|
||||
:param url: a partial URL, e.g., '/servers/my-server'
|
||||
"""
|
||||
return self.client.delete(url)
|
||||
|
||||
|
||||
class ManagerWithFind(BaseManager, metaclass=abc.ABCMeta):
|
||||
"""Manager with additional `find()`/`findall()` methods."""
|
||||
|
||||
@abc.abstractmethod
|
||||
def list(self):
|
||||
pass
|
||||
|
||||
def find(self, **kwargs):
|
||||
"""Find a single item with attributes matching ``**kwargs``.
|
||||
|
||||
This isn't very efficient: it loads the entire list then filters on
|
||||
the Python side.
|
||||
"""
|
||||
matches = self.findall(**kwargs)
|
||||
num_matches = len(matches)
|
||||
if num_matches == 0:
|
||||
msg = _("No %(name)s matching %(args)s.") % {
|
||||
'name': self.resource_class.__name__,
|
||||
'args': kwargs
|
||||
}
|
||||
raise exceptions.NotFound(msg)
|
||||
elif num_matches > 1:
|
||||
raise exceptions.NoUniqueMatch()
|
||||
else:
|
||||
return matches[0]
|
||||
|
||||
def findall(self, **kwargs):
|
||||
"""Find all items with attributes matching ``**kwargs``.
|
||||
|
||||
This isn't very efficient: it loads the entire list then filters on
|
||||
the Python side.
|
||||
"""
|
||||
found = []
|
||||
searches = kwargs.items()
|
||||
|
||||
for obj in self.list():
|
||||
try:
|
||||
if all(getattr(obj, attr) == value
|
||||
for (attr, value) in searches):
|
||||
found.append(obj)
|
||||
except AttributeError:
|
||||
continue
|
||||
|
||||
return found
|
||||
|
||||
|
||||
class CrudManager(BaseManager):
|
||||
"""Base manager class for manipulating entities.
|
||||
|
||||
Children of this class are expected to define a `collection_key` and `key`.
|
||||
|
||||
- `collection_key`: Usually a plural noun by convention (e.g. `entities`);
|
||||
used to refer collections in both URL's (e.g. `/v3/entities`) and JSON
|
||||
objects containing a list of member resources (e.g. `{'entities': [{},
|
||||
{}, {}]}`).
|
||||
- `key`: Usually a singular noun by convention (e.g. `entity`); used to
|
||||
refer to an individual member of the collection.
|
||||
|
||||
"""
|
||||
collection_key = None
|
||||
key = None
|
||||
|
||||
def build_url(self, base_url=None, **kwargs):
|
||||
"""Builds a resource URL for the given kwargs.
|
||||
|
||||
Given an example collection where `collection_key = 'entities'` and
|
||||
`key = 'entity'`, the following URL's could be generated.
|
||||
|
||||
By default, the URL will represent a collection of entities, e.g.::
|
||||
|
||||
/entities
|
||||
|
||||
If kwargs contains an `entity_id`, then the URL will represent a
|
||||
specific member, e.g.::
|
||||
|
||||
/entities/{entity_id}
|
||||
|
||||
:param base_url: if provided, the generated URL will be appended to it
|
||||
"""
|
||||
url = base_url if base_url is not None else ''
|
||||
|
||||
url += '/%s' % self.collection_key
|
||||
|
||||
# do we have a specific entity?
|
||||
entity_id = kwargs.get('%s_id' % self.key)
|
||||
if entity_id is not None:
|
||||
url += '/%s' % entity_id
|
||||
|
||||
return url
|
||||
|
||||
def _filter_kwargs(self, kwargs):
|
||||
"""Drop null values and handle ids."""
|
||||
for key, ref in kwargs.copy().items():
|
||||
if ref is None:
|
||||
kwargs.pop(key)
|
||||
else:
|
||||
if isinstance(ref, Resource):
|
||||
kwargs.pop(key)
|
||||
kwargs['%s_id' % key] = getid(ref)
|
||||
return kwargs
|
||||
|
||||
def create(self, **kwargs):
|
||||
kwargs = self._filter_kwargs(kwargs)
|
||||
return self._post(
|
||||
self.build_url(**kwargs),
|
||||
{self.key: kwargs},
|
||||
self.key)
|
||||
|
||||
def get(self, **kwargs):
|
||||
kwargs = self._filter_kwargs(kwargs)
|
||||
return self._get(
|
||||
self.build_url(**kwargs),
|
||||
self.key)
|
||||
|
||||
def head(self, **kwargs):
|
||||
kwargs = self._filter_kwargs(kwargs)
|
||||
return self._head(self.build_url(**kwargs))
|
||||
|
||||
def list(self, base_url=None, **kwargs):
|
||||
"""List the collection.
|
||||
|
||||
:param base_url: if provided, the generated URL will be appended to it
|
||||
"""
|
||||
kwargs = self._filter_kwargs(kwargs)
|
||||
|
||||
return self._list(
|
||||
'%(base_url)s%(query)s' % {
|
||||
'base_url': self.build_url(base_url=base_url, **kwargs),
|
||||
'query': '?%s' % parse.urlencode(kwargs) if kwargs else '',
|
||||
},
|
||||
self.collection_key)
|
||||
|
||||
def put(self, base_url=None, **kwargs):
|
||||
"""Update an element.
|
||||
|
||||
:param base_url: if provided, the generated URL will be appended to it
|
||||
"""
|
||||
kwargs = self._filter_kwargs(kwargs)
|
||||
|
||||
return self._put(self.build_url(base_url=base_url, **kwargs))
|
||||
|
||||
def update(self, **kwargs):
|
||||
kwargs = self._filter_kwargs(kwargs)
|
||||
params = kwargs.copy()
|
||||
params.pop('%s_id' % self.key)
|
||||
|
||||
return self._patch(
|
||||
self.build_url(**kwargs),
|
||||
{self.key: params},
|
||||
self.key)
|
||||
|
||||
def delete(self, **kwargs):
|
||||
kwargs = self._filter_kwargs(kwargs)
|
||||
|
||||
return self._delete(
|
||||
self.build_url(**kwargs))
|
||||
|
||||
def find(self, base_url=None, **kwargs):
|
||||
"""Find a single item with attributes matching ``**kwargs``.
|
||||
|
||||
:param base_url: if provided, the generated URL will be appended to it
|
||||
"""
|
||||
kwargs = self._filter_kwargs(kwargs)
|
||||
|
||||
rl = self._list(
|
||||
'%(base_url)s%(query)s' % {
|
||||
'base_url': self.build_url(base_url=base_url, **kwargs),
|
||||
'query': '?%s' % parse.urlencode(kwargs) if kwargs else '',
|
||||
},
|
||||
self.collection_key)
|
||||
num = len(rl)
|
||||
|
||||
if num == 0:
|
||||
msg = _("No %(name)s matching %(args)s.") % {
|
||||
'name': self.resource_class.__name__,
|
||||
'args': kwargs
|
||||
}
|
||||
raise exceptions.NotFound(msg)
|
||||
elif num > 1:
|
||||
raise exceptions.NoUniqueMatch
|
||||
else:
|
||||
return rl[0]
|
||||
|
||||
|
||||
class Extension(HookableMixin):
|
||||
"""Extension descriptor."""
|
||||
|
||||
SUPPORTED_HOOKS = ('__pre_parse_args__', '__post_parse_args__')
|
||||
manager_class = None
|
||||
|
||||
def __init__(self, name, module):
|
||||
super(Extension, self).__init__()
|
||||
self.name = name
|
||||
self.module = module
|
||||
self._parse_extension_module()
|
||||
|
||||
def _parse_extension_module(self):
|
||||
self.manager_class = None
|
||||
for attr_name, attr_value in self.module.__dict__.items():
|
||||
if attr_name in self.SUPPORTED_HOOKS:
|
||||
self.add_hook(attr_name, attr_value)
|
||||
else:
|
||||
try:
|
||||
if issubclass(attr_value, BaseManager):
|
||||
self.manager_class = attr_value
|
||||
except TypeError:
|
||||
pass
|
||||
|
||||
def __repr__(self):
|
||||
return "<Extension '%s'>" % self.name
|
||||
|
||||
|
||||
class Resource(object):
|
||||
"""Base class for OpenStack resources (tenant, user, etc.).
|
||||
|
||||
This is pretty much just a bag for attributes.
|
||||
"""
|
||||
|
||||
HUMAN_ID = False
|
||||
NAME_ATTR = 'name'
|
||||
|
||||
def __init__(self, manager, info, loaded=False):
|
||||
"""Populate and bind to a manager.
|
||||
|
||||
:param manager: BaseManager object
|
||||
:param info: dictionary representing resource attributes
|
||||
:param loaded: prevent lazy-loading if set to True
|
||||
"""
|
||||
self.manager = manager
|
||||
self._info = info
|
||||
self._add_details(info)
|
||||
self._loaded = loaded
|
||||
|
||||
def __repr__(self):
|
||||
reprkeys = sorted(k
|
||||
for k in self.__dict__
|
||||
if k[0] != '_' and k != 'manager')
|
||||
info = ", ".join("%s=%s" % (k, getattr(self, k)) for k in reprkeys)
|
||||
return "<%s %s>" % (self.__class__.__name__, info)
|
||||
|
||||
@property
|
||||
def human_id(self):
|
||||
"""Human-readable ID which can be used for bash completion.
|
||||
"""
|
||||
if self.HUMAN_ID:
|
||||
name = getattr(self, self.NAME_ATTR, None)
|
||||
if name is not None:
|
||||
return strutils.to_slug(name)
|
||||
return None
|
||||
|
||||
def _add_details(self, info):
|
||||
for (k, v) in info.items():
|
||||
try:
|
||||
setattr(self, k, v)
|
||||
self._info[k] = v
|
||||
except AttributeError:
|
||||
# In this case we already defined the attribute on the class
|
||||
pass
|
||||
|
||||
def __getattr__(self, k):
|
||||
if k not in self.__dict__:
|
||||
# NOTE(bcwaldon): disallow lazy-loading if already loaded once
|
||||
if not self.is_loaded():
|
||||
self.get()
|
||||
return self.__getattr__(k)
|
||||
|
||||
raise AttributeError(k)
|
||||
else:
|
||||
return self.__dict__[k]
|
||||
|
||||
def get(self):
|
||||
"""Support for lazy loading details.
|
||||
|
||||
Some clients, such as novaclient have the option to lazy load the
|
||||
details, details which can be loaded with this function.
|
||||
"""
|
||||
# set_loaded() first ... so if we have to bail, we know we tried.
|
||||
self.set_loaded(True)
|
||||
if not hasattr(self.manager, 'get'):
|
||||
return
|
||||
|
||||
new = self.manager.get(self.id)
|
||||
if new:
|
||||
self._add_details(new._info)
|
||||
self._add_details(
|
||||
{'x_request_id': self.manager.client.last_request_id})
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, Resource):
|
||||
return NotImplemented
|
||||
# two resources of different types are not equal
|
||||
if not isinstance(other, self.__class__):
|
||||
return False
|
||||
if hasattr(self, 'id') and hasattr(other, 'id'):
|
||||
return self.id == other.id
|
||||
return self._info == other._info
|
||||
|
||||
def is_loaded(self):
|
||||
return self._loaded
|
||||
|
||||
def set_loaded(self, val):
|
||||
self._loaded = val
|
||||
|
||||
def to_dict(self):
|
||||
return copy.deepcopy(self._info)
|
|
@ -1,477 +0,0 @@
|
|||
# Copyright 2010 Jacob Kaplan-Moss
|
||||
# Copyright 2011 Nebula, Inc.
|
||||
# Copyright 2013 Alessio Ababilov
|
||||
# Copyright 2013 OpenStack Foundation
|
||||
# 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.
|
||||
|
||||
"""
|
||||
Exception definitions.
|
||||
"""
|
||||
|
||||
########################################################################
|
||||
#
|
||||
# THIS MODULE IS DEPRECATED
|
||||
#
|
||||
# Please refer to
|
||||
# https://etherpad.openstack.org/p/kilo-oslo-library-proposals for
|
||||
# the discussion leading to this deprecation.
|
||||
#
|
||||
# We recommend checking out the python-openstacksdk project
|
||||
# (https://launchpad.net/python-openstacksdk) instead.
|
||||
#
|
||||
########################################################################
|
||||
|
||||
import inspect
|
||||
import sys
|
||||
|
||||
from searchlightclient.i18n import _
|
||||
|
||||
|
||||
class ClientException(Exception):
|
||||
"""The base exception class for all exceptions this library raises.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class ValidationError(ClientException):
|
||||
"""Error in validation on API client side."""
|
||||
pass
|
||||
|
||||
|
||||
class UnsupportedVersion(ClientException):
|
||||
"""User is trying to use an unsupported version of the API."""
|
||||
pass
|
||||
|
||||
|
||||
class CommandError(ClientException):
|
||||
"""Error in CLI tool."""
|
||||
pass
|
||||
|
||||
|
||||
class AuthorizationFailure(ClientException):
|
||||
"""Cannot authorize API client."""
|
||||
pass
|
||||
|
||||
|
||||
class ConnectionError(ClientException):
|
||||
"""Cannot connect to API service."""
|
||||
pass
|
||||
|
||||
|
||||
class ConnectionRefused(ConnectionError):
|
||||
"""Connection refused while trying to connect to API service."""
|
||||
pass
|
||||
|
||||
|
||||
class AuthPluginOptionsMissing(AuthorizationFailure):
|
||||
"""Auth plugin misses some options."""
|
||||
def __init__(self, opt_names):
|
||||
super(AuthPluginOptionsMissing, self).__init__(
|
||||
_("Authentication failed. Missing options: %s") %
|
||||
", ".join(opt_names))
|
||||
self.opt_names = opt_names
|
||||
|
||||
|
||||
class AuthSystemNotFound(AuthorizationFailure):
|
||||
"""User has specified an AuthSystem that is not installed."""
|
||||
def __init__(self, auth_system):
|
||||
super(AuthSystemNotFound, self).__init__(
|
||||
_("AuthSystemNotFound: %r") % auth_system)
|
||||
self.auth_system = auth_system
|
||||
|
||||
|
||||
class NoUniqueMatch(ClientException):
|
||||
"""Multiple entities found instead of one."""
|
||||
pass
|
||||
|
||||
|
||||
class EndpointException(ClientException):
|
||||
"""Something is rotten in Service Catalog."""
|
||||
pass
|
||||
|
||||
|
||||
class EndpointNotFound(EndpointException):
|
||||
"""Could not find requested endpoint in Service Catalog."""
|
||||
pass
|
||||
|
||||
|
||||
class AmbiguousEndpoints(EndpointException):
|
||||
"""Found more than one matching endpoint in Service Catalog."""
|
||||
def __init__(self, endpoints=None):
|
||||
super(AmbiguousEndpoints, self).__init__(
|
||||
_("AmbiguousEndpoints: %r") % endpoints)
|
||||
self.endpoints = endpoints
|
||||
|
||||
|
||||
class HttpError(ClientException):
|
||||
"""The base exception class for all HTTP exceptions.
|
||||
"""
|
||||
http_status = 0
|
||||
message = _("HTTP Error")
|
||||
|
||||
def __init__(self, message=None, details=None,
|
||||
response=None, request_id=None,
|
||||
url=None, method=None, http_status=None):
|
||||
self.http_status = http_status or self.http_status
|
||||
self.message = message or self.message
|
||||
self.details = details
|
||||
self.request_id = request_id
|
||||
self.response = response
|
||||
self.url = url
|
||||
self.method = method
|
||||
formatted_string = "%s (HTTP %s)" % (self.message, self.http_status)
|
||||
if request_id:
|
||||
formatted_string += " (Request-ID: %s)" % request_id
|
||||
super(HttpError, self).__init__(formatted_string)
|
||||
|
||||
|
||||
class HTTPRedirection(HttpError):
|
||||
"""HTTP Redirection."""
|
||||
message = _("HTTP Redirection")
|
||||
|
||||
|
||||
class HTTPClientError(HttpError):
|
||||
"""Client-side HTTP error.
|
||||
|
||||
Exception for cases in which the client seems to have erred.
|
||||
"""
|
||||
message = _("HTTP Client Error")
|
||||
|
||||
|
||||
class HttpServerError(HttpError):
|
||||
"""Server-side HTTP error.
|
||||
|
||||
Exception for cases in which the server is aware that it has
|
||||
erred or is incapable of performing the request.
|
||||
"""
|
||||
message = _("HTTP Server Error")
|
||||
|
||||
|
||||
class MultipleChoices(HTTPRedirection):
|
||||
"""HTTP 300 - Multiple Choices.
|
||||
|
||||
Indicates multiple options for the resource that the client may follow.
|
||||
"""
|
||||
|
||||
http_status = 300
|
||||
message = _("Multiple Choices")
|
||||
|
||||
|
||||
class BadRequest(HTTPClientError):
|
||||
"""HTTP 400 - Bad Request.
|
||||
|
||||
The request cannot be fulfilled due to bad syntax.
|
||||
"""
|
||||
http_status = 400
|
||||
message = _("Bad Request")
|
||||
|
||||
|
||||
class Unauthorized(HTTPClientError):
|
||||
"""HTTP 401 - Unauthorized.
|
||||
|
||||
Similar to 403 Forbidden, but specifically for use when authentication
|
||||
is required and has failed or has not yet been provided.
|
||||
"""
|
||||
http_status = 401
|
||||
message = _("Unauthorized")
|
||||
|
||||
|
||||
class PaymentRequired(HTTPClientError):
|
||||
"""HTTP 402 - Payment Required.
|
||||
|
||||
Reserved for future use.
|
||||
"""
|
||||
http_status = 402
|
||||
message = _("Payment Required")
|
||||
|
||||
|
||||
class Forbidden(HTTPClientError):
|
||||
"""HTTP 403 - Forbidden.
|
||||
|
||||
The request was a valid request, but the server is refusing to respond
|
||||
to it.
|
||||
"""
|
||||
http_status = 403
|
||||
message = _("Forbidden")
|
||||
|
||||
|
||||
class NotFound(HTTPClientError):
|
||||
"""HTTP 404 - Not Found.
|
||||
|
||||
The requested resource could not be found but may be available again
|
||||
in the future.
|
||||
"""
|
||||
http_status = 404
|
||||
message = _("Not Found")
|
||||
|
||||
|
||||
class MethodNotAllowed(HTTPClientError):
|
||||
"""HTTP 405 - Method Not Allowed.
|
||||
|
||||
A request was made of a resource using a request method not supported
|
||||
by that resource.
|
||||
"""
|
||||
http_status = 405
|
||||
message = _("Method Not Allowed")
|
||||
|
||||
|
||||
class NotAcceptable(HTTPClientError):
|
||||
"""HTTP 406 - Not Acceptable.
|
||||
|
||||
The requested resource is only capable of generating content not
|
||||
acceptable according to the Accept headers sent in the request.
|
||||
"""
|
||||
http_status = 406
|
||||
message = _("Not Acceptable")
|
||||
|
||||
|
||||
class ProxyAuthenticationRequired(HTTPClientError):
|
||||
"""HTTP 407 - Proxy Authentication Required.
|
||||
|
||||
The client must first authenticate itself with the proxy.
|
||||
"""
|
||||
http_status = 407
|
||||
message = _("Proxy Authentication Required")
|
||||
|
||||
|
||||
class RequestTimeout(HTTPClientError):
|
||||
"""HTTP 408 - Request Timeout.
|
||||
|
||||
The server timed out waiting for the request.
|
||||
"""
|
||||
http_status = 408
|
||||
message = _("Request Timeout")
|
||||
|
||||
|
||||
class Conflict(HTTPClientError):
|
||||
"""HTTP 409 - Conflict.
|
||||
|
||||
Indicates that the request could not be processed because of conflict
|
||||
in the request, such as an edit conflict.
|
||||
"""
|
||||
http_status = 409
|
||||
message = _("Conflict")
|
||||
|
||||
|
||||
class Gone(HTTPClientError):
|
||||
"""HTTP 410 - Gone.
|
||||
|
||||
Indicates that the resource requested is no longer available and will
|
||||
not be available again.
|
||||
"""
|
||||
http_status = 410
|
||||
message = _("Gone")
|
||||
|
||||
|
||||
class LengthRequired(HTTPClientError):
|
||||
"""HTTP 411 - Length Required.
|
||||
|
||||
The request did not specify the length of its content, which is
|
||||
required by the requested resource.
|
||||
"""
|
||||
http_status = 411
|
||||
message = _("Length Required")
|
||||
|
||||
|
||||
class PreconditionFailed(HTTPClientError):
|
||||
"""HTTP 412 - Precondition Failed.
|
||||
|
||||
The server does not meet one of the preconditions that the requester
|
||||
put on the request.
|
||||
"""
|
||||
http_status = 412
|
||||
message = _("Precondition Failed")
|
||||
|
||||
|
||||
class RequestEntityTooLarge(HTTPClientError):
|
||||
"""HTTP 413 - Request Entity Too Large.
|
||||
|
||||
The request is larger than the server is willing or able to process.
|
||||
"""
|
||||
http_status = 413
|
||||
message = _("Request Entity Too Large")
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
try:
|
||||
self.retry_after = int(kwargs.pop('retry_after'))
|
||||
except (KeyError, ValueError):
|
||||
self.retry_after = 0
|
||||
|
||||
super(RequestEntityTooLarge, self).__init__(*args, **kwargs)
|
||||
|
||||
|
||||
class RequestUriTooLong(HTTPClientError):
|
||||
"""HTTP 414 - Request-URI Too Long.
|
||||
|
||||
The URI provided was too long for the server to process.
|
||||
"""
|
||||
http_status = 414
|
||||
message = _("Request-URI Too Long")
|
||||
|
||||
|
||||
class UnsupportedMediaType(HTTPClientError):
|
||||
"""HTTP 415 - Unsupported Media Type.
|
||||
|
||||
The request entity has a media type which the server or resource does
|
||||
not support.
|
||||
"""
|
||||
http_status = 415
|
||||
message = _("Unsupported Media Type")
|
||||
|
||||
|
||||
class RequestedRangeNotSatisfiable(HTTPClientError):
|
||||
"""HTTP 416 - Requested Range Not Satisfiable.
|
||||
|
||||
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 ExpectationFailed(HTTPClientError):
|
||||
"""HTTP 417 - Expectation Failed.
|
||||
|
||||
The server cannot meet the requirements of the Expect request-header field.
|
||||
"""
|
||||
http_status = 417
|
||||
message = _("Expectation Failed")
|
||||
|
||||
|
||||
class UnprocessableEntity(HTTPClientError):
|
||||
"""HTTP 422 - Unprocessable Entity.
|
||||
|
||||
The request was well-formed but was unable to be followed due to semantic
|
||||
errors.
|
||||
"""
|
||||
http_status = 422
|
||||
message = _("Unprocessable Entity")
|
||||
|
||||
|
||||
class InternalServerError(HttpServerError):
|
||||
"""HTTP 500 - Internal Server Error.
|
||||
|
||||
A generic error message, given when no more specific message is suitable.
|
||||
"""
|
||||
http_status = 500
|
||||
message = _("Internal Server Error")
|
||||
|
||||
|
||||
# NotImplemented is a python keyword.
|
||||
class HttpNotImplemented(HttpServerError):
|
||||
"""HTTP 501 - Not Implemented.
|
||||
|
||||
The server either does not recognize the request method, or it lacks
|
||||
the ability to fulfill the request.
|
||||
"""
|
||||
http_status = 501
|
||||
message = _("Not Implemented")
|
||||
|
||||
|
||||
class BadGateway(HttpServerError):
|
||||
"""HTTP 502 - Bad Gateway.
|
||||
|
||||
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 ServiceUnavailable(HttpServerError):
|
||||
"""HTTP 503 - Service Unavailable.
|
||||
|
||||
The server is currently unavailable.
|
||||
"""
|
||||
http_status = 503
|
||||
message = _("Service Unavailable")
|
||||
|
||||
|
||||
class GatewayTimeout(HttpServerError):
|
||||
"""HTTP 504 - Gateway Timeout.
|
||||
|
||||
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(HttpServerError):
|
||||
"""HTTP 505 - HttpVersion Not Supported.
|
||||
|
||||
The server does not support the HTTP protocol version used in the request.
|
||||
"""
|
||||
http_status = 505
|
||||
message = _("HTTP Version Not Supported")
|
||||
|
||||
|
||||
# _code_map contains all the classes that have http_status attribute.
|
||||
_code_map = dict(
|
||||
(getattr(obj, 'http_status', None), obj)
|
||||
for name, obj in vars(sys.modules[__name__]).items()
|
||||
if inspect.isclass(obj) and getattr(obj, 'http_status', False)
|
||||
)
|
||||
|
||||
|
||||
def from_response(response, method, url):
|
||||
"""Returns an instance of :class:`HttpError` or subclass based on response.
|
||||
|
||||
:param response: instance of `requests.Response` class
|
||||
:param method: HTTP method used for request
|
||||
:param url: URL used for request
|
||||
"""
|
||||
|
||||
req_id = response.headers.get("x-openstack-request-id")
|
||||
# NOTE(hdd) true for older versions of nova and cinder
|
||||
if not req_id:
|
||||
req_id = response.headers.get("x-compute-request-id")
|
||||
kwargs = {
|
||||
"http_status": response.status_code,
|
||||
"response": response,
|
||||
"method": method,
|
||||
"url": url,
|
||||
"request_id": req_id,
|
||||
}
|
||||
if "retry-after" in response.headers:
|
||||
kwargs["retry_after"] = response.headers["retry-after"]
|
||||
|
||||
content_type = response.headers.get("Content-Type", "")
|
||||
if content_type.startswith("application/json"):
|
||||
try:
|
||||
body = response.json()
|
||||
except ValueError:
|
||||
pass
|
||||
else:
|
||||
if isinstance(body, dict):
|
||||
error = body.get(list(body)[0])
|
||||
if isinstance(error, dict):
|
||||
kwargs["message"] = (error.get("message") or
|
||||
error.get("faultstring"))
|
||||
kwargs["details"] = (error.get("details") or
|
||||
str(body))
|
||||
elif content_type.startswith("text/"):
|
||||
kwargs["details"] = getattr(response, 'text', '')
|
||||
|
||||
try:
|
||||
cls = _code_map[response.status_code]
|
||||
except KeyError:
|
||||
if 500 <= response.status_code < 600:
|
||||
cls = HttpServerError
|
||||
elif 400 <= response.status_code < 500:
|
||||
cls = HTTPClientError
|
||||
else:
|
||||
cls = HttpError
|
||||
return cls(**kwargs)
|
|
@ -1,313 +0,0 @@
|
|||
# Copyright 2012 OpenStack Foundation
|
||||
# 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.
|
||||
|
||||
import base64
|
||||
import logging
|
||||
import os
|
||||
import textwrap
|
||||
import uuid
|
||||
|
||||
from oslo_serialization import jsonutils
|
||||
from oslo_utils import encodeutils
|
||||
from oslo_utils import importutils
|
||||
import prettytable
|
||||
from urllib import error
|
||||
from urllib import parse
|
||||
from urllib import request
|
||||
import yaml
|
||||
|
||||
from searchlightclient import exc
|
||||
from searchlightclient.i18n import _
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
supported_formats = {
|
||||
"json": lambda x: jsonutils.dumps(x, indent=2),
|
||||
"yaml": yaml.safe_dump
|
||||
}
|
||||
|
||||
|
||||
def env(*args, **kwargs):
|
||||
"""Returns the first environment variable set.
|
||||
|
||||
If all are empty, defaults to '' or keyword arg `default`.
|
||||
"""
|
||||
for arg in args:
|
||||
value = os.environ.get(arg)
|
||||
if value:
|
||||
return value
|
||||
return kwargs.get('default', '')
|
||||
|
||||
|
||||
def link_formatter(links):
|
||||
def format_link(l):
|
||||
if 'rel' in l:
|
||||
return "%s (%s)" % (l.get('href', ''), l.get('rel', ''))
|
||||
else:
|
||||
return "%s" % (l.get('href', ''))
|
||||
return '\n'.join(format_link(l) for l in links or [])
|
||||
|
||||
|
||||
def resource_nested_identifier(rsrc):
|
||||
nested_link = [l for l in rsrc.links or []
|
||||
if l.get('rel') == 'nested']
|
||||
if nested_link:
|
||||
nested_href = nested_link[0].get('href')
|
||||
nested_identifier = nested_href.split("/")[-2:]
|
||||
return "/".join(nested_identifier)
|
||||
|
||||
|
||||
def json_formatter(js):
|
||||
return jsonutils.dumps(js, indent=2, ensure_ascii=False,
|
||||
separators=(', ', ': '))
|
||||
|
||||
|
||||
def yaml_formatter(js):
|
||||
return yaml.safe_dump(js, default_flow_style=False)
|
||||
|
||||
|
||||
def text_wrap_formatter(d):
|
||||
return '\n'.join(textwrap.wrap(d or '', 55))
|
||||
|
||||
|
||||
def newline_list_formatter(r):
|
||||
return '\n'.join(r or [])
|
||||
|
||||
|
||||
def print_dict(d, formatters=None):
|
||||
formatters = formatters or {}
|
||||
pt = prettytable.PrettyTable(['Property', 'Value'],
|
||||
caching=False, print_empty=False)
|
||||
pt.align = 'l'
|
||||
|
||||
for field in d:
|
||||
if field in formatters:
|
||||
pt.add_row([field, formatters[field](d[field])])
|
||||
else:
|
||||
pt.add_row([field, d[field]])
|
||||
print(pt.get_string(sortby='Property'))
|
||||
|
||||
|
||||
def event_log_formatter(events):
|
||||
"""Return the events in log format."""
|
||||
event_log = []
|
||||
log_format = _("%(event_date)s %(event_time)s %(event_id)s "
|
||||
"[%(rsrc_name)s]: %(rsrc_status)s %(rsrc_status_reason)s")
|
||||
for event in events:
|
||||
event_time = getattr(event, 'event_time', '')
|
||||
time_date = event_time.split('T')
|
||||
try:
|
||||
event_date = time_date[0]
|
||||
event_time = time_date[1]
|
||||
except IndexError:
|
||||
event_time = event_date = ''
|
||||
|
||||
log = log_format % {
|
||||
'event_date': event_date, 'event_time': event_time,
|
||||
'event_id': getattr(event, 'id', ''),
|
||||
'rsrc_name': getattr(event, 'resource_name', ''),
|
||||
'rsrc_status': getattr(event, 'resource_status', ''),
|
||||
'rsrc_status_reason': getattr(event, 'resource_status_reason', '')
|
||||
}
|
||||
event_log.append(log)
|
||||
|
||||
return "\n".join(event_log)
|
||||
|
||||
|
||||
def print_update_list(lst, fields, formatters=None):
|
||||
"""Print the stack-update --dry-run output as a table.
|
||||
|
||||
This function is necessary to print the stack-update --dry-run
|
||||
output, which contains additional information about the update.
|
||||
"""
|
||||
formatters = formatters or {}
|
||||
pt = prettytable.PrettyTable(fields, caching=False, print_empty=False)
|
||||
pt.align = 'l'
|
||||
|
||||
for change in lst:
|
||||
row = []
|
||||
for field in fields:
|
||||
if field in formatters:
|
||||
row.append(formatters[field](change.get(field, None)))
|
||||
else:
|
||||
row.append(change.get(field, None))
|
||||
|
||||
pt.add_row(row)
|
||||
|
||||
print(encodeutils.safe_encode(pt.get_string()).decode())
|
||||
|
||||
|
||||
def find_resource(manager, name_or_id):
|
||||
"""Helper for the _find_* methods."""
|
||||
# first try to get entity as integer id
|
||||
try:
|
||||
if isinstance(name_or_id, int) or name_or_id.isdigit():
|
||||
return manager.get(int(name_or_id))
|
||||
except exc.NotFound:
|
||||
pass
|
||||
|
||||
# now try to get entity as uuid
|
||||
try:
|
||||
uuid.UUID(str(name_or_id))
|
||||
return manager.get(name_or_id)
|
||||
except (ValueError, exc.NotFound):
|
||||
pass
|
||||
|
||||
# finally try to find entity by name
|
||||
try:
|
||||
return manager.find(name=name_or_id)
|
||||
except exc.NotFound:
|
||||
msg = _("No %(name)s with a name or ID of "
|
||||
"'%(name_or_id)s' exists.") % \
|
||||
{
|
||||
'name': manager.resource_class.__name__.lower(),
|
||||
'name_or_id': name_or_id}
|
||||
raise exc.CommandError(msg)
|
||||
|
||||
|
||||
def import_versioned_module(version, submodule=None):
|
||||
module = 'searchlightclient.v%s' % version
|
||||
if submodule:
|
||||
module = '.'.join((module, submodule))
|
||||
return importutils.import_module(module)
|
||||
|
||||
|
||||
def format_parameters(params, parse_semicolon=True):
|
||||
'''Reformat parameters into dict of format expected by the API.'''
|
||||
|
||||
if not params:
|
||||
return {}
|
||||
|
||||
if parse_semicolon:
|
||||
# expect multiple invocations of --parameters but fall back
|
||||
# to ; delimited if only one --parameters is specified
|
||||
if len(params) == 1:
|
||||
params = params[0].split(';')
|
||||
|
||||
parameters = {}
|
||||
for p in params:
|
||||
try:
|
||||
(n, v) = p.split(('='), 1)
|
||||
except ValueError:
|
||||
msg = _('Malformed parameter(%s). Use the key=value format.') % p
|
||||
raise exc.CommandError(msg)
|
||||
|
||||
if n not in parameters:
|
||||
parameters[n] = v
|
||||
else:
|
||||
if not isinstance(parameters[n], list):
|
||||
parameters[n] = [parameters[n]]
|
||||
parameters[n].append(v)
|
||||
|
||||
return parameters
|
||||
|
||||
|
||||
def format_all_parameters(params, param_files,
|
||||
template_file=None, template_url=None):
|
||||
parameters = {}
|
||||
parameters.update(format_parameters(params))
|
||||
parameters.update(format_parameter_file(
|
||||
param_files,
|
||||
template_file,
|
||||
template_url))
|
||||
return parameters
|
||||
|
||||
|
||||
def format_parameter_file(param_files, template_file=None,
|
||||
template_url=None):
|
||||
'''Reformat file parameters into dict of format expected by the API.'''
|
||||
if not param_files:
|
||||
return {}
|
||||
params = format_parameters(param_files, False)
|
||||
|
||||
template_base_url = None
|
||||
if template_file or template_url:
|
||||
template_base_url = base_url_for_url(get_template_url(
|
||||
template_file, template_url))
|
||||
|
||||
param_file = {}
|
||||
for key, value in iter(params.items()):
|
||||
param_file[key] = resolve_param_get_file(value,
|
||||
template_base_url)
|
||||
return param_file
|
||||
|
||||
|
||||
def resolve_param_get_file(file, base_url):
|
||||
if base_url and not base_url.endswith('/'):
|
||||
base_url = base_url + '/'
|
||||
str_url = parse.urljoin(base_url, file)
|
||||
return read_url_content(str_url)
|
||||
|
||||
|
||||
def format_output(output, format='yaml'):
|
||||
"""Format the supplied dict as specified."""
|
||||
output_format = format.lower()
|
||||
try:
|
||||
return supported_formats[output_format](output)
|
||||
except KeyError:
|
||||
raise exc.HTTPUnsupported(_("The format(%s) is unsupported.")
|
||||
% output_format)
|
||||
|
||||
|
||||
def parse_query_url(url):
|
||||
base_url, query_params = url.split('?')
|
||||
return base_url, parse.parse_qs(query_params)
|
||||
|
||||
|
||||
def get_template_url(template_file=None, template_url=None):
|
||||
if template_file:
|
||||
template_url = normalise_file_path_to_url(template_file)
|
||||
return template_url
|
||||
|
||||
|
||||
def read_url_content(url):
|
||||
try:
|
||||
content = request.urlopen(url).read()
|
||||
except error.URLError:
|
||||
raise exc.CommandError(_('Could not fetch contents for %s') % url)
|
||||
|
||||
if content:
|
||||
try:
|
||||
content.decode('utf-8')
|
||||
except ValueError:
|
||||
content = base64.encodestring(content)
|
||||
return content
|
||||
|
||||
|
||||
def base_url_for_url(url):
|
||||
parsed = parse.urlparse(url)
|
||||
parsed_dir = os.path.dirname(parsed.path)
|
||||
return parse.urljoin(url, parsed_dir)
|
||||
|
||||
|
||||
def normalise_file_path_to_url(path):
|
||||
if parse.urlparse(path).scheme:
|
||||
return path
|
||||
path = os.path.abspath(path)
|
||||
return parse.urljoin('file:', request.pathname2url(path))
|
||||
|
||||
|
||||
def get_response_body(resp):
|
||||
body = resp.content
|
||||
if 'application/json' in resp.headers.get('content-type', ''):
|
||||
try:
|
||||
body = resp.json()
|
||||
except ValueError:
|
||||
LOG.error('Could not decode response body as JSON')
|
||||
else:
|
||||
body = None
|
||||
return body
|
|
@ -1,191 +0,0 @@
|
|||
# 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.
|
||||
|
||||
import sys
|
||||
|
||||
from oslo_serialization import jsonutils
|
||||
|
||||
from searchlightclient.i18n import _
|
||||
|
||||
verbose = 0
|
||||
|
||||
|
||||
class BaseException(Exception):
|
||||
"""An error occurred."""
|
||||
def __init__(self, message=None):
|
||||
self.message = message
|
||||
|
||||
def __str__(self):
|
||||
return self.message or self.__class__.__doc__
|
||||
|
||||
|
||||
class CommandError(BaseException):
|
||||
"""Invalid usage of CLI."""
|
||||
|
||||
|
||||
class InvalidEndpoint(BaseException):
|
||||
"""The provided endpoint is invalid."""
|
||||
|
||||
|
||||
class CommunicationError(BaseException):
|
||||
"""Unable to communicate with server."""
|
||||
|
||||
|
||||
class HTTPException(BaseException):
|
||||
"""Base exception for all HTTP-derived exceptions."""
|
||||
code = 'N/A'
|
||||
|
||||
def __init__(self, message=None):
|
||||
super(HTTPException, self).__init__(message)
|
||||
try:
|
||||
self.error = jsonutils.loads(message)
|
||||
if 'error' not in self.error:
|
||||
raise KeyError(_('Key "error" not exists'))
|
||||
except KeyError:
|
||||
# NOTE(jianingy): If key 'error' happens not exist,
|
||||
# self.message becomes no sense. In this case, we
|
||||
# return doc of current exception class instead.
|
||||
self.error = {'error':
|
||||
{'message': self.__class__.__doc__}}
|
||||
except Exception:
|
||||
self.error = {'error':
|
||||
{'message': self.message or self.__class__.__doc__}}
|
||||
|
||||
def __str__(self):
|
||||
message = self.error['error'].get('message', 'Internal Error')
|
||||
if verbose:
|
||||
traceback = self.error['error'].get('traceback', '')
|
||||
return (_('ERROR: %(message)s\n%(traceback)s') %
|
||||
{'message': message, 'traceback': traceback})
|
||||
else:
|
||||
return _('ERROR: %s') % message
|
||||
|
||||
|
||||
class HTTPMultipleChoices(HTTPException):
|
||||
code = 300
|
||||
|
||||
def __str__(self):
|
||||
self.details = _("Requested version of Searchlight API is not"
|
||||
"available.")
|
||||
return (_("%(name)s (HTTP %(code)s) %(details)s") %
|
||||
{
|
||||
'name': self.__class__.__name__,
|
||||
'code': self.code,
|
||||
'details': self.details})
|
||||
|
||||
|
||||
class BadRequest(HTTPException):
|
||||
"""DEPRECATED."""
|
||||
code = 400
|
||||
|
||||
|
||||
class HTTPBadRequest(BadRequest):
|
||||
pass
|
||||
|
||||
|
||||
class Unauthorized(HTTPException):
|
||||
"""DEPRECATED."""
|
||||
code = 401
|
||||
|
||||
|
||||
class HTTPUnauthorized(Unauthorized):
|
||||
pass
|
||||
|
||||
|
||||
class Forbidden(HTTPException):
|
||||
"""DEPRECATED."""
|
||||
code = 403
|
||||
|
||||
|
||||
class HTTPForbidden(Forbidden):
|
||||
pass
|
||||
|
||||
|
||||
class NotFound(HTTPException):
|
||||
"""DEPRECATED."""
|
||||
code = 404
|
||||
|
||||
|
||||
class HTTPNotFound(NotFound):
|
||||
pass
|
||||
|
||||
|
||||
class HTTPMethodNotAllowed(HTTPException):
|
||||
code = 405
|
||||
|
||||
|
||||
class Conflict(HTTPException):
|
||||
"""DEPRECATED."""
|
||||
code = 409
|
||||
|
||||
|
||||
class HTTPConflict(Conflict):
|
||||
pass
|
||||
|
||||
|
||||
class OverLimit(HTTPException):
|
||||
"""DEPRECATED."""
|
||||
code = 413
|
||||
|
||||
|
||||
class HTTPOverLimit(OverLimit):
|
||||
pass
|
||||
|
||||
|
||||
class HTTPUnsupported(HTTPException):
|
||||
code = 415
|
||||
|
||||
|
||||
class HTTPInternalServerError(HTTPException):
|
||||
code = 500
|
||||
|
||||
|
||||
class HTTPNotImplemented(HTTPException):
|
||||
code = 501
|
||||
|
||||
|
||||
class HTTPBadGateway(HTTPException):
|
||||
code = 502
|
||||
|
||||
|
||||
class ServiceUnavailable(HTTPException):
|
||||
"""DEPRECATED."""
|
||||
code = 503
|
||||
|
||||
|
||||
class HTTPServiceUnavailable(ServiceUnavailable):
|
||||
pass
|
||||
|
||||
|
||||
# NOTE(bcwaldon): Build a mapping of HTTP codes to corresponding exception
|
||||
# classes
|
||||
_code_map = {}
|
||||
for obj_name in dir(sys.modules[__name__]):
|
||||
if obj_name.startswith('HTTP'):
|
||||
obj = getattr(sys.modules[__name__], obj_name)
|
||||
_code_map[obj.code] = obj
|
||||
|
||||
|
||||
def from_response(response):
|
||||
"""Return an instance of an HTTPException based on requests response."""
|
||||
cls = _code_map.get(response.status_code, HTTPException)
|
||||
return cls(response.content)
|
||||
|
||||
|
||||
class NoTokenLookupException(Exception):
|
||||
"""DEPRECATED."""
|
||||
pass
|
||||
|
||||
|
||||
class EndpointNotFound(Exception):
|
||||
"""DEPRECATED."""
|
||||
pass
|
|
@ -1,25 +0,0 @@
|
|||
# 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.
|
||||
|
||||
"""oslo_i18n integration module for searchlightclient.
|
||||
|
||||
See https://docs.openstack.org/oslo.i18n/latest/user/index.html.
|
||||
|
||||
"""
|
||||
|
||||
import oslo_i18n
|
||||
|
||||
|
||||
_translators = oslo_i18n.TranslatorFactory(domain='searchlightclient')
|
||||
|
||||
# The primary translation function using the well-known name "_"
|
||||
_ = _translators.primary
|
|
@ -1,61 +0,0 @@
|
|||
# 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.
|
||||
#
|
||||
|
||||
"""OpenStackClient plugin for Search service."""
|
||||
|
||||
import logging
|
||||
|
||||
from osc_lib import utils
|
||||
|
||||
DEFAULT_SEARCH_API_VERSION = '1'
|
||||
API_VERSION_OPTION = 'os_search_api_version'
|
||||
API_NAME = 'search'
|
||||
API_VERSIONS = {
|
||||
'1': 'searchlightclient.v1.client.Client',
|
||||
}
|
||||
|
||||
|
||||
def make_client(instance):
|
||||
"""Returns a search service client"""
|
||||
search_client = utils.get_client_class(
|
||||
API_NAME,
|
||||
instance._api_version[API_NAME],
|
||||
API_VERSIONS)
|
||||
|
||||
# Set client http_log_debug to True if verbosity level is high enough
|
||||
http_log_debug = utils.get_effective_log_level() <= logging.DEBUG
|
||||
|
||||
# Remember interface only if it is set
|
||||
kwargs = utils.build_kwargs_dict('endpoint_type', instance._interface)
|
||||
client = search_client(
|
||||
session=instance.session,
|
||||
http_log_debug=http_log_debug,
|
||||
region_name=instance._region_name,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
return client
|
||||
|
||||
|
||||
def build_option_parser(parser):
|
||||
"""Hook to add global options"""
|
||||
parser.add_argument(
|
||||
'--os-search-api-version',
|
||||
metavar='<search-api-version>',
|
||||
default=utils.env(
|
||||
'OS_SEARCH_API_VERSION',
|
||||
default=DEFAULT_SEARCH_API_VERSION),
|
||||
help='Search API version, default=' +
|
||||
DEFAULT_SEARCH_API_VERSION +
|
||||
' (Env: OS_SEARCH_API_VERSION)')
|
||||
return parser
|
|
@ -1,79 +0,0 @@
|
|||
# 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.
|
||||
#
|
||||
|
||||
"""Searchlight v1 Facet action implementations"""
|
||||
|
||||
import logging
|
||||
|
||||
from osc_lib.command import command
|
||||
from osc_lib import utils
|
||||
|
||||
|
||||
class ListFacet(command.Lister):
|
||||
"""List Searchlight Facet."""
|
||||
|
||||
log = logging.getLogger(__name__ + ".ListFacet")
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(ListFacet, self).get_parser(prog_name)
|
||||
parser.add_argument(
|
||||
"--type",
|
||||
metavar="<resource-type>",
|
||||
help="Get facets for a particular resource type"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--limit-terms",
|
||||
metavar="<limit>",
|
||||
help="Restricts the number of options returned for "
|
||||
"fields that support facet terms"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--all-projects",
|
||||
action='store_true',
|
||||
default=False,
|
||||
help="Request facet terms for all projects (admin only)"
|
||||
)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
self.log.debug("take_action(%s)", parsed_args)
|
||||
|
||||
search_client = self.app.client_manager.search
|
||||
columns = (
|
||||
"Resource Type",
|
||||
"Type",
|
||||
"Name",
|
||||
"Options"
|
||||
)
|
||||
params = {
|
||||
"type": parsed_args.type,
|
||||
"limit_terms": parsed_args.limit_terms,
|
||||
"all_projects": parsed_args.all_projects
|
||||
}
|
||||
data = search_client.facets.list(**params)
|
||||
result = []
|
||||
for resource_type, values in data.items():
|
||||
if isinstance(values, list):
|
||||
# Cope with pre-1.0 service APIs
|
||||
facets = values
|
||||
else:
|
||||
facets = values['facets']
|
||||
for s in facets:
|
||||
options = []
|
||||
for o in s.get('options', []):
|
||||
options.append(
|
||||
str(o['key']) + '(' + str(o['doc_count']) + ')')
|
||||
s["options"] = ', '.join(options)
|
||||
s["resource_type"] = resource_type
|
||||
result.append(utils.get_dict_properties(s, columns))
|
||||
return (columns, result)
|
|
@ -1,49 +0,0 @@
|
|||
# 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.
|
||||
#
|
||||
|
||||
"""Searchlight v1 Resource Type action implementations"""
|
||||
|
||||
import logging
|
||||
|
||||
from osc_lib.command import command
|
||||
|
||||
|
||||
class ListResourceType(command.Lister):
|
||||
"""List Searchlight Resource Type (Plugin)."""
|
||||
|
||||
log = logging.getLogger(__name__ + ".ListResourceType")
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
self.log.debug("take_action(%s)", parsed_args)
|
||||
|
||||
search_client = self.app.client_manager.search
|
||||
columns = (
|
||||
"Alias Searching",
|
||||
"Alias Indexing",
|
||||
"Type"
|
||||
)
|
||||
data = search_client.resource_types.list()
|
||||
return (columns,
|
||||
(self.get_item_properties(
|
||||
s, columns,
|
||||
) for s in data))
|
||||
|
||||
def get_item_properties(self, item, fields):
|
||||
# osc_lib.utils.get_item_properties doesn't work because
|
||||
# the field names are using "-" instead of "_".
|
||||
row = []
|
||||
for field in fields:
|
||||
field_name = field.lower().replace(' ', '-')
|
||||
data = getattr(item, field_name, '')
|
||||
row.append(data)
|
||||
return tuple(row)
|
|
@ -1,136 +0,0 @@
|
|||
# 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.
|
||||
#
|
||||
|
||||
"""Searchlight v1 Search action implementations"""
|
||||
|
||||
import logging
|
||||
|
||||
from oslo_serialization import jsonutils
|
||||
|
||||
from osc_lib.command import command
|
||||
from osc_lib import utils
|
||||
|
||||
|
||||
class SearchResource(command.Lister):
|
||||
"""Search Searchlight resource."""
|
||||
|
||||
log = logging.getLogger(__name__ + ".SearchResource")
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(SearchResource, self).get_parser(prog_name)
|
||||
parser.add_argument(
|
||||
"query",
|
||||
metavar="<query>",
|
||||
help="Query resources by Elasticsearch query string or json "
|
||||
"format DSL. Query string example: 'name: cirros AND "
|
||||
"updated_at: [now-1y TO now]'. DSL example: "
|
||||
"'{\"term\": {\"name\": \"cirros\"}}'. "
|
||||
"See Elasticsearch DSL or Searchlight documentation for "
|
||||
"more detail."
|
||||
)
|
||||
parser.add_argument(
|
||||
"--json",
|
||||
action='store_true',
|
||||
default=False,
|
||||
help="Treat the query argument as a JSON formatted DSL query."
|
||||
)
|
||||
parser.add_argument(
|
||||
"--type",
|
||||
nargs='*',
|
||||
metavar="<resource-type>",
|
||||
help="One or more types to search. Uniquely identifies resource "
|
||||
"types. Example: --type OS::Glance::Image "
|
||||
"OS::Nova::Server"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--all-projects",
|
||||
action='store_true',
|
||||
default=False,
|
||||
help="By default searches are restricted to the current project "
|
||||
"unless all_projects is set"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--source",
|
||||
nargs='?',
|
||||
const='all_sources',
|
||||
metavar="[<field>,...]",
|
||||
help="Whether to display the json source. If not specified, "
|
||||
"it will not be displayed. If specified with no argument, "
|
||||
"the full source will be displayed. Otherwise, specify the "
|
||||
"fields combined with ',' to return the fields you want. "
|
||||
"It is recommended that you use the --max-width argument "
|
||||
"with this option."
|
||||
)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
self.log.debug("take_action(%s)", parsed_args)
|
||||
|
||||
search_client = self.app.client_manager.search
|
||||
mapping = {"_score": "score", "_type": "type", "_id": "id",
|
||||
"_index": "index", "_source": "source"}
|
||||
|
||||
params = {
|
||||
"type": parsed_args.type,
|
||||
"all_projects": parsed_args.all_projects
|
||||
}
|
||||
source = parsed_args.source
|
||||
if source:
|
||||
columns = ("ID", "Score", "Type", "Source")
|
||||
if source != "all_sources":
|
||||
params["_source"] = (["id"] +
|
||||
[s for s in source.split(",") if s != 'id'])
|
||||
else:
|
||||
columns = ("ID", "Name", "Score", "Type", "Updated")
|
||||
# Only return the required fields when source not specified.
|
||||
params["_source"] = ["id", "name", "updated_at"]
|
||||
|
||||
if parsed_args.query:
|
||||
if parsed_args.json:
|
||||
query = jsonutils.loads(parsed_args.query)
|
||||
else:
|
||||
try:
|
||||
jsonutils.loads(parsed_args.query)
|
||||
print("You should use the --json flag when specifying "
|
||||
"a JSON object.")
|
||||
exit(1)
|
||||
except Exception:
|
||||
qs = self._modify_query_string(parsed_args.query)
|
||||
query = {"query_string": {"query": qs}}
|
||||
|
||||
params['query'] = query
|
||||
|
||||
data = search_client.search.search(**params)
|
||||
result = []
|
||||
for r in data.hits['hits']:
|
||||
converted = {}
|
||||
extra = {}
|
||||
# hit._id may include extra information appended after _,
|
||||
# so use r['_source']['id'] for safe.
|
||||
r['_id'] = r.get('_source', {}).get('id')
|
||||
for k, v in r.items():
|
||||
map_key = mapping.get(k)
|
||||
if map_key is not None:
|
||||
converted[map_key] = v
|
||||
if k == "_source" and not parsed_args.source:
|
||||
converted["name"] = v.get("name")
|
||||
converted["updated"] = v.get("updated_at")
|
||||
else:
|
||||
extra[k] = v
|
||||
if extra:
|
||||
self.log.debug("extra info returned: %s", extra)
|
||||
result.append(utils.get_dict_properties(converted, columns))
|
||||
return (columns, result)
|
||||
|
||||
def _modify_query_string(self, query_string):
|
||||
return query_string.replace(r'/', r'\/')
|
|
@ -1,67 +0,0 @@
|
|||
# Copyright 2013 Nebula 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.
|
||||
#
|
||||
|
||||
import sys
|
||||
|
||||
|
||||
AUTH_TOKEN = "foobar"
|
||||
AUTH_URL = "http://0.0.0.0"
|
||||
|
||||
|
||||
class FakeStdout:
|
||||
def __init__(self):
|
||||
self.content = []
|
||||
|
||||
def write(self, text):
|
||||
self.content.append(text)
|
||||
|
||||
def make_string(self):
|
||||
result = ''
|
||||
for line in self.content:
|
||||
result = result + line
|
||||
return result
|
||||
|
||||
|
||||
class FakeApp(object):
|
||||
def __init__(self, _stdout):
|
||||
self.stdout = _stdout
|
||||
self.client_manager = None
|
||||
self.stdin = sys.stdin
|
||||
self.stdout = _stdout or sys.stdout
|
||||
self.stderr = sys.stderr
|
||||
|
||||
|
||||
class FakeClientManager(object):
|
||||
def __init__(self):
|
||||
self.session = None
|
||||
self.auth_ref = None
|
||||
|
||||
|
||||
class FakeResource(object):
|
||||
def __init__(self, manager, info, loaded=False):
|
||||
self.manager = manager
|
||||
self._info = info
|
||||
self._add_details(info)
|
||||
self._loaded = loaded
|
||||
|
||||
def _add_details(self, info):
|
||||
for (k, v) in info.items():
|
||||
setattr(self, k, v)
|
||||
|
||||
def __repr__(self):
|
||||
reprkeys = sorted(k for k in self.__dict__ if k[0] != '_' and
|
||||
k != 'manager')
|
||||
info = ", ".join("%s=%s" % (k, getattr(self, k)) for k in reprkeys)
|
||||
return "<%s %s>" % (self.__class__.__name__, info)
|
|
@ -1,93 +0,0 @@
|
|||
# Copyright 2012-2013 OpenStack Foundation
|
||||
# Copyright 2013 Nebula 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.
|
||||
#
|
||||
|
||||
import os
|
||||
|
||||
import fixtures
|
||||
import sys
|
||||
import testtools
|
||||
|
||||
from searchlightclient.tests.unit.osc import fakes
|
||||
|
||||
|
||||
class TestCase(testtools.TestCase):
|
||||
def setUp(self):
|
||||
testtools.TestCase.setUp(self)
|
||||
|
||||
if (os.environ.get("OS_STDOUT_CAPTURE") == "True" or
|
||||
os.environ.get("OS_STDOUT_CAPTURE") == "1"):
|
||||
stdout = self.useFixture(fixtures.StringStream("stdout")).stream
|
||||
self.useFixture(fixtures.MonkeyPatch("sys.stdout", stdout))
|
||||
|
||||
if (os.environ.get("OS_STDERR_CAPTURE") == "True" or
|
||||
os.environ.get("OS_STDERR_CAPTURE") == "1"):
|
||||
stderr = self.useFixture(fixtures.StringStream("stderr")).stream
|
||||
self.useFixture(fixtures.MonkeyPatch("sys.stderr", stderr))
|
||||
|
||||
def assertNotCalled(self, m, msg=None):
|
||||
"""Assert a function was not called"""
|
||||
|
||||
if m.called:
|
||||
if not msg:
|
||||
msg = 'method %s should not have been called' % m
|
||||
self.fail(msg)
|
||||
|
||||
# 2.6 doesn't have the assert dict equals so make sure that it exists
|
||||
if tuple(sys.version_info)[0:2] < (2, 7):
|
||||
|
||||
def assertIsInstance(self, obj, cls, msg=None):
|
||||
"""self.assertTrue(isinstance(obj, cls)), with a nicer message"""
|
||||
|
||||
if not isinstance(obj, cls):
|
||||
standardMsg = '%s is not an instance of %r' % (obj, cls)
|
||||
self.fail(self._formatMessage(msg, standardMsg))
|
||||
|
||||
def assertDictEqual(self, d1, d2, msg=None):
|
||||
# Simple version taken from 2.7
|
||||
self.assertIsInstance(d1, dict,
|
||||
'First argument is not a dictionary')
|
||||
self.assertIsInstance(d2, dict,
|
||||
'Second argument is not a dictionary')
|
||||
if d1 != d2:
|
||||
if msg:
|
||||
self.fail(msg)
|
||||
else:
|
||||
standardMsg = '%r != %r' % (d1, d2)
|
||||
self.fail(standardMsg)
|
||||
|
||||
|
||||
class TestCommand(TestCase):
|
||||
"""Test osc_lib command classes"""
|
||||
|
||||
def setUp(self):
|
||||
super(TestCommand, self).setUp()
|
||||
# Build up a fake app
|
||||
self.fake_stdout = fakes.FakeStdout()
|
||||
self.app = fakes.FakeApp(self.fake_stdout)
|
||||
self.app.client_manager = fakes.FakeClientManager()
|
||||
|
||||
def check_parser(self, cmd, args, verify_args):
|
||||
cmd_parser = cmd.get_parser('check_parser')
|
||||
try:
|
||||
parsed_args = cmd_parser.parse_args(args)
|
||||
except SystemExit:
|
||||
raise Exception("Argument parse failed")
|
||||
for av in verify_args:
|
||||
attr, value = av
|
||||
if attr:
|
||||
self.assertIn(attr, parsed_args)
|
||||
self.assertEqual(getattr(parsed_args, attr), value)
|
||||
return parsed_args
|
|
@ -1,89 +0,0 @@
|
|||
# Copyright 2014 OpenStack Foundation
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
from unittest import mock
|
||||
|
||||
from searchlightclient.tests.unit.osc import fakes
|
||||
from searchlightclient.tests.unit.osc import utils
|
||||
|
||||
|
||||
ResourceType = {
|
||||
"alias-searching": "searchlight-search",
|
||||
"alias-indexing": "searchlight-listener",
|
||||
"type": "OS::Nova::Server"
|
||||
}
|
||||
|
||||
|
||||
OldFacet = {
|
||||
"OS::Nova::Server":
|
||||
[
|
||||
{"type": "string", "name": "id"},
|
||||
{"type": "date", "name": "created_at"},
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
# The alternate facet format introduced in
|
||||
# https://blueprints.launchpad.net/searchlight/+spec/count-endpoint
|
||||
Facet = {
|
||||
"OS::Nova::Server":
|
||||
{
|
||||
"doc_count": 2,
|
||||
"facets": [
|
||||
{"type": "string", "name": "id"},
|
||||
{"type": "date", "name": "created_at"},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Resource = {
|
||||
"hits":
|
||||
{"hits":
|
||||
[
|
||||
{"_score": 0.3, "_type": "OS::Glance::Image", "_id": "1",
|
||||
"_source": {"id": "1", "name": "image1",
|
||||
"updated_at": "2016-01-01T00:00:00Z"}},
|
||||
{"_score": 0.3, "_type": "OS::Nova::Server", "_id": "2_ADMIN",
|
||||
"_source": {"id": "2", "name": "instance1",
|
||||
"updated_at": "2016-01-01T00:00:00Z"}},
|
||||
],
|
||||
"_shards": {"successful": 5, "failed": 0, "total": 5},
|
||||
"took": 5, "timed_out": False
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class FakeSearchv1Client(object):
|
||||
def __init__(self, **kwargs):
|
||||
self.http_client = mock.Mock()
|
||||
self.http_client.auth_token = kwargs['token']
|
||||
self.http_client.management_url = kwargs['endpoint']
|
||||
self.resource_types = mock.Mock()
|
||||
self.resource_types.list = mock.Mock(return_value=[])
|
||||
self.facets = mock.Mock()
|
||||
self.facets.list = mock.Mock(return_value=[])
|
||||
self.search = mock.Mock()
|
||||
self.search.search = mock.Mock(return_value=[])
|
||||
|
||||
|
||||
class TestSearchv1(utils.TestCommand):
|
||||
def setUp(self):
|
||||
super(TestSearchv1, self).setUp()
|
||||
|
||||
self.app.client_manager.search = FakeSearchv1Client(
|
||||
endpoint=fakes.AUTH_URL,
|
||||
token=fakes.AUTH_TOKEN,
|
||||
)
|
|
@ -1,70 +0,0 @@
|
|||
# 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.
|
||||
#
|
||||
|
||||
from searchlightclient.osc.v1 import facet
|
||||
from searchlightclient.tests.unit.osc.v1 import fakes as searchlight_fakes
|
||||
|
||||
|
||||
class TestFacet(searchlight_fakes.TestSearchv1):
|
||||
def setUp(self):
|
||||
super(TestFacet, self).setUp()
|
||||
self.facet_client = self.app.client_manager.search.facets
|
||||
|
||||
|
||||
class TestFacetListBase(TestFacet):
|
||||
def setUp(self):
|
||||
super(TestFacetListBase, self).setUp()
|
||||
self.cmd = facet.ListFacet(self.app, None)
|
||||
self.facet_client.list.return_value = searchlight_fakes.Facet
|
||||
|
||||
def _test_list(self, arglist, **assertArgs):
|
||||
parsed_args = self.check_parser(self.cmd, arglist, [])
|
||||
columns, data = self.cmd.take_action(parsed_args)
|
||||
self.facet_client.list.assert_called_with(**assertArgs)
|
||||
|
||||
collist = ('Resource Type', 'Type', 'Name', 'Options')
|
||||
self.assertEqual(collist, columns)
|
||||
|
||||
datalist = (
|
||||
('OS::Nova::Server', 'string', 'id', ''),
|
||||
('OS::Nova::Server', 'date', 'created_at', ''),
|
||||
)
|
||||
self.assertEqual(datalist, tuple(data))
|
||||
|
||||
|
||||
class TestFacetList(TestFacetListBase):
|
||||
def test_list(self):
|
||||
self._test_list([], all_projects=False, limit_terms=None, type=None)
|
||||
|
||||
def test_list_all_projects(self):
|
||||
self._test_list(['--all-projects'],
|
||||
all_projects=True, limit_terms=None, type=None)
|
||||
|
||||
def test_list_with_type(self):
|
||||
self._test_list(['--type', 'fake_res_type'],
|
||||
all_projects=False,
|
||||
limit_terms=None, type='fake_res_type')
|
||||
|
||||
def test_list_with_limit_terms(self):
|
||||
self._test_list(['--limit-terms', 'fake_limit'],
|
||||
all_projects=False,
|
||||
limit_terms='fake_limit', type=None)
|
||||
|
||||
|
||||
class TestOldFacetList(TestFacetListBase):
|
||||
def setUp(self):
|
||||
super(TestOldFacetList, self).setUp()
|
||||
self.facet_client.list.return_value = searchlight_fakes.OldFacet
|
||||
|
||||
def test_list(self):
|
||||
self._test_list([], all_projects=False, limit_terms=None, type=None)
|
|
@ -1,51 +0,0 @@
|
|||
# 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.
|
||||
#
|
||||
|
||||
import copy
|
||||
|
||||
from searchlightclient.osc.v1 import resource_type
|
||||
from searchlightclient.tests.unit.osc import fakes
|
||||
from searchlightclient.tests.unit.osc.v1 import fakes as searchlight_fakes
|
||||
|
||||
|
||||
class TestResourceType(searchlight_fakes.TestSearchv1):
|
||||
def setUp(self):
|
||||
super(TestResourceType, self).setUp()
|
||||
self.rtype_client = self.app.client_manager.search.resource_types
|
||||
|
||||
|
||||
class TestResourceTypeList(TestResourceType):
|
||||
|
||||
def setUp(self):
|
||||
super(TestResourceTypeList, self).setUp()
|
||||
self.cmd = resource_type.ListResourceType(self.app, None)
|
||||
self.rtype_client.list.return_value = [
|
||||
fakes.FakeResource(
|
||||
None,
|
||||
copy.deepcopy(searchlight_fakes.ResourceType),
|
||||
loaded=True,
|
||||
),
|
||||
]
|
||||
|
||||
def test_list(self):
|
||||
parsed_args = self.check_parser(self.cmd, [], [])
|
||||
columns, data = self.cmd.take_action(parsed_args)
|
||||
self.rtype_client.list.assert_called_with()
|
||||
|
||||
collist = ('Alias Searching', 'Alias Indexing', 'Type')
|
||||
self.assertEqual(collist, columns)
|
||||
|
||||
datalist = (('searchlight-search',
|
||||
'searchlight-listener',
|
||||
'OS::Nova::Server'),)
|
||||
self.assertEqual(datalist, tuple(data))
|
|
@ -1,133 +0,0 @@
|
|||
# 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.
|
||||
#
|
||||
|
||||
import copy
|
||||
|
||||
from searchlightclient.osc.v1 import search
|
||||
from searchlightclient.tests.unit.osc import fakes
|
||||
from searchlightclient.tests.unit.osc.v1 import fakes as searchlight_fakes
|
||||
|
||||
|
||||
class TestSearch(searchlight_fakes.TestSearchv1):
|
||||
def setUp(self):
|
||||
super(TestSearch, self).setUp()
|
||||
self.search_client = self.app.client_manager.search.search
|
||||
|
||||
|
||||
class TestSearchResource(TestSearch):
|
||||
|
||||
def setUp(self):
|
||||
super(TestSearchResource, self).setUp()
|
||||
self.cmd = search.SearchResource(self.app, None)
|
||||
fake_data = copy.deepcopy(searchlight_fakes.Resource)
|
||||
fake_data['hits']['hits'][0]['is_not_processed'] = 'foo'
|
||||
self.search_client.search.return_value = \
|
||||
fakes.FakeResource(None, fake_data, loaded=True)
|
||||
|
||||
def _test_search(self, arglist, **assertArgs):
|
||||
parsed_args = self.check_parser(self.cmd, arglist, [])
|
||||
columns, data = self.cmd.take_action(parsed_args)
|
||||
details = False
|
||||
if assertArgs.get("source"):
|
||||
details = True
|
||||
if assertArgs.get("source") == "all_resources":
|
||||
assertArgs.pop("source")
|
||||
else:
|
||||
assertArgs["_source"] = assertArgs.pop("source")
|
||||
self.search_client.search.assert_called_with(**assertArgs)
|
||||
|
||||
if details:
|
||||
collist = ("ID", "Score", "Type", "Source")
|
||||
datalist = (
|
||||
('1', 0.3, 'OS::Glance::Image',
|
||||
{'id': '1', 'name': 'image1',
|
||||
'updated_at': '2016-01-01T00:00:00Z'}),
|
||||
('2', 0.3, 'OS::Nova::Server',
|
||||
{'id': '2', 'name': 'instance1',
|
||||
'updated_at': '2016-01-01T00:00:00Z'}))
|
||||
else:
|
||||
collist = ("ID", "Name", "Score", "Type", "Updated")
|
||||
datalist = (('1', 'image1', 0.3, 'OS::Glance::Image',
|
||||
'2016-01-01T00:00:00Z'),
|
||||
('2', 'instance1', 0.3, 'OS::Nova::Server',
|
||||
'2016-01-01T00:00:00Z'))
|
||||
|
||||
self.assertEqual(collist, columns)
|
||||
self.assertEqual(datalist, tuple(data))
|
||||
|
||||
def test_search(self):
|
||||
self._test_search(["name: fake"],
|
||||
query={"query_string": {"query": "name: fake"}},
|
||||
_source=['id', 'name', 'updated_at'],
|
||||
all_projects=False, type=None)
|
||||
|
||||
def test_search_resource(self):
|
||||
self._test_search(["name: fake", "--type", "res1", "res2"],
|
||||
query={"query_string": {"query": "name: fake"}},
|
||||
_source=['id', 'name', 'updated_at'],
|
||||
type=["res1", "res2"],
|
||||
all_projects=False)
|
||||
|
||||
def test_search_query_string(self):
|
||||
self._test_search(["name: fake"],
|
||||
query={"query_string": {"query": "name: fake"}},
|
||||
_source=['id', 'name', 'updated_at'],
|
||||
all_projects=False, type=None)
|
||||
|
||||
def test_search_regexp_slashes_in_query_string(self):
|
||||
"""Escape slashes in querystrings so not to be treated as regexp"""
|
||||
self._test_search(["this/has/some/slashes"],
|
||||
query={"query_string": {"query": r"this\/has\/some\/slashes"}},
|
||||
_source=['id', 'name', 'updated_at'],
|
||||
all_projects=False, type=None)
|
||||
|
||||
def test_search_regexp_slashes_in_query(self):
|
||||
"""Don't escape slashes in DSL queries"""
|
||||
self._test_search(['--json',
|
||||
'{"term": {"name": "this/has/some/slashes"}}'],
|
||||
query={"term": {"name": "this/has/some/slashes"}},
|
||||
_source=['id', 'name', 'updated_at'],
|
||||
all_projects=False, type=None)
|
||||
|
||||
def test_search_query_dsl(self):
|
||||
self._test_search(['--json',
|
||||
'{"term": {"status": "active"}}'],
|
||||
query={'term': {'status': 'active'}},
|
||||
_source=['id', 'name', 'updated_at'],
|
||||
all_projects=False, type=None)
|
||||
|
||||
def test_search_query_dsl_no_json_flag(self):
|
||||
self.assertRaises(
|
||||
SystemExit, self._test_search,
|
||||
['{"term": {"status": "active"}}'],
|
||||
query={'term': {'status': 'active'}},
|
||||
_source=['id', 'name', 'updated_at'],
|
||||
all_projects=False, type=None)
|
||||
|
||||
def test_list_all_projects(self):
|
||||
self._test_search(["name: fake", "--all-projects"],
|
||||
query={"query_string": {"query": "name: fake"}},
|
||||
_source=['id', 'name', 'updated_at'],
|
||||
all_projects=True, type=None)
|
||||
|
||||
def test_list_source(self):
|
||||
self._test_search(["name: fake", "--source"],
|
||||
query={"query_string": {"query": "name: fake"}},
|
||||
all_projects=False, source="all_resources",
|
||||
type=None)
|
||||
|
||||
def test_list_optional_source(self):
|
||||
self._test_search(["name: fake", "--source", "f1,f2"],
|
||||
query={"query_string": {"query": "name: fake"}},
|
||||
all_projects=False, source=["id", "f1", "f2"],
|
||||
type=None)
|
|
@ -1,44 +0,0 @@
|
|||
# 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.
|
||||
|
||||
from searchlightclient.v1 import facets
|
||||
|
||||
import testtools
|
||||
from unittest import mock
|
||||
|
||||
|
||||
class FacetsManagerTest(testtools.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(FacetsManagerTest, self).setUp()
|
||||
self.manager = facets.FacetsManager(None)
|
||||
self.manager.client = mock.Mock()
|
||||
|
||||
def test_list_all_projects(self):
|
||||
self.manager.list(all_projects=True)
|
||||
self.manager.client.get.assert_called_once_with(
|
||||
'/v1/search/facets?all_projects=True')
|
||||
|
||||
def test_list_by_type(self):
|
||||
self.manager.list(type='fake_type')
|
||||
self.manager.client.get.assert_called_once_with(
|
||||
'/v1/search/facets?type=fake_type')
|
||||
|
||||
def test_list_by_index(self):
|
||||
self.manager.list(index='fake_index')
|
||||
self.manager.client.get.assert_called_once_with(
|
||||
'/v1/search/facets?index=fake_index')
|
||||
|
||||
def test_list_by_limit_terms(self):
|
||||
self.manager.list(limit_terms='10')
|
||||
self.manager.client.get.assert_called_once_with(
|
||||
'/v1/search/facets?limit_terms=10')
|
|
@ -1,25 +0,0 @@
|
|||
# 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.
|
||||
|
||||
from searchlightclient.v1 import resource_types
|
||||
|
||||
import testtools
|
||||
from unittest import mock
|
||||
|
||||
|
||||
class ResourceTypeManagerTest(testtools.TestCase):
|
||||
|
||||
def test_list(self):
|
||||
manager = resource_types.ResourceTypeManager(None)
|
||||
manager._list = mock.Mock()
|
||||
manager.list()
|
||||
manager._list.assert_called_once_with('/v1/search/plugins', 'plugins')
|
|
@ -1,73 +0,0 @@
|
|||
# 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.
|
||||
|
||||
from searchlightclient.v1 import search
|
||||
|
||||
import testtools
|
||||
from unittest import mock
|
||||
|
||||
|
||||
class SearchManagerTest(testtools.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(SearchManagerTest, self).setUp()
|
||||
self.manager = search.SearchManager(None)
|
||||
self.manager._post = mock.Mock()
|
||||
|
||||
def test_search_with_query(self):
|
||||
query_string = {
|
||||
'query_string': {
|
||||
'query': 'database'
|
||||
}
|
||||
}
|
||||
self.manager.search(query=query_string)
|
||||
self.manager._post.assert_called_once_with(
|
||||
'/v1/search', {'query': query_string})
|
||||
|
||||
def test_search_with_type(self):
|
||||
self.manager.search(type='fake_type')
|
||||
self.manager._post.assert_called_once_with(
|
||||
'/v1/search', {'type': 'fake_type'})
|
||||
|
||||
def test_search_with_offset(self):
|
||||
self.manager.search(offset='fake_offset')
|
||||
self.manager._post.assert_called_once_with(
|
||||
'/v1/search', {'offset': 'fake_offset'})
|
||||
|
||||
def test_search_with_limit(self):
|
||||
self.manager.search(limit=10)
|
||||
self.manager._post.assert_called_once_with(
|
||||
'/v1/search', {'limit': 10})
|
||||
|
||||
def test_search_with_sort(self):
|
||||
self.manager.search(sort='asc')
|
||||
self.manager._post.assert_called_once_with(
|
||||
'/v1/search', {'sort': 'asc'})
|
||||
|
||||
def test_search_with_source(self):
|
||||
self.manager.search(_source=['fake_source'])
|
||||
self.manager._post.assert_called_once_with(
|
||||
'/v1/search', {'_source': ['fake_source']})
|
||||
|
||||
def test_search_with_highlight(self):
|
||||
self.manager.search(highlight='fake_highlight')
|
||||
self.manager._post.assert_called_once_with(
|
||||
'/v1/search', {'highlight': 'fake_highlight'})
|
||||
|
||||
def test_search_with_all_projects(self):
|
||||
self.manager.search(all_projects=True)
|
||||
self.manager._post.assert_called_once_with(
|
||||
'/v1/search', {'all_projects': True})
|
||||
|
||||
def test_search_with_invalid_option(self):
|
||||
self.manager.search(invalid='fake')
|
||||
self.manager._post.assert_called_once_with('/v1/search', {})
|
|
@ -1,42 +0,0 @@
|
|||
# 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.
|
||||
|
||||
from searchlightclient import client
|
||||
from searchlightclient.v1 import facets
|
||||
from searchlightclient.v1 import resource_types
|
||||
from searchlightclient.v1 import search
|
||||
|
||||
|
||||
class Client(object):
|
||||
"""Client for the Searchlight v1 API.
|
||||
|
||||
:param session: a keystoneauth/keystoneclient session object
|
||||
:type session: keystoneclient.session.Session
|
||||
:param str service_type: The default service_type for URL discovery
|
||||
:param str interface: The default interface for URL discovery
|
||||
(Default: public)
|
||||
:param str region_name: The default region_name for URL discovery
|
||||
:param str endpoint_override: Always use this endpoint URL for requests
|
||||
for this ceiloclient
|
||||
:param auth: An auth plugin to use instead of the session one
|
||||
:type auth: keystoneclient.auth.base.BaseAuthPlugin
|
||||
:param str user_agent: The User-Agent string to set
|
||||
(Default is python-searchlightclient)
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""Initialize a new client for the Searchlight v1 API."""
|
||||
self.http_client = client._construct_http_client(*args, **kwargs)
|
||||
self.resource_types = resource_types.ResourceTypeManager(
|
||||
self.http_client)
|
||||
self.facets = facets.FacetsManager(self.http_client)
|
||||
self.search = search.SearchManager(self.http_client)
|
|
@ -1,53 +0,0 @@
|
|||
# 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.
|
||||
|
||||
from urllib import parse
|
||||
|
||||
from searchlightclient.common import base
|
||||
|
||||
|
||||
class Facets(base.Resource):
|
||||
def __repr__(self):
|
||||
return "<Facets %s>" % self._info
|
||||
|
||||
def list(self, **kwargs):
|
||||
return self.manager.list(self, **kwargs)
|
||||
|
||||
|
||||
class FacetsManager(base.BaseManager):
|
||||
resource_class = Facets
|
||||
|
||||
def list(self, **kwargs):
|
||||
"""Get a list of facets.
|
||||
:param index: Index name to query.
|
||||
:param all_projects: By default, facet terms are limited to the
|
||||
currently scoped project. Administrators are
|
||||
able to request facet terms for all projects
|
||||
by specify all_projects=True.
|
||||
:param limit_terms: Limit the number of options returned for fields
|
||||
that support facet terms.
|
||||
:param type: Request facets for a particular type by adding a type
|
||||
query parameter.
|
||||
:rtype: dict of {resource_type: {'facets': [:class:`Facets`],
|
||||
'doc_count':, :class:int}}
|
||||
"""
|
||||
params = {}
|
||||
if kwargs.get('index'):
|
||||
params['index'] = kwargs['index']
|
||||
if kwargs.get('type'):
|
||||
params['type'] = kwargs['type']
|
||||
if kwargs.get('limit_terms'):
|
||||
params['limit_terms'] = kwargs['limit_terms']
|
||||
if kwargs.get('all_projects') is not None:
|
||||
params['all_projects'] = kwargs['all_projects']
|
||||
url = '/v1/search/facets?%s' % parse.urlencode(params, True)
|
||||
return self.client.get(url).json()
|
|
@ -1,31 +0,0 @@
|
|||
# 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.
|
||||
|
||||
from searchlightclient.common import base
|
||||
|
||||
|
||||
class ResourceType(base.Resource):
|
||||
def __repr__(self):
|
||||
return "<ResourceType %s>" % self._info
|
||||
|
||||
def list(self, **kwargs):
|
||||
return self.manager.list(self, **kwargs)
|
||||
|
||||
|
||||
class ResourceTypeManager(base.BaseManager):
|
||||
resource_class = ResourceType
|
||||
|
||||
def list(self, **kwargs):
|
||||
"""Get a list of plugins.
|
||||
:rtype: list of :class:`ResourceType`
|
||||
"""
|
||||
return self._list('/v1/search/plugins', 'plugins')
|
|
@ -1,58 +0,0 @@
|
|||
# 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.
|
||||
|
||||
from searchlightclient.common import base
|
||||
|
||||
|
||||
class Search(base.Resource):
|
||||
def __repr__(self):
|
||||
return "<Resource %s>" % self._info
|
||||
|
||||
def search(self, **kwargs):
|
||||
return self.manager.search(self, **kwargs)
|
||||
|
||||
|
||||
class SearchManager(base.BaseManager):
|
||||
resource_class = Search
|
||||
|
||||
def search(self, **kwargs):
|
||||
"""Executes a search query against searchlight and returns the 'hits'
|
||||
from the response. Currently accepted parameters are (all optional):
|
||||
|
||||
:param query: see Elasticsearch DSL or Searchlight documentation;
|
||||
defaults to match everything
|
||||
:param type: one or more types to search. Uniquely identifies resource
|
||||
types. Example: OS::Glance::Image
|
||||
:param offset: skip over this many results
|
||||
:param limit: return this many results
|
||||
:param sort: sort by one or more fields
|
||||
:param _source: restrict the fields returned for each document
|
||||
:param highlight: add an Elasticsearch highlight clause
|
||||
:param all_projects: by default searches are restricted to the
|
||||
current project unless all_projects is set
|
||||
:param simplified: return only _source data
|
||||
"""
|
||||
search_params = {}
|
||||
for k, v in kwargs.items():
|
||||
if k in ('query', 'type', 'offset',
|
||||
'limit', 'sort', '_source', 'highlight', 'all_projects'):
|
||||
search_params[k] = v
|
||||
resources = self._post('/v1/search', search_params)
|
||||
|
||||
# NOTE: This could be done at the server side to reduce data
|
||||
# transfer, since the data have been wrapped several times
|
||||
# before transfer, and the data overhead is pretty small comparing
|
||||
# to the data payload('_source'), it is done here for simplicity.
|
||||
if 'simplified' in kwargs and kwargs['simplified']:
|
||||
resources = [h['_source'] for h in resources.hits['hits']]
|
||||
|
||||
return resources
|
35
setup.cfg
35
setup.cfg
|
@ -1,35 +0,0 @@
|
|||
[metadata]
|
||||
name = python-searchlightclient
|
||||
summary = OpenStack Indexing and Search API Client Library
|
||||
description-file =
|
||||
README.rst
|
||||
author = OpenStack
|
||||
author-email = openstack-discuss@lists.openstack.org
|
||||
home-page = https://docs.openstack.org/python-searchlightclient/latest
|
||||
python-requires = >=3.6
|
||||
classifier =
|
||||
Environment :: OpenStack
|
||||
Intended Audience :: Information Technology
|
||||
Intended Audience :: System Administrators
|
||||
License :: OSI Approved :: Apache Software License
|
||||
Operating System :: POSIX :: Linux
|
||||
Programming Language :: Python
|
||||
Programming Language :: Python :: Implementation :: CPython
|
||||
Programming Language :: Python :: 3 :: Only
|
||||
Programming Language :: Python :: 3
|
||||
Programming Language :: Python :: 3.6
|
||||
Programming Language :: Python :: 3.7
|
||||
Programming Language :: Python :: 3.8
|
||||
|
||||
[files]
|
||||
packages =
|
||||
searchlightclient
|
||||
|
||||
[entry_points]
|
||||
openstack.cli.extension =
|
||||
search = searchlightclient.osc.plugin
|
||||
|
||||
openstack.search.v1 =
|
||||
search_resource_type_list = searchlightclient.osc.v1.resource_type:ListResourceType
|
||||
search_facet_list = searchlightclient.osc.v1.facet:ListFacet
|
||||
search_query = searchlightclient.osc.v1.search:SearchResource
|
20
setup.py
20
setup.py
|
@ -1,20 +0,0 @@
|
|||
# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# 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.
|
||||
|
||||
import setuptools
|
||||
|
||||
setuptools.setup(
|
||||
setup_requires=['pbr>=2.0.0'],
|
||||
pbr=True)
|
|
@ -1,10 +0,0 @@
|
|||
# The order of packages is significant, because pip processes them in the order
|
||||
# of appearance. Changing the order has an impact on the overall integration
|
||||
# process, which may cause wedges in the gate later.
|
||||
|
||||
# Hacking already pins down pep8, pyflakes and flake8
|
||||
hacking>=3.0.1,<3.1.0 # Apache-2.0
|
||||
coverage!=4.4,>=4.0 # Apache-2.0
|
||||
fixtures>=3.0.0 # Apache-2.0/BSD
|
||||
stestr>=2.0.0 # Apache-2.0
|
||||
testtools>=2.2.0 # MIT
|
|
@ -1,75 +0,0 @@
|
|||
# Copyright 2010 United States Government as represented by the
|
||||
# Administrator of the National Aeronautics and Space Administration.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Copyright 2010 OpenStack Foundation
|
||||
# Copyright 2013 IBM Corp.
|
||||
# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# 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.
|
||||
|
||||
import ConfigParser
|
||||
import os
|
||||
import sys
|
||||
|
||||
import install_venv_common as install_venv # flake8: noqa
|
||||
|
||||
|
||||
def print_help(project, venv, root):
|
||||
help = """
|
||||
%(project)s development environment setup is complete.
|
||||
|
||||
%(project)s development uses virtualenv to track and manage Python
|
||||
dependencies while in development and testing.
|
||||
|
||||
To activate the %(project)s virtualenv for the extent of your current
|
||||
shell session you can run:
|
||||
|
||||
$ source %(venv)s/bin/activate
|
||||
|
||||
Or, if you prefer, you can run commands in the virtualenv on a case by
|
||||
case basis by running:
|
||||
|
||||
$ %(root)s/tools/with_venv.sh <your command>
|
||||
"""
|
||||
print(help % dict(project=project, venv=venv, root=root))
|
||||
|
||||
|
||||
def main(argv):
|
||||
root = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
|
||||
|
||||
if os.environ.get('tools_path'):
|
||||
root = os.environ['tools_path']
|
||||
venv = os.path.join(root, '.venv')
|
||||
if os.environ.get('venv'):
|
||||
venv = os.environ['venv']
|
||||
|
||||
pip_requires = os.path.join(root, 'requirements.txt')
|
||||
test_requires = os.path.join(root, 'test-requirements.txt')
|
||||
py_version = "python%s.%s" % (sys.version_info[0], sys.version_info[1])
|
||||
setup_cfg = ConfigParser.ConfigParser()
|
||||
setup_cfg.read('setup.cfg')
|
||||
project = setup_cfg.get('metadata', 'name')
|
||||
|
||||
install = install_venv.InstallVenv(
|
||||
root, venv, pip_requires, test_requires, py_version, project)
|
||||
options = install.parse_args(argv)
|
||||
install.check_python_version()
|
||||
install.check_dependencies()
|
||||
install.create_virtualenv(no_site_packages=options.no_site_packages)
|
||||
install.install_dependencies()
|
||||
print_help(project, venv, root)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main(sys.argv)
|
|
@ -1,170 +0,0 @@
|
|||
# Copyright 2013 OpenStack Foundation
|
||||
# Copyright 2013 IBM Corp.
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""Provides methods needed by installation script for OpenStack development
|
||||
virtual environments.
|
||||
|
||||
Since this script is used to bootstrap a virtualenv from the system's Python
|
||||
environment, it should be kept strictly compatible with Python 2.6.
|
||||
|
||||
Synced in from openstack-common
|
||||
"""
|
||||
|
||||
import optparse
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
|
||||
class InstallVenv(object):
|
||||
|
||||
def __init__(self, root, venv, requirements,
|
||||
test_requirements, py_version,
|
||||
project):
|
||||
self.root = root
|
||||
self.venv = venv
|
||||
self.requirements = requirements
|
||||
self.test_requirements = test_requirements
|
||||
self.py_version = py_version
|
||||
self.project = project
|
||||
|
||||
def die(self, message, *args):
|
||||
print(message % args, file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
def check_python_version(self):
|
||||
if sys.version_info < (2, 6):
|
||||
self.die("Need Python Version >= 2.6")
|
||||
|
||||
def run_command_with_code(self, cmd, redirect_output=True,
|
||||
check_exit_code=True):
|
||||
"""Runs a command in an out-of-process shell.
|
||||
|
||||
Returns the output of that command. Working directory is self.root.
|
||||
"""
|
||||
if redirect_output:
|
||||
stdout = subprocess.PIPE
|
||||
else:
|
||||
stdout = None
|
||||
|
||||
proc = subprocess.Popen(cmd, cwd=self.root, stdout=stdout)
|
||||
output = proc.communicate()[0]
|
||||
if check_exit_code and proc.returncode != 0:
|
||||
self.die('Command "%s" failed.\n%s', ' '.join(cmd), output)
|
||||
return (output, proc.returncode)
|
||||
|
||||
def run_command(self, cmd, redirect_output=True, check_exit_code=True):
|
||||
return self.run_command_with_code(cmd, redirect_output,
|
||||
check_exit_code)[0]
|
||||
|
||||
def get_distro(self):
|
||||
if (os.path.exists('/etc/fedora-release') or
|
||||
os.path.exists('/etc/redhat-release')):
|
||||
return Fedora(
|
||||
self.root, self.venv, self.requirements,
|
||||
self.test_requirements, self.py_version, self.project)
|
||||
else:
|
||||
return Distro(
|
||||
self.root, self.venv, self.requirements,
|
||||
self.test_requirements, self.py_version, self.project)
|
||||
|
||||
def check_dependencies(self):
|
||||
self.get_distro().install_virtualenv()
|
||||
|
||||
def create_virtualenv(self, no_site_packages=True):
|
||||
"""Creates the virtual environment and installs PIP.
|
||||
|
||||
Creates the virtual environment and installs PIP only into the
|
||||
virtual environment.
|
||||
"""
|
||||
if not os.path.isdir(self.venv):
|
||||
print('Creating venv...', end=' ')
|
||||
if no_site_packages:
|
||||
self.run_command(['virtualenv', '-q', '--no-site-packages',
|
||||
self.venv])
|
||||
else:
|
||||
self.run_command(['virtualenv', '-q', self.venv])
|
||||
print('done.')
|
||||
else:
|
||||
print("venv already exists...")
|
||||
pass
|
||||
|
||||
def pip_install(self, *args):
|
||||
self.run_command(['tools/with_venv.sh',
|
||||
'pip', 'install', '--upgrade'] + list(args),
|
||||
redirect_output=False)
|
||||
|
||||
def install_dependencies(self):
|
||||
print('Installing dependencies with pip (this can take a while)...')
|
||||
|
||||
# First things first, make sure our venv has the latest pip and
|
||||
# setuptools and pbr
|
||||
self.pip_install('pip>=1.4')
|
||||
self.pip_install('setuptools')
|
||||
self.pip_install('pbr')
|
||||
|
||||
self.pip_install('-r', self.requirements, '-r', self.test_requirements)
|
||||
|
||||
def parse_args(self, argv):
|
||||
"""Parses command-line arguments."""
|
||||
parser = optparse.OptionParser()
|
||||
parser.add_option('-n', '--no-site-packages',
|
||||
action='store_true',
|
||||
help="Do not inherit packages from global Python "
|
||||
"install")
|
||||
return parser.parse_args(argv[1:])[0]
|
||||
|
||||
|
||||
class Distro(InstallVenv):
|
||||
|
||||
def check_cmd(self, cmd):
|
||||
return bool(self.run_command(['which', cmd],
|
||||
check_exit_code=False).strip())
|
||||
|
||||
def install_virtualenv(self):
|
||||
if self.check_cmd('virtualenv'):
|
||||
return
|
||||
|
||||
if self.check_cmd('easy_install'):
|
||||
print('Installing virtualenv via easy_install...', end=' ')
|
||||
if self.run_command(['easy_install', 'virtualenv']):
|
||||
print('Succeeded')
|
||||
return
|
||||
else:
|
||||
print('Failed')
|
||||
|
||||
self.die('ERROR: virtualenv not found.\n\n%s development'
|
||||
' requires virtualenv, please install it using your'
|
||||
' favorite package management tool' % self.project)
|
||||
|
||||
|
||||
class Fedora(Distro):
|
||||
"""This covers all Fedora-based distributions.
|
||||
|
||||
Includes: Fedora, RHEL, CentOS, Scientific Linux
|
||||
"""
|
||||
|
||||
def check_pkg(self, pkg):
|
||||
return self.run_command_with_code(['rpm', '-q', pkg],
|
||||
check_exit_code=False)[1] == 0
|
||||
|
||||
def install_virtualenv(self):
|
||||
if self.check_cmd('virtualenv'):
|
||||
return
|
||||
|
||||
if not self.check_pkg('python-virtualenv'):
|
||||
self.die("Please install 'python-virtualenv'.")
|
||||
|
||||
super(Fedora, self).install_virtualenv()
|
|
@ -1,10 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
command -v tox > /dev/null 2>&1
|
||||
if [ $? -ne 0 ]; then
|
||||
echo 'This script requires "tox" to run.'
|
||||
echo 'You can install it with "pip install tox".'
|
||||
exit 1;
|
||||
fi
|
||||
|
||||
tox -evenv -- $@
|
77
tox.ini
77
tox.ini
|
@ -1,77 +0,0 @@
|
|||
[tox]
|
||||
envlist = py37,pep8
|
||||
minversion = 3.1.1
|
||||
skipsdist = True
|
||||
ignore_basepython_conflict = True
|
||||
|
||||
[testenv]
|
||||
basepython = python3
|
||||
usedevelop = True
|
||||
install_command = pip install {opts} {packages}
|
||||
deps =
|
||||
-c{env:UPPER_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master}
|
||||
-r{toxinidir}/requirements.txt
|
||||
-r{toxinidir}/test-requirements.txt
|
||||
commands =
|
||||
find . -type f -name "*.pyc" -delete
|
||||
stestr run --slowest {posargs}
|
||||
whitelist_externals = find
|
||||
|
||||
[testenv:pep8]
|
||||
sitepackages = False
|
||||
commands = flake8
|
||||
|
||||
[testenv:venv]
|
||||
commands = {posargs}
|
||||
|
||||
[testenv:functional]
|
||||
setenv =
|
||||
OS_TEST_PATH = ./searchlightclient/tests/functional
|
||||
passenv = OS_*
|
||||
|
||||
[testenv:cover]
|
||||
setenv =
|
||||
VIRTUAL_ENV={envdir}
|
||||
PYTHON=coverage run --source searchlightclient --parallel-mode
|
||||
commands =
|
||||
stestr run {posargs}
|
||||
coverage combine
|
||||
coverage html -d cover
|
||||
coverage xml -o cover/coverage.xml
|
||||
coverage report
|
||||
|
||||
[testenv:docs]
|
||||
whitelist_externals =
|
||||
rm
|
||||
deps = -r{toxinidir}/doc/requirements.txt
|
||||
commands =
|
||||
rm -rf doc/build
|
||||
sphinx-build --keep-going -b html -d doc/build/doctrees doc/source doc/build/html
|
||||
|
||||
[testenv:pdf-docs]
|
||||
deps = -r{toxinidir}/doc/requirements.txt
|
||||
envdir = {toxworkdir}/docs
|
||||
whitelist_externals =
|
||||
make
|
||||
commands =
|
||||
sphinx-build -W -b latex doc/source doc/build/pdf
|
||||
make -C doc/build/pdf
|
||||
|
||||
[flake8]
|
||||
# E128 continuation line under-indented for visual indent
|
||||
# E265 block comment should start with '# '
|
||||
# H405: multi line docstring summary not separated with an empty line
|
||||
# W504 line break after binary operator
|
||||
ignore = E128,E265,H405,W504
|
||||
show-source = True
|
||||
exclude=.venv,.git,.tox,dist,*lib/python*,*egg,build
|
||||
max-complexity=20
|
||||
|
||||
[hacking]
|
||||
import_exceptions = searchlightclient.openstack.common._i18n
|
||||
|
||||
[testenv:lower-constraints]
|
||||
deps =
|
||||
-c{toxinidir}/lower-constraints.txt
|
||||
-r{toxinidir}/test-requirements.txt
|
||||
-r{toxinidir}/requirements.txt
|
Loading…
Reference in New Issue