diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..90cd6fa --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +SPHINXPROJ = OpenStack-In-A-Box +SOURCEDIR = . +BUILDDIR = _build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/api/index.rst b/docs/api/index.rst new file mode 100644 index 0000000..8a4abb5 --- /dev/null +++ b/docs/api/index.rst @@ -0,0 +1,10 @@ +Class and Functions +=================== + +.. toctree:: + :maxdepth: 2 + + manager + models/index + services/index + utils/index diff --git a/docs/api/manager.rst b/docs/api/manager.rst new file mode 100644 index 0000000..8007cde --- /dev/null +++ b/docs/api/manager.rst @@ -0,0 +1,23 @@ +.. _manager: + +OpenStackInABox Manager +======================= + +.. currentmodule:: openstackinabox.manager + +Exceptions +---------- + +.. autoexception:: OpenStackServiceManagerException + +.. autoexception:: ServiceNotAvailable + +.. autoexception:: ServiceVersionNotAvailable + +.. autoexception:: KeystoneUrlNotSet + +Manager +------- + +.. autoclass:: OpenStackServicesManager + :members: diff --git a/docs/api/models/base_db.rst b/docs/api/models/base_db.rst new file mode 100644 index 0000000..fd9813d --- /dev/null +++ b/docs/api/models/base_db.rst @@ -0,0 +1,9 @@ +.. _base_db: + +Model Database Base +=================== + +.. currentmodule:: openstackinabox.models.base_db + +.. autoclass:: ModelDbBase + :members: diff --git a/docs/api/models/base_model.rst b/docs/api/models/base_model.rst new file mode 100644 index 0000000..0e0d999 --- /dev/null +++ b/docs/api/models/base_model.rst @@ -0,0 +1,11 @@ +.. _base_model: + +Base Model +========== + +.. currentmodule:: openstackinabox.models.base_model + +.. autoexception:: BaseModelExceptions + +.. autoclass:: BaseModel + :members: diff --git a/docs/api/models/cinder/index.rst b/docs/api/models/cinder/index.rst new file mode 100644 index 0000000..444fb19 --- /dev/null +++ b/docs/api/models/cinder/index.rst @@ -0,0 +1,9 @@ +OpenStack Cinder Services +========================= + +.. currentmodule:: openstackinabox.models.cinder + +.. toctree:: + :maxdepth: 2 + + model diff --git a/docs/api/models/cinder/model.rst b/docs/api/models/cinder/model.rst new file mode 100644 index 0000000..0577ad9 --- /dev/null +++ b/docs/api/models/cinder/model.rst @@ -0,0 +1,9 @@ +.. _cinder_model: + +Cinder Model +============ + +.. currentmodule:: openstackinabox.models.cinder.model + +.. autoclass:: CinderModel + :members: diff --git a/docs/api/models/index.rst b/docs/api/models/index.rst new file mode 100644 index 0000000..6698258 --- /dev/null +++ b/docs/api/models/index.rst @@ -0,0 +1,13 @@ +OpenStack Service Models +======================== + +.. currentmodule:: openstackinabox.models + +.. toctree:: + :maxdepth: 2 + + base_db + base_model + cinder/index + keystone/index + swift/index diff --git a/docs/api/models/keystone/db/base.rst b/docs/api/models/keystone/db/base.rst new file mode 100644 index 0000000..20bd65b --- /dev/null +++ b/docs/api/models/keystone/db/base.rst @@ -0,0 +1,9 @@ +.. _keystone_db_base: + +Keystone Database Base +====================== + +.. currentmodule:: openstackinabox.models.keystone.db.base + +.. autoclass:: KeystoneDbBase + :members: diff --git a/docs/api/models/keystone/db/endpoints.rst b/docs/api/models/keystone/db/endpoints.rst new file mode 100644 index 0000000..bc2d971 --- /dev/null +++ b/docs/api/models/keystone/db/endpoints.rst @@ -0,0 +1,9 @@ +.. _keystone_db_endpoints: + +Keystone Endpoints Database +=========================== + +.. currentmodule:: openstackinabox.models.keystone.db.endpoints + +.. autoclass:: KeystoneDbServiceEndpoints + :members: diff --git a/docs/api/models/keystone/db/index.rst b/docs/api/models/keystone/db/index.rst new file mode 100644 index 0000000..10f377e --- /dev/null +++ b/docs/api/models/keystone/db/index.rst @@ -0,0 +1,15 @@ +OpenStack Keystone Services +=========================== + +.. currentmodule:: openstackinabox.models.keystone.db + +.. toctree:: + :maxdepth: 2 + + base + endpoints + roles + services + tenants + tokens + users diff --git a/docs/api/models/keystone/db/roles.rst b/docs/api/models/keystone/db/roles.rst new file mode 100644 index 0000000..8b827ba --- /dev/null +++ b/docs/api/models/keystone/db/roles.rst @@ -0,0 +1,9 @@ +.. _keystone_db_roles: + +Keystone Roles Database +======================= + +.. currentmodule:: openstackinabox.models.keystone.db.roles + +.. autoclass:: KeystoneDbRoles + :members: diff --git a/docs/api/models/keystone/db/services.rst b/docs/api/models/keystone/db/services.rst new file mode 100644 index 0000000..728b7b2 --- /dev/null +++ b/docs/api/models/keystone/db/services.rst @@ -0,0 +1,9 @@ +.. _keystone_db_services: + +Keystone Services Database +========================== + +.. currentmodule:: openstackinabox.models.keystone.db.services + +.. autoclass:: KeystoneDbServices + :members: diff --git a/docs/api/models/keystone/db/tenants.rst b/docs/api/models/keystone/db/tenants.rst new file mode 100644 index 0000000..7273f58 --- /dev/null +++ b/docs/api/models/keystone/db/tenants.rst @@ -0,0 +1,9 @@ +.. _keystone_db_tenants: + +Keystone Tenants Database +========================= + +.. currentmodule:: openstackinabox.models.keystone.db.tenants + +.. autoclass:: KeystoneDbTenants + :members: diff --git a/docs/api/models/keystone/db/tokens.rst b/docs/api/models/keystone/db/tokens.rst new file mode 100644 index 0000000..1aa64f2 --- /dev/null +++ b/docs/api/models/keystone/db/tokens.rst @@ -0,0 +1,9 @@ +.. _keystone_db_tokens: + +Keystone Tokens Database +======================== + +.. currentmodule:: openstackinabox.models.keystone.db.tokens + +.. autoclass:: KeystoneDbTokens + :members: diff --git a/docs/api/models/keystone/db/users.rst b/docs/api/models/keystone/db/users.rst new file mode 100644 index 0000000..d0342d3 --- /dev/null +++ b/docs/api/models/keystone/db/users.rst @@ -0,0 +1,9 @@ +.. _keystone_db_users: + +Keystone Users Database +======================= + +.. currentmodule:: openstackinabox.models.keystone.db.users + +.. autoclass:: KeystoneDbUsers + :members: diff --git a/docs/api/models/keystone/exceptions.rst b/docs/api/models/keystone/exceptions.rst new file mode 100644 index 0000000..7c184ab --- /dev/null +++ b/docs/api/models/keystone/exceptions.rst @@ -0,0 +1,40 @@ +.. _keystone_model_exceptions: + +Keystone Model Exceptions +========================= + +.. currentmodule:: openstackinabox.models.keystone.exceptions + +.. autoexception:: KeystoneError + +.. autoexception:: KeystoneTenantError + +.. autoexception:: KeystoneUserError + +.. autoexception:: KeystoneDisabledUserError + +.. autoexception:: KeystoneUnknownUserError + +.. autoexception:: KeystoneUserAuthError + +.. autoexception:: KeystoneUserInvalidPasswordError + +.. autoexception:: KeystoneUserInvalidApiKeyError + +.. autoexception:: KeystoneTokenError + +.. autoexception:: KeystoneInvalidTokenError + +.. autoexception:: KeystoneRevokedTokenError + +.. autoexception:: KeystoneExpiredTokenError + +.. autoexception:: KeystoneRoleError + +.. autoexception:: KeystoneServiceCatalogError + +.. autoexception:: KeystoneServiceCatalogServiceError + +.. autoexception:: KeystoneServiceCatalogEndpointError + +.. autoexception:: KeystoneEndpointUrlError diff --git a/docs/api/models/keystone/index.rst b/docs/api/models/keystone/index.rst new file mode 100644 index 0000000..f885609 --- /dev/null +++ b/docs/api/models/keystone/index.rst @@ -0,0 +1,11 @@ +OpenStack Keystone Services +=========================== + +.. currentmodule:: openstackinabox.models.keystone + +.. toctree:: + :maxdepth: 2 + + exceptions + model + db/index diff --git a/docs/api/models/keystone/model.rst b/docs/api/models/keystone/model.rst new file mode 100644 index 0000000..1c88955 --- /dev/null +++ b/docs/api/models/keystone/model.rst @@ -0,0 +1,9 @@ +.. _keystone_model: + +Keystone Model +============== + +.. currentmodule:: openstackinabox.models.keystone.model + +.. autoclass:: KeystoneModel + :members: diff --git a/docs/api/models/swift/exceptions.rst b/docs/api/models/swift/exceptions.rst new file mode 100644 index 0000000..6b8b687 --- /dev/null +++ b/docs/api/models/swift/exceptions.rst @@ -0,0 +1,14 @@ +.. _swift_model_exceptions: + +Swift Model Exceptions +====================== + +.. currentmodule:: openstackinabox.models.swift.exceptions + +.. autoexception:: SwiftExceptions + +.. autoexception:: SwiftUnknownTenantError + +.. autoexception:: SwiftUnknownContainerError + +.. autoexception:: SwiftUnknownObjectError diff --git a/docs/api/models/swift/index.rst b/docs/api/models/swift/index.rst new file mode 100644 index 0000000..6a519c6 --- /dev/null +++ b/docs/api/models/swift/index.rst @@ -0,0 +1,11 @@ +OpenStack Swift Services +========================= + +.. currentmodule:: openstackinabox.models.swift + +.. toctree:: + :maxdepth: 2 + + exceptions + model + storage diff --git a/docs/api/models/swift/model.rst b/docs/api/models/swift/model.rst new file mode 100644 index 0000000..fa2e2a5 --- /dev/null +++ b/docs/api/models/swift/model.rst @@ -0,0 +1,9 @@ +.. _swift_model: + +Swift Model +=========== + +.. currentmodule:: openstackinabox.models.swift.model + +.. autoclass:: SwiftServiceModel + :members: diff --git a/docs/api/models/swift/storage.rst b/docs/api/models/swift/storage.rst new file mode 100644 index 0000000..80c368d --- /dev/null +++ b/docs/api/models/swift/storage.rst @@ -0,0 +1,9 @@ +.. _swift_model_storage: + +Swift Storage Model +=================== + +.. currentmodule:: openstackinabox.models.swift.storage + +.. autoclass:: SwiftStorage + :members: diff --git a/docs/api/services/base_service.rst b/docs/api/services/base_service.rst new file mode 100644 index 0000000..5f47d7a --- /dev/null +++ b/docs/api/services/base_service.rst @@ -0,0 +1,9 @@ +.. _base_service: + +Base Service +============ + +.. currentmodule:: openstackinabox.services.base_service + +.. autoclass:: BaseService + :members: diff --git a/docs/api/services/cinder/index.rst b/docs/api/services/cinder/index.rst new file mode 100644 index 0000000..50e4c17 --- /dev/null +++ b/docs/api/services/cinder/index.rst @@ -0,0 +1,9 @@ +OpenStack Cinder Service +======================== + +.. currentmodule:: openstackinabox.services.cinder + +.. toctree:: + :maxdepth: 2 + + v1/index diff --git a/docs/api/services/cinder/v1/base.rst b/docs/api/services/cinder/v1/base.rst new file mode 100644 index 0000000..11922f0 --- /dev/null +++ b/docs/api/services/cinder/v1/base.rst @@ -0,0 +1,9 @@ +.. _cinder_v1_base: + +Cinder v1 Base Service +====================== + +.. currentmodule:: openstackinabox.services.cinder.v1.base + +.. autoclass:: CinderV1ServiceBase + :members: diff --git a/docs/api/services/cinder/v1/index.rst b/docs/api/services/cinder/v1/index.rst new file mode 100644 index 0000000..415e95d --- /dev/null +++ b/docs/api/services/cinder/v1/index.rst @@ -0,0 +1,10 @@ +OpenStack Cinder v1 Service +=========================== + +.. currentmodule:: openstackinabox.services.cinder.v1 + +.. toctree:: + :maxdepth: 2 + + base + volumes diff --git a/docs/api/services/cinder/v1/volumes.rst b/docs/api/services/cinder/v1/volumes.rst new file mode 100644 index 0000000..3e99276 --- /dev/null +++ b/docs/api/services/cinder/v1/volumes.rst @@ -0,0 +1,9 @@ +.. _volumes: + +Cinder v1 Volumes +================= + +.. currentmodule:: openstackinabox.services.cinder.v1.volumes + +.. autoclass:: CinderV1Volumes + :members: diff --git a/docs/api/services/index.rst b/docs/api/services/index.rst new file mode 100644 index 0000000..514ab3d --- /dev/null +++ b/docs/api/services/index.rst @@ -0,0 +1,12 @@ +OpenStack Services +================== + +.. currentmodule:: openstackinabox.services + +.. toctree:: + :maxdepth: 2 + + base_service + cinder/index + keystone/index + swift/index diff --git a/docs/api/services/keystone/index.rst b/docs/api/services/keystone/index.rst new file mode 100644 index 0000000..d0e5e46 --- /dev/null +++ b/docs/api/services/keystone/index.rst @@ -0,0 +1,9 @@ +OpenStack Keystone Services +=========================== + +.. currentmodule:: openstackinabox.services.keystone + +.. toctree:: + :maxdepth: 2 + + v2/index diff --git a/docs/api/services/keystone/v2/base.rst b/docs/api/services/keystone/v2/base.rst new file mode 100644 index 0000000..d466155 --- /dev/null +++ b/docs/api/services/keystone/v2/base.rst @@ -0,0 +1,9 @@ +.. _keystone_v2_base: + +Keystone V2 Base Service +======================== + +.. currentmodule:: openstackinabox.services.keystone.v2.base + +.. autoclass:: KeystoneV2ServiceBase + :members: diff --git a/docs/api/services/keystone/v2/exceptions.rst b/docs/api/services/keystone/v2/exceptions.rst new file mode 100644 index 0000000..0b3d5ab --- /dev/null +++ b/docs/api/services/keystone/v2/exceptions.rst @@ -0,0 +1,14 @@ +.. _exceptions: + +Keystone v2 Exceptions +====================== + +.. currentmodule:: openstackinabox.services.keystone.v2.exceptions + +.. autoexception:: KeystoneV2Errors + +.. autoexception:: KeystoneV2AuthError + +.. autoexception:: KeystoneV2AuthForbiddenError + +.. autoexception:: KeystoneV2AuthUnauthorizedError diff --git a/docs/api/services/keystone/v2/index.rst b/docs/api/services/keystone/v2/index.rst new file mode 100644 index 0000000..79d45ec --- /dev/null +++ b/docs/api/services/keystone/v2/index.rst @@ -0,0 +1,13 @@ +OpenStack Keystone v2 Service +============================= + +.. currentmodule:: openstackinabox.services.keystone.v2 + +.. toctree:: + :maxdepth: 2 + + base + exceptions + tenants + tokens + users diff --git a/docs/api/services/keystone/v2/tenants.rst b/docs/api/services/keystone/v2/tenants.rst new file mode 100644 index 0000000..17434f9 --- /dev/null +++ b/docs/api/services/keystone/v2/tenants.rst @@ -0,0 +1,9 @@ +.. _tenants: + +Keystone V2 Tenants +=================== + +.. currentmodule:: openstackinabox.services.keystone.v2.tenants + +.. autoclass:: KeystoneV2ServiceTenants + :members: diff --git a/docs/api/services/keystone/v2/tokens.rst b/docs/api/services/keystone/v2/tokens.rst new file mode 100644 index 0000000..933717f --- /dev/null +++ b/docs/api/services/keystone/v2/tokens.rst @@ -0,0 +1,9 @@ +.. _tokens: + +Keystone V2 Tokens +================== + +.. currentmodule:: openstackinabox.services.keystone.v2.tokens + +.. autoclass:: KeystoneV2ServiceTokens + :members: diff --git a/docs/api/services/keystone/v2/users.rst b/docs/api/services/keystone/v2/users.rst new file mode 100644 index 0000000..4a9917e --- /dev/null +++ b/docs/api/services/keystone/v2/users.rst @@ -0,0 +1,9 @@ +.. _users: + +Keystone V2 Users +================= + +.. currentmodule:: openstackinabox.services.keystone.v2.users + +.. autoclass:: KeystoneV2ServiceUsers + :members: diff --git a/docs/api/services/swift/index.rst b/docs/api/services/swift/index.rst new file mode 100644 index 0000000..3a229ef --- /dev/null +++ b/docs/api/services/swift/index.rst @@ -0,0 +1,9 @@ +OpenStack Swift Service +======================= + +.. currentmodule:: openstackinabox.services.swift + +.. toctree:: + :maxdepth: 2 + + v1/index diff --git a/docs/api/services/swift/v1/index.rst b/docs/api/services/swift/v1/index.rst new file mode 100644 index 0000000..27d16a6 --- /dev/null +++ b/docs/api/services/swift/v1/index.rst @@ -0,0 +1,7 @@ +OpenStack Swift v1 Service +=========================== + +.. currentmodule:: openstackinabox.services.swift.v1 + +.. autoclass:: SwiftV1Service + :members: diff --git a/docs/api/utils/directory.rst b/docs/api/utils/directory.rst new file mode 100644 index 0000000..3691fa9 --- /dev/null +++ b/docs/api/utils/directory.rst @@ -0,0 +1,9 @@ +.. _directory: + +Temporary Directory Utility +=========================== + +.. currentmodule:: openstackinabox.utils.directory + +.. autoclass:: TemporaryDirectory + :members: diff --git a/docs/api/utils/index.rst b/docs/api/utils/index.rst new file mode 100644 index 0000000..e38f4ef --- /dev/null +++ b/docs/api/utils/index.rst @@ -0,0 +1,7 @@ +Utilities +========= + +.. toctree:: + :maxdepth: 2 + + directory diff --git a/docs/changes/0.1.rst b/docs/changes/0.1.rst new file mode 100644 index 0000000..5b60ba0 --- /dev/null +++ b/docs/changes/0.1.rst @@ -0,0 +1,8 @@ +.. _0.1: + +ChangeLog for Stack-In-A-Box 0.1 +================================ + +Initial Release + +- Basic Keystone v2 Authentication Functionality diff --git a/docs/changes/index.rst b/docs/changes/index.rst new file mode 100644 index 0000000..92004b6 --- /dev/null +++ b/docs/changes/index.rst @@ -0,0 +1,7 @@ +ChangeLog +========= + +.. toctree:: + + latest + 0.1 <0.1> diff --git a/docs/changes/latest.rst b/docs/changes/latest.rst new file mode 100644 index 0000000..d7c8f91 --- /dev/null +++ b/docs/changes/latest.rst @@ -0,0 +1,23 @@ +.. _latest: + +ChangeLog for Stack-In-A-Box (latest) +===================================== + +New +--- +- Sphinx Documentation +- Added a service manager for easier deployments and + management of the OpenStack services +- Added Swift Service Object Support +- Added base Cinder Service Support + +.. note:: Swift Service only supports the Object level services. + +.. note:: Cinder Service is presently hard-coded and will need some big + refactoring before being fully useful. + +Breaking Changes +---------------- + +Fixed +----- diff --git a/docs/community/about.rst b/docs/community/about.rst new file mode 100644 index 0000000..d8407f3 --- /dev/null +++ b/docs/community/about.rst @@ -0,0 +1,15 @@ +.. _about: + +About OpenStack-In-A-Box +======================== + +OpenStackInABox initially started out as modules that were an integrated part +of StackInABox_. However, it soon became clear that StackInABox_ was a really +valuable project by itself, and the OpenStack Services implemented on top of +it should be a separate project. With some minor refactoring the two were +split apart. + +References +========== + +.. _StackInABox: http://stackinabox.readthedocs.io/ diff --git a/docs/community/contribute.rst b/docs/community/contribute.rst new file mode 100644 index 0000000..c1d3c81 --- /dev/null +++ b/docs/community/contribute.rst @@ -0,0 +1,45 @@ +.. _contribute: + +Contribute to OpenStack-In-A-Box +================================ + +`Benjamen R. Meyer `_ is the creator and +current maintainer of OpenStack-In-A-Box. Volunteers like yourself - who review +patches, add features, fix bugs, and help with documentation - are welcome +to contribute. + +Your ideas and patches are always welcome. + +IRC +--- + +If you are interested in helping out, please join the **##stackinabox** +IRC channel on `Freenode `_. It's a great way to commmunicate, +discuss ideas, ask questions, and stay in touch with fellow contributors. + +Pull Requests +------------- + +Before submitting a pull request, please make sure to: + +* Update existing tests as necessary +* Add new tests for any new functionality +* All tests pass +* PEP 8 compliance + +**Additional Style Rules** + +* Docstrings are required for classes, attributes, methods, and functions +* Format non-trivial commands using some appropriate prefixes. Here's some + examples:: + + # Enhancement: Added new functionality + # Bug Fix: Corrected bug + # Refactor: changed around code + +* Use whitespace to separate blocks to improve readability +* Do not use single character variables except for well-known cases as part of + loops or formulas. +* Do not try to be complex or clever; but if you must heavily document it and + why +* When in doubt, go for readability diff --git a/docs/community/faq.rst b/docs/community/faq.rst new file mode 100644 index 0000000..844b6cd --- /dev/null +++ b/docs/community/faq.rst @@ -0,0 +1,24 @@ +.. _faq: + +Frequently Asked Questions +========================== + +Why not use Mimic_? +------------------- + +Mimic is a great tool for what it does. However, it still introduces external +dependencies and potential errors that have nothing to do with your testing as +it is a server that runs outside your testing framework over the network - +locally or otherwise. + +Local, unit testing should use a tool like StackInABox; while more formal +integration testing where multiple systems may be interacting together and all +need to have a common mocked backend fit better with Mimic_; though work is +underway to create a means of using existing Stack-In-A-Box services to +provide a similar role as Mimic_ via StackInAWSGI_. + +References +---------- + +.. _Mimic: https://pypi.python.org/pypi/mimic/ +.. _StackInAWSGI: https://github.com/TestInABox/stackInAWSGI diff --git a/docs/community/help.rst b/docs/community/help.rst new file mode 100644 index 0000000..fa7c54b --- /dev/null +++ b/docs/community/help.rst @@ -0,0 +1,17 @@ +.. _help: + +Get Help +======== + +Welcome to the OpenStack-In-A-Box community. We're focused on making testing +OpenStack Services our number one priority for you. + +Please spread the word and grow the community. + +IRC +--- + +While starting your journey into OpenStack-In-A-Box, feel free to join the +community in the **##stackinabox** IRC channel on +`Freenode `_. It's a great way to ask questions, +share ideas, and learn more. diff --git a/docs/community/index.rst b/docs/community/index.rst new file mode 100644 index 0000000..a727a14 --- /dev/null +++ b/docs/community/index.rst @@ -0,0 +1,10 @@ +Community Guide +=============== + +.. toctree:: + :maxdepth: 2 + + about + help + contribute + faq diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 0000000..bbc121e --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,308 @@ +# -*- coding: utf-8 -*- +# +# OpenStack-In-A-Box documentation build configuration file, created by +# sphinx-quickstart on Fri Nov 10 01:07:32 2017. +# +# This file is execfile()d with the current directory set to its +# containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys +import os +import os.path +import shlex + +sys.path.insert( + 0, + os.path.abspath( + os.getcwd() + '/../openstackinabox/' + ) +) + +import openstackinabox + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +#sys.path.insert(0, os.path.abspath('.')) + +# -- General configuration ------------------------------------------------ + +# If your documentation needs a minimal Sphinx version, state it here. +#needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + 'sphinx.ext.autodoc', + 'sphinx.ext.doctest', + 'sphinx.ext.coverage', + 'sphinx.ext.viewcode', +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# source_suffix = ['.rst', '.md'] +source_suffix = '.rst' + +# The encoding of source files. +#source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'OpenStack-In-A-Box' +copyright = u'2017, Benjamen R. Meyer' +author = u'Benjamen R. Meyer' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = u'0.1' +# The full version, including alpha/beta/rc tags. +release = u'0.1' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This patterns also effect to html_static_path and html_extra_path +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] + +# 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 = [] + +# If true, keep warnings as "system message" paragraphs in the built documents. +#keep_warnings = False + +# If true, `todo` and `todoList` produce output, else they produce nothing. +todo_include_todos = False + + +# -- Options for HTML output ---------------------------------------------- +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +html_theme = 'default' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +#html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +#html_theme_path = [] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +#html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# Add any extra paths that contain custom files (such as robots.txt or +# .htaccess) here, relative to this directory. These files are copied +# directly to the root of the documentation. +#html_extra_path = [] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, must be a dictionary that maps document names +# to template names. +# +# This is required for the alabaster theme +# refs: http://alabaster.readthedocs.io/en/latest/installation.html#sidebars +html_sidebars = { + '**': [ + 'relations.html', # needs 'show_related': True theme option to display + 'searchbox.html', + ] +} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_domain_indices = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +#html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +#html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +#html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = None + +# Language to be used for generating the HTML full-text search index. +# Sphinx supports the following languages: +# 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' +# 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr' +#html_search_language = 'en' + +# A dictionary with options for the search language support, empty by default. +# Now only 'ja' uses this config value +#html_search_options = {'type': 'default'} + +# The name of a javascript file (relative to the configuration directory) that +# implements a search results scorer. If empty, the default will be used. +#html_search_scorer = 'scorer.js' + +# Output file base name for HTML help builder. +htmlhelp_basename = 'OpenStack-In-A-Boxdoc' + +# -- Options for LaTeX output --------------------------------------------- + +latex_elements = { + # The paper size ('letterpaper' or 'a4paper'). + #'papersize': 'letterpaper', + + # The font size ('10pt', '11pt' or '12pt'). + #'pointsize': '10pt', + + # Additional stuff for the LaTeX preamble. + #'preamble': '', + + # Latex figure (float) alignment + #'figure_align': 'htbp', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + (master_doc, 'OpenStack-In-A-Box.tex', u'OpenStack-In-A-Box Documentation', + u'Benjamen R. Meyer', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# If true, show page references after internal links. +#latex_show_pagerefs = False + +# If true, show URL addresses after external links. +#latex_show_urls = False + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_domain_indices = True + + +# -- Options for manual page output --------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + (master_doc, 'openstack-in-a-box', u'OpenStack-In-A-Box Documentation', + [author], 1) +] + +# If true, show URL addresses after external links. +#man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + (master_doc, 'OpenStack-In-A-Box', u'OpenStack-In-A-Box Documentation', + author, 'OpenStack-In-A-Box', 'One line description of project.', + 'Miscellaneous'), +] + +# Documents to append as an appendix to all manuals. +#texinfo_appendices = [] + +# If false, no module index is generated. +#texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +#texinfo_show_urls = 'footnote' + +# If true, do not generate a @detailmenu in the "Top" node's menu. +#texinfo_no_detailmenu = False diff --git a/docs/docs-requirements.txt b/docs/docs-requirements.txt new file mode 100644 index 0000000..9b4ed21 --- /dev/null +++ b/docs/docs-requirements.txt @@ -0,0 +1,2 @@ +sphinx +doc8 diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 0000000..922a201 --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,18 @@ +Welcome to OpenstackInABox's documentation! +=========================================== + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + + community/index + user/index + api/index + changes/index + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 0000000..2a61ca0 --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,37 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=. +set BUILDDIR=_build +set SPHINXPROJ=OpenstackInABox + +if "%1" == "" goto help + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 +) + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% + +:end +popd + diff --git a/docs/user/index.rst b/docs/user/index.rst new file mode 100644 index 0000000..a9c359f --- /dev/null +++ b/docs/user/index.rst @@ -0,0 +1,10 @@ +User Guide +========== + +.. toctree:: + :maxdepth: 2 + + intro + install + quickstart + tutorial diff --git a/docs/user/install.rst b/docs/user/install.rst new file mode 100644 index 0000000..31956c6 --- /dev/null +++ b/docs/user/install.rst @@ -0,0 +1,10 @@ +.. _install: + +Installing +========== + +Installation is simple: + +.. code-block:: bash + + pip install openstackinabox diff --git a/docs/user/intro.rst b/docs/user/intro.rst new file mode 100644 index 0000000..5fed7c6 --- /dev/null +++ b/docs/user/intro.rst @@ -0,0 +1,8 @@ +.. _intro: + +Introduction to OpenStack-In-A-Box +================================== + +OpenStack-In-A-Box is a set of Stack-In-A-Box models for the OpenStack APIs. +Stack-In-A-Box is a framework to enable users of RESTful APIs to test the +applications against those APIs in meaningful ways. diff --git a/docs/user/quickstart.rst b/docs/user/quickstart.rst new file mode 100644 index 0000000..7f92c20 --- /dev/null +++ b/docs/user/quickstart.rst @@ -0,0 +1,109 @@ +.. _quickstart: + +Quick Start +=========== + +Install OpenStack-In-A-Box per :ref:`install` before continuing. + +Running a Test +-------------- + +We'll borrow the ```requests-mock``` example from the README.rst to show how +to use the OpenStack-In-A-Box in an actual test: + +.. code-block:: python + + import unittest + + import requests + + import stackinabox.util.requests_mock + from stackinabox.stack import StackInABox + + from openstackinabox.models.keystone.model import KeystoneModel + from openstackinabox.services.keystone import KeystoneV2Service + + class TestRequestsMock(unittest.TestCase): + + @staticmethod + def make_tenant_name(): + return 'tenant_{0}'.format(str(uuid.uuid4())) + + def setUp(self): + super(TestRequestsMock, self).setUp() + + self.keystone = KeystoneV2Service() + + # Create the tenant information + self.tenantname = self.make_tenant_name() + self.username = 'user_{0}'.format(str(uuid.uuid4())) + self.password = 'pAss{0}'.format( + str(uuid.uuid4()).replace('-', '') + ) + self.apikey = str(uuid.uuid4()) + self.email = '{0}@stackinabox.mock'.format(self.username) + self.keystone.model.tenants.add( + tenant_name=self.tenantname, + description="test tenant" + ) + tenant_data = self.keystone.model.tenants.get_by_name( + tenant_name=self.tenantname + ) + self.tenantid = tenant_data['id'] + + # Create the user information + self.keystone.model.users.add( + tenant_id=self.tenantid, + username=self.username, + password=self.password, + apikey=self.apikey, + email=self.email + ) + user_data = self.keystone.model.users.get_by_name( + tenant_id=self.tenantid, + username=self.username + ) + self.userid = user_data['user_id'] + + # Add a token + self.token = self.keystone.model.tokens.make_token() + self.keystone.model.tokens.add( + tenant_id=self.tenantid, + user_id=self.userid, + token=self.token + ) + + StackInABox.register_service(self.keystone) + self.session = requests.Session() + + def tearDown(self): + super(TestRequestsMock, self).tearDown() + StackInABox.reset_services() + self.session.close() + + def test_basic_requests_mock(self): + with stackinabox.util.requests_mock.core.activate(): + stackinabox.util.requests_mock.\ + requests_mock_session_registration( + 'localhost', self.session) + + stackinabox.util.requests_mock.core.requests_mock_registration( + 'localhost') + + auth_data = { + 'auth': { + self.dictApiKey: { + 'username': self.username, + 'apiKey': self.apikey + } + } + } + + res = requests.post( + 'http://localhost/keystone/v2.0/tokens', + data=json.dumps(auth_data) + ) + self.assertEqual(res.status_code, 200) + + result = res.json() + # token info, user info, and service catalog diff --git a/docs/user/tutorial-first-step.rst b/docs/user/tutorial-first-step.rst new file mode 100644 index 0000000..95da0f4 --- /dev/null +++ b/docs/user/tutorial-first-step.rst @@ -0,0 +1,75 @@ +First Step +---------- + +Before continuing be sure to :ref:`install ` OpenStackInABox +in your test environment. Any supported OpenStack Service will now +be available to the tests with a few simple steps. + +To start, the tests must be orchestrated with StackInABox: + +.. code-block:: python + + import unittest + + import requests + + import stackinabox.util.requests_mock + + class TestRequestsMock(unittest.TestCase): + + def setUp(self): + super(TestRequestsMock, self).setUp() + self.session = requests.Session() + + def tearDown(self): + super(TestRequestsMock, self).tearDown() + StackInABox.reset_services() + self.session.close() + + def test_basic_requests_mock(self): + with stackinabox.util.requests_mock.core.activate(): + stackinabox.util.requests_mock.core.requests_mock_registration( + 'localhost' + ) + +After StackInABox is configured, then the desired services can be added: + +.. code-block:: python + + import unittest + + import requests + + import stackinabox.util.requests_mock + + # Import OpenStack Keystone Service + from openstackinabox.services.keystone import KeystoneV2Service + + class TestRequestsMock(unittest.TestCase): + + def setUp(self): + super(TestRequestsMock, self).setUp() + # Create an instance of the service + self.keystone = KeystoneV2Service() + # Register the service into StackInABox + StackInABox.register_service(self.keystone) + self.session = requests.Session() + + def tearDown(self): + super(TestRequestsMock, self).tearDown() + StackInABox.reset_services() + self.session.close() + + def test_basic_requests_mock(self): + with stackinabox.util.requests_mock.core.activate(): + stackinabox.util.requests_mock.core.requests_mock_registration( + 'localhost' + ) + + # Keystone URL: http://localhost/keystone/v2.0/ + # Now you can use the Keystone Service + + +.. note:: Depending on what the test is doing it may be necessary to add data + into the Service Model. If there are inter-model dependencies then all + the involved models will need to have the data added. diff --git a/docs/user/tutorial.rst b/docs/user/tutorial.rst new file mode 100644 index 0000000..98069bd --- /dev/null +++ b/docs/user/tutorial.rst @@ -0,0 +1,11 @@ +.. _tutorial: + +Tutorial +======== + +In this tutorial we will walk through the building of a simple +StackInABoxService that implements a simple key-value store as a +RESTful API. Then we'll show how to hook it up to a few tests +using ```requests-mock```. + +.. include:: tutorial-first-step.rst diff --git a/openstackinabox/manager.py b/openstackinabox/manager.py index c469467..e83243f 100644 --- a/openstackinabox/manager.py +++ b/openstackinabox/manager.py @@ -11,24 +11,32 @@ class OpenStackServiceManagerException(Exception): - pass + """ + OpenStack Service Manager Base Exception + """ class ServiceNotAvailable(OpenStackServiceManagerException): - pass + """ + OpenStack Service Manager - Service Not Available + """ class ServiceVersionNotAvailable(OpenStackServiceManagerException): - pass + """ + OpenStack Service Manager - Service Version Not Available + """ class KeystoneUrlNotSet(OpenStackServiceManagerException): - pass + """ + OpenStack Service Manager - Keystone URL not set + """ class OpenStackServicesManager(object): """ - Service Manager + OpenStack Service Manager Provides the ability to decide what services are instantiated for a test and which of those are in the service catalog. @@ -44,58 +52,62 @@ class OpenStackServicesManager(object): For `_available_services` the basic structure of the dictionary is as follows: - { - '': { - '': { - 'service': , - 'access': { - 'in_service_catalog': , - 'keystone_service': None|Keystone's Version String - }, - 'entries': [ - { - 'version': version #, (required) - 'type': , - 'name': , - 'urls': { - : + .. code-block:: python + + { + '': { + '': { + 'service': ``, + 'access': { + 'in_service_catalog': ``, + 'keystone_service': None|`Keystone's Version String` + }, + 'entries': [ + { + 'version': 'version #', (required) + 'type': '', + 'name': '', + 'urls': { + '': '' + } } - } - ] + ] + } } } - } For `_active_services` the basic structure of the dictionary is as follows: - { - '': { - '': { - 'instance': , - 'registrations': [ - { - 'name': , - 'type': , - 'regions': [ - 'list', - 'of', - 'deployed', - 'regions' - ], - 'urls': { - : + .. code-block:: python + + { + '': { + '': { + 'instance': ``, + 'registrations': [ + { + 'name': '', + 'type': '', + 'regions': [ + 'list', + 'of', + 'deployed', + 'regions' + ], + 'urls': { + '': '' + } } - } - ] + ] + } } } - } .. note:: Services that need access to Keystone should specify the `keystone_service` parameter which will then cause the Keystone service instance of the specified version to be passed into it - via the __init__ parameters. + via the `__init__` parameters. .. note:: If the service is not going to be in the Service Catalog provided by Keystone, then the `entries` can be simplified just @@ -232,6 +244,9 @@ def get_service(self, service_name, service_version): ) def reset_all_services(self): + """ + Reset all services, clearing the service catalog + """ removal_set = [] for service_name, service_version in six.iteritems( self._active_services @@ -257,7 +272,7 @@ def add_service( .. note:: This is independent of the internally known built-in services, and can be used to add random services into the system. - The service_data dict has the following format: + The service_data dict has the following format:: { 'service': , @@ -373,28 +388,28 @@ def remove_region(registration_count): ) def remove_registrations(): - total_registrations = len( - self._active_services[ - service_name][service_version][ - self.SERVICE_REGISTRATIONS] - ) + total_registrations = len( + self._active_services[ + service_name][service_version][ + self.SERVICE_REGISTRATIONS] + ) - registrations_to_remove = [] - for registration_count in range(total_registrations): - remove_region(registration_count) - if not len(self._active_services[ - service_name][service_version][ - self.SERVICE_REGISTRATIONS][registration_count][ - self.SERVICE_ENTRIES_REGIONS] - ): - registrations_to_remove.append(registration_count) + registrations_to_remove = [] + for registration_count in range(total_registrations): + remove_region(registration_count) + if not len(self._active_services[ + service_name][service_version][ + self.SERVICE_REGISTRATIONS][registration_count][ + self.SERVICE_ENTRIES_REGIONS] + ): + registrations_to_remove.append(registration_count) - registrations_to_remove.reverse() + registrations_to_remove.reverse() - for count in registrations_to_remove: - del self._active_services[ - service_name][service_version][ - self.SERVICE_REGISTRATIONS][count] + for count in registrations_to_remove: + del self._active_services[ + service_name][service_version][ + self.SERVICE_REGISTRATIONS][count] def remove_version(): if service_version in self._active_services[service_name]: diff --git a/openstackinabox/models/base_db.py b/openstackinabox/models/base_db.py index 056f1fa..3cfba91 100644 --- a/openstackinabox/models/base_db.py +++ b/openstackinabox/models/base_db.py @@ -2,31 +2,65 @@ class ModelDbBase(BaseModel): + """ + Model Base for common functionality + """ def __init__(self, name, master, db): + """ + :param unicode name: name of the model + :param obj master: master model, f.e KeystoneModel + :param sqlite3 db: Sqlite3 DB instance for storing data + + .. note:: Typically the Master Model is the Keystone Model in order + to make it easy to do token validation, look-up accounts, etc. + + .. note:: objects are configured but full initialization is delayed + until `initialize` is called on the object. This allows some + testing to be done when data has not been configured. + """ super(ModelDbBase, self).__init__(name) self.__master = master self.__db = db @property def master(self): + """ + Access the master model + """ return self.__master @property def database(self): + """ + Access the model's database + """ return self.__db @staticmethod def bool_from_database(value): + """ + Convert the value from a storable value to a boolean + :param value: parameter to convert + :retval: boolean + """ if value: return True return False @staticmethod def bool_to_database(value): + """ + Convert the value to a storable value from a boolean + :param value: parameter to convert + :retval: integer + """ if value: return 1 return 0 def initialize(self, *args, **kwargs): + """ + Complete the object initialize, configuring the database, etc. + """ raise NotImplementedError("Not Implemented By Base Model") diff --git a/openstackinabox/models/base_model.py b/openstackinabox/models/base_model.py index 4eee8f6..cead0dc 100644 --- a/openstackinabox/models/base_model.py +++ b/openstackinabox/models/base_model.py @@ -5,26 +5,54 @@ class BaseModelExceptions(Exception): - pass + """ + Base Exception for all the model exceptions + """ class BaseModel(object): + """ + Base Model for common operations + """ def __init__(self, name): + """ + :param unicode name: name of the model + """ self.name = name def log_debug(self, msg): + """ + Enter a debug message into the log + + :param unicode msg: message to log + """ logger.debug('{0} ({1}): {2}' .format(self.name, id(self), msg)) def log_info(self, msg): + """ + Enter a informational message into the log + + :param unicode msg: message to log + """ logger.info('{0} ({1}): {2}' .format(self.name, id(self), msg)) def log_exception(self, msg): + """ + Enter a exception message into the log + + :param unicode msg: message to log + """ logger.exception('{0} ({1}): {2}' .format(self.name, id(self), msg)) def log_error(self, msg): + """ + Enter a error message into the log + + :param unicode msg: message to log + """ logger.error('{0} ({1}): {2}' .format(self.name, id(self), msg)) diff --git a/openstackinabox/models/cinder/model.py b/openstackinabox/models/cinder/model.py index ba4b011..3e264f7 100644 --- a/openstackinabox/models/cinder/model.py +++ b/openstackinabox/models/cinder/model.py @@ -18,12 +18,26 @@ class CinderModel(base_model.BaseModel): + """ + OpenStack Cinder Model for the OpenStack-In-A-Box Cinder Service + + :cvar list CHILD_MODELS: list of any child models to be instantiated + as part of the object. + + .. note:: The CHILD_MODELS are useful for splitting up functionality + into a series of modules for better supportability. + """ CHILD_MODELS = { } @staticmethod def initialize_db_schema(db_instance): + """ + Initialize the database with the model schema + + :param sqlite3 db_instance: Sqlite3 DB Instance Object + """ dbcursor = db_instance.cursor() for table_sql in schema: dbcursor.execute(table_sql) @@ -31,12 +45,29 @@ def initialize_db_schema(db_instance): @classmethod def get_child_models(cls, instance, db_instance): + """ + Retrieve the initialized child models + + :param base_model.BaseModel instance: primary model instance + :param sqltie3 db_instance: Sqlite3 database for storing the model data + :retval: list of base_model.BaseModel instances that make + up the remainder of the model. + + .. note:: The model and child models all share the same database + instance so that the information can be validated easily. + """ return { model_name: model_type(instance, db_instance) for model_name, model_type in six.iteritems(cls.CHILD_MODELS) } def __init__(self, keystone_model, initialize=True): + """ + :param KeystoneModel keystone_model: Keystone Model to use for user + data + :param boolean initialize: whether or not to intialize the underlying + data (e.g database); useful to delay initialization at times. + """ super(CinderModel, self).__init__('CinderModel') self.keystone_model = keystone_model self.database = sqlite3.connect(':memory:') @@ -45,6 +76,9 @@ def __init__(self, keystone_model, initialize=True): self.init_database() def init_database(self): + """ + Initialize the model's database + """ self.log_info('Initializing database') self.initialize_db_schema(self.database) diff --git a/openstackinabox/models/keystone/db/base.py b/openstackinabox/models/keystone/db/base.py index fb86410..4f3b3a7 100644 --- a/openstackinabox/models/keystone/db/base.py +++ b/openstackinabox/models/keystone/db/base.py @@ -7,15 +7,35 @@ class KeystoneDbBase(ModelDbBase): + """ + Base Keystone Model providing some common functionality + """ def __init__(self, name, master, db): + """ + :param unicode name: name of the model + :param ModelDbBase master: master model for cross-referencing + :param sqlite3 db: Sqlite3 database for data storage + """ super(KeystoneDbBase, self).__init__(name, master, db) @staticmethod def make_token(): + """ + Generate an auth-token value + + :retval: unicode containing the auth token + """ return str(uuid.uuid4()) def validate_username(self, username): + """ + Validate the username meets the Keystone requirements for a username + per the documentation + + :param unicode name: username to validate + :retval: boolean - True if valid, otherwise False + """ self.log_debug('Validating username {0}'.format(username)) regex = re.compile('^[a-zA-Z]+[\w\.@-]*$') if regex.match(username) is None: @@ -26,6 +46,13 @@ def validate_username(self, username): return True def validate_tenant_name(self, tenant_name): + """ + Validate the tenant name meets the Keystone requirements for a tenant + name per the documentation + + :param unicode name: tenant name to validate + :retval: boolean - True if valid, otherwise False + """ self.log_debug('Validating tenant name {0}'.format(tenant_name)) regex = re.compile('^[a-zA-Z]+[\w\.@-]*$') if regex.match(tenant_name) is None: @@ -36,6 +63,13 @@ def validate_tenant_name(self, tenant_name): return True def validate_tenant_id(self, tenant_id): + """ + Validate the tenant id meets the Keystone requirements for a tenant + id per the implementation + + :param int name: tenant id to validate + :retval: boolean - True if valid, otherwise False + """ self.log_debug('Validating tenant id {0}'.format(tenant_id)) if not isinstance(tenant_id, int): self.log_debug('tenant id {0} is INVALID'.format(tenant_id)) @@ -45,6 +79,13 @@ def validate_tenant_id(self, tenant_id): return True def validate_password(self, password): + """ + Validate the password meets the Keystone requirements for a password + per the implementation + + :param unicode password: password to validate + :retval: boolean - True if valid, otherwise False + """ self.log_debug('Validating password {0}'.format(password)) regexes = [ re.compile('^[a-zA-Z]+[\w\.@-]*$'), @@ -63,7 +104,21 @@ def validate_password(self, password): return True def validate_apikey(self, apikey): + """ + Validate the apikey meets the Keystone requirements for a API Key + per the implementation + + :param unicode apikey: API Key to validate + :retval: boolean - True if valid, otherwise False + """ return isinstance(apikey, six.string_types) def validate_token(self, token): + """ + Validate the token meets the Keystone requirements for a token + per the implementation + + :param unicode token: token to validate + :retval: boolean - True if valid, otherwise False + """ return isinstance(token, six.string_types) diff --git a/openstackinabox/models/keystone/db/endpoints.py b/openstackinabox/models/keystone/db/endpoints.py index 241659a..a558e17 100644 --- a/openstackinabox/models/keystone/db/endpoints.py +++ b/openstackinabox/models/keystone/db/endpoints.py @@ -67,16 +67,36 @@ class KeystoneDbServiceEndpoints(KeystoneDbBase): + """ + Service Endpoint Model + """ def __init__(self, master, db): + """ + :param ModelDbBase master: master model for cross-referencing + :param sqlite3 db: Sqlite3 database for data storage + """ super(KeystoneDbServiceEndpoints, self).__init__( "KeystoneServiceEndpoints", master, db ) def initialize(self): - pass + """ + Nothing to initialize + """ def add(self, service_id, region, version_info, version_list, version_id): + """ + Add an endpoint for an existing service + + :param unicode service_id: internal service id for an existing service + :param unicode region: datacenter/region for the services + :param unicode version_info: version information + :param unicode version_list: version list + :param int version_id: version identifier + :raises: KeystoneServiceCatalogEndpointError + :retval: int - internal endpoint identifier + """ dbcursor = self.database.cursor() args = { 'service_id': service_id, @@ -111,6 +131,18 @@ def add(self, service_id, region, version_info, version_list, version_id): return endpoint_id def get(self, service_id, endpoint_id=None): + """ + Access the endpoint data + + :param int service_id: internal service id for the endpoint data + :param int endpoint_id: (optional) internal endpoint id + + .. note:: If the endpoint_id is not specified then all of the endpoints + are returned. + + :retval: iterable of dictionaries that contain the endpoint + information + """ dbcursor = self.database.cursor() args = { 'service_id': service_id @@ -132,6 +164,13 @@ def get(self, service_id, endpoint_id=None): } def delete(self, service_id, endpoint_id): + """ + Remove the endpoint data + + :param int service_id: internal service id for the endpoint data + :param int endpoint_id: internal endpoint id + :raises: KeystoneServiceCatalogEndpointError + """ dbcursor = self.database.cursor() args = { 'service_id': service_id, @@ -147,6 +186,15 @@ def delete(self, service_id, endpoint_id): self.database.commit() def add_url(self, endpoint_id, name, url): + """ + Add a URL for an endpoint + + :param int endpoint_id: internal endpoint id + :param unicode name: name of the URL + :param unicode url: URL for the endpoint + :raises: KeystoneEndpointUrlError + :retval: int - internal endpoint url id + """ dbcursor = self.database.cursor() args = { 'endpoint_id': endpoint_id, @@ -179,6 +227,18 @@ def add_url(self, endpoint_id, name, url): return url_id def get_url(self, endpoint_id, url_id=None): + """ + Get the URL(s) for the specified endpoint + + :param int endpoint_id: internal endpoint id + :param int url_id: (optional) internal endpoint url id + + .. note:: If the url_id is not specified then all of the + urls are returned. + + :retval: iterable of dictionaries that contain the endpoint + url information. + """ dbcursor = self.database.cursor() args = { 'endpoint_id': endpoint_id @@ -198,6 +258,13 @@ def get_url(self, endpoint_id, url_id=None): } def delete_url(self, endpoint_id, url_id): + """ + Remove the url for the specified endpoint + + :param int endpoint_id: internal endpoint id + :param int url_id: (optional) internal endpoint url id + :raises: KeystoneEndpointUrlError + """ dbcursor = self.database.cursor() args = { 'endpoint_id': endpoint_id, diff --git a/openstackinabox/models/keystone/db/roles.py b/openstackinabox/models/keystone/db/roles.py index 8e898df..edc807d 100644 --- a/openstackinabox/models/keystone/db/roles.py +++ b/openstackinabox/models/keystone/db/roles.py @@ -31,28 +31,54 @@ class KeystoneDbRoles(KeystoneDbBase): + """ + Service User Role Model + + :cvar unicode IDENTITY_ADMIN_ROLE: global tenant admin group + :cvar unicode IDENTITY_VIEWER_ROLE: global tenant view-only group + """ IDENTITY_ADMIN_ROLE = 'identity:user-admin' IDENTITY_VIEWER_ROLE = 'identity:observer' def __init__(self, master, db): + """ + :param ModelDbBase master: master model for cross-referencing + :param sqlite3 db: Sqlite3 database for data storage + """ super(KeystoneDbRoles, self).__init__("KeystoneRoles", master, db) self.__admin_role_id = None self.__viewer_role_id = None @property def admin_role_id(self): + """ + Access the internal role id of the built-in admin group + """ return self.__admin_role_id @property def viewer_role_id(self): + """ + Access the internal role id for the built-in view-only group + """ return self.__viewer_role_id def initialize(self): + """ + Create the built-in groups + """ self.__admin_role_id = self.add(self.IDENTITY_ADMIN_ROLE) self.__viewer_role_id = self.add(self.IDENTITY_VIEWER_ROLE) def add(self, name): + """ + Add a new role + + :param unicode name: name of the role + :raises: KeystoneRoleError + :retval: int - internal id of the role + """ dbcursor = self.database.cursor() args = { 'name': name @@ -66,6 +92,13 @@ def add(self, name): return self.get(name)['id'] def get(self, name): + """ + Retrieve the role information + + :param unicode name: name of the role + :raises: KeystoneRoleError + :retval: dict containing the role information + """ dbcursor = self.database.cursor() args = { 'name': name @@ -81,6 +114,14 @@ def get(self, name): } def add_user_role_by_id(self, tenant_id=None, user_id=None, role_id=None): + """ + Map a user to a role using the internal role id + + :param int tenant_id: internal tenant id + :param int user_id: internal user id + :param int role_id: internal role id + :raises: KeystoneRoleError + """ dbcursor = self.database.cursor() args = { 'tenant_id': tenant_id, @@ -108,6 +149,17 @@ def add_user_role_by_id(self, tenant_id=None, user_id=None, role_id=None): def add_user_role_by_role_name(self, tenant_id=None, user_id=None, role_name=None): + """ + Map a user to a role using the role name + + :param int tenant_id: internal tenant id + :param int user_id: internal user id + :param unicode role_name: name of the role + :raises: KeystoneRoleError + + .. note:: If two roles with the same name may have an + unpredictable result. + """ role_data = self.get(name=role_name) self.add_user_role_by_id( tenant_id=tenant_id, @@ -116,6 +168,14 @@ def add_user_role_by_role_name(self, tenant_id=None, user_id=None, ) def get_user_roles(self, tenant_id=None, user_id=None): + """ + Access all roles associated with a given user for a given tenant + + :param int tenant_id: internal tenant id + :param int user_id: internal user id + :retval: list of dicts containing all the role information for + the specified tenant's user-id + """ dbcursor = self.database.cursor() args = { 'tenant_id': tenant_id, diff --git a/openstackinabox/models/keystone/db/services.py b/openstackinabox/models/keystone/db/services.py index 5f5d75b..3b2118b 100644 --- a/openstackinabox/models/keystone/db/services.py +++ b/openstackinabox/models/keystone/db/services.py @@ -32,16 +32,33 @@ class KeystoneDbServices(KeystoneDbBase): + """ + Service Model + """ def __init__(self, master, db): + """ + :param ModelDbBase master: master model for cross-referencing + :param sqlite3 db: Sqlite3 database for data storage + """ super(KeystoneDbServices, self).__init__( "KeystoneServices", master, db ) def initialize(self): - pass + """ + Nothing to initialize + """ def add(self, service_name, service_type): + """ + Add a new service + + :param unicode service_name: name of the service + :param unicode service_type: type of service + :raises: KeystoneServiceCatalogServiceError + :retval: int - internal id of the service + """ dbcursor = self.database.cursor() args = { 'name': service_name, @@ -50,11 +67,7 @@ def add(self, service_name, service_type): dbcursor.execute(SQL_ADD_SERVICE, args) if not dbcursor.rowcount: raise exceptions.KeystoneServiceCatalogServiceError( - 'Unable to add service' - ) - self.database.commit() - dbcursor.execute(SQL_GET_MAX_SERVICE_ID) service_data = dbcursor.fetchone() if service_data is None: raise exceptions.KeystoneServiceCatalogServiceError( @@ -72,11 +85,19 @@ def add(self, service_name, service_type): return service_id def get(self, service_id=None): + """ + Retrieve the service information + + :param int service_id: (optional) internal service id + :retval: iterable of dict containing the role information + + .. note:: if service_id is not specified then all services + will be returned + """ dbcursor = self.database.cursor() args = {} - query = SQL_GET_SERVICES - if service_id is not None: + query = SQL_GET_SERVICESif service_id is not None: args['service_id'] = service_id query = SQL_GET_SERVICE_BY_ID @@ -88,6 +109,12 @@ def get(self, service_id=None): } def delete(self, service_id): + """ + Remove the service information + + :param int service_id: internal service id + :retval: iterable of dict containing the role information + """ dbcursor = self.database.cursor() args = { 'service_id': service_id, diff --git a/openstackinabox/models/keystone/db/tenants.py b/openstackinabox/models/keystone/db/tenants.py index 5cefc00..134079d 100644 --- a/openstackinabox/models/keystone/db/tenants.py +++ b/openstackinabox/models/keystone/db/tenants.py @@ -45,15 +45,29 @@ class KeystoneDbTenants(KeystoneDbBase): + """ + Tenant Model + + :cvar unicode SYSTEM_TENANT_NAME: global system tenant used for the admin + service user + :cvar unicode SYSTEM_TENANT_DESCRIPTION: global tenant description + """ SYSTEM_TENANT_NAME = 'system' SYSTEM_TENANT_DESCRIPTION = 'system administrator' def __init__(self, master, db): + """ + :param ModelDbBase master: master model for cross-referencing + :param sqlite3 db: Sqlite3 database for data storage + """ super(KeystoneDbTenants, self).__init__("KeystoneTenants", master, db) self.__admin_tenant_id = None def initialize(self): + """ + Create the built-in tenant + """ self.__admin_tenant_id = self.add( tenant_name=self.SYSTEM_TENANT_NAME, description=self.SYSTEM_TENANT_DESCRIPTION, @@ -62,9 +76,22 @@ def initialize(self): @property def admin_tenant_id(self): + """ + Access the administrative tenant id + """ return self.__admin_tenant_id def add(self, tenant_name=None, description=None, enabled=True): + """ + Add a new tenant + + :param unicode tenant_name: tenant display name + :param unicode description: account description + :param boolean enabled: whether or not the account is active + and available for user + :raises: KeystoneTenantError + :retval: int - tenant id + """ args = { 'name': tenant_name, 'description': description, @@ -96,6 +123,11 @@ def add(self, tenant_name=None, description=None, enabled=True): return tenant_id def get(self): + """ + Retrieve all tenants + + :retval: list of dict entries, one entry per tenant + """ dbcursor = self.database.cursor() tenant_list = [] for row in dbcursor.execute(SQL_GET_ALL_TENANTS): @@ -110,6 +142,13 @@ def get(self): # TODO(BenjamenMeyer): delete def get_by_id(self, tenant_id): + """ + Get a tenant for a given tenant id + + :param int tenant_id: tenant id of the desired tenant + :raises: KeystoneTenantError + :retval: dict containing the tenant information + """ dbcursor = self.database.cursor() args = { 'tenant_id': tenant_id @@ -127,6 +166,18 @@ def get_by_id(self, tenant_id): } def get_by_name(self, tenant_name): + """ + Get a tenant for a given tenant name + + :param unicode tenant_name: tenant name of the desired tenant + :raises: KeystoneTenantError + :retval: dict containing the tenant information + + + .. note:: This is not guaranteed to be unique and therefore may + not return what is expected if multiple tenants have the + same tenant name, in which case only the first is returned + """ dbcursor = self.database.cursor() args = { 'tenant_name': tenant_name @@ -144,6 +195,16 @@ def get_by_name(self, tenant_name): } def update_description(self, tenant_id=None, description=None): + """ + Update the account description for a given tenant + + :param int tenant_id: tenant id of the desired tenant + :param unicode description: new account description for the tenant + :raises: KeystoneTenantError + + .. note:: parameters are keyword parameters but are nonetheless + required + """ dbcursor = self.database.cursor() args = { 'tenant_id': tenant_id, @@ -156,6 +217,16 @@ def update_description(self, tenant_id=None, description=None): self.database.commit() def update_status(self, tenant_id=None, enabled=None): + """ + Update the account active status for a given tenant + + :param int tenant_id: tenant id of the desired tenant + :param boolean enabled: whether or not the account is enabled + :raises: KeystoneTenantError + + .. note:: parameters are keyword parameters but are nonetheless + required + """ dbcursor = self.database.cursor() args = { 'tenant_id': tenant_id, diff --git a/openstackinabox/models/keystone/db/tokens.py b/openstackinabox/models/keystone/db/tokens.py index 2845302..927b9a2 100644 --- a/openstackinabox/models/keystone/db/tokens.py +++ b/openstackinabox/models/keystone/db/tokens.py @@ -7,17 +7,32 @@ class UtcTimezone(datetime.tzinfo): + """ + UTC Timezone manager so all times come out in UTC + """ def _offset(self): + """ + Timezone Offset - GMT+0 + """ return datetime.timedelta(0) def utcoffset(self, dt): + """ + Access the UTC Offset + """ return self._offset() def tzname(self, dt): + """ + Timezone Name + """ return 'UTC' def dst(self, dt): + """ + Access the Daylight Saving Time (DST) Offset + """ return self._offset() @@ -91,23 +106,44 @@ def dst(self, dt): class KeystoneDbTokens(KeystoneDbBase): + """ + Token Model + + :cvar unicode EXPIRE_TIME_FORMAT: time format for the tokens + """ '''2015-02-03 02:31:17''' EXPIRE_TIME_FORMAT = '%Y-%m-%d %H:%M:%S' def __init__(self, master, db): + """ + :param ModelDbBase master: master model for cross-referencing + :param sqlite3 db: Sqlite3 database for data storage + """ super(KeystoneDbTokens, self).__init__("KeystoneTokens", master, db) self.__admin_token = None def initialize(self): + """ + Initialize the special service admin token + """ self.__admin_token = 'adminstrate_with_this_{0}'.format(uuid.uuid4()) @property def admin_token(self): + """ + Access the special service admin token + """ return self.__admin_token @staticmethod def convert_to_utc(dt): + """ + Convert the time to UTC + + :param datetime.datetime dt: datatime to convert + :retval: datetime.datetime - datetime converted to UTC + """ if dt.utcoffset() is not None: return dt.replace(tzinfo=UtcTimezone()) else: @@ -115,6 +151,20 @@ def convert_to_utc(dt): def add(self, tenant_id=None, user_id=None, expire_time=None, token=None): + """ + Add a new token + + :param int tenant_id: tenant id to add the token for + :param int user_id: internal user id for to add the token for + :param datetime.datetime expire_time: (optional) expiration time of the token + :param unicode token: (optional) token to add + :raises: KeystoneTokenError + :retval: unicode - token assigned to the user + + .. note:: if no token is specified then one is auto-generated + .. note:: if the expiration time is not specified then the default lifetime + of 12-hours is used. + """ if token is None: token = self.make_token() @@ -142,6 +192,15 @@ def add(self, tenant_id=None, user_id=None, return token def revoke(self, tenant_id=None, user_id=None, token=None, reset=False): + """ + Revoke or reset the revocation status of a token + + :param int tenant_id: tenant id to add the token for + :param int user_id: internal user id for to add the token for + :param unicode token: token to revoke or re-enable + :param boolean reset: whether or not to re-enable the token for use + :raises: KeystoneTokenError + """ dbcursor = self.database.cursor() args = { 'tenant_id': tenant_id, @@ -160,6 +219,13 @@ def revoke(self, tenant_id=None, user_id=None, token=None, reset=False): self.database.commit() def delete(self, tenant_id=None, user_id=None, token=None): + """ + Remove a specified token + + :param int tenant_id: tenant id to add the token for + :param int user_id: internal user id for to add the token for + :param unicode token: token to remove + """ dbcursor = self.database.cursor() args = { 'tenant_id': tenant_id, @@ -178,6 +244,16 @@ def delete(self, tenant_id=None, user_id=None, token=None): self.database.commit() def get_by_user_id(self, user_id=None): + """ + Retrieve a token for the user + + :param int user_id: user id to retrieve the token information for + :raises: KeystoneUnknownUserError + :retval: dict containing the token information + + .. note:: this only returns the first token even if multiple are + valid for the user. + """ dbcursor = self.database.cursor() args = { 'user_id': user_id @@ -198,6 +274,13 @@ def get_by_user_id(self, user_id=None): } def get_by_tenant_id(self, tenant_id): + """ + Retrieve all tokens for the tenant + + :param int tenant_id: tenant id to retrieve the token information for + :retval: list of dict containing the token information for all + users under the tenant + """ dbcursor = self.database.cursor() args = { 'tenant_id': tenant_id @@ -216,6 +299,16 @@ def get_by_tenant_id(self, tenant_id): ] def get_by_username(self, username=None): + """ + Retrieve a token for the username + + :param unicode username: user name to retrieve the token information for + :raises: KeystoneUnknownUserError + :retval: dict containing the token information + + .. note:: this only returns the first token even if multiple are + valid for the user. + """ dbcursor = self.database.cursor() args = { 'username': username @@ -235,6 +328,13 @@ def get_by_username(self, username=None): @classmethod def check_expiration(cls, token): + """ + Check if the token has expired + + :param dict token: token information + :raises: KeystoneExpiredTokenError if the token has expired + :raises: KeystoneRevokedTokenError if the token has been revoked + """ if token['revoked']: raise exceptions.KeystoneRevokedTokenError('Token was revoked') @@ -250,6 +350,15 @@ def check_expiration(cls, token): ) def validate_token(self, token): + """ + Check whether a given token is valid + + :param unicode token: token to validate + :raises: KeystoneInvalidTokenError if the token is not valid + :raises: KeystoneExpiredTokenError if the token has expired + :raises: KeystoneRevokedTokenError if the token has been revoked + :retval: dict contianing the token information + """ dbcursor = self.database.cursor() args = { 'token': token diff --git a/openstackinabox/models/keystone/db/users.py b/openstackinabox/models/keystone/db/users.py index 1dfcf58..b8020a8 100644 --- a/openstackinabox/models/keystone/db/users.py +++ b/openstackinabox/models/keystone/db/users.py @@ -64,12 +64,22 @@ class KeystoneDbUsers(KeystoneDbBase): + """ + User Model + """ + def __init__(self, master, db): + """ + :param ModelDbBase master: master model for cross-referencing + :param sqlite3 db: Sqlite3 database for data storage + """ super(KeystoneDbUsers, self).__init__("KeystoneUsers", master, db) self.__admin_user_id = None def initialize(self): - # Create an admin user and add the admin token to that user + """ + Create an admin user and add the admin token to that user + """ self.__admin_user_id = self.add( tenant_id=self.master.tenants.admin_tenant_id, username='system', @@ -85,10 +95,26 @@ def initialize(self): @property def admin_user_id(self): + """ + Access the admin service user id + """ return self.__admin_user_id def add(self, tenant_id=None, username=None, email=None, password=None, apikey=None, enabled=True): + """ + Add a new user to the tenant + + :param int tenant_id: tenant id to create the user for + :param unicode username: name of the use + :param unicode email: email address for contacting the user + :param unicode password: password for the user + :param unicode apikey: API Key for the user + :param boolean enabled: whether or not the user is active + :raises: KeystoneUserError + :retval: int - internal user id + """ + args = { 'tenant_id': tenant_id, 'username': username, @@ -119,6 +145,12 @@ def add(self, tenant_id=None, username=None, email=None, return user_id def delete(self, tenant_id=None, user_id=None): + """ + Remove a user for a given tenant + + :param int tenant_id: tenant id to remove the user from + :param int user_id: internal user id to remove + """ args = { 'tenant_id': tenant_id, 'user_id': user_id @@ -129,6 +161,14 @@ def delete(self, tenant_id=None, user_id=None): self.database.commit() def get_by_id(self, tenant_id=None, user_id=None): + """ + Get user by id + + :param int tenant_id: tenant id containing the user + :param int user_id: internal user id of the user + :raises: KeystoneUnknownUserError + :retval: dict containing the user information + """ dbcursor = self.database.cursor() args = { 'tenant_id': tenant_id, @@ -152,6 +192,14 @@ def get_by_id(self, tenant_id=None, user_id=None): } def get_by_name(self, tenant_id=None, username=None): + """ + Get the user by username + + :param int tenant_id: tenant id containing the user + :param unicode username: username to lookup + :raises: KeystoneUnknownUserError + :retval: dict containing the user information + """ dbcursor = self.database.cursor() args = { 'tenant_id': tenant_id, @@ -175,6 +223,17 @@ def get_by_name(self, tenant_id=None, username=None): } def get_by_name_or_tenant_id(self, tenant_id=None, username=None): + """ + Get all users by username or tenant-id + + :param int tenant_id: (optional) tenant id to add the token for + :param int username: (optional) name of the user to query + :retval: iterable of the dicts containing the user information + + .. note:: tenant_id or username must be specified; username + takes precedences. tenant_id is default. + .. note:: This query may return values across tenants. + """ sql_query = None args = {} if username is not None: @@ -198,6 +257,20 @@ def get_by_name_or_tenant_id(self, tenant_id=None, username=None): def update_by_id(self, tenant_id=None, user_id=None, email=None, password=None, apikey=None, enabled=True): + """ + Update the user information by user-id + + :param int tenant_id: tenant id to create the user for + :param int user_id: user id for user + :param unicode email: email address for contacting the user + :param unicode password: password for the user + :param unicode apikey: API Key for the user + :param boolean enabled: whether or not the user is active + :raises: KeystoneUnknownUserError + + .. note:: the username is not allowed to be changed + """ + dbcursor = self.database.cursor() args = { 'tenant_id': tenant_id, @@ -216,6 +289,12 @@ def update_by_id(self, tenant_id=None, user_id=None, email=None, self.database.commit() def get_for_tenant_id(self, tenant_id): + """ + Get all users for a given tenant-id + + :param int tenant_id: tenant id to add the token for + :retval: list of the dicts containing the user information + """ dbcursor = self.database.cursor() args = { 'tenant_id': tenant_id diff --git a/openstackinabox/models/keystone/exceptions.py b/openstackinabox/models/keystone/exceptions.py index 6ede11b..f5e4c7c 100644 --- a/openstackinabox/models/keystone/exceptions.py +++ b/openstackinabox/models/keystone/exceptions.py @@ -5,68 +5,102 @@ class KeystoneError(BaseModelExceptions): - pass + """ + Base Keystone Model Exception + """ class KeystoneTenantError(KeystoneError): - pass + """ + Tenant Related Errors + """ class KeystoneUserError(KeystoneError): - pass + """ + User Related Errors + """ class KeystoneDisabledUserError(KeystoneUserError): - pass + """ + User is disabled + """ class KeystoneUnknownUserError(KeystoneUserError): - pass + """ + Unknown User Error + """ class KeystoneUserAuthError(KeystoneUserError): - pass + """ + User Authentication Error + """ class KeystoneUserInvalidPasswordError(KeystoneUserAuthError): - pass + """ + User Password is invalid + """ class KeystoneUserInvalidApiKeyError(KeystoneUserAuthError): - pass + """ + User API Key is invalid + """ class KeystoneTokenError(KeystoneError): - pass + """ + Token related errors + """ class KeystoneInvalidTokenError(KeystoneTokenError): - pass + """ + Invalid Token Error + """ class KeystoneRevokedTokenError(KeystoneInvalidTokenError): - pass + """ + Token Invalid due to being explicitly revoked + """ class KeystoneExpiredTokenError(KeystoneInvalidTokenError): - pass + """ + Token invalid due to expiration + """ class KeystoneRoleError(KeystoneError): - pass + """ + Role related errors + """ class KeystoneServiceCatalogError(KeystoneError): - pass + """ + Service Catalog related errors + """ class KeystoneServiceCatalogServiceError(KeystoneServiceCatalogError): - pass + """ + Service Catalog Service entry error + """ class KeystoneServiceCatalogEndpointError(KeystoneServiceCatalogError): - pass + """ + Service Catalog Endpoint Error + """ class KeystoneEndpointUrlError(KeystoneServiceCatalogEndpointError): - pass + """ + Service Catalog Endpoint URL Error + """ diff --git a/openstackinabox/models/keystone/model.py b/openstackinabox/models/keystone/model.py index 54e0884..7448e7e 100644 --- a/openstackinabox/models/keystone/model.py +++ b/openstackinabox/models/keystone/model.py @@ -19,20 +19,6 @@ from openstackinabox.models.keystone.db.users import KeystoneDbUsers -""" - - Build a service catalog - - Add tenants - - Add services - - Add roles - - Authentication - - POST /v2.0/users - - POST /v2.0/tokens/ - DELETE /v2.0/tokens/{token} - GET /v2.0/tentants{?name} -""" - schema = [ ''' CREATE TABLE keystone_tenants @@ -119,7 +105,14 @@ class KeystoneModel(base_model.BaseModel): + """ + Keystone Model + + :ivar sqlite3 database: sqlite3 database used for the data store + :ivar iterable child_models: iterable containing the child models + """ + # child models for easier maintenance CHILD_MODELS = { 'roles': KeystoneDbRoles, 'services': KeystoneDbServices, @@ -131,6 +124,12 @@ class KeystoneModel(base_model.BaseModel): @staticmethod def initialize_db_schema(db_instance): + """ + Initialize the db instance + + :param sqlite3 db_instance: SQLite3 DB instance to use for the model + data storage + """ dbcursor = db_instance.cursor() for table_sql in schema: dbcursor.execute(table_sql) @@ -138,12 +137,25 @@ def initialize_db_schema(db_instance): @classmethod def get_child_models(cls, instance, db_instance): + """ + Retrieve the child models + + :param obj instance: instance to use as the master model for the + child model + :param sqlite3 db_instance: SQLite3 db to provide the child models + for their data storage + :retval: iterable with all the child models instantiated + """ return { model_name: model_type(instance, db_instance) for model_name, model_type in six.iteritems(cls.CHILD_MODELS) } def __init__(self, initialize=True): + """ + :param boolean initialize: whether or not to initialize the database + on object instantiation + """ super(KeystoneModel, self).__init__('KeystoneModel') self.database = sqlite3.connect(':memory:') self.child_models = self.get_child_models(self, self.database) @@ -152,29 +164,53 @@ def __init__(self, initialize=True): @property def users(self): + """ + Access the user model + """ return self.child_models['users'] @property def tenants(self): + """ + Access the tenant model + """ return self.child_models['tenants'] @property def tokens(self): + """ + Access the token model + """ return self.child_models['tokens'] @property def roles(self): + """ + Access the role model + """ return self.child_models['roles'] @property def services(self): + """ + Access the service catalog model + """ return self.child_models['services'] @property def endpoints(self): + """ + Access the service endpoint model + """ return self.child_models['endpoints'] def init_database(self): + """ + Initialize the database + + .. note:: This also ends up initializing the built-in admin auth + functionality and setting up the Admin account and token. + """ self.log_info('Initializing database') self.initialize_db_schema(self.database) @@ -194,6 +230,14 @@ def init_database(self): self.log_info('Database initialized') def validate_token_admin(self, token): + """ + Validate the incoming token as an admin token + + :param unicode token: user specified token to validate + :retval: dict containing the user data + :raises: KeystoneInvalidTokenError if the token is invalid + or there are any other errors during the token lookup + """ try: self.log_debug('Checking token {0} for registration...' .format(token)) @@ -234,6 +278,15 @@ def validate_token_admin(self, token): raise exceptions.KeystoneInvalidTokenError('Invalid Token') def validate_token_service_admin(self, token): + """ + Validate the incoming token as an admin token and it is the special + internally generated service admin token. + + :param unicode token: user specified token to validate + :retval: dict containing the user data + :raises: KeystoneInvalidTokenError if the token is invalid + or there are any other errors during the token lookup + """ try: self.log_debug('Checking token {0} for validity...'.format(token)) user_data = self.validate_token_admin(token) @@ -257,7 +310,13 @@ def validate_token_service_admin(self, token): ) def get_auth_token_entry(self, token_data, user_data): - # build the 'auth' section of the service catalog + """ + Build the `auth` section of the service catalog + + :param dict token_data: dict containing the token data + :param dict user_data: dict containing the user data + :retval: dict containing the auth section of the service catalog + """ return { 'id': token_data['token'], 'expires': token_data['expires'], @@ -271,6 +330,12 @@ def get_auth_token_entry(self, token_data, user_data): } def get_auth_user_entry(self, user_data): + """ + Build the `user` section of the service catalog + + :param dict user_data: dict containing the user data + :retval: dict contianing the user section of the service catalog + """ return { 'id': user_data['user_id'], 'roles': [ @@ -290,9 +355,33 @@ def get_auth_user_entry(self, user_data): } def get_auth_service_catalog(self, user_data): - # build the services section of the service catalog + """ + Build the `services` section of the service catalog + + :param dict user_data: dict containing the user data + :retval: dict containing the service section of the service catalog + """ def get_endpoints_for_service(service_id): - def insert_if_not_none(dest, dest_key, source, source_key): + """ + Build the endpoints section of a service's entry in the service + catalog + + :param integer service_id: the internal service_id of the service + :retval: dict containing the individual endpoint entries for the + service catalog + """ + def log_and_insert(dest, dest_key, source, source_key): + """ + Log the data and set the key from the source to the + destination. + + :param dict dest: dictionary to set the value into + :param unicode dest_key: key in the `dest` dictionary to hold + the data + :param dict source: dictionary to extract the value from + :param unicode source_key: key in the `source` dictionary to + extract the data + """ for i in [dest, dest_key, source, source_key]: print("param: {0}".format(i)) dest[dest_key] = source[source_key] @@ -310,7 +399,7 @@ def insert_if_not_none(dest, dest_key, source, source_key): } for key_copy in optional_keys: - insert_if_not_none( + log_and_insert( endpoint_info, key_copy['dest'], endpoint_data, @@ -340,6 +429,12 @@ def insert_if_not_none(dest, dest_key, source, source_key): ] def get_service_catalog(self, token, user): + """ + Build the service catalog for the given user and token combination + + :param dict token: dict containing the token data + :param dict user: dict containing the user data + """ return { 'serviceCatalog': self.get_auth_service_catalog(user), 'token': self.get_auth_token_entry(token, user), @@ -347,6 +442,20 @@ def get_service_catalog(self, token, user): } def password_authenticate(self, password_data): + """ + Authenticate a user+password combination + + :param dict password_data: dict containing the username and + password + :raises: KeystoneUserError if the input data is incorrect + :raises: KeystoneUserInvalidPasswordError if the password is invalid + for the user + :raises: KeystoneUnknownUserError if the user is not found + :raises: KeystoneDIsabledUserError if the user is valid but disabled + :retval: dict containing the service catalog + + .. note:: This creates and adds a token for the user to the model data. + """ if not self.users.validate_username(password_data['username']): self.log_error('Username Validation Failed') raise exceptions.KeystoneUserError('Invalid User Data - Username') @@ -391,6 +500,20 @@ def password_authenticate(self, password_data): return self.get_service_catalog(token, user) def apikey_authenticate(self, apikey_data): + """ + Authenticate a user+apikey combination + + :param dict apikey_data: dict containing the username and + apikey + :raises: KeystoneUserError if the input data is incorrect + :raises: KeystoneUserInvalidApikeyError if the apikey is invalid + for the user + :raises: KeystoneUnknownUserError if the user is not found + :raises: KeystoneDIsabledUserError if the user is valid but disabled + :retval: dict containing the service catalog + + .. note:: This creates and adds a token for the user to the model data. + """ if not self.users.validate_username(apikey_data['username']): self.log_error('Username Validation Failed') raise exceptions.KeystoneUserError('Invalid User Data - Username') @@ -435,6 +558,18 @@ def apikey_authenticate(self, apikey_data): return self.get_service_catalog(token, user) def tenant_id_token_auth(self, auth_data): + """ + Authenticate a tenantid + token combination + + :param dict auth_data: dict containing the username and + token + :raises: KeystoneUserError if the input data is incorrect + :raises: KeystoneTenantError if the specified tenant does not match + the tenant on the token + :raises: KeystoneUnknownUserError if the user is not found + :raises: KeystoneDIsabledUserError if the user is valid but disabled + :retval: dict containing the service catalog + """ try: tenant_id = auth_data['tenantId'] token = auth_data['token']['id'] @@ -474,6 +609,18 @@ def tenant_id_token_auth(self, auth_data): return self.get_service_catalog(token_data, user) def tenant_name_token_auth(self, auth_data): + """ + Authenticate a tenant name + token combination + + :param dict auth_data: dict containing the username and + token + :raises: KeystoneUserError if the input data is incorrect + :retval: dict containing the service catalog + + .. note:: see `tenant_id_token_auth` for more details as this + is a basic wrapper around it for converting from the + tenant-name to tenant-id. + """ try: tenant_name = auth_data['tenantName'] auth_data['token']['id'] diff --git a/openstackinabox/models/swift/exceptions.py b/openstackinabox/models/swift/exceptions.py index 284a13f..8440a81 100644 --- a/openstackinabox/models/swift/exceptions.py +++ b/openstackinabox/models/swift/exceptions.py @@ -1,18 +1,28 @@ """ +OpenStack-In-A-Box Swift Model Exceptions """ +from openstackinabox.models import base_model -class SwiftExceptions(Exception): - pass +class SwiftExceptions(base_model.BaseModelExceptions): + """ + Base of the exceptions for the OpenStack-In-A-Box Swift Model + """ class SwiftUnknownTenantError(SwiftExceptions): - pass + """ + Unknown Tenant + """ class SwiftUnknownContainerError(SwiftExceptions): - pass + """ + Unknown Container Error + """ class SwiftUnknownObjectError(SwiftExceptions): - pass + """ + Unknown Object Error + """ diff --git a/openstackinabox/models/swift/storage.py b/openstackinabox/models/swift/storage.py index 50b2617..10c74f1 100644 --- a/openstackinabox/models/swift/storage.py +++ b/openstackinabox/models/swift/storage.py @@ -1,4 +1,5 @@ """ +OpenStack-In-A-Box Swift Model Storage Backend """ import hashlib import io @@ -18,9 +19,18 @@ class SwiftStorage(object): + """ + OpenStack-In-A-Box Swift Model Storage + """ @staticmethod def get_etag(data): + """ + Calculate the ETAG for the data + + :param bytes data: data to calculate the ETAG for + :retval: string containing the ETAG value + """ etag_generator = hashlib.md5() etag_generator.update(data) @@ -28,6 +38,12 @@ def get_etag(data): @staticmethod def get_file_etag(file_name): + """ + Calculate the ETAG for the file data + + :param bytes data: filename of the file to calculate the ETAG for + :retval: string containing the ETAG value + """ etag_generator = hashlib.md5() with open(file_name, 'rb') as input_data: for chunk in input_data: @@ -36,6 +52,11 @@ def get_file_etag(file_name): return etag_generator.hexdigest() def __init__(self, service_id, model): + """ + :param unicode service_id: ID to use to track logs + :param SwiftModel model: SwiftModel where the local paths will be + stored and related to the objects. + """ self.__id = service_id self.__model = model self.__storage = TemporaryDirectory() @@ -44,38 +65,79 @@ def __init__(self, service_id, model): @property def model(self): + """ + Access the SwiftModel instance + """ return self.__model @model.setter def model(self, value): + """ + Set a new object as the SwiftModel + + :param obj value: object to use for the SwiftModel + """ self.__model = value @property def storage(self): + """ + Access the storage area + """ return self.__storage @property def location(self): + """ + Access the storage location + """ return self.storage.name @property def metadata(self): + """ + Access object metadata + """ return self.__metadata_information @property def custom_metadata(self): + """ + Access client specified metadata + """ return self.__custom_metadata def get_tenant_path(self, tenantid): + """ + Access the local path for the given tenant + + :param unicode tenantid: The Account's Tenant-ID + :retval: string containing the path + """ return '{0}/{1}'.format(self.location, tenantid) def get_container_path(self, tenantid, container_name): + """ + Access the local path for the tenant's container + + :param unicode tenantid: The Account's Tenant-ID + :param unicode container_name: Tenant's Object Container + :retval: string containing the path + """ return '{0}/{1}'.format( self.get_tenant_path(tenantid), container_name ) def get_object_path(self, tenantid, container_name, object_name): + """ + Access the local path for the tenant's object in the container + + :param unicode tenantid: The Account's Tenant-ID + :param unicode container_name: Tenant's Object Container + :param unicode object_name: Object Name + :retval: string containing the path + """ try: intTenantId = self.model.has_tenant(tenantid) intContainerId = self.model.has_container( @@ -111,6 +173,12 @@ def get_object_path(self, tenantid, container_name, object_name): raise def add_tenant(self, tenantid): + """ + Add a tenant for the storage + + :param unicode tenantid: The Account's Tenant-ID + :retval: integer - internal tenant-id from the SwiftModel + """ LOG.debug( 'Swift Service ({0}): Checking if Tenant {1} exists...'.format( self.__id, tenantid @@ -145,6 +213,12 @@ def add_tenant(self, tenantid): return intTenantId def has_tenant(self, tenantid): + """ + Validate the tenant-id + + :param unicode tenantid: The Account's Tenant-ID + :retval: boolean + """ try: intTenantId = self.model.has_tenant(tenantid) @@ -160,6 +234,13 @@ def has_tenant(self, tenantid): return False def add_container(self, tenantid, container_name): + """ + Add a container to tenant for the storage + + :param unicode tenantid: The Account's Tenant-ID + :param unicode container_name: Tenant's Object Container + :retval: integer - internal container-id from the SwiftModel + """ LOG.debug( 'Swift Service ({0}): Checking for container\'s tenant - ' '{1}'.format( @@ -204,6 +285,13 @@ def add_container(self, tenantid, container_name): return (intTenantId, intContainerId) def has_container(self, tenantid, container_name): + """ + Validate a container for tenant in storage + + :param unicode tenantid: The Account's Tenant-ID + :param unicode container_name: Tenant's Object Container + :retval: boolean + """ try: intTenantId = self.model.has_tenant(tenantid) intContainerId = self.model.has_container( @@ -227,6 +315,13 @@ def has_container(self, tenantid, container_name): return False def get_container(self, tenantid, container_name): + """ + Access the container to tenant for the storage + + :param unicode tenantid: The Account's Tenant-ID + :param unicode container_name: Tenant's Object Container + :retval: tuple - (Internal Tenant-Id, Internal Container-Id) + """ try: intTenantId = self.model.has_tenant(tenantid) intContainerId = self.model.has_container( @@ -245,6 +340,14 @@ def get_container(self, tenantid, container_name): return (None, None) def has_object(self, tenantid, container_name, object_name): + """ + Validate a object in container for tenant in storage + + :param unicode tenantid: The Account's Tenant-ID + :param unicode container_name: Tenant's Object Container + :param unicode object_name: Object Name for the object in the container + :retval: boolean + """ try: intTenantId = self.model.has_tenant(tenantid) intContainerId = self.model.has_container( @@ -278,6 +381,18 @@ def load_object_from_file( self, tenantid, container_name, object_name, file_name, file_size=None, allow_file_size_mismatch=False ): + """ + Load the data from a given file into the object storage + + :param unicode tenantid: The Account's Tenant-ID + :param unicode container_name: Tenant's Object Container + :param unicode object_name: Object Name for the object in the container + :param unicode file_name: file to load the data from + :param integer file_size: size of the file data, used for the + content-length + :param boolean allow_file_size_mismatch: whether the actual file size + and the specified file_size match up + """ LOG.debug( 'Swift Service ({0}): Checking that tenant {1} has container ' '{2}'.format( @@ -318,6 +433,18 @@ def load_object( self, tenantid, container_name, object_name, content, file_size=None, allow_file_size_mismatch=False ): + """ + Load the data into the object storage + + :param unicode tenantid: The Account's Tenant-ID + :param unicode container_name: Tenant's Object Container + :param unicode object_name: Object Name for the object in the container + :param unicode content: data to store + :param integer file_size: size of the file data, used for the + content-length + :param boolean allow_file_size_mismatch: whether the actual file size + and the specified file_size match up + """ LOG.debug( 'Swift Service ({0}): Checking that tenant {1} has container ' '{2}'.format( @@ -354,6 +481,16 @@ def load_object( def update_object_etag( self, tenantid, container_name, object_name, new_etag ): + """ + Update the ETAG on the object + + :param unicode tenantid: The Account's Tenant-ID + :param unicode container_name: Tenant's Object Container + :param unicode object_name: Object Name for the object in the container + :param unicode new_etag: the etag to set on the object + + .. note:: This only updates if the object already exists + """ path = self.get_object_path(tenantid, container_name, object_name) if path in self.metadata: self.metadata[path]['etag'] = new_etag @@ -361,6 +498,14 @@ def update_object_etag( def store_or_update_custom_metadata( self, tenantid, container_name, object_name, metadata ): + """ + Store or update the custom metadata for the object + + :param unicode tenantid: The Account's Tenant-ID + :param unicode container_name: Tenant's Object Container + :param unicode object_name: Object Name for the object in the container + :param dict metadata: object metadata + """ LOG.debug( 'Swift Service ({0}): Storing custom metadata - {1}'.format( self.__id, metadata @@ -402,6 +547,13 @@ def retrieve_custom_metadata(self, tenantid, container_name, object_name): return custom_metadata def remove_custom_metadata(self, tenantid, container_name, object_name): + """ + Remove the custom metadata for the object + + :param unicode tenantid: The Account's Tenant-ID + :param unicode container_name: Tenant's Object Container + :param unicode object_name: Object Name for the object in the container + """ path = self.get_object_path(tenantid, container_name, object_name) if path in self.custom_metadata: del self.custom_metadata[path] @@ -410,6 +562,19 @@ def store_object( self, tenantid, container_name, object_name, content, metadata, file_size=None, allow_file_size_mismatch=False ): + """ + store the data into the storage backend + + :param unicode tenantid: The Account's Tenant-ID + :param unicode container_name: Tenant's Object Container + :param unicode object_name: Object Name for the object in the container + :param unicode content: data to store + :param integer file_size: size of the file data, used for the + content-length + :param boolean allow_file_size_mismatch: whether the actual file size + and the specified file_size match up + :raises: RuntimeError if the data could not be written properly + """ path = None if self.has_container(tenantid, container_name): intTenantId, intContainerId = self.get_container( @@ -518,6 +683,14 @@ def store_object( LOG.debug('Swift Service ({0}): Metadata stored'.format(self.__id)) def retrieve_object(self, tenantid, container_name, object_name): + """ + retrieve the object data from the storage backend + + :param unicode tenantid: The Account's Tenant-ID + :param unicode container_name: Tenant's Object Container + :param unicode object_name: Object Name for the object in the container + :retval: tuple - (object data, object's metadata) + """ if self.has_object(tenantid, container_name, object_name): metadata = CaseInsensitiveDict() path = self.get_object_path( @@ -566,7 +739,7 @@ def retrieve_object(self, tenantid, container_name, object_name): ) data.seek(0, os.SEEK_SET) - except Exception as ex: + except Exception: LOG.exception('Failed to read object from disk') data = None @@ -578,6 +751,13 @@ def retrieve_object(self, tenantid, container_name, object_name): return (None, None) def remove_object(self, tenantid, container_name, object_name): + """ + remove the data from the storage backend + + :param unicode tenantid: The Account's Tenant-ID + :param unicode container_name: Tenant's Object Container + :param unicode object_name: Object Name for the object in the container + """ if self.has_object(tenantid, container_name, object_name): intTenantId, intContainerId = self.get_container( tenantid, container_name diff --git a/openstackinabox/services/cinder/v1/base.py b/openstackinabox/services/cinder/v1/base.py index 3f2a90b..076a85c 100644 --- a/openstackinabox/services/cinder/v1/base.py +++ b/openstackinabox/services/cinder/v1/base.py @@ -58,7 +58,7 @@ def helper_validate_token(self, request_headers, user_data['userid'] ) ) - except Exception as ex: + except Exception: self.log_exception('invalid or expired auth token') raise exceptions.KeystoneV2AuthUnauthorizedError( 'invalid or expired auth token' diff --git a/openstackinabox/services/keystone/v2/base.py b/openstackinabox/services/keystone/v2/base.py index 44c619e..ddfff05 100644 --- a/openstackinabox/services/keystone/v2/base.py +++ b/openstackinabox/services/keystone/v2/base.py @@ -74,7 +74,7 @@ def helper_validate_token(self, request_headers, user_data['userid'] ) ) - except Exception as ex: + except Exception: self.log_exception('invalid or expired auth token') raise exceptions.KeystoneV2AuthUnauthorizedError( 'invalid or expired auth token' diff --git a/openstackinabox/services/keystone/v2/tenants.py b/openstackinabox/services/keystone/v2/tenants.py index 44e3f60..ed43330 100644 --- a/openstackinabox/services/keystone/v2/tenants.py +++ b/openstackinabox/services/keystone/v2/tenants.py @@ -19,8 +19,7 @@ def __init__(self, model): def handle_listing(self, request, uri, headers): ''' 200, 203 -> OK - 400 -> Bad Request: one or more required parameters - are missing or invalid + 400 -> Bad Request: one or more required parameters are missing or invalid 401 -> not authorized 403 -> forbidden (no permission) 404 -> Not found @@ -37,13 +36,16 @@ def handle_listing(self, request, uri, headers): """ Body on success: - body = { - 'tenants' : [ {'id': 01234, - 'name': 'bob', - 'description': 'joe bob', - 'enabled': True }] - 'tenants_links': [] - } + + .. code-block:: python + + body = { + 'tenants' : [ {'id': 01234, + 'name': 'bob', + 'description': 'joe bob', + 'enabled': True }] + 'tenants_links': [] + } """ response_body = { 'tenants': [ diff --git a/openstackinabox/services/keystone/v2/tokens.py b/openstackinabox/services/keystone/v2/tokens.py index c6f979f..78690e6 100644 --- a/openstackinabox/services/keystone/v2/tokens.py +++ b/openstackinabox/services/keystone/v2/tokens.py @@ -21,60 +21,61 @@ def handle_authenticate(self, request, uri, headers): ''' POST /tokens - Headers: + .. code-block:: python + + Headers: - Body: one of the following - 1. user + password - { - 'auth': { - 'passwordCredentials': { - 'username': None, - 'password': None + Body: one of the following + 1. user + password + { + 'auth': { + 'passwordCredentials': { + 'username': None, + 'password': None + } } } - } - 2. user + apikey - { - 'auth': { - 'RAX-KSKEY:apiKeyCredentials': { - 'username': None, - 'apiKey': None + 2. user + apikey + { + 'auth': { + 'RAX-KSKEY:apiKeyCredentials': { + 'username': None, + 'apiKey': None + } } } - } - 3. tenant-id + apikey - { - 'auth': { - 'RAX-KSKEY:apiKeyCredentials': { - 'username': None, - 'apiKey': None + 3. tenant-id + apikey + { + 'auth': { + 'RAX-KSKEY:apiKeyCredentials': { + 'username': None, + 'apiKey': None + } + 'tenantId': None + 'tenantName': None } - 'tenantId': None - 'tenantName': None } - } - 4. tenant-id + token - { - 'auth': { - 'tenantId': None, - 'token': { - 'id': None + 4. tenant-id + token + { + 'auth': { + 'tenantId': None, + 'token': { + 'id': None + } } } - } - 5. tenant-name + token - { - 'auth': { - 'tenantName': None, - 'token': { - 'id': None + 5. tenant-name + token + { + 'auth': { + 'tenantName': None, + 'token': { + 'id': None + } } } - } 200 -> OK + JSON Body w/ Service Catalog - 400 -> Bad Request: one or more required parameters - are missing or invalid + 400 -> Bad Request: one or more required parameters are missing or invalid 401 -> not authorized 403 -> forbidden (no permission) 404 -> Not found diff --git a/openstackinabox/services/keystone/v2/users.py b/openstackinabox/services/keystone/v2/users.py index 43e16be..3f33b0b 100644 --- a/openstackinabox/services/keystone/v2/users.py +++ b/openstackinabox/services/keystone/v2/users.py @@ -71,8 +71,8 @@ def user_data_filter(user): response_body = { 'users': [ - user_data_filter(user_info) - for user_info in + user_data_filter(tenant_user_info) + for tenant_user_info in self.model.users.get_for_tenant_id( user_data['tenantid'] ) @@ -83,8 +83,7 @@ def user_data_filter(user): def handle_add_user(self, request, uri, headers): ''' 201 -> created - 400 -> Bad Request - missing one or more element, or values were - invalid + 400 -> Bad Request - missing one or more element, or values were invalid 401 -> unauthorized 403 -> forbidden (no permission) 404 -> not found @@ -94,44 +93,46 @@ def handle_add_user(self, request, uri, headers): 415 -> bad media type 503 -> service not available - Note: Admins can add up to 100 users to an account. + .. note:: Admins can add up to 100 users to an account. - Required: - x-auth-token + .. note:: password is optional - Body: - { - 'username': , - 'email': , - 'enabled': True/False, - 'OS-KSADM:password': - } + .. code-block:: text - Note: password is optional - - Spec: - username: - must start with a letter - must be at least 1 character long - may contain: upper and lower case characters and ._@- - password: - must start with a letter - must be at least 8 characters long - must contain 1 upper case, 1 lowercase, and 1 numeric value - may contain: .-@_ - - Response: - { - 'user': { + Required: + x-auth-token + + Body: + { 'username': , - 'OS-KSADM:password': , - 'email': - 'RAX-AUTH:defaultRegion': , - 'RAX-AUTH:domainId': , - 'id': , - 'enabled': True/False + 'email': , + 'enabled': True/False, + 'OS-KSADM:password': + } + + Spec: + username: + must start with a letter + must be at least 1 character long + may contain: upper and lower case characters and ._@- + password: + must start with a letter + must be at least 8 characters long + must contain 1 upper case, 1 lowercase, and 1 numeric value + may contain: .-@_ + + Response: + { + 'user': { + 'username': , + 'OS-KSADM:password': , + 'email': + 'RAX-AUTH:defaultRegion': , + 'RAX-AUTH:domainId': , + 'id': , + 'enabled': True/False + } } - } ''' self.log_request(uri, request) req_headers = request.headers @@ -186,7 +187,7 @@ def handle_add_user(self, request, uri, headers): return (201, headers, '') - except Exception as ex: + except Exception: self.log_exception('User Add Failure') # is 404 correct? return (404, headers, 'failed to add user') @@ -209,18 +210,20 @@ def handle_get_user_by_id(self, request, uri, headers): No body - response: - { - 'user': { - 'RAX-AUTH:defaultRegion': - 'RAX-AUTH:domainId': - 'RAX-AUTH:multiFactorEnabled': True/False - 'id': - 'username': - 'email': - 'enabled': True/False + .. code-block:: text + + response: + { + 'user': { + 'RAX-AUTH:defaultRegion': + 'RAX-AUTH:domainId': + 'RAX-AUTH:multiFactorEnabled': True/False + 'id': + 'username': + 'email': + 'enabled': True/False + } } - } ''' self.log_request(uri, request) req_headers = request.headers @@ -237,7 +240,7 @@ def handle_get_user_by_id(self, request, uri, headers): self.log_debug('Lookup of user id {0} requested' .format(user_id)) - except Exception as ex: # pragma: no cover + except Exception: # pragma: no cover self.log_exception('Failed to get user id from path') return (400, headers, 'bad request') @@ -293,7 +296,7 @@ def handle_update_user_by_id(self, request, uri, headers): user_id = KeystoneV2ServiceUsers.get_user_id_from_path(uri) self.log_debug('Lookup of user id {0} requested'.format(user_id)) - except Exception as ex: # pragma: no cover + except Exception: # pragma: no cover self.log_exception('Failed to get user id from path') return (400, headers, 'bad request') @@ -313,7 +316,7 @@ def handle_update_user_by_id(self, request, uri, headers): tenant_id=user_data['tenantid'], user_id=user_id ) - except Exception as ex: + except Exception: self.log_exception('failed to get user data') return (404, headers, 'Not Found') @@ -335,7 +338,7 @@ def handle_update_user_by_id(self, request, uri, headers): apikey=user_info['apikey'], enabled=user_info['enabled'] ) - except Exception as ex: # pragma: no cover + except Exception: # pragma: no cover self.log_exception('failed to update user') return (503, headers, 'Server error') @@ -367,7 +370,7 @@ def handle_delete_user_by_id(self, request, uri, headers): self.log_debug('Lookup of user id {0} requested' .format(user_id)) - except Exception as ex: # pragma: no cover + except Exception: # pragma: no cover self.log_exception('Failed to get user id from path') return (400, headers, 'bad request') @@ -376,7 +379,7 @@ def handle_delete_user_by_id(self, request, uri, headers): tenant_id=user_data['tenantid'], user_id=user_id ) - except Exception as ex: + except Exception: self.log_exception('failed to get user data') return (404, headers, 'Not Found') @@ -386,7 +389,7 @@ def handle_delete_user_by_id(self, request, uri, headers): user_id=user_id ) - except Exception as ex: # pragma: no cover + except Exception: # pragma: no cover self.log_exception('failed to delete user') return (503, headers, 'Server error') @@ -428,7 +431,7 @@ def handle_add_credentials_to_user(self, request, uri, headers): user_id = KeystoneV2ServiceUsers.get_user_id_from_path(uri) self.log_debug('Lookup of user id {0} requested'.format(user_id)) - except Exception as ex: # pragma: no cover + except Exception: # pragma: no cover self.log_exception('Failed to get user id from path') return (400, headers, 'bad request') @@ -457,7 +460,7 @@ def handle_add_credentials_to_user(self, request, uri, headers): enabled=user_info['enabled'] ) - except Exception as ex: # pragma: no cover + except Exception: # pragma: no cover self.log_exception('failed to update user') return (503, headers, 'Server error') diff --git a/openstackinabox/services/swift/v1/__init__.py b/openstackinabox/services/swift/v1/__init__.py index bab2e6e..0bdcaaa 100644 --- a/openstackinabox/services/swift/v1/__init__.py +++ b/openstackinabox/services/swift/v1/__init__.py @@ -314,7 +314,7 @@ def put_object_handler(self, request, uri, headers): 'Swift Service ({0}): Object Stored'.format(self.__id) ) - except Exception as ex: + except Exception: LOG.exception( 'Swift Service ({0}): Failed to store object'.format(self.__id) ) diff --git a/openstackinabox/tests/services/keystone/test_keystone.py b/openstackinabox/tests/services/keystone/test_keystone.py index 957ee1b..0b1b1d0 100644 --- a/openstackinabox/tests/services/keystone/test_keystone.py +++ b/openstackinabox/tests/services/keystone/test_keystone.py @@ -1,5 +1,5 @@ """ -Stack-In-A-Box: Basic Test +OpenStack-In-A-Box: Basic Test """ import unittest diff --git a/tools/docs-requirements.txt b/tools/docs-requirements.txt new file mode 100644 index 0000000..9b4ed21 --- /dev/null +++ b/tools/docs-requirements.txt @@ -0,0 +1,2 @@ +sphinx +doc8 diff --git a/tools/flake8-requirements.txt b/tools/flake8-requirements.txt new file mode 100644 index 0000000..36bf817 --- /dev/null +++ b/tools/flake8-requirements.txt @@ -0,0 +1,2 @@ +setuptools>=1.1.6 +flake8 diff --git a/tools/pep8-requirements.txt b/tools/pep8-requirements.txt new file mode 100644 index 0000000..7dfd1f6 --- /dev/null +++ b/tools/pep8-requirements.txt @@ -0,0 +1,2 @@ +setuptools>=1.1.6 +pep8 diff --git a/tox.ini b/tox.ini index bb5d01a..6453852 100644 --- a/tox.ini +++ b/tox.ini @@ -1,28 +1,33 @@ [tox] -envlist = py27, pypy, py34, py35, pep8, flake8 +minversion=1.8 +envlist = py27,pypy,{py34,py35,py36},pep8,flake8,docs +skip_missing_interpreters=True -[testenv:py27] -deps = -r{toxinidir}/tools/test-requirements.txt -commands = nosetests {posargs} +[testenv] +basepython = + py27: python2.7 + pypy: pypy + py34: python3.4 + py35: python3.5 + py36: python3.6 + pep8: python2.7 + flake8: python2.7 + docs: python2.7 +deps = + py27,pypy,py34,py35,py36,docs: -r{toxinidir}/tools/test-requirements.txt + pep8: -r{toxinidir}/tools/pep8-requirements.txt + flake8: -r{toxinidir}/tools/flake8-requirements.txt + docs: -r{toxinidir}/tools/docs-requirements.txt +commands = + py27,pypy,py34,py35,py36: nosetests {posargs} + pep8: pep8 --exclude=.tox,dist,doc,docs,*env*,.*env*,build --ignore=E128 + flake8: flake8 openstackinabox/ setup.py + docs: sphinx-build -b html -d {envtmpdir}/doctrees docs docs/_build/html + docs: sphinx-build -b latex -d {envtmpdir}/doctrees docs docs/_build/latex + docs: sphinx-build -b doctest -d {envtmpdir}/doctrees docs docs/_build/html + docs: doc8 --allow-long-titles docs/ +setenv = + py34,py35,py36: VIRTUAL_ENV={envdir} LC_ALL = en_US.utf-8 -[testenv:pypy] -deps = -r{toxinidir}/tools/test-requirements.txt -commands = nosetests {posargs} - -[testenv:py34] -deps = -r{toxinidir}/tools/test-requirements.txt -commands = nosetests {posargs} - -[testenv:py35] -deps = -r{toxinidir}/tools/test-requirements.txt -commands = nosetests {posargs} - -[testenv:pep8] -deps = setuptools>=1.1.6 - pep8 -commands = pep8 --exclude=.tox,dist,doc,*env*,.*env*,build --ignore=E128 - -[testenv:flake8] -deps = setuptools>=1.1.6 - flake8 -commands = flake8 openstackinabox/ setup.py +[doc8] +extensions = rst