Retire craton
Remove everything, add a README with explanation. Change-Id: I7c8fcfac0d904f144b6a3838d25db0987d1e74a5
This commit is contained in:
parent
555d0c8856
commit
e88f6b747a
@ -1,7 +0,0 @@
|
||||
[run]
|
||||
branch = True
|
||||
source = craton
|
||||
omit = craton/openstack/*
|
||||
|
||||
[report]
|
||||
ignore_errors = True
|
55
.gitignore
vendored
55
.gitignore
vendored
@ -1,55 +0,0 @@
|
||||
*.py[cod]
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Packages
|
||||
*.egg*
|
||||
*.egg-info
|
||||
dist
|
||||
build
|
||||
eggs
|
||||
parts
|
||||
bin
|
||||
var
|
||||
sdist
|
||||
develop-eggs
|
||||
.installed.cfg
|
||||
lib
|
||||
lib64
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
cover/
|
||||
.coverage*
|
||||
!.coveragerc
|
||||
.tox
|
||||
nosetests.xml
|
||||
.testrepository
|
||||
.venv
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
|
||||
# Mr Developer
|
||||
.mr.developer.cfg
|
||||
.project
|
||||
.pydevproject
|
||||
|
||||
# Complexity
|
||||
output/*.html
|
||||
output/*/index.html
|
||||
|
||||
# Sphinx
|
||||
doc/build
|
||||
|
||||
# pbr generates these
|
||||
AUTHORS
|
||||
ChangeLog
|
||||
|
||||
# Editors
|
||||
*~
|
||||
.*.swp
|
||||
.*sw?
|
@ -1,4 +0,0 @@
|
||||
[gerrit]
|
||||
host=review.openstack.org
|
||||
port=29418
|
||||
project=openstack/craton.git
|
3
.mailmap
3
.mailmap
@ -1,3 +0,0 @@
|
||||
# Format is:
|
||||
# <preferred e-mail> <other e-mail 1>
|
||||
# <preferred e-mail> <other e-mail 2>
|
@ -1,7 +0,0 @@
|
||||
[DEFAULT]
|
||||
test_command=OS_STDOUT_CAPTURE=${OS_STDOUT_CAPTURE:-1} \
|
||||
OS_STDERR_CAPTURE=${OS_STDERR_CAPTURE:-1} \
|
||||
OS_TEST_TIMEOUT=${OS_TEST_TIMEOUT:-60} \
|
||||
${PYTHON:-python} -m subunit.run discover -t ./ . $LISTOPT $IDOPTION
|
||||
test_id_option=--load-list $IDFILE
|
||||
test_list_option=--list
|
@ -1,23 +0,0 @@
|
||||
Developer's Guide
|
||||
-----------------
|
||||
If you would like to contribute to the development of OpenStack, you must
|
||||
follow the steps in this page:
|
||||
|
||||
http://docs.openstack.org/infra/manual/developers.html
|
||||
|
||||
Development Workflow
|
||||
--------------------
|
||||
If you already have a good understanding of how the system works and your
|
||||
OpenStack accounts are set up, you can skip to the development workflow
|
||||
section of this documentation to learn how changes to OpenStack should be
|
||||
submitted for review via the Gerrit tool:
|
||||
|
||||
http://docs.openstack.org/infra/manual/developers.html#development-workflow
|
||||
|
||||
Engagement
|
||||
----------
|
||||
Pull requests submitted through GitHub will be ignored.
|
||||
|
||||
Bugs should be filed on Launchpad, not GitHub:
|
||||
|
||||
https://bugs.launchpad.net/craton
|
85
Dockerfile
85
Dockerfile
@ -1,85 +0,0 @@
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
############################################################################
|
||||
## Usage:
|
||||
## docker build --pull -t craton-api:latest .
|
||||
## docker run -t --name craton-api -p 127.0.0.1:7780:7780 -d craton-api:latest
|
||||
## docker logs <container> and copy the username, api_key, and project_id
|
||||
## python tools/generate_fake_data.py --url http://127.0.0.1:7780/v1 --user bootstrap --project <project-id from above> --key <api_key from above>
|
||||
## Use the credentials from above to try different commands using python-cratonclient.
|
||||
##############################################################################
|
||||
|
||||
# Get Ubuntu base image
|
||||
FROM ubuntu:16.04
|
||||
|
||||
# File Author / Maintainer
|
||||
MAINTAINER Sulochan Acharya
|
||||
|
||||
# Install required software and tools
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y \
|
||||
gcc \
|
||||
git \
|
||||
curl \
|
||||
build-essential \
|
||||
python3.5 \
|
||||
python3.5-dev
|
||||
|
||||
# Get pip
|
||||
ADD https://bootstrap.pypa.io/get-pip.py /root/get-pip.py
|
||||
|
||||
# Install pip
|
||||
RUN python3.5 /root/get-pip.py
|
||||
|
||||
# Install MySQL 5.7
|
||||
ENV MYSQL_ROOTPW root
|
||||
RUN echo "mysql-server mysql-server/root_password password $MYSQL_ROOTPW" | debconf-set-selections && \
|
||||
echo "mysql-server mysql-server/root_password_again password $MYSQL_ROOTPW" | debconf-set-selections
|
||||
RUN apt-get install -y mysql-server-5.7 mysql-client-5.7
|
||||
RUN service mysql start && \
|
||||
mysqladmin -u root -p"$MYSQL_ROOTPW" password '' && \
|
||||
service mysql stop
|
||||
|
||||
# Change mysql bind address
|
||||
RUN sed -i -e"s/^bind-address\s*=\s*127.0.0.1/bind-address = 0.0.0.0/" /etc/mysql/mysql.conf.d/mysqld.cnf
|
||||
|
||||
# Install MySQL-python
|
||||
RUN apt-get install -y libmysqlclient-dev python-mysqldb
|
||||
|
||||
# pip install virtualenv
|
||||
RUN pip3 install virtualenv
|
||||
|
||||
# Expose port
|
||||
EXPOSE 7780 3306
|
||||
|
||||
Add ./requirements.txt /requirements.txt
|
||||
|
||||
# Init virutalenv
|
||||
RUN virtualenv -p /usr/bin/python3.5 /craton
|
||||
|
||||
# Change Working Dir
|
||||
WORKDIR /craton
|
||||
|
||||
# Install requirements
|
||||
RUN bin/pip install -r /requirements.txt
|
||||
|
||||
# pip install mysql-python
|
||||
RUN bin/pip install mysqlclient
|
||||
|
||||
# Add Craton
|
||||
ADD . /craton
|
||||
|
||||
# Install Craton
|
||||
RUN bin/pip install .
|
||||
|
||||
CMD ["tools/docker_run.sh"]
|
@ -1,4 +0,0 @@
|
||||
craton Style Commandments
|
||||
===============================================
|
||||
|
||||
Read the OpenStack Style Commandments http://docs.openstack.org/developer/hacking/
|
176
LICENSE
176
LICENSE
@ -1,176 +0,0 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
35
README.rst
35
README.rst
@ -1,29 +1,10 @@
|
||||
Craton
|
||||
======
|
||||
This project is no longer maintained.
|
||||
|
||||
Craton is a new project we plan to propose for OpenStack inclusion.
|
||||
Craton supports deploying and operating OpenStack clouds by providing
|
||||
scalable fleet management:
|
||||
The contents of this repository are still available in the Git
|
||||
source code management system. To see the contents of this
|
||||
repository before it reached its end of life, please check out the
|
||||
previous commit with "git checkout HEAD^1".
|
||||
|
||||
* Inventory of configurable physical devices/hosts (the fleet)
|
||||
* Audit and remediation workflows against this inventory
|
||||
* REST APIs, CLI, and Python client to manage
|
||||
|
||||
Support for workflows, CLI, and the Python client is in progress.
|
||||
|
||||
For more information, please refer to the following project resources:
|
||||
|
||||
* **Free software:** under the `Apache license <http://www.apache.org/licenses/LICENSE-2.0>`_
|
||||
* **Documentation:** https://craton.readthedocs.io
|
||||
* **Source:** https://github.com/openstack/craton
|
||||
* **Blueprints:** https://blueprints.launchpad.net/craton
|
||||
* **Bugs:** https://bugs.launchpad.net/craton
|
||||
|
||||
For information on how to contribute to Craton, please see the
|
||||
contents of the `CONTRIBUTING.rst file <CONTRIBUTING.rst>`_.
|
||||
|
||||
For information on how to setup a Developer's Environment, please
|
||||
see the contents of `INSTALL.RST file <doc/source/dev/install.rst>`_.
|
||||
|
||||
For more information on Craton distribution license, please see
|
||||
the contents of the `LICENSE file <LICENSE>`_.
|
||||
For any further questions, please email
|
||||
openstack-dev@lists.openstack.org or join #openstack-dev on
|
||||
Freenode.
|
||||
|
@ -1,273 +0,0 @@
|
||||
.. -*- rst -*-
|
||||
|
||||
======
|
||||
Cells
|
||||
======
|
||||
|
||||
Definition of cell
|
||||
|
||||
Create Cell
|
||||
============
|
||||
|
||||
.. rest_method:: POST /v1/cells
|
||||
|
||||
Create a new Cell
|
||||
|
||||
Normal response codes: OK(201)
|
||||
|
||||
Error response codes: invalid request(400), validation exception(405)
|
||||
|
||||
Request
|
||||
-------
|
||||
|
||||
.. rest_parameters:: parameters.yaml
|
||||
|
||||
- name : cell_name
|
||||
- region_id: region_id_body
|
||||
- project_id: project_id
|
||||
|
||||
Required Header
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
- Content-Type: Content_Type
|
||||
- X-Auth-Token: X-Auth-Token
|
||||
- X-Auth-User: X-Auth-User
|
||||
- X-Auth-Project: X-Auth-Project
|
||||
|
||||
**Example Create Cell** (TO-DO)
|
||||
|
||||
..literalinclude:: ../../doc/api_samples/cells/cells-create-req.json
|
||||
:language: javascript
|
||||
|
||||
Response
|
||||
--------
|
||||
|
||||
.. rest_parameters:: parameters.yaml
|
||||
|
||||
- cell: cell
|
||||
- id: cell_id_body
|
||||
- name: cell_name
|
||||
- region_id: region_id_body
|
||||
- project_id: project_id
|
||||
- note: note
|
||||
- variables: variables
|
||||
|
||||
**Example Create Cell** (TO-DO)
|
||||
|
||||
..literalinclude:: ../../doc/api_samples/cells/cells-create-resp.json
|
||||
:language: javascript
|
||||
|
||||
List Cells
|
||||
==========
|
||||
|
||||
.. rest_method:: GET /v1/cells
|
||||
|
||||
Gets all Cells
|
||||
|
||||
Normal response codes: OK(200)
|
||||
|
||||
Error response codes: invalid request(400), cell not found(404), validation exception(405)
|
||||
|
||||
Default response: unexpected error
|
||||
|
||||
Request
|
||||
--------
|
||||
|
||||
.. rest_parameters:: parameters.yaml
|
||||
|
||||
- cell: cell_name_query
|
||||
- region: region_name_query
|
||||
|
||||
Required Header
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
- Content-Type: Content_Type
|
||||
- X-Auth-Token: X-Auth-Token
|
||||
- X-Auth-User: X-Auth-User
|
||||
- X-Auth-Project: X-Auth-Project
|
||||
|
||||
Response
|
||||
--------
|
||||
|
||||
.. rest_parameters:: parameters.yaml
|
||||
|
||||
- cells: cells
|
||||
- id: cell_id_body
|
||||
- name: cell_name
|
||||
- region_id: region_id_body
|
||||
- project_id: project_id
|
||||
- note: note
|
||||
- variables: variables
|
||||
|
||||
**Example List Cells** (TO-DO)
|
||||
|
||||
..literalinclude:: ../../doc/api_samples/cells/cells-list-resp.json
|
||||
:language: javascript
|
||||
|
||||
**Example Unexpected Error **
|
||||
|
||||
..literalinclude:: ../../doc/api_samples/errors/errors-unexpected-resp.json
|
||||
:language: javascript
|
||||
|
||||
Update Cells
|
||||
============
|
||||
|
||||
.. rest_method:: PUT /v1/cells/{cell_id}
|
||||
|
||||
Update an existing cell
|
||||
|
||||
Normal response codes: OK(200)
|
||||
|
||||
Error response codes: invalid request(400), cell not found(404), validation exception(405)
|
||||
|
||||
Request
|
||||
-------
|
||||
|
||||
.. rest_parameters:: parameters.yaml
|
||||
|
||||
- id: cell_id_body
|
||||
- name: cell_name
|
||||
- region_id: region_id_body
|
||||
- project_id: project_id
|
||||
- note: note
|
||||
- variables: variables
|
||||
- cell_id: cell_id
|
||||
|
||||
Required Header
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
- Content-Type: Content_Type
|
||||
- X-Auth-Token: X-Auth-Token
|
||||
- X-Auth-User: X-Auth-User
|
||||
- X-Auth-Project: X-Auth-Project
|
||||
|
||||
**Example Update Cell** (TO-DO)
|
||||
|
||||
..literalinclude:: ../../doc/api_samples/cells/cells-update-req.json
|
||||
:language: javascript
|
||||
|
||||
Response
|
||||
--------
|
||||
|
||||
.. rest_parameters:: parameters.yaml
|
||||
|
||||
- cell: cell
|
||||
- id: cell_id_body
|
||||
- name: cell_name
|
||||
- region_id: region_id_body
|
||||
- project_id: project_id
|
||||
- note: note
|
||||
- variables: variables
|
||||
|
||||
**Example Update Cell** (TO-DO)
|
||||
|
||||
..literalinclude:: ../../doc/api_samples/cells/cells-update-resp.json
|
||||
:language: javascript
|
||||
|
||||
Update Cell Data
|
||||
==================
|
||||
|
||||
.. rest_method:: PUT /v1/cells/{cell_id}/variables
|
||||
|
||||
Update user defined variables for the cell
|
||||
|
||||
Normal response codes: OK(200)
|
||||
|
||||
Error response codes: invalid request(400), cell not found(404), validation exception(405)
|
||||
|
||||
Request
|
||||
-------
|
||||
|
||||
.. rest_parameters:: parameters.yaml
|
||||
|
||||
- key: key
|
||||
- value: value
|
||||
- cell_id: cell_id
|
||||
|
||||
Required Header
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
- Content-Type: Content_Type
|
||||
- X-Auth-Token: X-Auth-Token
|
||||
- X-Auth-User: X-Auth-User
|
||||
- X-Auth-Project: X-Auth-Project
|
||||
|
||||
**Example Update Cell Data** (TO-DO)
|
||||
|
||||
..literalinclude:: ../../doc/api_samples/cells/cells-upadate—data-req.json
|
||||
:language: javascript
|
||||
|
||||
Response
|
||||
--------
|
||||
|
||||
.. rest_parameters:: parameters.yaml
|
||||
|
||||
- key: key
|
||||
- value: value
|
||||
|
||||
**Example Update Cell Data** (TO-DO)
|
||||
|
||||
..literalinclude:: ../../doc/api_samples/cells/cells-update-data-resp.json
|
||||
:language: javascript
|
||||
|
||||
Delete Cell
|
||||
===========
|
||||
|
||||
.. rest_method:: DELETE /v1/cells/{cell_id}
|
||||
|
||||
Deletes an existing record of a Cell
|
||||
|
||||
Normal response codes: OK(200)
|
||||
|
||||
Error response codes: invalid request(400), cell not found(404)
|
||||
|
||||
Request
|
||||
-------
|
||||
|
||||
.. rest_parameters:: parameters.yaml
|
||||
|
||||
- cell_id: cell_id
|
||||
|
||||
Required Header
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
- Content-Type: Content_Type
|
||||
- X-Auth-Token: X-Auth-Token
|
||||
- X-Auth-User: X-Auth-User
|
||||
- X-Auth-Project: X-Auth-Project
|
||||
|
||||
Response
|
||||
--------
|
||||
|
||||
No body content is returned on a successful DELETE
|
||||
|
||||
Delete Cell Data
|
||||
================
|
||||
|
||||
.. rest_method:: DELETE /v1/cells/{cell_id}/variables
|
||||
|
||||
Delete existing key/value variable for the cell
|
||||
|
||||
Normal response codes: OK(200)
|
||||
|
||||
Error response codes: invalid request(400), cell not found(404) validation exception(405)
|
||||
|
||||
Request
|
||||
-------
|
||||
|
||||
.. rest_parameters:: parameters.yaml
|
||||
|
||||
- cell_id: cell_id
|
||||
|
||||
Required Header
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
- Content-Type: Content_Type
|
||||
- X-Auth-Token: X-Auth-Token
|
||||
- X-Auth-User: X-Auth-User
|
||||
- X-Auth-Project: X-Auth-Project
|
||||
|
||||
Response
|
||||
--------
|
||||
|
||||
No body content is returned on a successful DELETE
|
@ -1,301 +0,0 @@
|
||||
.. -*- rst -*-
|
||||
|
||||
=====
|
||||
Hosts
|
||||
=====
|
||||
|
||||
Definition of host
|
||||
|
||||
Create Host
|
||||
============
|
||||
|
||||
.. rest_method:: POST /v1/hosts
|
||||
|
||||
Create a new host
|
||||
|
||||
Normal response codes: OK(201)
|
||||
|
||||
Error response codes: invalid request(400), validation exception(405)
|
||||
|
||||
Request
|
||||
-------
|
||||
|
||||
.. rest_parameters:: parameters.yaml
|
||||
|
||||
- name: host_name
|
||||
- region_id: region_id_body
|
||||
- project_id: project_id
|
||||
- ip_address: ip_address
|
||||
- device_type: device_type
|
||||
|
||||
Required Header
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
- Content-Type: Content_Type
|
||||
- X-Auth-Token: X-Auth-Token
|
||||
- X-Auth-User: X-Auth-User
|
||||
- X-Auth-Project: X-Auth-Project
|
||||
|
||||
**Example Create Host** (TO-DO)
|
||||
|
||||
..literalinclude:: ../../doc/api_samples/hosts/hosts-create-req.json
|
||||
:language: javascript
|
||||
|
||||
Response
|
||||
--------
|
||||
|
||||
.. rest_parameters:: parameters.yaml
|
||||
|
||||
- host: host
|
||||
- id: host_id_body
|
||||
- name: host_name
|
||||
- cell_id: cell_id_body
|
||||
- parent_id: parent_id
|
||||
- project_id: project_id
|
||||
- region_id: region_id_body
|
||||
- ip_address: ip_address
|
||||
- device_type: device_type
|
||||
- labels: labels
|
||||
- note: note
|
||||
- variables: variables
|
||||
|
||||
**Example Create Host** (TO-DO)
|
||||
|
||||
..literalinclude:: ../../doc/api_samples/hosts/hosts-create-resp.json
|
||||
:language: javascript
|
||||
|
||||
List Hosts
|
||||
==========
|
||||
|
||||
.. rest_method:: GET /v1/hosts
|
||||
|
||||
Gets all Host
|
||||
|
||||
Normal response codes: OK(200)
|
||||
|
||||
Error response codes: invalid request(400), host not found(404), validation exception(405)
|
||||
|
||||
Default response: unexpected error
|
||||
|
||||
Request
|
||||
--------
|
||||
|
||||
.. rest_parameters:: parameters.yaml
|
||||
|
||||
- limit: limit
|
||||
- name: host_name_query
|
||||
- id: host_id_query
|
||||
- region: region_name_query
|
||||
- cell: cell_name_query
|
||||
- ip_address: ip_address_query
|
||||
- service: service
|
||||
|
||||
Required Header
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
- Content-Type: Content_Type
|
||||
- X-Auth-Token: X-Auth-Token
|
||||
- X-Auth-User: X-Auth-User
|
||||
- X-Auth-Project: X-Auth-Project
|
||||
|
||||
Response
|
||||
--------
|
||||
|
||||
.. rest_parameters:: parameters.yaml
|
||||
|
||||
- hosts: hosts
|
||||
- id: host_id_body
|
||||
- name: host_name
|
||||
- cell_id: cell_id_body
|
||||
- parent_id: parent_id
|
||||
- project_id: project_id
|
||||
- region_id: region_id_body
|
||||
- ip_address: ip_address
|
||||
- device_type: device_type
|
||||
- labels: labels
|
||||
- note: note
|
||||
- variables: variables
|
||||
|
||||
**Example List Host** (TO-DO)
|
||||
|
||||
..literalinclude:: ../../doc/api_samples/hosts/hosts-list-resp.json
|
||||
:language: javascript
|
||||
|
||||
**Example Unexpected Error **
|
||||
|
||||
..literalinclude:: ../../doc/api_samples/errors/errors-unexpected-resp.json
|
||||
:language: javascript
|
||||
|
||||
Update Hosts
|
||||
============
|
||||
|
||||
.. rest_method:: PUT /v1/hosts/{host_id}
|
||||
|
||||
Update an existing host
|
||||
|
||||
Normal response codes: OK(200)
|
||||
|
||||
Error response codes: invalid request(400), host not found(404), validation exception(405)
|
||||
|
||||
Request
|
||||
-------
|
||||
|
||||
.. rest_parameters:: parameters.yaml
|
||||
|
||||
- id: host_id_body
|
||||
- name: host_name
|
||||
- cell_id: cell_id_body
|
||||
- parent_id: parent_id
|
||||
- project_id: project_id
|
||||
- region_id: region_id_body
|
||||
- ip_address: ip_address
|
||||
- device_type: device_type
|
||||
- labels: labels
|
||||
- note: note
|
||||
- variables: variables
|
||||
- host_id: host_id
|
||||
|
||||
|
||||
Required Header
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
- Content-Type: Content_Type
|
||||
- X-Auth-Token: X-Auth-Token
|
||||
- X-Auth-User: X-Auth-User
|
||||
- X-Auth-Project: X-Auth-Project
|
||||
|
||||
**Example Update Host** (TO-DO)
|
||||
|
||||
..literalinclude:: ../../doc/api_samples/hosts/hosts-update-req.json
|
||||
:language: javascript
|
||||
|
||||
Response
|
||||
--------
|
||||
|
||||
.. rest_parameters:: parameters.yaml
|
||||
|
||||
- host: host
|
||||
- id: host_id_body
|
||||
- name: host_name
|
||||
- cell_id: cell_id_body
|
||||
- parent_id: parent_id
|
||||
- project_id: project_id
|
||||
- region_id: region_id_body
|
||||
- ip_address: ip_address
|
||||
- device_type: device_type
|
||||
- labels: labels
|
||||
- note: note
|
||||
- variables: variables
|
||||
|
||||
**Example Update Host** (TO-DO)
|
||||
|
||||
..literalinclude:: ../../doc/api_samples/hosts/hosts-update-resp.json
|
||||
:language: javascript
|
||||
|
||||
Update Host Data
|
||||
==================
|
||||
|
||||
.. rest_method:: PUT /v1/hosts/{host_id}/variables
|
||||
|
||||
Update user defined variables for the host
|
||||
|
||||
Normal response codes: OK(200)
|
||||
|
||||
Error response codes: invalid request(400), host not found(404), validation exception(405)
|
||||
|
||||
Request
|
||||
-------
|
||||
|
||||
.. rest_parameters:: parameters.yaml
|
||||
|
||||
- key: key
|
||||
- value: value
|
||||
- host_id: host_id
|
||||
|
||||
Required Header
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
- Content-Type: Content_Type
|
||||
- X-Auth-Token: X-Auth-Token
|
||||
- X-Auth-User: X-Auth-User
|
||||
- X-Auth-Project: X-Auth-Project
|
||||
|
||||
**Example Update Host Data** (TO-DO)
|
||||
|
||||
..literalinclude:: ../../doc/api_samples/hosts/hosts-upadate—data-req.json
|
||||
:language: javascript
|
||||
|
||||
Response
|
||||
--------
|
||||
|
||||
.. rest_parameters:: parameters.yaml
|
||||
|
||||
- key: key
|
||||
- value: value
|
||||
|
||||
**Example Update Host Data** (TO-DO)
|
||||
|
||||
..literalinclude:: ../../doc/api_samples/hosts/hosts-update-data-resp.json
|
||||
:language: javascript
|
||||
|
||||
Delete Host
|
||||
===========
|
||||
|
||||
.. rest_method:: DELETE /v1/hosts/{host_id}
|
||||
|
||||
Deletes an existing record of a Host
|
||||
|
||||
Normal response codes: OK(200)
|
||||
|
||||
Error response codes: invalid request(400), host not found(404)
|
||||
|
||||
Request
|
||||
-------
|
||||
|
||||
.. rest_parameters:: parameters.yaml
|
||||
|
||||
- host_id: host_id
|
||||
|
||||
Required Header
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
- Content-Type: Content_Type
|
||||
- X-Auth-Token: X-Auth-Token
|
||||
- X-Auth-User: X-Auth-User
|
||||
- X-Auth-Project: X-Auth-Project
|
||||
|
||||
Response
|
||||
--------
|
||||
|
||||
No body content is returned on a successful DELETE
|
||||
|
||||
Delete Host Data
|
||||
================
|
||||
|
||||
.. rest_method:: DELETE /v1/hosts/{host_id}/variables
|
||||
|
||||
Delete existing key/value variables for the Host
|
||||
|
||||
Normal response codes: OK(200)
|
||||
|
||||
Error response codes: invalid request(400), host not found(404) validation exception(405)
|
||||
|
||||
Request
|
||||
-------
|
||||
|
||||
.. rest_parameters:: parameters.yaml
|
||||
|
||||
- host_id: host_id
|
||||
|
||||
Required Header
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
- Content-Type: Content_Type
|
||||
- X-Auth-Token: X-Auth-Token
|
||||
- X-Auth-User: X-Auth-User
|
||||
- X-Auth-Project: X-Auth-Project
|
||||
|
||||
Response
|
||||
--------
|
||||
|
||||
No body content is returned on a successful DELETE
|
@ -1,11 +0,0 @@
|
||||
:tocdepth: 2
|
||||
|
||||
===========
|
||||
Craton API
|
||||
===========
|
||||
|
||||
.. rest_expand_all::
|
||||
|
||||
.. include:: cells.inc
|
||||
.. include:: hosts.inc
|
||||
.. include:: regions.inc
|
@ -1,201 +0,0 @@
|
||||
# variables in header
|
||||
Content-Type:
|
||||
description: |
|
||||
Type of content sent using cURL
|
||||
in: header
|
||||
required: true
|
||||
type: string
|
||||
X-Auth-Project:
|
||||
description: |
|
||||
ID of the project this user is assigned to.
|
||||
in: header
|
||||
required: true
|
||||
type: integer
|
||||
X-Auth-Token:
|
||||
description: |
|
||||
User authentication token for the current session
|
||||
in: header
|
||||
required: true
|
||||
type: string
|
||||
X-Auth-User
|
||||
description: |
|
||||
User of the current session
|
||||
in: header
|
||||
required: true
|
||||
type: string
|
||||
# variables in path
|
||||
cell_id:
|
||||
description: |
|
||||
The unique ID of the cell
|
||||
in: path
|
||||
required: true
|
||||
type: integer
|
||||
host_id:
|
||||
description: |
|
||||
The unique ID of the host
|
||||
region_id:
|
||||
description: |
|
||||
The unique ID of the region
|
||||
in: path
|
||||
required: true
|
||||
type: integer
|
||||
# variables in body
|
||||
cell:
|
||||
description: |
|
||||
A cell object
|
||||
in: body
|
||||
required: false
|
||||
type: object
|
||||
cell_id_body:
|
||||
description: |
|
||||
Unique ID of the cell
|
||||
in: body
|
||||
required: false
|
||||
type: integer
|
||||
cell_name:
|
||||
description: |
|
||||
Unique name of the cell
|
||||
in: body
|
||||
required: true
|
||||
type: string
|
||||
cells:
|
||||
description: |
|
||||
An array of cell objects
|
||||
in: body
|
||||
required: false
|
||||
type: array
|
||||
variables:
|
||||
description: |
|
||||
User defined information
|
||||
in: body
|
||||
required: false
|
||||
type: object
|
||||
device_type:
|
||||
description: |
|
||||
Type of host
|
||||
in: body
|
||||
required: false
|
||||
type: string
|
||||
host:
|
||||
description: |
|
||||
A host object
|
||||
in: body
|
||||
required: false
|
||||
type: object
|
||||
host_id_body:
|
||||
description: |
|
||||
Unique ID of the host
|
||||
in: body
|
||||
required: false
|
||||
type: integer
|
||||
host_name:
|
||||
description: |
|
||||
Unique name of the host
|
||||
hosts:
|
||||
description: |
|
||||
An array of host objects
|
||||
in: body
|
||||
required: false
|
||||
type: array
|
||||
ip_address:
|
||||
description: |
|
||||
IP address
|
||||
in: body
|
||||
type: string
|
||||
labels:
|
||||
description: |
|
||||
User defined labels
|
||||
in: body
|
||||
required: false
|
||||
type: string
|
||||
parent_id:
|
||||
description: |
|
||||
Parent ID of this host
|
||||
in: body
|
||||
required: false
|
||||
type: integer
|
||||
project_id:
|
||||
description: |
|
||||
ID of the project
|
||||
in: body
|
||||
required: true
|
||||
type: integer
|
||||
note:
|
||||
description: |
|
||||
Note used for governance
|
||||
in: body
|
||||
required: false
|
||||
type: string
|
||||
region:
|
||||
description: |
|
||||
A region object
|
||||
in: body
|
||||
required: false
|
||||
type: object
|
||||
region_id_body:
|
||||
description: |
|
||||
The unique ID of the region
|
||||
in: body
|
||||
required: false
|
||||
type: integer
|
||||
region_name:
|
||||
description: |
|
||||
Unique name of the region
|
||||
in: body
|
||||
required: true
|
||||
type: string
|
||||
regions:
|
||||
description: |
|
||||
An array of region objects
|
||||
in: body
|
||||
required: true
|
||||
type: array
|
||||
# variables in query
|
||||
cell_name_query:
|
||||
description: |
|
||||
Name of the cell to get
|
||||
in: query
|
||||
required: false
|
||||
type: string
|
||||
ip_address_query:
|
||||
description: |
|
||||
IP address to get
|
||||
in: query
|
||||
required: false
|
||||
type: string
|
||||
host_id_query:
|
||||
description: |
|
||||
ID of the host to get
|
||||
in: query
|
||||
required: false
|
||||
type: integer
|
||||
host_name_query:
|
||||
description: |
|
||||
Name of host to get
|
||||
in: query
|
||||
required: false
|
||||
type: string
|
||||
limit:
|
||||
description: |
|
||||
Number of host to return ranging from 1 - 10000. Default = 1000
|
||||
in: query
|
||||
required: false
|
||||
type: integer
|
||||
region_id_query:
|
||||
description: |
|
||||
ID of the region to get
|
||||
in: query
|
||||
required: false
|
||||
type: string
|
||||
region_name_query:
|
||||
description: |
|
||||
Name of the the region to get
|
||||
in: query
|
||||
required: false
|
||||
type: string
|
||||
service:
|
||||
description: |
|
||||
Openstack service to query host by
|
||||
in: query
|
||||
required: false
|
||||
type: array
|
@ -1,260 +0,0 @@
|
||||
.. -*- rst -*-
|
||||
|
||||
=======
|
||||
Regions
|
||||
=======
|
||||
|
||||
Definition of region
|
||||
|
||||
Create Region
|
||||
==============
|
||||
|
||||
.. rest_method:: POST /v1/region
|
||||
|
||||
Creates a new Region
|
||||
|
||||
Normal response codes: OK(201)
|
||||
|
||||
Error response codes: invalid request(400), validation exception(405)
|
||||
|
||||
Request
|
||||
-------
|
||||
|
||||
.. rest_parameters:: parameters.yaml
|
||||
|
||||
- name: region_name
|
||||
- project_id: project_id
|
||||
|
||||
Required Header
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
- Content-Type: Content_Type
|
||||
- X-Auth-Token: X-Auth-Token
|
||||
- X-Auth-User: X-Auth-User
|
||||
- X-Auth-Project: X-Auth-Project
|
||||
|
||||
**Example Create Region**
|
||||
|
||||
..literalinclude:: ../../doc/api_samples/regions/regions-create-req.json
|
||||
:language: javascript
|
||||
|
||||
Response
|
||||
--------
|
||||
|
||||
- region: region
|
||||
- id: region_id_body
|
||||
- name: region_name
|
||||
- project_id: project_id
|
||||
- cells: cells
|
||||
- variables: variables
|
||||
|
||||
**Example Create Region**
|
||||
|
||||
..literalinclude:: ../../doc/api_samples/regions/regions-create-resp.json
|
||||
:language: javascript
|
||||
|
||||
List Regions
|
||||
==============
|
||||
|
||||
.. rest_method:: GET /v1/regions
|
||||
|
||||
Gets all Regions
|
||||
|
||||
Normal response codes: OK(200)
|
||||
|
||||
Error response codes: invalid request(400), validation exception(405)
|
||||
|
||||
Default response: unexpected error
|
||||
|
||||
Request
|
||||
--------
|
||||
|
||||
- name: region_name_query
|
||||
- id: region_id_query
|
||||
|
||||
Required Header
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
- Content-Type: Content_Type
|
||||
- X-Auth-Token: X-Auth-Token
|
||||
- X-Auth-User: X-Auth-User
|
||||
- X-Auth-Project: X-Auth-Project
|
||||
|
||||
Response
|
||||
--------
|
||||
|
||||
.. rest_parameters:: parameters.yaml
|
||||
|
||||
- region: region
|
||||
- id: region_id_body
|
||||
- name: region_name
|
||||
- project_id: project_id
|
||||
- cells: cells
|
||||
- variables: variables
|
||||
|
||||
**Example List Regions**
|
||||
|
||||
..literalinclude:: ../../doc/api_samples/regions/regions-list-resp.json
|
||||
:language: javascript
|
||||
|
||||
**Example Unexpected Error **
|
||||
|
||||
..literalinclude:: ../../doc/api_samples/errors/errors-unexpected-resp.json
|
||||
:language: javascript
|
||||
|
||||
Update Region
|
||||
=============
|
||||
|
||||
.. rest_method:: PUT /v1/regions/{region_id}
|
||||
|
||||
Update an existing region
|
||||
|
||||
Normal response codes: OK(200)
|
||||
|
||||
Error response codes: invalid request(400), region not found(404), validation exception(405)
|
||||
|
||||
Request
|
||||
-------
|
||||
|
||||
.. rest_parameters:: parameters.yaml
|
||||
|
||||
- id: region_id_body
|
||||
- name: region_name
|
||||
- project_id: project_id
|
||||
- cells: cells
|
||||
- variables: variables
|
||||
- region_id: region_id
|
||||
|
||||
Required Header
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
- Content-Type: Content_Type
|
||||
- X-Auth-Token: X-Auth-Token
|
||||
- X-Auth-User: X-Auth-User
|
||||
- X-Auth-Project: X-Auth-Project
|
||||
|
||||
**Example Update Region** (TO-DO)
|
||||
|
||||
..literalinclude:: ../../doc/api_samples/regions/regions-update-req.json
|
||||
:language: javascript
|
||||
|
||||
Response
|
||||
--------
|
||||
|
||||
- region: region
|
||||
- id: region_id_body
|
||||
- name: region_name
|
||||
- project_id: project_id
|
||||
- cells: cells
|
||||
- variables: variables
|
||||
|
||||
**Example Update Region** (TO-DO)
|
||||
|
||||
..literalinclude:: ../../doc/api_samples/regions/regions-update-resp.json
|
||||
:language: javascript
|
||||
|
||||
Update Region Data
|
||||
==================
|
||||
|
||||
.. rest_method:: PUT /v1/regions/{region_id}/variables
|
||||
|
||||
Update user defined variables for the region
|
||||
|
||||
Normal response codes: OK(200)
|
||||
|
||||
Error response codes: invalid request(400), region not found(404), validation exception(405)
|
||||
|
||||
Request
|
||||
-------
|
||||
|
||||
.. rest_parameters:: parameters.yaml
|
||||
|
||||
- key: key
|
||||
- value: value
|
||||
- region_id: region_id
|
||||
|
||||
Required Header
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
- Content-Type: Content_Type
|
||||
- X-Auth-Token: X-Auth-Token
|
||||
- X-Auth-User: X-Auth-User
|
||||
- X-Auth-Project: X-Auth-Project
|
||||
|
||||
**Example Update Region Data** (TO-DO)
|
||||
|
||||
..literalinclude:: ../../doc/api_samples/regions/regions-upadate—data-req.json
|
||||
:language: javascript
|
||||
|
||||
Response
|
||||
--------
|
||||
|
||||
- key: key
|
||||
- value: value
|
||||
|
||||
**Example Update Region Data** (TO-DO)
|
||||
|
||||
..literalinclude:: ../../doc/api_samples/regions/regions-update-data-resp.json
|
||||
:language: javascript
|
||||
|
||||
Delete Region
|
||||
==============
|
||||
|
||||
.. rest_method:: DELETE /v1/regions/{region_id}
|
||||
|
||||
Deletes an existing record of a Region
|
||||
|
||||
Normal response codes: OK(200)
|
||||
|
||||
Error response codes: invalid request(400), region not found(404)
|
||||
|
||||
Request
|
||||
-------
|
||||
|
||||
.. rest_parameters:: parameters.yaml
|
||||
|
||||
- region_id: region_id
|
||||
|
||||
Required Header
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
- Content-Type: Content_Type
|
||||
- X-Auth-Token: X-Auth-Token
|
||||
- X-Auth-User: X-Auth-User
|
||||
- X-Auth-Project: X-Auth-Project
|
||||
|
||||
Response
|
||||
--------
|
||||
|
||||
No body content is returned on a successful DELETE
|
||||
|
||||
Delete Region Data
|
||||
==================
|
||||
|
||||
.. rest_method:: DELETE /v1/regions/{region_id}/variables
|
||||
|
||||
Delete existing key/value variables for the region
|
||||
|
||||
Normal response codes: OK(200)
|
||||
|
||||
Error response codes: invalid request(400), region not found(404) validation exception(405)
|
||||
|
||||
Request
|
||||
-------
|
||||
|
||||
.. rest_parameters:: parameters.yaml
|
||||
|
||||
- region_id: region_id
|
||||
|
||||
Required Header
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
- Content-Type: Content_Type
|
||||
- X-Auth-Token: X-Auth-Token
|
||||
- X-Auth-User: X-Auth-User
|
||||
- X-Auth-Project: X-Auth-Project
|
||||
|
||||
Response
|
||||
--------
|
||||
|
||||
No body content is returned on a successful DELETE
|
@ -1 +0,0 @@
|
||||
docker.io [platform:dpkg]
|
@ -1,19 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import pbr.version
|
||||
|
||||
|
||||
__version__ = pbr.version.VersionInfo(
|
||||
'craton').version_string()
|
@ -1,19 +0,0 @@
|
||||
"""oslo.i18n integration module.
|
||||
|
||||
See http://docs.openstack.org/developer/oslo.i18n/usage.html
|
||||
|
||||
"""
|
||||
|
||||
import oslo_i18n
|
||||
|
||||
|
||||
_translators = oslo_i18n.TranslatorFactory(domain='craton')
|
||||
|
||||
# The primary translation function using the well-known name "_"
|
||||
_ = _translators.primary
|
||||
|
||||
# The contextual translation function using the name "_C"
|
||||
_C = _translators.contextual_form
|
||||
|
||||
# The plural translation function using the name "_P"
|
||||
_P = _translators.plural_form
|
@ -1,75 +0,0 @@
|
||||
import os
|
||||
from paste import deploy
|
||||
from flask import Flask
|
||||
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
|
||||
from craton.api import v1
|
||||
from craton.util import JSON_KWARGS
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
api_opts = [
|
||||
cfg.StrOpt('api_paste_config',
|
||||
default="api-paste.ini",
|
||||
help="Configuration file for API service."),
|
||||
cfg.StrOpt('paste_pipeline',
|
||||
default="local-auth",
|
||||
choices=["local-auth", "keystone-auth"],
|
||||
help="""\
|
||||
The name of the Paste pipeline to use for Craton.
|
||||
|
||||
Pipelines are organized according to authentication scheme. The available
|
||||
choices are:
|
||||
|
||||
- ``local-auth`` (the default) Uses Craton's default authentication and
|
||||
authorization scheme
|
||||
- ``keystone-auth`` Uses Keystone for identity, authentication, and
|
||||
authorization
|
||||
"""),
|
||||
cfg.StrOpt('host',
|
||||
default="127.0.0.1",
|
||||
help="API host IP"),
|
||||
cfg.IntOpt('port',
|
||||
default=5000,
|
||||
help="API port to use.")
|
||||
]
|
||||
|
||||
CONF = cfg.CONF
|
||||
opt_group = cfg.OptGroup(name='api',
|
||||
title='Craton API service group options')
|
||||
CONF.register_group(opt_group)
|
||||
CONF.register_opts(api_opts, opt_group)
|
||||
|
||||
|
||||
def create_app(global_config, **local_config):
|
||||
return setup_app()
|
||||
|
||||
|
||||
def setup_app(config=None):
|
||||
app = Flask(__name__, static_folder=None)
|
||||
app.config.update(
|
||||
PROPAGATE_EXCEPTIONS=True,
|
||||
RESTFUL_JSON=JSON_KWARGS,
|
||||
)
|
||||
app.register_blueprint(v1.bp, url_prefix='/v1')
|
||||
return app
|
||||
|
||||
|
||||
def load_app():
|
||||
cfg_file = None
|
||||
cfg_path = CONF.api.api_paste_config
|
||||
paste_pipeline = CONF.api.paste_pipeline
|
||||
if not os.path.isabs(cfg_path):
|
||||
cfg_file = CONF.find_file(cfg_path)
|
||||
elif os.path.exists(cfg_path):
|
||||
cfg_file = cfg_path
|
||||
|
||||
if not cfg_file:
|
||||
raise cfg.ConfigFilesNotFoundError([cfg.CONF.api.api_paste_config])
|
||||
LOG.info("Loading craton-api with pipeline %(pipeline)s and WSGI config:"
|
||||
"%(conf)s", {'conf': cfg_file, 'pipeline': paste_pipeline})
|
||||
return deploy.loadapp("config:%s" % cfg_file, name=paste_pipeline)
|
@ -1,136 +0,0 @@
|
||||
from oslo_middleware import base
|
||||
from oslo_middleware import request_id
|
||||
from oslo_context import context
|
||||
from oslo_log import log
|
||||
from oslo_utils import uuidutils
|
||||
|
||||
from craton.db import api as dbapi
|
||||
from craton import exceptions
|
||||
from craton.util import handle_all_exceptions_decorator
|
||||
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
||||
class RequestContext(context.RequestContext):
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self.using_keystone = kwargs.pop('using_keystone', False)
|
||||
self.token_info = kwargs.pop('token_info', None)
|
||||
super(RequestContext, self).__init__(**kwargs)
|
||||
|
||||
|
||||
class ContextMiddleware(base.Middleware):
|
||||
|
||||
def make_context(self, request, *args, **kwargs):
|
||||
req_id = request.environ.get(request_id.ENV_REQUEST_ID)
|
||||
kwargs.setdefault('request_id', req_id)
|
||||
|
||||
# TODO(sulo): Insert Craton specific context here if needed,
|
||||
# for now we are using generic context object.
|
||||
ctxt = RequestContext(*args, **kwargs)
|
||||
request.environ['context'] = ctxt
|
||||
return ctxt
|
||||
|
||||
|
||||
class NoAuthContextMiddleware(ContextMiddleware):
|
||||
|
||||
def __init__(self, application):
|
||||
self.application = application
|
||||
|
||||
@handle_all_exceptions_decorator
|
||||
def process_request(self, request):
|
||||
# Simply insert some dummy context info
|
||||
self.make_context(
|
||||
request,
|
||||
auth_token='noauth-token',
|
||||
user='noauth-user',
|
||||
tenant=None,
|
||||
is_admin=True,
|
||||
is_admin_project=True,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def factory(cls, global_config, **local_config):
|
||||
def _factory(application):
|
||||
return cls(application)
|
||||
|
||||
return _factory
|
||||
|
||||
|
||||
class LocalAuthContextMiddleware(ContextMiddleware):
|
||||
|
||||
def __init__(self, application):
|
||||
self.application = application
|
||||
|
||||
@handle_all_exceptions_decorator
|
||||
def process_request(self, request):
|
||||
headers = request.headers
|
||||
project_id = headers.get('X-Auth-Project')
|
||||
if not uuidutils.is_uuid_like(project_id):
|
||||
raise exceptions.AuthenticationError(
|
||||
message="Project ID ('{}') is not a valid UUID".format(
|
||||
project_id
|
||||
)
|
||||
)
|
||||
|
||||
ctx = self.make_context(
|
||||
request,
|
||||
auth_token=headers.get('X-Auth-Token', None),
|
||||
user=headers.get('X-Auth-User', None),
|
||||
tenant=project_id,
|
||||
)
|
||||
|
||||
# NOTE(sulo): this means every api call hits the db
|
||||
# at least once for auth. Better way to handle this?
|
||||
try:
|
||||
user_info = dbapi.get_user_info(ctx,
|
||||
headers.get('X-Auth-User', None))
|
||||
if user_info.api_key != headers.get('X-Auth-Token', None):
|
||||
raise exceptions.AuthenticationError
|
||||
if user_info.is_root:
|
||||
ctx.is_admin = True
|
||||
ctx.is_admin_project = True
|
||||
elif user_info.is_admin:
|
||||
ctx.is_admin = True
|
||||
ctx.is_admin_project = False
|
||||
else:
|
||||
ctx.is_admin = False
|
||||
ctx.is_admin_project = False
|
||||
except exceptions.NotFound:
|
||||
raise exceptions.AuthenticationError
|
||||
|
||||
@classmethod
|
||||
def factory(cls, global_config, **local_config):
|
||||
def _factory(application):
|
||||
return cls(application)
|
||||
return _factory
|
||||
|
||||
|
||||
class KeystoneContextMiddleware(ContextMiddleware):
|
||||
|
||||
@handle_all_exceptions_decorator
|
||||
def process_request(self, request):
|
||||
headers = request.headers
|
||||
environ = request.environ
|
||||
if headers.get('X-Identity-Status', '').lower() != 'confirmed':
|
||||
raise exceptions.AuthenticationError
|
||||
|
||||
token_info = environ['keystone.token_info']['token']
|
||||
roles = (role['name'] for role in token_info['roles'])
|
||||
self.make_context(
|
||||
request,
|
||||
auth_token=headers.get('X-Auth-Token'),
|
||||
is_admin=any(name == 'admin' for name in roles),
|
||||
is_admin_project=environ['HTTP_X_IS_ADMIN_PROJECT'],
|
||||
user=token_info['user']['name'],
|
||||
tenant=token_info['project']['id'],
|
||||
using_keystone=True,
|
||||
token_info=token_info,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def factory(cls, global_config, **local_config):
|
||||
def _factory(application):
|
||||
return cls(application)
|
||||
return _factory
|
@ -1,21 +0,0 @@
|
||||
from flask import Blueprint
|
||||
import flask_restful as restful
|
||||
|
||||
from craton.api.v1.routes import routes
|
||||
from craton.util import handle_all_exceptions
|
||||
|
||||
|
||||
class CratonApi(restful.Api):
|
||||
|
||||
def error_router(self, _, e):
|
||||
return self.handle_error(e)
|
||||
|
||||
def handle_error(self, e):
|
||||
return handle_all_exceptions(e)
|
||||
|
||||
|
||||
bp = Blueprint('v1', __name__)
|
||||
api = CratonApi(bp, catch_all_404s=False)
|
||||
|
||||
for route in routes:
|
||||
api.add_resource(route.pop('resource'), *route.pop('urls'), **route)
|
@ -1,85 +0,0 @@
|
||||
import functools
|
||||
import re
|
||||
import urllib.parse as urllib
|
||||
|
||||
import flask
|
||||
import flask_restful as restful
|
||||
|
||||
from craton.api.v1.validators import ensure_project_exists
|
||||
from craton.api.v1.validators import request_validate
|
||||
from craton.api.v1.validators import response_filter
|
||||
|
||||
|
||||
SORT_KEY_SPLITTER = re.compile('[ ,]')
|
||||
|
||||
|
||||
class Resource(restful.Resource):
|
||||
method_decorators = [request_validate, ensure_project_exists,
|
||||
response_filter]
|
||||
|
||||
|
||||
def pagination_context(function):
|
||||
@functools.wraps(function)
|
||||
def wrapper(self, context, request_args):
|
||||
pagination_parameters = {
|
||||
'limit': limit_from(request_args),
|
||||
'marker': request_args.pop('marker', None),
|
||||
}
|
||||
sort_keys = request_args.get('sort_keys')
|
||||
if sort_keys is not None:
|
||||
request_args['sort_keys'] = SORT_KEY_SPLITTER.split(sort_keys)
|
||||
return function(self, context, request_args=request_args,
|
||||
pagination_params=pagination_parameters)
|
||||
return wrapper
|
||||
|
||||
|
||||
def limit_from(filters, minimum=10, default=30, maximum=100):
|
||||
"""Retrieve the limit from query filters."""
|
||||
limit_str = filters.pop('limit', None)
|
||||
|
||||
if limit_str is None:
|
||||
return default
|
||||
|
||||
limit = int(limit_str)
|
||||
|
||||
# NOTE(sigmavirus24): If our limit falls within in our constraints, just
|
||||
# return that
|
||||
if minimum <= limit <= maximum:
|
||||
return limit
|
||||
|
||||
if limit < minimum:
|
||||
return minimum
|
||||
|
||||
# NOTE(sigmavirus24): If our limit isn't within the constraints, and it
|
||||
# isn't too small, then it must be too big. In that case, let's just
|
||||
# return the maximum.
|
||||
return maximum
|
||||
|
||||
|
||||
def links_from(link_params):
|
||||
"""Generate the list of hypermedia link relations from their parameters.
|
||||
|
||||
This uses the request thread-local to determine the endpoint and generate
|
||||
URLs from that.
|
||||
|
||||
:param dict link_params:
|
||||
A dictionary mapping the relation name to the query parameters.
|
||||
:returns:
|
||||
List of dictionaries to represent hypermedia link relations.
|
||||
:rtype:
|
||||
list
|
||||
"""
|
||||
links = []
|
||||
relations = ["first", "prev", "self", "next"]
|
||||
base_url = flask.request.base_url
|
||||
|
||||
for relation in relations:
|
||||
query_params = link_params.get(relation)
|
||||
if not query_params:
|
||||
continue
|
||||
link_rel = {
|
||||
"rel": relation,
|
||||
"href": base_url + "?" + urllib.urlencode(query_params),
|
||||
}
|
||||
links.append(link_rel)
|
||||
return links
|
@ -1,105 +0,0 @@
|
||||
from collections import OrderedDict
|
||||
from operator import attrgetter
|
||||
from oslo_serialization import jsonutils
|
||||
from oslo_log import log
|
||||
|
||||
from craton.api.v1 import base
|
||||
from craton import db as dbapi
|
||||
from craton import exceptions
|
||||
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
||||
class AnsibleInventory(base.Resource):
|
||||
|
||||
def get_hierarchy(self, devices):
|
||||
regions = set()
|
||||
cells = set()
|
||||
labels = set()
|
||||
|
||||
for device in devices:
|
||||
if device.region not in regions:
|
||||
regions.add(device.region)
|
||||
|
||||
if device.cell:
|
||||
if device.cell not in cells:
|
||||
cells.add(device.cell)
|
||||
for label in device.labels:
|
||||
if label not in labels:
|
||||
labels.add(label)
|
||||
|
||||
regions = sorted(regions, key=attrgetter('name'))
|
||||
cells = sorted(cells, key=attrgetter('name'))
|
||||
labels = sorted(labels, key=attrgetter('label'))
|
||||
devices = sorted(devices, key=attrgetter('ip_address'))
|
||||
return regions, cells, labels, devices
|
||||
|
||||
def generate_ansible_inventory(self, hosts):
|
||||
"""Generate and return Ansible inventory in json format
|
||||
for hosts given by provided filters.
|
||||
"""
|
||||
regions, cells, labels, hosts = self.get_hierarchy(hosts)
|
||||
hosts_set = set(hosts)
|
||||
# Set group 'all' and set '_meta'
|
||||
inventory = OrderedDict(
|
||||
[('all', {'hosts': []}),
|
||||
('_meta', {'hostvars': OrderedDict()})]
|
||||
)
|
||||
|
||||
for host in hosts:
|
||||
ip = str(host.ip_address)
|
||||
inventory['all']['hosts'].append(ip)
|
||||
inventory['_meta']['hostvars'][ip] = host.resolved
|
||||
|
||||
def matching_hosts(obj):
|
||||
return sorted(
|
||||
[str(device.ip_address) for device in obj.devices
|
||||
if device in hosts_set])
|
||||
|
||||
# Group hosts by label
|
||||
# TODO(sulo): make sure we have a specified label to
|
||||
# identify host group. Fix this after label refractoring.
|
||||
for label in labels:
|
||||
inventory[label.label] = {
|
||||
'hosts': matching_hosts(label),
|
||||
'vars': label.variables
|
||||
}
|
||||
|
||||
for cell in cells:
|
||||
inventory['%s-%s' % (cell.region.name, cell.name)] = {
|
||||
'hosts': matching_hosts(cell),
|
||||
'vars': cell.variables
|
||||
}
|
||||
|
||||
for region in regions:
|
||||
ch = ['%s-%s' % (region.name, cell.name) for cell in region.cells]
|
||||
inventory['%s' % region.name] = {
|
||||
'children': ch,
|
||||
'vars': region.variables
|
||||
}
|
||||
return inventory
|
||||
|
||||
def get(self, context, request_args):
|
||||
region_id = request_args["region_id"]
|
||||
cell_id = request_args["cell_id"]
|
||||
|
||||
filters = {}
|
||||
if region_id:
|
||||
filters['region_id'] = region_id
|
||||
|
||||
# TODO(sulo): allow other filters based on services
|
||||
if cell_id:
|
||||
filters['cell_id'] = cell_id
|
||||
|
||||
try:
|
||||
hosts_obj = dbapi.hosts_get_all(context, filters)
|
||||
except exceptions.NotFound:
|
||||
return self.error_response(404, 'Not Found')
|
||||
except Exception as err:
|
||||
LOG.error("Error during host get: %s" % err)
|
||||
return self.error_response(500, 'Unknown Error')
|
||||
|
||||
_inventory = self.generate_ansible_inventory(hosts_obj)
|
||||
inventory = jsonutils.to_primitive(_inventory)
|
||||
return inventory, 200, None
|
@ -1,65 +0,0 @@
|
||||
from oslo_serialization import jsonutils
|
||||
from oslo_log import log
|
||||
|
||||
from craton.api import v1
|
||||
from craton.api.v1 import base
|
||||
from craton.api.v1.resources import utils
|
||||
from craton import db as dbapi
|
||||
from craton import util
|
||||
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
||||
class Cells(base.Resource):
|
||||
|
||||
@base.pagination_context
|
||||
def get(self, context, request_args, pagination_params):
|
||||
"""Get all cells, with optional filtering."""
|
||||
details = request_args.get("details")
|
||||
|
||||
cells_obj, link_params = dbapi.cells_get_all(
|
||||
context, request_args, pagination_params,
|
||||
)
|
||||
if details:
|
||||
cells_obj = [utils.get_resource_with_vars(request_args, cell)
|
||||
for cell in cells_obj]
|
||||
|
||||
links = base.links_from(link_params)
|
||||
response_body = {'cells': cells_obj, 'links': links}
|
||||
return jsonutils.to_primitive(response_body), 200, None
|
||||
|
||||
def post(self, context, request_data):
|
||||
"""Create a new cell."""
|
||||
json = util.copy_project_id_into_json(context, request_data)
|
||||
cell_obj = dbapi.cells_create(context, json)
|
||||
cell = jsonutils.to_primitive(cell_obj)
|
||||
if 'variables' in json:
|
||||
cell["variables"] = jsonutils.to_primitive(cell_obj.variables)
|
||||
else:
|
||||
cell["variables"] = {}
|
||||
|
||||
location = v1.api.url_for(
|
||||
CellById, id=cell_obj.id, _external=True
|
||||
)
|
||||
headers = {'Location': location}
|
||||
|
||||
return cell, 201, headers
|
||||
|
||||
|
||||
class CellById(base.Resource):
|
||||
|
||||
def get(self, context, id, request_args):
|
||||
cell_obj = dbapi.cells_get_by_id(context, id)
|
||||
cell = utils.get_resource_with_vars(request_args, cell_obj)
|
||||
return cell, 200, None
|
||||
|
||||
def put(self, context, id, request_data):
|
||||
"""Update existing cell."""
|
||||
cell_obj = dbapi.cells_update(context, id, request_data)
|
||||
return jsonutils.to_primitive(cell_obj), 200, None
|
||||
|
||||
def delete(self, context, id):
|
||||
"""Delete existing cell."""
|
||||
dbapi.cells_delete(context, id)
|
||||
return None, 204, None
|
@ -1,82 +0,0 @@
|
||||
from oslo_serialization import jsonutils
|
||||
from oslo_log import log
|
||||
|
||||
from craton.api import v1
|
||||
from craton.api.v1 import base
|
||||
from craton.api.v1.resources import utils
|
||||
from craton import db as dbapi
|
||||
from craton import util
|
||||
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
||||
class Clouds(base.Resource):
|
||||
|
||||
@base.pagination_context
|
||||
def get(self, context, request_args, pagination_params):
|
||||
"""Get cloud(s) for the project. Get cloud details if
|
||||
for a particular cloud.
|
||||
"""
|
||||
cloud_id = request_args.get("id")
|
||||
cloud_name = request_args.get("name")
|
||||
details = request_args.get("details")
|
||||
|
||||
if not (cloud_id or cloud_name):
|
||||
# Get all clouds for this project
|
||||
clouds_obj, link_params = dbapi.clouds_get_all(
|
||||
context, request_args, pagination_params,
|
||||
)
|
||||
if details:
|
||||
clouds_obj = [utils.get_resource_with_vars(request_args, c)
|
||||
for c in clouds_obj]
|
||||
else:
|
||||
if cloud_name:
|
||||
cloud_obj = dbapi.clouds_get_by_name(context, cloud_name)
|
||||
cloud_obj.data = cloud_obj.variables
|
||||
|
||||
if cloud_id:
|
||||
cloud_obj = dbapi.clouds_get_by_id(context, cloud_id)
|
||||
cloud_obj.data = cloud_obj.variables
|
||||
|
||||
clouds_obj = [cloud_obj]
|
||||
link_params = {}
|
||||
links = base.links_from(link_params)
|
||||
response_body = {'clouds': clouds_obj, 'links': links}
|
||||
return jsonutils.to_primitive(response_body), 200, None
|
||||
|
||||
def post(self, context, request_data):
|
||||
"""Create a new cloud."""
|
||||
json = util.copy_project_id_into_json(context, request_data)
|
||||
cloud_obj = dbapi.clouds_create(context, json)
|
||||
cloud = jsonutils.to_primitive(cloud_obj)
|
||||
if 'variables' in json:
|
||||
cloud["variables"] = jsonutils.to_primitive(cloud_obj.variables)
|
||||
else:
|
||||
cloud["variables"] = {}
|
||||
|
||||
location = v1.api.url_for(
|
||||
CloudsById, id=cloud_obj.id, _external=True
|
||||
)
|
||||
headers = {'Location': location}
|
||||
|
||||
return cloud, 201, headers
|
||||
|
||||
|
||||
class CloudsById(base.Resource):
|
||||
|
||||
def get(self, context, id):
|
||||
cloud_obj = dbapi.clouds_get_by_id(context, id)
|
||||
cloud = jsonutils.to_primitive(cloud_obj)
|
||||
cloud['variables'] = jsonutils.to_primitive(cloud_obj.variables)
|
||||
return cloud, 200, None
|
||||
|
||||
def put(self, context, id, request_data):
|
||||
"""Update existing cloud."""
|
||||
cloud_obj = dbapi.clouds_update(context, id, request_data)
|
||||
return jsonutils.to_primitive(cloud_obj), 200, None
|
||||
|
||||
def delete(self, context, id):
|
||||
"""Delete existing cloud."""
|
||||
dbapi.clouds_delete(context, id)
|
||||
return None, 204, None
|
@ -1,49 +0,0 @@
|
||||
from oslo_serialization import jsonutils
|
||||
from oslo_log import log
|
||||
|
||||
from craton.api.v1 import base
|
||||
from craton.api.v1.resources import utils
|
||||
from craton import exceptions
|
||||
from craton import db as dbapi
|
||||
from craton.db.sqlalchemy import models
|
||||
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
||||
class Devices(base.Resource):
|
||||
|
||||
@base.pagination_context
|
||||
def get(self, context, request_args, pagination_params):
|
||||
"""Get all devices, with optional filtering."""
|
||||
details = request_args.get("details")
|
||||
device_objs, link_params = dbapi.devices_get_all(
|
||||
context, request_args, pagination_params,
|
||||
)
|
||||
links = base.links_from(link_params)
|
||||
|
||||
devices = {"hosts": [], "network-devices": []}
|
||||
for device_obj in device_objs:
|
||||
if details:
|
||||
device = utils.get_resource_with_vars(request_args,
|
||||
device_obj)
|
||||
else:
|
||||
device = jsonutils.to_primitive(device_obj)
|
||||
|
||||
utils.add_up_link(context, device)
|
||||
|
||||
if isinstance(device_obj, models.Host):
|
||||
devices["hosts"].append(device)
|
||||
elif isinstance(device_obj, models.NetworkDevice):
|
||||
devices["network-devices"].append(device)
|
||||
else:
|
||||
LOG.error(
|
||||
"The device is of unknown type: '%s'", device_obj
|
||||
)
|
||||
raise exceptions.UnknownException
|
||||
|
||||
response_body = jsonutils.to_primitive(
|
||||
{'devices': devices, 'links': links}
|
||||
)
|
||||
|
||||
return response_body, 200, None
|
@ -1,104 +0,0 @@
|
||||
from oslo_serialization import jsonutils
|
||||
from oslo_log import log
|
||||
|
||||
from craton.api import v1
|
||||
from craton.api.v1 import base
|
||||
from craton.api.v1.resources import utils
|
||||
from craton import db as dbapi
|
||||
from craton import util
|
||||
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
||||
class Hosts(base.Resource):
|
||||
|
||||
@base.pagination_context
|
||||
def get(self, context, request_args, pagination_params):
|
||||
"""Get all hosts for region, with optional filtering."""
|
||||
details = request_args.get("details")
|
||||
hosts_obj, link_params = dbapi.hosts_get_all(
|
||||
context, request_args, pagination_params,
|
||||
)
|
||||
if details:
|
||||
hosts_obj = [utils.get_resource_with_vars(request_args, h)
|
||||
for h in hosts_obj]
|
||||
|
||||
links = base.links_from(link_params)
|
||||
response_body = jsonutils.to_primitive(
|
||||
{'hosts': hosts_obj, 'links': links}
|
||||
)
|
||||
|
||||
for host in response_body["hosts"]:
|
||||
utils.add_up_link(context, host)
|
||||
|
||||
return response_body, 200, None
|
||||
|
||||
def post(self, context, request_data):
|
||||
"""Create a new host."""
|
||||
json = util.copy_project_id_into_json(context, request_data)
|
||||
host_obj = dbapi.hosts_create(context, json)
|
||||
host = jsonutils.to_primitive(host_obj)
|
||||
if 'variables' in json:
|
||||
host["variables"] = jsonutils.to_primitive(host_obj.variables)
|
||||
else:
|
||||
host["variables"] = {}
|
||||
|
||||
utils.add_up_link(context, host)
|
||||
|
||||
location = v1.api.url_for(
|
||||
HostById, id=host_obj.id, _external=True
|
||||
)
|
||||
headers = {'Location': location}
|
||||
|
||||
return host, 201, headers
|
||||
|
||||
|
||||
class HostById(base.Resource):
|
||||
|
||||
def get(self, context, id, request_args):
|
||||
"""Get host by given id"""
|
||||
host_obj = dbapi.hosts_get_by_id(context, id)
|
||||
host = utils.get_resource_with_vars(request_args, host_obj)
|
||||
|
||||
utils.add_up_link(context, host)
|
||||
|
||||
return host, 200, None
|
||||
|
||||
def put(self, context, id, request_data):
|
||||
"""Update existing host data, or create if it does not exist."""
|
||||
host_obj = dbapi.hosts_update(context, id, request_data)
|
||||
|
||||
host = jsonutils.to_primitive(host_obj)
|
||||
|
||||
utils.add_up_link(context, host)
|
||||
|
||||
return host, 200, None
|
||||
|
||||
def delete(self, context, id):
|
||||
"""Delete existing host."""
|
||||
dbapi.hosts_delete(context, id)
|
||||
return None, 204, None
|
||||
|
||||
|
||||
class HostsLabels(base.Resource):
|
||||
|
||||
def get(self, context, id):
|
||||
"""Get labels for given host device."""
|
||||
host_obj = dbapi.hosts_get_by_id(context, id)
|
||||
response = {"labels": list(host_obj.labels)}
|
||||
return response, 200, None
|
||||
|
||||
def put(self, context, id, request_data):
|
||||
"""
|
||||
Update existing device label entirely, or add if it does
|
||||
not exist.
|
||||
"""
|
||||
resp = dbapi.hosts_labels_update(context, id, request_data)
|
||||
response = {"labels": list(resp.labels)}
|
||||
return response, 200, None
|
||||
|
||||
def delete(self, context, id, request_data):
|
||||
"""Delete device label entirely."""
|
||||
dbapi.hosts_labels_delete(context, id, request_data)
|
||||
return None, 204, None
|
@ -1,210 +0,0 @@
|
||||
from oslo_serialization import jsonutils
|
||||
from oslo_log import log
|
||||
|
||||
from craton.api import v1
|
||||
from craton.api.v1 import base
|
||||
from craton.api.v1.resources import utils
|
||||
from craton import db as dbapi
|
||||
from craton import util
|
||||
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
||||
class Networks(base.Resource):
|
||||
"""Controller for Networks resources."""
|
||||
|
||||
@base.pagination_context
|
||||
def get(self, context, request_args, pagination_params):
|
||||
"""Get all networks, with optional filtering."""
|
||||
details = request_args.get("details")
|
||||
networks_obj, link_params = dbapi.networks_get_all(
|
||||
context, request_args, pagination_params,
|
||||
)
|
||||
if details:
|
||||
networks_obj = [utils.get_resource_with_vars(request_args, n)
|
||||
for n in networks_obj]
|
||||
|
||||
links = base.links_from(link_params)
|
||||
response_body = {'networks': networks_obj, 'links': links}
|
||||
return jsonutils.to_primitive(response_body), 200, None
|
||||
|
||||
def post(self, context, request_data):
|
||||
"""Create a new network."""
|
||||
json = util.copy_project_id_into_json(context, request_data)
|
||||
network_obj = dbapi.networks_create(context, json)
|
||||
network = jsonutils.to_primitive(network_obj)
|
||||
if 'variables' in json:
|
||||
network["variables"] = jsonutils.to_primitive(
|
||||
network_obj.variables)
|
||||
else:
|
||||
network["variables"] = {}
|
||||
|
||||
location = v1.api.url_for(
|
||||
NetworkById, id=network_obj.id, _external=True
|
||||
)
|
||||
headers = {'Location': location}
|
||||
|
||||
return network, 201, headers
|
||||
|
||||
|
||||
class NetworkById(base.Resource):
|
||||
"""Controller for Networks by ID."""
|
||||
|
||||
def get(self, context, id):
|
||||
"""Get network by given id"""
|
||||
obj = dbapi.networks_get_by_id(context, id)
|
||||
device = jsonutils.to_primitive(obj)
|
||||
device['variables'] = jsonutils.to_primitive(obj.variables)
|
||||
return device, 200, None
|
||||
|
||||
def put(self, context, id, request_data):
|
||||
"""Update existing network values."""
|
||||
net_obj = dbapi.networks_update(context, id, request_data)
|
||||
return jsonutils.to_primitive(net_obj), 200, None
|
||||
|
||||
def delete(self, context, id):
|
||||
"""Delete existing network."""
|
||||
dbapi.networks_delete(context, id)
|
||||
return None, 204, None
|
||||
|
||||
|
||||
class NetworkDevices(base.Resource):
|
||||
"""Controller for Network Device resources."""
|
||||
|
||||
@base.pagination_context
|
||||
def get(self, context, request_args, pagination_params):
|
||||
"""Get all network devices."""
|
||||
details = request_args.get("details")
|
||||
devices_obj, link_params = dbapi.network_devices_get_all(
|
||||
context, request_args, pagination_params,
|
||||
)
|
||||
if details:
|
||||
devices_obj = [utils.get_resource_with_vars(request_args, d)
|
||||
for d in devices_obj]
|
||||
|
||||
links = base.links_from(link_params)
|
||||
response_body = jsonutils.to_primitive(
|
||||
{'network_devices': devices_obj, 'links': links}
|
||||
)
|
||||
|
||||
for device in response_body["network_devices"]:
|
||||
utils.add_up_link(context, device)
|
||||
|
||||
return response_body, 200, None
|
||||
|
||||
def post(self, context, request_data):
|
||||
"""Create a new network device."""
|
||||
json = util.copy_project_id_into_json(context, request_data)
|
||||
obj = dbapi.network_devices_create(context, json)
|
||||
device = jsonutils.to_primitive(obj)
|
||||
if 'variables' in json:
|
||||
device["variables"] = jsonutils.to_primitive(obj.variables)
|
||||
else:
|
||||
device["variables"] = {}
|
||||
|
||||
utils.add_up_link(context, device)
|
||||
|
||||
location = v1.api.url_for(
|
||||
NetworkDeviceById, id=obj.id, _external=True
|
||||
)
|
||||
headers = {'Location': location}
|
||||
|
||||
return device, 201, headers
|
||||
|
||||
|
||||
class NetworkDeviceById(base.Resource):
|
||||
"""Controller for Network Devices by ID."""
|
||||
|
||||
def get(self, context, id, request_args):
|
||||
"""Get network device by given id"""
|
||||
obj = dbapi.network_devices_get_by_id(context, id)
|
||||
obj = utils.format_variables(request_args, obj)
|
||||
device = jsonutils.to_primitive(obj)
|
||||
device['variables'] = jsonutils.to_primitive(obj.vars)
|
||||
|
||||
utils.add_up_link(context, device)
|
||||
|
||||
return device, 200, None
|
||||
|
||||
def put(self, context, id, request_data):
|
||||
"""Update existing device values."""
|
||||
net_obj = dbapi.network_devices_update(context, id, request_data)
|
||||
|
||||
device = jsonutils.to_primitive(net_obj)
|
||||
utils.add_up_link(context, device)
|
||||
|
||||
return device, 200, None
|
||||
|
||||
def delete(self, context, id):
|
||||
"""Delete existing network device."""
|
||||
dbapi.network_devices_delete(context, id)
|
||||
return None, 204, None
|
||||
|
||||
|
||||
class NetworkDeviceLabels(base.Resource):
|
||||
"""Controller for Netowrk Device Labels."""
|
||||
|
||||
def get(self, context, id):
|
||||
"""Get labels for given network device."""
|
||||
obj = dbapi.network_devices_get_by_id(context, id)
|
||||
response = {"labels": list(obj.labels)}
|
||||
return response, 200, None
|
||||
|
||||
def put(self, context, id, request_data):
|
||||
"""Update existing device label. Adds if it does not exist."""
|
||||
resp = dbapi.network_devices_labels_update(context, id, request_data)
|
||||
response = {"labels": list(resp.labels)}
|
||||
return response, 200, None
|
||||
|
||||
def delete(self, context, id, request_data):
|
||||
"""Delete device label(s)."""
|
||||
dbapi.network_devices_labels_delete(context, id, request_data)
|
||||
return None, 204, None
|
||||
|
||||
|
||||
class NetworkInterfaces(base.Resource):
|
||||
"""Controller for Netowrk Interfaces."""
|
||||
|
||||
@base.pagination_context
|
||||
def get(self, context, request_args, pagination_params):
|
||||
"""Get all network interfaces."""
|
||||
interfaces_obj, link_params = dbapi.network_interfaces_get_all(
|
||||
context, request_args, pagination_params,
|
||||
)
|
||||
links = base.links_from(link_params)
|
||||
response_body = {'network_interfaces': interfaces_obj, 'links': links}
|
||||
return jsonutils.to_primitive(response_body), 200, None
|
||||
|
||||
def post(self, context, request_data):
|
||||
"""Create a new network interface."""
|
||||
json = util.copy_project_id_into_json(context, request_data)
|
||||
obj = dbapi.network_interfaces_create(context, json)
|
||||
interface = jsonutils.to_primitive(obj)
|
||||
|
||||
location = v1.api.url_for(
|
||||
NetworkInterfaceById, id=obj.id, _external=True
|
||||
)
|
||||
headers = {'Location': location}
|
||||
|
||||
return interface, 201, headers
|
||||
|
||||
|
||||
class NetworkInterfaceById(base.Resource):
|
||||
|
||||
def get(self, context, id):
|
||||
"""Get network interface by given id"""
|
||||
obj = dbapi.network_interfaces_get_by_id(context, id)
|
||||
interface = jsonutils.to_primitive(obj)
|
||||
interface['variables'] = jsonutils.to_primitive(obj.variables)
|
||||
return interface, 200, None
|
||||
|
||||
def put(self, context, id, request_data):
|
||||
"""Update existing network interface values."""
|
||||
net_obj = dbapi.network_interfaces_update(context, id, request_data)
|
||||
return jsonutils.to_primitive(net_obj), 200, None
|
||||
|
||||
def delete(self, context, id):
|
||||
"""Delete existing network interface."""
|
||||
dbapi.network_interfaces_delete(context, id)
|
||||
return None, 204, None
|
@ -1,81 +0,0 @@
|
||||
from oslo_serialization import jsonutils
|
||||
from oslo_log import log
|
||||
|
||||
from craton.api import v1
|
||||
from craton.api.v1 import base
|
||||
from craton.api.v1.resources import utils
|
||||
from craton import db as dbapi
|
||||
from craton import util
|
||||
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
||||
class Regions(base.Resource):
|
||||
|
||||
@base.pagination_context
|
||||
def get(self, context, request_args, pagination_params):
|
||||
"""Get region(s) for the project. Get region details if
|
||||
for a particular region.
|
||||
"""
|
||||
region_id = request_args.get("id")
|
||||
region_name = request_args.get("name")
|
||||
details = request_args.get("details")
|
||||
|
||||
if not (region_id or region_name):
|
||||
# Get all regions for this tenant
|
||||
regions_obj, link_params = dbapi.regions_get_all(
|
||||
context, request_args, pagination_params,
|
||||
)
|
||||
if details:
|
||||
regions_obj = [utils.get_resource_with_vars(request_args, r)
|
||||
for r in regions_obj]
|
||||
else:
|
||||
if region_name:
|
||||
region_obj = dbapi.regions_get_by_name(context, region_name)
|
||||
region_obj.data = region_obj.variables
|
||||
|
||||
if region_id:
|
||||
region_obj = dbapi.regions_get_by_id(context, region_id)
|
||||
region_obj.data = region_obj.variables
|
||||
|
||||
regions_obj = [region_obj]
|
||||
link_params = {}
|
||||
links = base.links_from(link_params)
|
||||
response_body = {'regions': regions_obj, 'links': links}
|
||||
return jsonutils.to_primitive(response_body), 200, None
|
||||
|
||||
def post(self, context, request_data):
|
||||
"""Create a new region."""
|
||||
json = util.copy_project_id_into_json(context, request_data)
|
||||
region_obj = dbapi.regions_create(context, json)
|
||||
region = jsonutils.to_primitive(region_obj)
|
||||
if 'variables' in json:
|
||||
region["variables"] = jsonutils.to_primitive(region_obj.variables)
|
||||
else:
|
||||
region["variables"] = {}
|
||||
|
||||
location = v1.api.url_for(
|
||||
RegionsById, id=region_obj.id, _external=True
|
||||
)
|
||||
headers = {'Location': location}
|
||||
|
||||
return region, 201, headers
|
||||
|
||||
|
||||
class RegionsById(base.Resource):
|
||||
|
||||
def get(self, context, id, request_args):
|
||||
region_obj = dbapi.regions_get_by_id(context, id)
|
||||
region = utils.get_resource_with_vars(request_args, region_obj)
|
||||
return region, 200, None
|
||||
|
||||
def put(self, context, id, request_data):
|
||||
"""Update existing region."""
|
||||
region_obj = dbapi.regions_update(context, id, request_data)
|
||||
return jsonutils.to_primitive(region_obj), 200, None
|
||||
|
||||
def delete(self, context, id):
|
||||
"""Delete existing region."""
|
||||
dbapi.regions_delete(context, id)
|
||||
return None, 204, None
|
@ -1,67 +0,0 @@
|
||||
from oslo_serialization import jsonutils
|
||||
from oslo_log import log
|
||||
|
||||
from craton.api import v1
|
||||
from craton.api.v1 import base
|
||||
from craton.api.v1.resources import utils
|
||||
from craton import db as dbapi
|
||||
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
||||
class Projects(base.Resource):
|
||||
|
||||
@base.pagination_context
|
||||
def get(self, context, request_args, pagination_params):
|
||||
"""Get all projects. Requires super admin privileges."""
|
||||
project_name = request_args["name"]
|
||||
details = request_args.get("details")
|
||||
|
||||
if project_name:
|
||||
projects_obj, link_params = dbapi.projects_get_by_name(
|
||||
context, project_name, request_args, pagination_params,
|
||||
)
|
||||
else:
|
||||
projects_obj, link_params = dbapi.projects_get_all(
|
||||
context, request_args, pagination_params,
|
||||
)
|
||||
if details:
|
||||
projects_obj = [utils.get_resource_with_vars(request_args, p)
|
||||
for p in projects_obj]
|
||||
|
||||
links = base.links_from(link_params)
|
||||
response_body = {'projects': projects_obj, 'links': links}
|
||||
return jsonutils.to_primitive(response_body), 200, None
|
||||
|
||||
def post(self, context, request_data):
|
||||
"""Create a new project. Requires super admin privileges."""
|
||||
project_obj = dbapi.projects_create(context, request_data)
|
||||
|
||||
location = v1.api.url_for(
|
||||
ProjectById, id=project_obj.id, _external=True
|
||||
)
|
||||
headers = {'Location': location}
|
||||
|
||||
project = jsonutils.to_primitive(project_obj)
|
||||
if 'variables' in request_data:
|
||||
project["variables"] = \
|
||||
jsonutils.to_primitive(project_obj.variables)
|
||||
else:
|
||||
project["variables"] = {}
|
||||
return project, 201, headers
|
||||
|
||||
|
||||
class ProjectById(base.Resource):
|
||||
|
||||
def get(self, context, id):
|
||||
"""Get a project details by id. Requires super admin privileges."""
|
||||
project_obj = dbapi.projects_get_by_id(context, id)
|
||||
project = jsonutils.to_primitive(project_obj)
|
||||
project['variables'] = jsonutils.to_primitive(project_obj.variables)
|
||||
return project, 200, None
|
||||
|
||||
def delete(self, context, id):
|
||||
"""Delete existing project. Requires super admin privileges."""
|
||||
dbapi.projects_delete(context, id)
|
||||
return None, 204, None
|
@ -1,68 +0,0 @@
|
||||
from oslo_serialization import jsonutils
|
||||
from oslo_log import log
|
||||
from oslo_utils import uuidutils
|
||||
|
||||
from craton.api import v1
|
||||
from craton.api.v1 import base
|
||||
from craton import db as dbapi
|
||||
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
||||
class Users(base.Resource):
|
||||
|
||||
@base.pagination_context
|
||||
def get(self, context, request_args, pagination_params):
|
||||
"""Get all users. Requires project admin privileges."""
|
||||
user_id = request_args["id"]
|
||||
user_name = request_args["name"]
|
||||
|
||||
if user_id:
|
||||
user_obj = dbapi.users_get_by_id(context, user_id)
|
||||
user_obj.data = user_obj.variables
|
||||
users_obj = [user_obj]
|
||||
link_params = {}
|
||||
|
||||
if user_name:
|
||||
users_obj, link_params = dbapi.users_get_by_name(
|
||||
context, user_name, request_args, pagination_params,
|
||||
)
|
||||
else:
|
||||
users_obj, link_params = dbapi.users_get_all(
|
||||
context, request_args, pagination_params,
|
||||
)
|
||||
links = base.links_from(link_params)
|
||||
response_body = {'users': users_obj, 'links': links}
|
||||
return jsonutils.to_primitive(response_body), 200, None
|
||||
|
||||
def post(self, context, request_data):
|
||||
"""Create a new user. Requires project admin privileges."""
|
||||
# NOTE(sulo): Instead of using context project_id from
|
||||
# header, here we always ensure, user create gets project_id
|
||||
# from request param.
|
||||
project_id = request_data["project_id"]
|
||||
dbapi.projects_get_by_id(context, project_id)
|
||||
api_key = uuidutils.generate_uuid()
|
||||
request_data["api_key"] = api_key
|
||||
user_obj = dbapi.users_create(context, request_data)
|
||||
|
||||
location = v1.api.url_for(
|
||||
UserById, id=user_obj.id, _external=True
|
||||
)
|
||||
headers = {'Location': location}
|
||||
|
||||
return jsonutils.to_primitive(user_obj), 201, headers
|
||||
|
||||
|
||||
class UserById(base.Resource):
|
||||
|
||||
def get(self, context, id):
|
||||
"""Get a user details by id. Requires project admin privileges."""
|
||||
user_obj = dbapi.users_get_by_id(context, id)
|
||||
return jsonutils.to_primitive(user_obj), 200, None
|
||||
|
||||
def delete(self, context, id):
|
||||
"""Delete existing user. Requires project admin privileges."""
|
||||
dbapi.users_delete(context, id)
|
||||
return None, 204, None
|
@ -1,69 +0,0 @@
|
||||
import binascii
|
||||
import os
|
||||
from flask import url_for
|
||||
from oslo_serialization import jsonutils
|
||||
|
||||
from craton import db as dbapi
|
||||
|
||||
|
||||
def format_variables(args, obj):
|
||||
"""Update resource response with requested type of variables."""
|
||||
if args:
|
||||
resolved_values = args.get("resolved-values", None)
|
||||
else:
|
||||
resolved_values = None
|
||||
|
||||
if resolved_values:
|
||||
obj.vars = obj.resolved
|
||||
else:
|
||||
obj.vars = obj.variables
|
||||
return obj
|
||||
|
||||
|
||||
def get_resource_with_vars(args, obj):
|
||||
"""Get resource in json primitive with variables."""
|
||||
obj = format_variables(args, obj)
|
||||
res = jsonutils.to_primitive(obj)
|
||||
res['variables'] = jsonutils.to_primitive(obj.vars)
|
||||
return res
|
||||
|
||||
|
||||
def get_device_type(context, device_id):
|
||||
device = dbapi.resource_get_by_id(context, "devices", device_id)
|
||||
return device.type
|
||||
|
||||
|
||||
def get_resource_url(resource_type, resource_id):
|
||||
resources = {
|
||||
"cells": "v1.cells_id",
|
||||
"hosts": "v1.hosts_id",
|
||||
"network_devices": "v1.network_devices_id",
|
||||
"regions": "v1.regions_id",
|
||||
}
|
||||
return url_for(resources[resource_type], id=resource_id, _external=True)
|
||||
|
||||
|
||||
def add_up_link(context, device):
|
||||
if device["parent_id"]:
|
||||
device_type = get_device_type(context, device["parent_id"])
|
||||
link_url = get_resource_url(device_type, device["parent_id"])
|
||||
elif device["cell_id"]:
|
||||
link_url = get_resource_url("cells", device["cell_id"])
|
||||
else:
|
||||
link_url = get_resource_url("regions", device["region_id"])
|
||||
|
||||
link = {
|
||||
"href": link_url,
|
||||
"rel": "up",
|
||||
}
|
||||
|
||||
links = device.setdefault("links", [])
|
||||
links.append(link)
|
||||
|
||||
|
||||
def gen_api_key():
|
||||
"""Generates crypto strong 16 bytes api key."""
|
||||
# NOTE(sulo): this implementation is taken from secrets
|
||||
# moudule available in python 3.6
|
||||
tbytes = os.urandom(16)
|
||||
return binascii.hexlify(tbytes).decode('ascii')
|
@ -1,43 +0,0 @@
|
||||
from oslo_serialization import jsonutils
|
||||
from oslo_log import log
|
||||
|
||||
|
||||
from craton.api.v1 import base
|
||||
from craton.api.v1.resources import utils
|
||||
from craton import db as dbapi
|
||||
|
||||
|
||||
# NOTE(thomasem): LOG must exist for craton.api.v1.base module to introspect
|
||||
# and execute this modules LOG.
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
||||
class Variables(base.Resource):
|
||||
|
||||
def get(self, context, resources, id, request_args=None):
|
||||
"""Get variables for given resource."""
|
||||
obj = dbapi.resource_get_by_id(context, resources, id)
|
||||
obj = utils.format_variables(request_args, obj)
|
||||
resp = {"variables": jsonutils.to_primitive(obj.vars)}
|
||||
return resp, 200, None
|
||||
|
||||
def put(self, context, resources, id, request_data):
|
||||
"""
|
||||
Update existing resource variables, or create if it does
|
||||
not exist.
|
||||
"""
|
||||
obj = dbapi.variables_update_by_resource_id(
|
||||
context, resources, id, request_data
|
||||
)
|
||||
resp = {"variables": jsonutils.to_primitive(obj.variables)}
|
||||
return resp, 200, None
|
||||
|
||||
def delete(self, context, resources, id, request_data):
|
||||
"""Delete resource variables."""
|
||||
# NOTE(sulo): this is not that great. Find a better way to do this.
|
||||
# We can pass multiple keys suchs as key1=one key2=two etc. but not
|
||||
# the best way to do this.
|
||||
dbapi.variables_delete_by_resource_id(
|
||||
context, resources, id, request_data
|
||||
)
|
||||
return None, 204, None
|
@ -1,93 +0,0 @@
|
||||
from craton.api.v1.resources import users
|
||||
from craton.api.v1.resources import projects
|
||||
from craton.api.v1.resources import variables
|
||||
|
||||
from craton.api.v1.resources.inventory import ansible_inventory
|
||||
from craton.api.v1.resources.inventory import cells
|
||||
from craton.api.v1.resources.inventory import clouds
|
||||
from craton.api.v1.resources.inventory import devices
|
||||
from craton.api.v1.resources.inventory import hosts
|
||||
from craton.api.v1.resources.inventory import regions
|
||||
from craton.api.v1.resources.inventory import networks
|
||||
|
||||
|
||||
VARS_RESOLVE = ", ".join(map(repr, ("hosts", )))
|
||||
VARS_NOT_RESOLVE = ", ".join(
|
||||
map(repr, ("network-devices", "cells", "regions", "networks", "projects",
|
||||
"clouds"))
|
||||
)
|
||||
|
||||
routes = [
|
||||
dict(resource=ansible_inventory.AnsibleInventory,
|
||||
urls=['/ansible-inventory'],
|
||||
endpoint='ansible_inventory'),
|
||||
dict(resource=devices.Devices,
|
||||
urls=['/devices'],
|
||||
endpoint='devices'),
|
||||
dict(resource=hosts.HostsLabels,
|
||||
urls=['/hosts/<id>/labels'],
|
||||
endpoint='hosts_labels'),
|
||||
dict(resource=hosts.HostById,
|
||||
urls=['/hosts/<id>'],
|
||||
endpoint='hosts_id'),
|
||||
dict(resource=hosts.Hosts,
|
||||
urls=['/hosts'],
|
||||
endpoint='hosts'),
|
||||
dict(resource=regions.Regions,
|
||||
urls=['/regions'],
|
||||
endpoint='regions'),
|
||||
dict(resource=regions.RegionsById,
|
||||
urls=['/regions/<id>'],
|
||||
endpoint='regions_id'),
|
||||
dict(resource=clouds.Clouds,
|
||||
urls=['/clouds'],
|
||||
endpoint='clouds'),
|
||||
dict(resource=clouds.CloudsById,
|
||||
urls=['/clouds/<id>'],
|
||||
endpoint='clouds_id'),
|
||||
dict(resource=cells.CellById,
|
||||
urls=['/cells/<id>'],
|
||||
endpoint='cells_id'),
|
||||
dict(resource=cells.Cells,
|
||||
urls=['/cells'],
|
||||
endpoint='cells'),
|
||||
dict(resource=projects.Projects,
|
||||
urls=['/projects'],
|
||||
endpoint='projects'),
|
||||
dict(resource=projects.ProjectById,
|
||||
urls=['/projects/<id>'],
|
||||
endpoint='projects_id'),
|
||||
dict(resource=users.Users,
|
||||
urls=['/users'],
|
||||
endpoint='users'),
|
||||
dict(resource=users.UserById,
|
||||
urls=['/users/<id>'],
|
||||
endpoint='users_id'),
|
||||
dict(resource=networks.Networks,
|
||||
urls=['/networks'],
|
||||
endpoint='networks'),
|
||||
dict(resource=networks.NetworkById,
|
||||
urls=['/networks/<id>'],
|
||||
endpoint='networks_id'),
|
||||
dict(resource=networks.NetworkInterfaces,
|
||||
urls=['/network-interfaces'],
|
||||
endpoint='network_interfaces'),
|
||||
dict(resource=networks.NetworkInterfaceById,
|
||||
urls=['/network-interfaces/<id>'],
|
||||
endpoint='network_interfaces_id'),
|
||||
dict(resource=networks.NetworkDevices,
|
||||
urls=['/network-devices'],
|
||||
endpoint='network_devices'),
|
||||
dict(resource=networks.NetworkDeviceById,
|
||||
urls=['/network-devices/<id>'],
|
||||
endpoint='network_devices_id'),
|
||||
dict(resource=networks.NetworkDeviceLabels,
|
||||
urls=['/network-devices/<id>/labels'],
|
||||
endpoint='network_devices_labels'),
|
||||
dict(resource=variables.Variables,
|
||||
urls=['/<any({}):resources>/<id>/variables'.format(VARS_RESOLVE)],
|
||||
endpoint='variables_with_resolve'),
|
||||
dict(resource=variables.Variables,
|
||||
urls=['/<any({}):resources>/<id>/variables'.format(VARS_NOT_RESOLVE)],
|
||||
endpoint='variables_without_resolve'),
|
||||
]
|
File diff suppressed because it is too large
Load Diff
@ -1,285 +0,0 @@
|
||||
# The code is auto generated, your change will be overwritten by
|
||||
# code generating.
|
||||
|
||||
from functools import wraps
|
||||
|
||||
from werkzeug.datastructures import MultiDict, Headers
|
||||
from flask import request
|
||||
from jsonschema import Draft4Validator
|
||||
from oslo_log import log
|
||||
|
||||
from craton.api.v1.schemas import filters
|
||||
from craton.api.v1.schemas import validators
|
||||
from craton import db as dbapi
|
||||
from craton import exceptions
|
||||
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
||||
def merge_default(schema, value):
|
||||
# TODO: more types support
|
||||
type_defaults = {
|
||||
'integer': 9573,
|
||||
'string': 'something',
|
||||
'object': {},
|
||||
'array': [],
|
||||
'boolean': False
|
||||
}
|
||||
|
||||
return normalize(schema, value, type_defaults)[0]
|
||||
|
||||
|
||||
def normalize(schema, data, required_defaults=None):
|
||||
|
||||
if required_defaults is None:
|
||||
required_defaults = {}
|
||||
errors = []
|
||||
|
||||
class DataWrapper(object):
|
||||
|
||||
def __init__(self, data):
|
||||
super(DataWrapper, self).__init__()
|
||||
self.data = data
|
||||
|
||||
def get(self, key, default=None):
|
||||
if isinstance(self.data, dict):
|
||||
return self.data.get(key, default)
|
||||
if hasattr(self.data, key):
|
||||
return getattr(self.data, key)
|
||||
else:
|
||||
return default
|
||||
|
||||
def has(self, key):
|
||||
if isinstance(self.data, dict):
|
||||
return key in self.data
|
||||
return hasattr(self.data, key)
|
||||
|
||||
def keys(self):
|
||||
if isinstance(self.data, dict):
|
||||
return self.data.keys()
|
||||
return vars(self.data).keys()
|
||||
|
||||
def _normalize_dict(schema, data):
|
||||
result = {}
|
||||
if not isinstance(data, DataWrapper):
|
||||
data = DataWrapper(data)
|
||||
|
||||
for pattern, _schema in (schema.get('patternProperties', {})).items():
|
||||
if pattern == "^.+":
|
||||
for key in data.keys():
|
||||
result[key] = _normalize(_schema, data.get(key))
|
||||
|
||||
for key, _schema in schema.get('properties', {}).items():
|
||||
# set default
|
||||
type_ = _schema.get('type', 'object')
|
||||
if ('default' not in _schema and
|
||||
key in schema.get('required', []) and
|
||||
type_ in required_defaults):
|
||||
_schema['default'] = required_defaults[type_]
|
||||
|
||||
# get value
|
||||
if data.has(key):
|
||||
result[key] = _normalize(_schema, data.get(key))
|
||||
elif 'default' in _schema:
|
||||
result[key] = _schema['default']
|
||||
elif key in schema.get('required', []):
|
||||
errors.append(dict(name='property_missing',
|
||||
message='`%s` is required' % key))
|
||||
|
||||
for _schema in schema.get('allOf', []):
|
||||
rs_component = _normalize(_schema, data)
|
||||
rs_component.update(result)
|
||||
result = rs_component
|
||||
|
||||
if schema.get('anyOf'):
|
||||
# In case of anyOf simply return data, since we dont
|
||||
# care in normalization of the data as long as
|
||||
# its been verified.
|
||||
result = data.data
|
||||
|
||||
additional_properties_schema = schema.get('additionalProperties',
|
||||
False)
|
||||
if additional_properties_schema:
|
||||
aproperties_set = set(data.keys()) - set(result.keys())
|
||||
for pro in aproperties_set:
|
||||
result[pro] = _normalize(additional_properties_schema,
|
||||
data.get(pro))
|
||||
|
||||
return result
|
||||
|
||||
def _normalize_list(schema, data):
|
||||
result = []
|
||||
if hasattr(data, '__iter__') and not isinstance(data, dict):
|
||||
for item in data:
|
||||
result.append(_normalize(schema.get('items'), item))
|
||||
elif 'default' in schema:
|
||||
result = schema['default']
|
||||
return result
|
||||
|
||||
def _normalize_default(schema, data):
|
||||
if data is None:
|
||||
return schema.get('default')
|
||||
else:
|
||||
return data
|
||||
|
||||
def _normalize(schema, data):
|
||||
if not schema:
|
||||
return None
|
||||
funcs = {
|
||||
'object': _normalize_dict,
|
||||
'array': _normalize_list,
|
||||
'default': _normalize_default,
|
||||
}
|
||||
type_ = schema.get('type', 'object')
|
||||
if type_ not in funcs:
|
||||
type_ = 'default'
|
||||
|
||||
return funcs[type_](schema, data)
|
||||
|
||||
return _normalize(schema, data), errors
|
||||
|
||||
|
||||
class FlaskValidatorAdaptor(object):
|
||||
|
||||
def __init__(self, schema):
|
||||
self.validator = Draft4Validator(schema)
|
||||
|
||||
def type_convert(self, obj):
|
||||
if obj is None:
|
||||
return None
|
||||
if isinstance(obj, (dict, list)) and not isinstance(obj, MultiDict):
|
||||
return obj
|
||||
if isinstance(obj, Headers):
|
||||
obj = MultiDict(obj)
|
||||
result = dict()
|
||||
|
||||
convert_funs = {
|
||||
'integer': lambda v: int(v[0]),
|
||||
'boolean': lambda v: v[0].lower() not in ['n', 'no',
|
||||
'false', '', '0'],
|
||||
'null': lambda v: None,
|
||||
'number': lambda v: float(v[0]),
|
||||
'string': lambda v: v[0]
|
||||
}
|
||||
|
||||
def convert_array(type_, v):
|
||||
func = convert_funs.get(type_, lambda v: v[0])
|
||||
return [func([i]) for i in v]
|
||||
|
||||
for k, values in obj.lists():
|
||||
prop = self.validator.schema['properties'].get(k, {})
|
||||
type_ = prop.get('type')
|
||||
fun = convert_funs.get(type_, lambda v: v[0])
|
||||
if type_ == 'array':
|
||||
item_type = prop.get('items', {}).get('type')
|
||||
result[k] = convert_array(item_type, values)
|
||||
else:
|
||||
result[k] = fun(values)
|
||||
return result
|
||||
|
||||
def validate(self, value):
|
||||
value = self.type_convert(value)
|
||||
errors = sorted(e.message for e in self.validator.iter_errors(value))
|
||||
if errors:
|
||||
msg = "The request included the following errors:\n- {}".format(
|
||||
"\n- ".join(errors)
|
||||
)
|
||||
raise exceptions.BadRequest(message=msg)
|
||||
return merge_default(self.validator.schema, value)
|
||||
|
||||
|
||||
def request_validate(view):
|
||||
|
||||
@wraps(view)
|
||||
def wrapper(*args, **kwargs):
|
||||
endpoint = request.endpoint.partition('.')[-1]
|
||||
# data
|
||||
method = request.method
|
||||
if method == 'HEAD':
|
||||
method = 'GET'
|
||||
locations = validators.get((endpoint, method), {})
|
||||
data_type = {"json": "request_data", "args": "request_args"}
|
||||
for location, schema in locations.items():
|
||||
value = getattr(request, location, MultiDict())
|
||||
validator = FlaskValidatorAdaptor(schema)
|
||||
result = validator.validate(value)
|
||||
LOG.info("Validated request %s: %s" % (location, result))
|
||||
if schema.get("maxProperties") == 0:
|
||||
continue
|
||||
else:
|
||||
kwargs[data_type[location]] = result
|
||||
|
||||
context = request.environ['context']
|
||||
return view(*args, context=context, **kwargs)
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
def ensure_project_exists(view):
|
||||
|
||||
@wraps(view)
|
||||
def wrapper(*args, **kwargs):
|
||||
context = request.environ['context']
|
||||
if context.using_keystone:
|
||||
find_or_create_project(request, context)
|
||||
return view(*args, **kwargs)
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
def response_filter(view):
|
||||
|
||||
@wraps(view)
|
||||
def wrapper(*args, **kwargs):
|
||||
resp = view(*args, **kwargs)
|
||||
|
||||
endpoint = request.endpoint.partition('.')[-1]
|
||||
method = request.method
|
||||
if method == 'HEAD':
|
||||
method = 'GET'
|
||||
try:
|
||||
resp_filter = filters[(endpoint, method)]
|
||||
except KeyError:
|
||||
LOG.error(
|
||||
'"(%(endpoint)s, %(method)s)" is not defined in the response '
|
||||
'filters.',
|
||||
{"endpoint": endpoint, "method": method}
|
||||
)
|
||||
raise exceptions.UnknownException
|
||||
|
||||
body, status, headers = resp
|
||||
|
||||
try:
|
||||
schemas = resp_filter[status]
|
||||
except KeyError:
|
||||
LOG.error(
|
||||
'The status code %(status)d is not defined in the response '
|
||||
'filter "(%(endpoint)s, %(method)s)".',
|
||||
{"status": status, "endpoint": endpoint, "method": method}
|
||||
)
|
||||
raise exceptions.UnknownException
|
||||
|
||||
body, errors = normalize(schemas['schema'], body)
|
||||
if schemas['headers']:
|
||||
headers, header_errors = normalize(
|
||||
{'properties': schemas['headers']}, headers)
|
||||
errors.extend(header_errors)
|
||||
if errors:
|
||||
LOG.error('Expectation Failed: %s', errors)
|
||||
raise exceptions.UnknownException
|
||||
|
||||
return body, status, headers
|
||||
return wrapper
|
||||
|
||||
|
||||
def find_or_create_project(request, context):
|
||||
project_id = context.tenant
|
||||
token_info = context.token_info
|
||||
try:
|
||||
dbapi.projects_get_by_id(context, project_id)
|
||||
except exceptions.NotFound:
|
||||
LOG.info('Adding Project "%s" to projects table', project_id)
|
||||
dbapi.projects_create(context,
|
||||
{'id': project_id,
|
||||
'name': token_info['project']['name']})
|
@ -1,3 +0,0 @@
|
||||
##########
|
||||
Craton CLI
|
||||
##########
|
@ -1,30 +0,0 @@
|
||||
import os
|
||||
import sys
|
||||
from wsgiref import simple_server
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
|
||||
from craton import api
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
def main():
|
||||
logging.register_options(CONF)
|
||||
CONF(sys.argv[1:],
|
||||
project='craton-api',
|
||||
default_config_files=[])
|
||||
logging.setup(CONF, 'craton-api')
|
||||
|
||||
app = api.load_app()
|
||||
host, port = cfg.CONF.api.host, cfg.CONF.api.port
|
||||
srv = simple_server.make_server(host, port, app)
|
||||
LOG.info("Starting API server in PID: %s" % os.getpid())
|
||||
srv.serve_forever()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
@ -1,86 +0,0 @@
|
||||
from oslo_config import cfg
|
||||
|
||||
from craton.db.sqlalchemy import migration
|
||||
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
class DBCommand(object):
|
||||
|
||||
def upgrade(self):
|
||||
migration.upgrade(CONF.command.revision)
|
||||
|
||||
def revision(self):
|
||||
migration.revision(CONF.command.message, CONF.command.autogenerate)
|
||||
|
||||
def stamp(self):
|
||||
migration.stamp(CONF.command.revision)
|
||||
|
||||
def version(self):
|
||||
print(migration.version())
|
||||
|
||||
def create_schema(self):
|
||||
migration.create_schema()
|
||||
|
||||
def bootstrap_project(self):
|
||||
name = 'bootstrap'
|
||||
project = migration.create_bootstrap_project(
|
||||
name,
|
||||
db_uri=CONF.database.connection)
|
||||
user = migration.create_bootstrap_user(
|
||||
project.id,
|
||||
name,
|
||||
db_uri=CONF.database.connection)
|
||||
|
||||
msg = ("\nProjectId: %s\nUsername: %s\nAPIKey: %s"
|
||||
% (user.project_id, user.username, user.api_key))
|
||||
print(msg)
|
||||
|
||||
|
||||
def add_command_parsers(subparsers):
|
||||
command_object = DBCommand()
|
||||
|
||||
parser = subparsers.add_parser(
|
||||
'upgrade',
|
||||
help=("Upgrade the database schema to the latest version. "
|
||||
"Optionally, use --revision to specify an alembic revision "
|
||||
"string to upgrade to."))
|
||||
parser.set_defaults(func=command_object.upgrade)
|
||||
parser.add_argument('--revision', nargs='?')
|
||||
|
||||
parser = subparsers.add_parser('stamp')
|
||||
parser.add_argument('--revision', nargs='?')
|
||||
parser.set_defaults(func=command_object.stamp)
|
||||
|
||||
parser = subparsers.add_parser(
|
||||
'revision',
|
||||
help=("Create a new alembic revision. "
|
||||
"Use --message to set the message string."))
|
||||
parser.add_argument('-m', '--message')
|
||||
parser.add_argument('--autogenerate', action='store_true')
|
||||
parser.set_defaults(func=command_object.revision)
|
||||
|
||||
parser = subparsers.add_parser(
|
||||
'version',
|
||||
help=("Print the current version information and exit."))
|
||||
parser.set_defaults(func=command_object.version)
|
||||
|
||||
parser = subparsers.add_parser(
|
||||
'create_schema',
|
||||
help=("Create the database schema."))
|
||||
parser.set_defaults(func=command_object.create_schema)
|
||||
|
||||
parser = subparsers.add_parser('bootstrap')
|
||||
parser.set_defaults(func=command_object.bootstrap_project)
|
||||
|
||||
|
||||
def main():
|
||||
command_opt = cfg.SubCommandOpt('command',
|
||||
title='Command',
|
||||
help=('Available commands'),
|
||||
handler=add_command_parsers)
|
||||
|
||||
CONF.register_cli_opt(command_opt)
|
||||
CONF(project='craton-api')
|
||||
CONF.command.func()
|
@ -1,84 +0,0 @@
|
||||
import contextlib
|
||||
import signal
|
||||
import sys
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
from oslo_utils import uuidutils
|
||||
from stevedore import driver
|
||||
from taskflow import engines
|
||||
from taskflow.persistence import models
|
||||
|
||||
from craton.workflow import worker
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
# This needs to be a globally accessible (ie: top-level) function, so
|
||||
# flow recovery can execute it to re-create the intended workflows.
|
||||
def workflow_factory(name, *args, **kwargs):
|
||||
mgr = driver.DriverManager(
|
||||
namespace='craton.workflow', name=name,
|
||||
invoke_on_load=True, invoke_args=args, invoke_kwds=kwargs)
|
||||
return mgr.driver.workflow()
|
||||
|
||||
|
||||
def main():
|
||||
logging.register_options(CONF)
|
||||
CONF(sys.argv[1:], project='craton-worker', default_config_files=[])
|
||||
logging.setup(CONF, 'craton')
|
||||
|
||||
persistence, board, conductor = worker.start(CONF)
|
||||
|
||||
def stop(signum, _frame):
|
||||
LOG.info('Caught signal %s, gracefully exiting', signum)
|
||||
conductor.stop()
|
||||
signal.signal(signal.SIGTERM, stop)
|
||||
|
||||
# TODO(gus): eventually feeding in jobs will happen elsewhere and
|
||||
# main() will end here.
|
||||
#
|
||||
# conductor.wait()
|
||||
# sys.exit(0)
|
||||
|
||||
def make_save_book(persistence, job_id,
|
||||
flow_plugin, plugin_args=(), plugin_kwds={}):
|
||||
flow_id = book_id = job_id # Do these need to be different?
|
||||
book = models.LogBook(book_id)
|
||||
detail = models.FlowDetail(flow_id, uuidutils.generate_uuid())
|
||||
book.add(detail)
|
||||
|
||||
factory_args = [flow_plugin] + list(plugin_args)
|
||||
factory_kwargs = plugin_kwds
|
||||
engines.save_factory_details(detail, workflow_factory,
|
||||
factory_args, factory_kwargs)
|
||||
with contextlib.closing(persistence.get_connection()) as conn:
|
||||
conn.save_logbook(book)
|
||||
return book
|
||||
|
||||
# Feed in example task
|
||||
job_uuid = uuidutils.generate_uuid()
|
||||
LOG.debug('Posting job %s', job_uuid)
|
||||
details = {
|
||||
'store': {
|
||||
'foo': 'bar',
|
||||
},
|
||||
}
|
||||
|
||||
job = board.post(
|
||||
job_uuid,
|
||||
book=make_save_book(
|
||||
persistence, job_uuid,
|
||||
'testflow', plugin_kwds=dict(task_delay=2)),
|
||||
details=details)
|
||||
|
||||
# Run forever. TODO(gus): This is what we want to do in production
|
||||
# conductor.wait()
|
||||
job.wait()
|
||||
LOG.debug('Job finished: %s', job.state)
|
||||
conductor.stop()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
@ -1,5 +0,0 @@
|
||||
"""
|
||||
DB abstraction for Craton Inventory
|
||||
"""
|
||||
|
||||
from craton.db.api import * # noqa
|
335
craton/db/api.py
335
craton/db/api.py
@ -1,335 +0,0 @@
|
||||
"""Defines interface for DB access."""
|
||||
|
||||
from collections import namedtuple
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_db import api as db_api
|
||||
|
||||
db_opts = [
|
||||
cfg.StrOpt('db_backend', default='sqlalchemy',
|
||||
help='The backend to use for DB.'),
|
||||
]
|
||||
|
||||
CONF = cfg.CONF
|
||||
CONF.register_opts(db_opts)
|
||||
|
||||
# entrypoint namespace for db backend
|
||||
BACKEND_MAPPING = {'sqlalchemy': 'craton.db.sqlalchemy.api'}
|
||||
IMPL = db_api.DBAPI.from_config(cfg.CONF, backend_mapping=BACKEND_MAPPING,
|
||||
lazy=True)
|
||||
|
||||
|
||||
# Blame supports generic blame tracking for variables
|
||||
# TODO(jimbaker) add additional governance support, such as
|
||||
# versioning, user, notes
|
||||
|
||||
Blame = namedtuple('Blame', ['source', 'variable'])
|
||||
|
||||
|
||||
def devices_get_all(context, filters, pagination_params):
|
||||
"""Get all available devices."""
|
||||
return IMPL.devices_get_all(context, filters, pagination_params)
|
||||
|
||||
|
||||
def get_user_info(context, user):
|
||||
return IMPL.get_user_info(context, user)
|
||||
|
||||
|
||||
def resource_get_by_id(context, resources, resource_id):
|
||||
"""Get resource for the unique resource id."""
|
||||
return IMPL.resource_get_by_id(context, resources, resource_id)
|
||||
|
||||
|
||||
def variables_update_by_resource_id(context, resources, resource_id, data):
|
||||
"""Update/create existing resource's variables."""
|
||||
return IMPL.variables_update_by_resource_id(
|
||||
context,
|
||||
resources,
|
||||
resource_id,
|
||||
data,
|
||||
)
|
||||
|
||||
|
||||
def variables_delete_by_resource_id(context, resources, resource_id, data):
|
||||
"""Delete the existing variables, if present, from resource's data."""
|
||||
return IMPL.variables_delete_by_resource_id(
|
||||
context,
|
||||
resources,
|
||||
resource_id,
|
||||
data,
|
||||
)
|
||||
|
||||
|
||||
# Cells
|
||||
|
||||
def cells_get_all(context, filters, pagination_params):
|
||||
"""Get all available cells."""
|
||||
return IMPL.cells_get_all(context, filters, pagination_params)
|
||||
|
||||
|
||||
def cells_get_by_id(context, cell_id):
|
||||
"""Get cell detail for the unique cell id."""
|
||||
return IMPL.cells_get_by_id(context, cell_id)
|
||||
|
||||
|
||||
def cells_create(context, values):
|
||||
"""Create a new cell."""
|
||||
return IMPL.cells_create(context, values)
|
||||
|
||||
|
||||
def cells_update(context, cell_id, values):
|
||||
"""Update an existing cell."""
|
||||
return IMPL.cells_update(context, cell_id, values)
|
||||
|
||||
|
||||
def cells_delete(context, cell_id):
|
||||
"""Delete an existing cell."""
|
||||
return IMPL.cells_delete(context, cell_id)
|
||||
|
||||
# Regions
|
||||
|
||||
|
||||
def regions_get_all(context, filters, pagination_params):
|
||||
"""Get all available regions."""
|
||||
return IMPL.regions_get_all(context, filters, pagination_params)
|
||||
|
||||
|
||||
def regions_get_by_name(context, name):
|
||||
"""Get cell detail for the region with given name."""
|
||||
return IMPL.regions_get_by_name(context, name)
|
||||
|
||||
|
||||
def regions_get_by_id(context, region_id):
|
||||
"""Get cell detail for the region with given id."""
|
||||
return IMPL.regions_get_by_id(context, region_id)
|
||||
|
||||
|
||||
def regions_create(context, values):
|
||||
"""Create a new region."""
|
||||
return IMPL.regions_create(context, values)
|
||||
|
||||
|
||||
def regions_update(context, region_id, values):
|
||||
"""Update an existing region."""
|
||||
return IMPL.regions_update(context, region_id, values)
|
||||
|
||||
|
||||
def regions_delete(context, region_id):
|
||||
"""Delete an existing region."""
|
||||
return IMPL.regions_delete(context, region_id)
|
||||
|
||||
# Clouds
|
||||
|
||||
|
||||
def clouds_get_all(context, filters, pagination_params):
|
||||
"""Get all available clouds."""
|
||||
return IMPL.clouds_get_all(context, filters, pagination_params)
|
||||
|
||||
|
||||
def clouds_get_by_name(context, name):
|
||||
"""Get clouds with given name."""
|
||||
return IMPL.clouds_get_by_name(context, name)
|
||||
|
||||
|
||||
def clouds_get_by_id(context, cloud_id):
|
||||
"""Get cloud detail for the cloud with given id."""
|
||||
return IMPL.clouds_get_by_id(context, cloud_id)
|
||||
|
||||
|
||||
def clouds_create(context, values):
|
||||
"""Create a new cloud."""
|
||||
return IMPL.clouds_create(context, values)
|
||||
|
||||
|
||||
def clouds_update(context, cloud_id, values):
|
||||
"""Update an existing cloud."""
|
||||
return IMPL.clouds_update(context, cloud_id, values)
|
||||
|
||||
|
||||
def clouds_delete(context, cloud_id):
|
||||
"""Delete an existing cloud."""
|
||||
return IMPL.clouds_delete(context, cloud_id)
|
||||
|
||||
# Hosts
|
||||
|
||||
|
||||
def hosts_get_all(context, filters, pagination_params):
|
||||
"""Get all hosts."""
|
||||
return IMPL.hosts_get_all(context, filters, pagination_params)
|
||||
|
||||
|
||||
def hosts_get_by_id(context, host_id):
|
||||
"""Get details for the host with given id."""
|
||||
return IMPL.hosts_get_by_id(context, host_id)
|
||||
|
||||
|
||||
def hosts_create(context, values):
|
||||
"""Create a new host."""
|
||||
return IMPL.hosts_create(context, values)
|
||||
|
||||
|
||||
def hosts_update(context, host_id, values):
|
||||
"""Update an existing host."""
|
||||
return IMPL.hosts_update(context, host_id, values)
|
||||
|
||||
|
||||
def hosts_delete(context, host_id):
|
||||
"""Delete an existing host."""
|
||||
return IMPL.hosts_delete(context, host_id)
|
||||
|
||||
|
||||
def hosts_labels_delete(context, host_id, labels):
|
||||
"""Delete existing device label(s)."""
|
||||
return IMPL.hosts_labels_delete(context, host_id, labels)
|
||||
|
||||
|
||||
def hosts_labels_update(context, host_id, labels):
|
||||
"""Update existing device label entirely."""
|
||||
return IMPL.hosts_labels_update(context, host_id, labels)
|
||||
|
||||
|
||||
# Projects
|
||||
|
||||
def projects_get_all(context, filters, pagination_params):
|
||||
"""Get all the projects."""
|
||||
return IMPL.projects_get_all(context, filters, pagination_params)
|
||||
|
||||
|
||||
def projects_get_by_name(context, project_name, filters, pagination_params):
|
||||
"""Get all projects that match the given name."""
|
||||
return IMPL.projects_get_by_name(context, project_name, filters,
|
||||
pagination_params)
|
||||
|
||||
|
||||
def projects_get_by_id(context, project_id):
|
||||
"""Get project by its id."""
|
||||
return IMPL.projects_get_by_id(context, project_id)
|
||||
|
||||
|
||||
def projects_create(context, values):
|
||||
"""Create a new project with given values."""
|
||||
return IMPL.projects_create(context, values)
|
||||
|
||||
|
||||
def projects_delete(context, project_id):
|
||||
"""Delete an existing project given by its id."""
|
||||
return IMPL.projects_delete(context, project_id)
|
||||
|
||||
|
||||
# Users
|
||||
|
||||
def users_get_all(context, filters, pagination_params):
|
||||
"""Get all the users."""
|
||||
return IMPL.users_get_all(context, filters, pagination_params)
|
||||
|
||||
|
||||
def users_get_by_name(context, user_name, filters, pagination_params):
|
||||
"""Get all users that match the given username."""
|
||||
return IMPL.users_get_by_name(context, user_name, filters,
|
||||
pagination_params)
|
||||
|
||||
|
||||
def users_get_by_id(context, user_id):
|
||||
"""Get user by its id."""
|
||||
return IMPL.users_get_by_id(context, user_id)
|
||||
|
||||
|
||||
def users_create(context, values):
|
||||
"""Create a new user with given values."""
|
||||
return IMPL.users_create(context, values)
|
||||
|
||||
|
||||
def users_delete(context, user_id):
|
||||
"""Delete an existing user given by its id."""
|
||||
return IMPL.users_delete(context, user_id)
|
||||
|
||||
|
||||
# Networks
|
||||
|
||||
def networks_get_all(context, filters, pagination_params):
|
||||
"""Get all networks for the given region."""
|
||||
return IMPL.networks_get_all(context, filters, pagination_params)
|
||||
|
||||
|
||||
def networks_get_by_id(context, network_id):
|
||||
"""Get a given network by its id."""
|
||||
return IMPL.networks_get_by_id(context, network_id)
|
||||
|
||||
|
||||
def networks_create(context, values):
|
||||
"""Create a new network."""
|
||||
return IMPL.networks_create(context, values)
|
||||
|
||||
|
||||
def networks_update(context, network_id, values):
|
||||
"""Update an existing network."""
|
||||
return IMPL.networks_update(context, network_id, values)
|
||||
|
||||
|
||||
def networks_delete(context, network_id):
|
||||
"""Delete existing network."""
|
||||
return IMPL.networks_delete(context, network_id)
|
||||
|
||||
|
||||
def network_devices_get_all(context, filters, pagination_params):
|
||||
"""Get all network devices."""
|
||||
return IMPL.network_devices_get_all(context, filters, pagination_params)
|
||||
|
||||
|
||||
def network_devices_get_by_id(context, network_device_id):
|
||||
"""Get a given network device by its id."""
|
||||
return IMPL.network_devices_get_by_id(context, network_device_id)
|
||||
|
||||
|
||||
def network_devices_create(context, values):
|
||||
"""Create a new network device."""
|
||||
return IMPL.network_devices_create(context, values)
|
||||
|
||||
|
||||
def network_devices_update(context, network_device_id, values):
|
||||
"""Update an existing network device"""
|
||||
return IMPL.network_devices_update(context, network_device_id, values)
|
||||
|
||||
|
||||
def network_devices_delete(context, network_device_id):
|
||||
"""Delete existing network device."""
|
||||
return IMPL.network_devices_delete(context, network_device_id)
|
||||
|
||||
|
||||
def network_devices_labels_delete(context, network_device_id, labels):
|
||||
"""Delete network device labels."""
|
||||
return IMPL.network_devices_labels_delete(context, network_device_id,
|
||||
labels)
|
||||
|
||||
|
||||
def network_devices_labels_update(context, network_device_id, labels):
|
||||
"""Update network device labels."""
|
||||
return IMPL.network_devices_labels_update(context, network_device_id,
|
||||
labels)
|
||||
|
||||
|
||||
def network_interfaces_get_all(context, filters, pagination_params):
|
||||
"""Get all network interfaces."""
|
||||
return IMPL.network_interfaces_get_all(
|
||||
context, filters, pagination_params,
|
||||
)
|
||||
|
||||
|
||||
def network_interfaces_get_by_id(context, interface_id):
|
||||
"""Get a given network interface by its id."""
|
||||
return IMPL.network_interfaces_get_by_id(context, interface_id)
|
||||
|
||||
|
||||
def network_interfaces_create(context, values):
|
||||
"""Create a new network interface."""
|
||||
return IMPL.network_interfaces_create(context, values)
|
||||
|
||||
|
||||
def network_interfaces_update(context, interface_id, values):
|
||||
"""Update an existing network interface."""
|
||||
return IMPL.network_interfaces_update(context, interface_id, values)
|
||||
|
||||
|
||||
def network_interfaces_delete(context, interface_id):
|
||||
"""Delete existing network interface."""
|
||||
return IMPL.network_interfaces_delete(context, interface_id)
|
@ -1,68 +0,0 @@
|
||||
# A generic, single database configuration.
|
||||
|
||||
[alembic]
|
||||
# path to migration scripts
|
||||
script_location = %(here)s/alembic
|
||||
|
||||
# template used to generate migration files
|
||||
# file_template = %%(rev)s_%%(slug)s
|
||||
|
||||
# max length of characters to apply to the
|
||||
# "slug" field
|
||||
#truncate_slug_length = 40
|
||||
|
||||
# set to 'true' to run the environment during
|
||||
# the 'revision' command, regardless of autogenerate
|
||||
# revision_environment = false
|
||||
|
||||
# set to 'true' to allow .pyc and .pyo files without
|
||||
# a source .py file to be detected as revisions in the
|
||||
# versions/ directory
|
||||
# sourceless = false
|
||||
|
||||
# version location specification; this defaults
|
||||
# to alembic/versions. When using multiple version
|
||||
# directories, initial revisions must be specified with --version-path
|
||||
# version_locations = %(here)s/bar %(here)s/bat alembic/versions
|
||||
|
||||
# the output encoding used when revision files
|
||||
# are written from script.py.mako
|
||||
# output_encoding = utf-8
|
||||
|
||||
#sqlalchemy.url = driver://user:pass@localhost/dbname
|
||||
|
||||
|
||||
# Logging configuration
|
||||
[loggers]
|
||||
keys = root,sqlalchemy,alembic
|
||||
|
||||
[handlers]
|
||||
keys = console
|
||||
|
||||
[formatters]
|
||||
keys = generic
|
||||
|
||||
[logger_root]
|
||||
level = WARN
|
||||
handlers = console
|
||||
qualname =
|
||||
|
||||
[logger_sqlalchemy]
|
||||
level = WARN
|
||||
handlers =
|
||||
qualname = sqlalchemy.engine
|
||||
|
||||
[logger_alembic]
|
||||
level = INFO
|
||||
handlers =
|
||||
qualname = alembic
|
||||
|
||||
[handler_console]
|
||||
class = StreamHandler
|
||||
args = (sys.stderr,)
|
||||
level = NOTSET
|
||||
formatter = generic
|
||||
|
||||
[formatter_generic]
|
||||
format = %(levelname)-5.5s [%(name)s] %(message)s
|
||||
datefmt = %H:%M:%S
|
@ -1,11 +0,0 @@
|
||||
Please see https://alembic.readthedocs.org/en/latest/index.html for general documentation
|
||||
|
||||
To create alembic migrations use:
|
||||
$ craton-dbsync --config-file=craton.conf revision --message "revision description" --autogenerate
|
||||
|
||||
Stamp db with most recent migration version, without actually running migrations
|
||||
$ craton-dbsync --config-file=craton.conf stamp head
|
||||
|
||||
Upgrade can be performed by:
|
||||
$ craton-dbsync --config-file=craton.conf upgrade
|
||||
$ craton-dbsync --config-file=craton.conf upgrade head
|
@ -1,66 +0,0 @@
|
||||
from __future__ import with_statement
|
||||
from alembic import context
|
||||
from logging.config import fileConfig
|
||||
|
||||
from craton.db.sqlalchemy import api as sa_api
|
||||
from craton.db.sqlalchemy import models as db_models
|
||||
|
||||
# this is the Alembic Config object, which provides
|
||||
# access to the values within the .ini file in use.
|
||||
config = context.config
|
||||
|
||||
# Interpret the config file for Python logging.
|
||||
# This line sets up loggers basically.
|
||||
fileConfig(config.config_file_name)
|
||||
|
||||
# add your model's MetaData object here
|
||||
# for 'autogenerate' support
|
||||
# from myapp import mymodel
|
||||
# target_metadata = mymodel.Base.metadata
|
||||
target_metadata = db_models.Base.metadata
|
||||
|
||||
# other values from the config, defined by the needs of env.py,
|
||||
# can be acquired:
|
||||
# my_important_option = config.get_main_option("my_important_option")
|
||||
# ... etc.
|
||||
|
||||
|
||||
def run_migrations_offline():
|
||||
"""Run migrations in 'offline' mode.
|
||||
|
||||
This configures the context with just a URL
|
||||
and not an Engine, though an Engine is acceptable
|
||||
here as well. By skipping the Engine creation
|
||||
we don't even need a DBAPI to be available.
|
||||
|
||||
Calls to context.execute() here emit the given string to the
|
||||
script output.
|
||||
|
||||
"""
|
||||
url = config.get_main_option("sqlalchemy.url")
|
||||
context.configure(
|
||||
url=url, target_metadata=target_metadata, literal_binds=True)
|
||||
|
||||
with context.begin_transaction():
|
||||
context.run_migrations()
|
||||
|
||||
|
||||
def run_migrations_online():
|
||||
"""Run migrations in 'online' mode.
|
||||
|
||||
In this scenario we need to create an Engine
|
||||
and associate a connection with the context.
|
||||
|
||||
"""
|
||||
engine = sa_api.get_engine()
|
||||
with engine.connect() as connection:
|
||||
context.configure(
|
||||
connection=connection,
|
||||
target_metadata=target_metadata
|
||||
)
|
||||
|
||||
with context.begin_transaction():
|
||||
context.run_migrations()
|
||||
|
||||
|
||||
run_migrations_online()
|
@ -1,24 +0,0 @@
|
||||
"""${message}
|
||||
|
||||
Revision ID: ${up_revision}
|
||||
Revises: ${down_revision | comma,n}
|
||||
Create Date: ${create_date}
|
||||
|
||||
"""
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = ${repr(up_revision)}
|
||||
down_revision = ${repr(down_revision)}
|
||||
branch_labels = ${repr(branch_labels)}
|
||||
depends_on = ${repr(depends_on)}
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
${imports if imports else ""}
|
||||
|
||||
def upgrade():
|
||||
${upgrades if upgrades else "pass"}
|
||||
|
||||
|
||||
def downgrade():
|
||||
${downgrades if downgrades else "pass"}
|
@ -1,407 +0,0 @@
|
||||
"""craton_inventory_init
|
||||
|
||||
Revision ID: ffdc1a500db1
|
||||
Revises:
|
||||
Create Date: 2016-06-03 09:52:55.302936
|
||||
|
||||
"""
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'ffdc1a500db1'
|
||||
down_revision = None
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
import sqlalchemy_utils
|
||||
|
||||
|
||||
def upgrade():
|
||||
op.create_table(
|
||||
'variable_association',
|
||||
sa.Column('created_at', sa.DateTime, nullable=False),
|
||||
sa.Column('updated_at', sa.DateTime, nullable=True),
|
||||
sa.Column('id', sa.Integer, primary_key=True),
|
||||
sa.Column('discriminator', sa.String(length=50), nullable=False),
|
||||
)
|
||||
|
||||
op.create_table(
|
||||
'variables',
|
||||
sa.Column('created_at', sa.DateTime, nullable=False),
|
||||
sa.Column('updated_at', sa.DateTime, nullable=True),
|
||||
sa.Column(
|
||||
'association_id', sa.Integer,
|
||||
sa.ForeignKey(
|
||||
'variable_association.id',
|
||||
name='fk_variables__variables_association',
|
||||
ondelete='cascade'),
|
||||
primary_key=True),
|
||||
sa.Column('key_', sa.String(length=255), primary_key=True),
|
||||
sa.Column('value_', sa.JSON, nullable=True),
|
||||
)
|
||||
|
||||
op.create_table(
|
||||
'projects',
|
||||
sa.Column('created_at', sa.DateTime, nullable=False),
|
||||
sa.Column('updated_at', sa.DateTime, nullable=True),
|
||||
sa.Column(
|
||||
'id', sqlalchemy_utils.types.UUIDType(binary=False),
|
||||
primary_key=True),
|
||||
sa.Column(
|
||||
'variable_association_id', sa.Integer,
|
||||
sa.ForeignKey(
|
||||
'variable_association.id',
|
||||
name='fk_projects__variable_association'),
|
||||
nullable=False),
|
||||
sa.Column('name', sa.String(length=255), nullable=True),
|
||||
)
|
||||
|
||||
op.create_table(
|
||||
'users',
|
||||
sa.Column('created_at', sa.DateTime, nullable=False),
|
||||
sa.Column('updated_at', sa.DateTime, nullable=True),
|
||||
sa.Column('id', sa.Integer, primary_key=True),
|
||||
sa.Column(
|
||||
'project_id', sqlalchemy_utils.types.UUIDType(binary=False),
|
||||
sa.ForeignKey(
|
||||
'projects.id', name='fk_users__projects', ondelete='cascade'),
|
||||
nullable=False),
|
||||
sa.Column(
|
||||
'variable_association_id', sa.Integer,
|
||||
sa.ForeignKey(
|
||||
'variable_association.id',
|
||||
name='fk_users__variable_association'),
|
||||
nullable=False),
|
||||
sa.Column('username', sa.String(length=255), nullable=True),
|
||||
sa.Column('api_key', sa.String(length=36), nullable=True),
|
||||
sa.Column('is_root', sa.Boolean, nullable=True),
|
||||
sa.Column('is_admin', sa.Boolean, nullable=True),
|
||||
sa.UniqueConstraint(
|
||||
'username', 'project_id',
|
||||
name='uq_users_username_project_id'),
|
||||
)
|
||||
op.create_index(
|
||||
op.f('ix_users_project_id'), 'users', ['project_id'], unique=False)
|
||||
|
||||
op.create_table(
|
||||
'clouds',
|
||||
sa.Column('created_at', sa.DateTime, nullable=False),
|
||||
sa.Column('updated_at', sa.DateTime, nullable=True),
|
||||
sa.Column('id', sa.Integer, primary_key=True),
|
||||
sa.Column(
|
||||
'project_id', sqlalchemy_utils.types.UUIDType(binary=False),
|
||||
sa.ForeignKey(
|
||||
'projects.id', name='fk_clouds__projects', ondelete='cascade'),
|
||||
nullable=False),
|
||||
sa.Column(
|
||||
'variable_association_id', sa.Integer,
|
||||
sa.ForeignKey(
|
||||
'variable_association.id',
|
||||
name='fk_clouds__variable_association'),
|
||||
nullable=False),
|
||||
sa.Column('name', sa.String(length=255), nullable=True),
|
||||
sa.Column('note', sa.Text, nullable=True),
|
||||
sa.UniqueConstraint(
|
||||
'project_id', 'name',
|
||||
name='uq_clouds__project_id__name'),
|
||||
)
|
||||
op.create_index(
|
||||
op.f('ix_clouds_project_id'), 'clouds', ['project_id'], unique=False)
|
||||
|
||||
op.create_table(
|
||||
'regions',
|
||||
sa.Column('created_at', sa.DateTime, nullable=False),
|
||||
sa.Column('updated_at', sa.DateTime, nullable=True),
|
||||
sa.Column('id', sa.Integer, primary_key=True),
|
||||
sa.Column(
|
||||
'project_id', sqlalchemy_utils.types.UUIDType(binary=False),
|
||||
sa.ForeignKey(
|
||||
'projects.id',
|
||||
name='fk_projects__regions', ondelete='cascade')),
|
||||
sa.Column(
|
||||
'cloud_id', sa.Integer,
|
||||
sa.ForeignKey(
|
||||
'clouds.id', name='fk_regions__clouds', ondelete='cascade'),
|
||||
nullable=False),
|
||||
sa.Column(
|
||||
'variable_association_id', sa.Integer,
|
||||
sa.ForeignKey(
|
||||
'variable_association.id',
|
||||
name='fk_regions__variable_association'),
|
||||
nullable=False),
|
||||
sa.Column('name', sa.String(length=255), nullable=True),
|
||||
sa.Column('note', sa.Text, nullable=True),
|
||||
sa.UniqueConstraint(
|
||||
'cloud_id', 'name', name='uq_regions__cloud_id__name'),
|
||||
)
|
||||
|
||||
op.create_index(
|
||||
op.f('ix_regions_project_id'), 'regions', ['project_id'], unique=False)
|
||||
op.create_index(
|
||||
op.f('ix_regions_cloud_id'), 'regions', ['cloud_id'], unique=False)
|
||||
|
||||
op.create_table(
|
||||
'cells',
|
||||
sa.Column('created_at', sa.DateTime, nullable=False),
|
||||
sa.Column('updated_at', sa.DateTime, nullable=True),
|
||||
sa.Column('id', sa.Integer, primary_key=True),
|
||||
sa.Column(
|
||||
'project_id', sqlalchemy_utils.types.UUIDType(binary=False),
|
||||
sa.ForeignKey(
|
||||
'projects.id', name='fk_cells__projects', ondelete='cascade'),
|
||||
nullable=False),
|
||||
sa.Column(
|
||||
'cloud_id', sa.Integer,
|
||||
sa.ForeignKey(
|
||||
'clouds.id', name='fk_cells__clouds', ondelete='cascade'),
|
||||
nullable=False),
|
||||
sa.Column(
|
||||
'region_id', sa.Integer,
|
||||
sa.ForeignKey(
|
||||
'regions.id', name='fk_cells__regions', ondelete='cascade'),
|
||||
nullable=False),
|
||||
sa.Column(
|
||||
'variable_association_id', sa.Integer,
|
||||
sa.ForeignKey(
|
||||
'variable_association.id',
|
||||
name='fk_cells__variable_association'),
|
||||
nullable=False),
|
||||
sa.Column('name', sa.String(length=255), nullable=True),
|
||||
sa.Column('note', sa.Text, nullable=True),
|
||||
sa.UniqueConstraint(
|
||||
'region_id', 'name', name='uq_cells__region_id__name'),
|
||||
)
|
||||
op.create_index(
|
||||
op.f('ix_cells_project_id'), 'cells', ['project_id'], unique=False)
|
||||
op.create_index(
|
||||
op.f('ix_cells_cloud_id'), 'cells', ['cloud_id'], unique=False)
|
||||
op.create_index(
|
||||
op.f('ix_cells_region_id'), 'cells', ['region_id'], unique=False)
|
||||
|
||||
op.create_table(
|
||||
'networks',
|
||||
sa.Column('created_at', sa.DateTime, nullable=False),
|
||||
sa.Column('updated_at', sa.DateTime, nullable=True),
|
||||
sa.Column('id', sa.Integer, primary_key=True),
|
||||
sa.Column(
|
||||
'project_id', sqlalchemy_utils.types.UUIDType(binary=False),
|
||||
sa.ForeignKey(
|
||||
'projects.id',
|
||||
name='fk_networks__projects', ondelete='cascade'),
|
||||
nullable=False),
|
||||
sa.Column(
|
||||
'cloud_id', sa.Integer,
|
||||
sa.ForeignKey(
|
||||
'clouds.id', name='fk_networks__clouds', ondelete='cascade'),
|
||||
nullable=False),
|
||||
sa.Column(
|
||||
'region_id', sa.Integer,
|
||||
sa.ForeignKey(
|
||||
'regions.id', name='fk_networks__regions', ondelete='cascade'),
|
||||
nullable=False),
|
||||
sa.Column(
|
||||
'cell_id', sa.Integer,
|
||||
sa.ForeignKey(
|
||||
'cells.id', name='fk_networks__cells', ondelete='cascade'),
|
||||
nullable=True),
|
||||
sa.Column(
|
||||
'variable_association_id', sa.Integer,
|
||||
sa.ForeignKey(
|
||||
'variable_association.id',
|
||||
name='fk_networks__variable_association'),
|
||||
nullable=False),
|
||||
sa.Column('name', sa.String(length=255), nullable=True),
|
||||
sa.Column('cidr', sa.String(length=255), nullable=True),
|
||||
sa.Column('gateway', sa.String(length=255), nullable=True),
|
||||
sa.Column('netmask', sa.String(length=255), nullable=True),
|
||||
sa.Column('ip_block_type', sa.String(length=255), nullable=True),
|
||||
sa.Column('nss', sa.String(length=255), nullable=True),
|
||||
sa.UniqueConstraint(
|
||||
'name', 'project_id', 'region_id',
|
||||
name='uq_networks__name__project_id__region_id'),
|
||||
)
|
||||
op.create_index(
|
||||
op.f('ix_networks_cell_id'), 'networks', ['cell_id'], unique=False)
|
||||
op.create_index(
|
||||
op.f('ix_networks_project_id'), 'networks', ['project_id'],
|
||||
unique=False)
|
||||
op.create_index(
|
||||
op.f('ix_networks_region_id'), 'networks', ['region_id'],
|
||||
unique=False)
|
||||
|
||||
op.create_table(
|
||||
'devices',
|
||||
sa.Column('created_at', sa.DateTime, nullable=False),
|
||||
sa.Column('updated_at', sa.DateTime, nullable=True),
|
||||
sa.Column('id', sa.Integer, primary_key=True),
|
||||
sa.Column(
|
||||
'project_id', sqlalchemy_utils.types.UUIDType(binary=False),
|
||||
sa.ForeignKey(
|
||||
'projects.id',
|
||||
name='fk_devices__projects', ondelete='cascade'),
|
||||
nullable=False),
|
||||
sa.Column(
|
||||
'cloud_id', sa.Integer,
|
||||
sa.ForeignKey(
|
||||
'clouds.id', name='fk_devices__clouds', ondelete='cascade'),
|
||||
nullable=False),
|
||||
sa.Column(
|
||||
'region_id', sa.Integer,
|
||||
sa.ForeignKey(
|
||||
'regions.id', name='fk_devices__regions', ondelete='cascade'),
|
||||
nullable=False),
|
||||
sa.Column(
|
||||
'cell_id', sa.Integer,
|
||||
sa.ForeignKey(
|
||||
'cells.id', name='fk_devices__cells', ondelete='cascade'),
|
||||
nullable=True),
|
||||
sa.Column(
|
||||
'parent_id', sa.Integer,
|
||||
sa.ForeignKey('devices.id', name='fk_devices__devices'),
|
||||
nullable=True),
|
||||
sa.Column(
|
||||
'variable_association_id', sa.Integer,
|
||||
sa.ForeignKey(
|
||||
'variable_association.id',
|
||||
name='fk_devices__variable_association'),
|
||||
nullable=False),
|
||||
sa.Column('type', sa.String(length=50), nullable=False),
|
||||
sa.Column('device_type', sa.String(length=255), nullable=False),
|
||||
sa.Column('name', sa.String(length=255), nullable=False),
|
||||
sa.Column(
|
||||
'ip_address',
|
||||
sqlalchemy_utils.types.IPAddressType(length=64),
|
||||
nullable=False),
|
||||
sa.Column('active', sa.Boolean(), nullable=True),
|
||||
sa.Column('note', sa.Text(), nullable=True),
|
||||
sa.UniqueConstraint('region_id', 'name',
|
||||
name='uq_device0regionid0name'),
|
||||
)
|
||||
op.create_index(
|
||||
op.f('ix_devices_cell_id'), 'devices', ['cell_id'], unique=False)
|
||||
op.create_index(
|
||||
op.f('ix_devices_project_id'), 'devices', ['project_id'], unique=False)
|
||||
op.create_index(
|
||||
op.f('ix_devices_region_id'), 'devices', ['region_id'], unique=False)
|
||||
op.create_index(
|
||||
op.f('ix_devices_cloud_id'), 'devices', ['cloud_id'], unique=False)
|
||||
|
||||
op.create_table(
|
||||
'hosts',
|
||||
sa.Column(
|
||||
'id', sa.Integer,
|
||||
sa.ForeignKey(
|
||||
'devices.id', name='fk_hosts__devices', ondelete='cascade'),
|
||||
primary_key=True)
|
||||
)
|
||||
|
||||
op.create_table(
|
||||
'network_devices',
|
||||
sa.Column(
|
||||
'id', sa.Integer,
|
||||
sa.ForeignKey(
|
||||
'devices.id',
|
||||
name='fk_network_devices__devices', ondelete='cascade'),
|
||||
primary_key=True),
|
||||
sa.Column('model_name', sa.String(length=255), nullable=True),
|
||||
sa.Column('os_version', sa.String(length=255), nullable=True),
|
||||
sa.Column('vlans', sa.JSON,
|
||||
nullable=True)
|
||||
)
|
||||
|
||||
op.create_table(
|
||||
'labels',
|
||||
sa.Column('created_at', sa.DateTime, nullable=False),
|
||||
sa.Column('updated_at', sa.DateTime, nullable=True),
|
||||
sa.Column(
|
||||
'device_id', sa.Integer,
|
||||
sa.ForeignKey(
|
||||
'devices.id',
|
||||
name='fk_labels__devices', ondelete='cascade'),
|
||||
primary_key=True),
|
||||
sa.Column('label', sa.String(length=255), primary_key=True),
|
||||
)
|
||||
op.create_index(
|
||||
op.f('ix_devices_labels'), 'labels', ['label', 'device_id'])
|
||||
|
||||
op.create_table(
|
||||
'network_interfaces',
|
||||
sa.Column('created_at', sa.DateTime, nullable=False),
|
||||
sa.Column('updated_at', sa.DateTime, nullable=True),
|
||||
sa.Column('id', sa.Integer, primary_key=True),
|
||||
sa.Column(
|
||||
'project_id', sqlalchemy_utils.types.UUIDType(binary=False),
|
||||
sa.ForeignKey(
|
||||
'projects.id',
|
||||
name='fk_network_interfaces__projects', ondelete='cascade'),
|
||||
nullable=False),
|
||||
sa.Column(
|
||||
'device_id', sa.Integer,
|
||||
sa.ForeignKey(
|
||||
'devices.id',
|
||||
name='fk_network_interfaces__devices', ondelete='cascade'),
|
||||
nullable=False),
|
||||
sa.Column(
|
||||
'network_id', sa.Integer,
|
||||
sa.ForeignKey(
|
||||
'networks.id',
|
||||
name='fk_network_interfaces__networks'),
|
||||
nullable=True),
|
||||
sa.Column(
|
||||
'variable_association_id', sa.Integer,
|
||||
sa.ForeignKey(
|
||||
'variable_association.id',
|
||||
name='fk_network_interfaces__variable_association'),
|
||||
nullable=False),
|
||||
sa.Column('name', sa.String(length=255), nullable=True),
|
||||
sa.Column('interface_type', sa.String(length=255), nullable=True),
|
||||
sa.Column('vlan_id', sa.Integer, nullable=True),
|
||||
sa.Column('port', sa.Integer, nullable=True),
|
||||
sa.Column('vlan', sa.String(length=255), nullable=True),
|
||||
sa.Column('duplex', sa.String(length=255), nullable=True),
|
||||
sa.Column('speed', sa.String(length=255), nullable=True),
|
||||
sa.Column('link', sa.String(length=255), nullable=True),
|
||||
sa.Column('cdp', sa.String(length=255), nullable=True),
|
||||
sa.Column('security', sa.String(length=255), nullable=True),
|
||||
sa.Column(
|
||||
'ip_address',
|
||||
sqlalchemy_utils.types.IPAddressType(length=64),
|
||||
nullable=False),
|
||||
sa.UniqueConstraint(
|
||||
'device_id', 'name',
|
||||
name='uq_network_interfaces__device_id__name'),
|
||||
)
|
||||
|
||||
|
||||
def downgrade():
|
||||
op.drop_table('network_interfaces')
|
||||
op.drop_index(op.f('ix_devices_labels'), table_name='labels')
|
||||
op.drop_table('labels')
|
||||
op.drop_table('network_devices')
|
||||
op.drop_table('hosts')
|
||||
op.drop_index(op.f('ix_networks_region_id'), table_name='networks')
|
||||
op.drop_index(op.f('ix_networks_cloud_id'), table_name='networks')
|
||||
op.drop_index(op.f('ix_networks_project_id'), table_name='networks')
|
||||
op.drop_index(op.f('ix_networks_cell_id'), table_name='networks')
|
||||
op.drop_table('networks')
|
||||
op.drop_index(op.f('ix_devices_region_id'), table_name='devices')
|
||||
op.drop_index(op.f('ix_devices_cloud_id'), table_name='devices')
|
||||
op.drop_index(op.f('ix_devices_project_id'), table_name='devices')
|
||||
op.drop_index(op.f('ix_devices_cell_id'), table_name='devices')
|
||||
op.drop_table('devices')
|
||||
op.drop_index(op.f('ix_cells_region_id'), table_name='cells')
|
||||
op.drop_index(op.f('ix_cells_cloud_id'), table_name='cells')
|
||||
op.drop_index(op.f('ix_cells_project_id'), table_name='cells')
|
||||
op.drop_table('cells')
|
||||
op.drop_index(op.f('ix_users_project_id'), table_name='users')
|
||||
op.drop_index(op.f('ix_regions_project_id'), table_name='regions')
|
||||
op.drop_index(op.f('ix_regions_cloud_id'), table_name='regions')
|
||||
op.drop_table('regions')
|
||||
op.drop_index(op.f('ix_clouds_project_id'), table_name='clouds')
|
||||
op.drop_table('clouds')
|
||||
op.drop_table('users')
|
||||
op.drop_table('projects')
|
||||
op.drop_index(op.f('ix_variable_keys'), table_name='variables')
|
||||
op.drop_table('variables')
|
||||
op.drop_table('variable_association')
|
File diff suppressed because it is too large
Load Diff
@ -1,114 +0,0 @@
|
||||
import alembic
|
||||
import os
|
||||
import uuid
|
||||
|
||||
from alembic import config as alembic_config
|
||||
import alembic.migration as alembic_migration
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy import exc
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
import sqlalchemy.orm.exc as sa_exc
|
||||
from oslo_db.sqlalchemy import enginefacade
|
||||
|
||||
from craton.api.v1.resources import utils
|
||||
from craton.db.sqlalchemy import models
|
||||
|
||||
|
||||
def _alembic_config():
|
||||
path = os.path.join(os.path.dirname(__file__), 'alembic.ini')
|
||||
config = alembic_config.Config(path)
|
||||
return config
|
||||
|
||||
|
||||
def version(config=None, engine=None):
|
||||
"""Current database version."""
|
||||
if engine is None:
|
||||
engine = enginefacade.get_legacy_facade().get_engine()
|
||||
with engine.connect() as conn:
|
||||
context = alembic_migration.MigrationContext.configure(conn)
|
||||
return context.get_current_revision()
|
||||
|
||||
|
||||
def upgrade(revision, config=None):
|
||||
"""Used for upgrading database.
|
||||
:param version: Desired database version
|
||||
"""
|
||||
revision = revision or 'head'
|
||||
config = config or _alembic_config()
|
||||
|
||||
alembic.command.upgrade(config, revision or 'head')
|
||||
|
||||
|
||||
def stamp(revision, config=None):
|
||||
"""Stamps database with provided revision.
|
||||
Don't run any migrations.
|
||||
:param revision: Should match one from repository or head - to stamp
|
||||
database with most recent revision
|
||||
"""
|
||||
config = config or _alembic_config()
|
||||
return alembic.command.stamp(config, revision=revision)
|
||||
|
||||
|
||||
def revision(message=None, autogenerate=False, config=None):
|
||||
"""Creates template for migration.
|
||||
:param message: Text that will be used for migration title
|
||||
:param autogenerate: If True - generates diff based on current database
|
||||
state
|
||||
"""
|
||||
config = config or _alembic_config()
|
||||
return alembic.command.revision(config, message=message,
|
||||
autogenerate=autogenerate)
|
||||
|
||||
|
||||
def create_bootstrap_project(name, project_id=None, db_uri=None):
|
||||
"""Creates a new project.
|
||||
:param name: Name of the new project
|
||||
"""
|
||||
if not project_id:
|
||||
project_id = str(uuid.uuid4())
|
||||
engine = create_engine(db_uri)
|
||||
Session = sessionmaker(bind=engine)
|
||||
session = Session()
|
||||
project = models.Project(name=name,
|
||||
id=project_id)
|
||||
|
||||
try:
|
||||
project = session.query(models.Project).filter_by(name=name).one()
|
||||
except sa_exc.NoResultFound:
|
||||
session.add(project)
|
||||
session.commit()
|
||||
|
||||
return project
|
||||
|
||||
|
||||
def create_bootstrap_user(project_id, username, db_uri=None):
|
||||
"""Creates a new project.
|
||||
:param username: Username for new user
|
||||
:param project_id: Project ID for the user
|
||||
"""
|
||||
engine = create_engine(db_uri)
|
||||
Session = sessionmaker(bind=engine)
|
||||
session = Session()
|
||||
|
||||
api_key = utils.gen_api_key()
|
||||
|
||||
user = models.User(project_id=project_id,
|
||||
username=username,
|
||||
api_key=api_key,
|
||||
is_admin=True,
|
||||
is_root=True)
|
||||
try:
|
||||
session.add(user)
|
||||
session.commit()
|
||||
except exc.IntegrityError as err:
|
||||
if err.orig.args[0] == 1062:
|
||||
# NOTE(sulo): 1062 is the normal sql duplicate error code
|
||||
# also see pymysql/constants/ER.py#L65
|
||||
session.rollback()
|
||||
user = session.query(models.User).filter_by(username=username)
|
||||
user = user.filter_by(project_id=project_id).one()
|
||||
return user
|
||||
else:
|
||||
raise
|
||||
|
||||
return user
|
@ -1,553 +0,0 @@
|
||||
"""Models inventory, as defined using SQLAlchemy ORM
|
||||
|
||||
Craton uses the following related aspects of inventory:
|
||||
|
||||
* Device inventory, with devices are further organized by region,
|
||||
cell, and labels. Variables are associated with all of these
|
||||
entities, with the ability to override via resolution and to track
|
||||
with blaming. This in terms forms the foundation of an *inventory
|
||||
fabric*, which is implemented above this level.
|
||||
|
||||
* Workflows are run against this inventory, taking in account the
|
||||
variable configuration; as well as any specifics baked into the
|
||||
workflow itself.
|
||||
|
||||
"""
|
||||
|
||||
from collections import ChainMap, deque, OrderedDict
|
||||
import itertools
|
||||
|
||||
from oslo_db.sqlalchemy import models
|
||||
from sqlalchemy import (
|
||||
Boolean, Column, ForeignKey, Integer, String, Text, UniqueConstraint, JSON)
|
||||
from sqlalchemy.ext.associationproxy import association_proxy
|
||||
from sqlalchemy.ext.declarative import declarative_base, declared_attr
|
||||
from sqlalchemy.ext.declarative.api import _declarative_constructor
|
||||
from sqlalchemy.orm import backref, object_mapper, relationship, validates
|
||||
from sqlalchemy.orm.collections import attribute_mapped_collection
|
||||
from sqlalchemy_utils.types.ip_address import IPAddressType
|
||||
from sqlalchemy_utils.types.uuid import UUIDType
|
||||
|
||||
from craton import exceptions
|
||||
from craton.db.api import Blame
|
||||
|
||||
|
||||
# TODO(jimbaker) set up table args for a given database/storage
|
||||
# engine, as configured. See
|
||||
# https://github.com/rackerlabs/craton/issues/19
|
||||
|
||||
|
||||
class CratonBase(models.ModelBase, models.TimestampMixin):
|
||||
def __repr__(self):
|
||||
mapper = object_mapper(self)
|
||||
cols = getattr(self, '_repr_columns', mapper.primary_key)
|
||||
items = [(p.key, getattr(self, p.key))
|
||||
for p in [
|
||||
mapper.get_property_by_column(c) for c in cols]]
|
||||
return "{0}({1})".format(
|
||||
self.__class__.__name__,
|
||||
', '.join(['{0}={1!r}'.format(*item) for item in items]))
|
||||
|
||||
|
||||
def _variable_mixin_aware_constructor(self, **kwargs):
|
||||
# The standard default for the underlying relationship for
|
||||
# variables sets it to None, which means it cannot directly be
|
||||
# used as a mappable collection. Cure the problem accordingly with
|
||||
# a different default.
|
||||
if isinstance(self, VariableMixin):
|
||||
kwargs.setdefault('variables', {})
|
||||
return _declarative_constructor(self, **kwargs)
|
||||
|
||||
|
||||
Base = declarative_base(
|
||||
cls=CratonBase, constructor=_variable_mixin_aware_constructor)
|
||||
|
||||
|
||||
class VariableAssociation(Base):
|
||||
"""Associates a collection of Variable key-value objects
|
||||
with a particular parent.
|
||||
|
||||
"""
|
||||
__tablename__ = "variable_association"
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
discriminator = Column(String(50), nullable=False)
|
||||
"""Refers to the type of parent, such as 'cell' or 'device'"""
|
||||
|
||||
variables = relationship(
|
||||
'Variable',
|
||||
collection_class=attribute_mapped_collection('key'),
|
||||
back_populates='association',
|
||||
cascade='all, delete-orphan', lazy='joined',
|
||||
)
|
||||
|
||||
def _variable_creator(key, value):
|
||||
# Necessary to create a single key/value setting, even once
|
||||
# the corresponding variable association has been setup
|
||||
return Variable(key=key, value=value)
|
||||
|
||||
values = association_proxy(
|
||||
'variables', 'value', creator=_variable_creator)
|
||||
|
||||
__mapper_args__ = {
|
||||
'polymorphic_on': discriminator,
|
||||
}
|
||||
|
||||
|
||||
class Variable(Base):
|
||||
"""The Variable class.
|
||||
|
||||
This represents all variable records in a single table.
|
||||
"""
|
||||
__tablename__ = 'variables'
|
||||
association_id = Column(
|
||||
Integer,
|
||||
ForeignKey(VariableAssociation.id,
|
||||
name='fk_variables_variable_association'),
|
||||
primary_key=True)
|
||||
# Use "key_", "value_" to avoid the use of reserved keywords in
|
||||
# MySQL. This difference in naming is only visible in the use of
|
||||
# raw SQL.
|
||||
key = Column('key_', String(255), primary_key=True)
|
||||
value = Column('value_', JSON)
|
||||
association = relationship(
|
||||
VariableAssociation, back_populates='variables',
|
||||
)
|
||||
parent = association_proxy('association', 'parent')
|
||||
|
||||
def __repr__(self):
|
||||
return '%s(key=%r, value=%r)' % \
|
||||
(self.__class__.__name__, self.key, self.value)
|
||||
|
||||
|
||||
# The VariableMixin mixin is adapted from this example code:
|
||||
# http://docs.sqlalchemy.org/en/latest/_modules/examples/generic_associations/discriminator_on_association.html
|
||||
# This blog post goes into more details about the underlying modeling:
|
||||
# http://techspot.zzzeek.org/2007/05/29/polymorphic-associations-with-sqlalchemy/
|
||||
|
||||
class VariableMixin(object):
|
||||
"""VariableMixin mixin, creates a relationship to
|
||||
the variable_association table for each parent.
|
||||
|
||||
"""
|
||||
|
||||
@declared_attr
|
||||
def variable_association_id(cls):
|
||||
return Column(
|
||||
Integer,
|
||||
ForeignKey(VariableAssociation.id,
|
||||
name='fk_%ss_variable_association' %
|
||||
cls.__name__.lower()))
|
||||
|
||||
@declared_attr
|
||||
def variable_association(cls):
|
||||
name = cls.__name__
|
||||
discriminator = name.lower()
|
||||
|
||||
# Defines a polymorphic class to distinguish variables stored
|
||||
# for regions, cells, etc.
|
||||
cls.variable_assoc_cls = assoc_cls = type(
|
||||
"%sVariableAssociation" % name,
|
||||
(VariableAssociation,),
|
||||
{
|
||||
'__tablename__': None, # because mapping into a shared table
|
||||
'__mapper_args__': {
|
||||
'polymorphic_identity': discriminator
|
||||
}
|
||||
})
|
||||
|
||||
def _assoc_creator(kv):
|
||||
assoc = assoc_cls()
|
||||
for key, value in kv.items():
|
||||
assoc.variables[key] = Variable(key=key, value=value)
|
||||
return assoc
|
||||
|
||||
cls._variables = association_proxy(
|
||||
'variable_association', 'variables', creator=_assoc_creator)
|
||||
|
||||
# Using a composite associative proxy here enables returning the
|
||||
# underlying values for a given key, as opposed to the
|
||||
# Variable object; we need both.
|
||||
cls.variables = association_proxy(
|
||||
'variable_association', 'values', creator=_assoc_creator)
|
||||
|
||||
def with_characteristic(self, key, value):
|
||||
return self._variables.any(key=key, value=value)
|
||||
|
||||
cls.with_characteristic = classmethod(with_characteristic)
|
||||
|
||||
rel = relationship(
|
||||
assoc_cls,
|
||||
collection_class=attribute_mapped_collection('key'),
|
||||
cascade='all, delete-orphan', lazy='joined',
|
||||
single_parent=True,
|
||||
backref=backref('parent', uselist=False))
|
||||
|
||||
return rel
|
||||
|
||||
# For resolution ordering, the default is to just include
|
||||
# self. Override as desired for other resolution policy.
|
||||
|
||||
@property
|
||||
def resolution_order(self):
|
||||
return [self]
|
||||
|
||||
@property
|
||||
def resolution_order_variables(self):
|
||||
return [obj.variables for obj in self.resolution_order]
|
||||
|
||||
@property
|
||||
def resolved(self):
|
||||
"""Provides a mapping that uses scope resolution for variables"""
|
||||
return ChainMap(*self.resolution_order_variables)
|
||||
|
||||
def blame(self, keys=None):
|
||||
"""Determines the sources of how variables have been set.
|
||||
:param keys: keys to check sourcing, or all keys if None
|
||||
|
||||
Returns the (source, variable) in a named tuple; note that
|
||||
variable contains certain audit/governance information
|
||||
(created_at, modified_at).
|
||||
|
||||
TODO(jimbaker) further extend schema on mixed-in variable tables
|
||||
to capture additional governance, such as user who set the key;
|
||||
this will then transparently become available in the blame.
|
||||
"""
|
||||
|
||||
if keys is None:
|
||||
keys = self.resolved.keys()
|
||||
blamed = {}
|
||||
for key in keys:
|
||||
for source in self.resolution_order:
|
||||
try:
|
||||
blamed[key] = Blame(source, source._variables[key])
|
||||
break
|
||||
except KeyError:
|
||||
pass
|
||||
return blamed
|
||||
|
||||
|
||||
class Project(Base, VariableMixin):
|
||||
"""Supports multitenancy for all other schema elements."""
|
||||
__tablename__ = 'projects'
|
||||
id = Column(UUIDType(binary=False), primary_key=True)
|
||||
name = Column(String(255))
|
||||
_repr_columns = [id, name]
|
||||
|
||||
# TODO(jimbaker) we will surely need to define more columns, but
|
||||
# this suffices to define multitenancy for MVP
|
||||
|
||||
# one-to-many relationship with the following objects
|
||||
clouds = relationship('Cloud', back_populates='project')
|
||||
regions = relationship('Region', back_populates='project')
|
||||
cells = relationship('Cell', back_populates='project')
|
||||
devices = relationship('Device', back_populates='project')
|
||||
users = relationship('User', back_populates='project')
|
||||
networks = relationship('Network', back_populates='project')
|
||||
interfaces = relationship('NetworkInterface', back_populates='project')
|
||||
|
||||
|
||||
class User(Base, VariableMixin):
|
||||
__tablename__ = 'users'
|
||||
__table_args__ = (
|
||||
UniqueConstraint("username", "project_id",
|
||||
name="uq_user0username0project"),
|
||||
)
|
||||
id = Column(Integer, primary_key=True)
|
||||
project_id = Column(
|
||||
UUIDType(binary=False), ForeignKey('projects.id'), index=True,
|
||||
nullable=False)
|
||||
username = Column(String(255))
|
||||
api_key = Column(String(36))
|
||||
# root = craton admin that can create other projects/users
|
||||
is_root = Column(Boolean, default=False)
|
||||
# admin = project context admin
|
||||
is_admin = Column(Boolean, default=False)
|
||||
_repr_columns = [id, username]
|
||||
|
||||
project = relationship('Project', back_populates='users')
|
||||
|
||||
|
||||
class Cloud(Base, VariableMixin):
|
||||
__tablename__ = 'clouds'
|
||||
__table_args__ = (
|
||||
UniqueConstraint("project_id", "name",
|
||||
name="uq_cloud0projectid0name"),
|
||||
)
|
||||
id = Column(Integer, primary_key=True)
|
||||
project_id = Column(
|
||||
UUIDType(binary=False), ForeignKey('projects.id'), index=True,
|
||||
nullable=False)
|
||||
name = Column(String(255))
|
||||
note = Column(Text)
|
||||
_repr_columns = [id, name]
|
||||
|
||||
project = relationship('Project', back_populates='clouds')
|
||||
regions = relationship('Region', back_populates='cloud')
|
||||
cells = relationship('Cell', back_populates='cloud')
|
||||
devices = relationship('Device', back_populates='cloud')
|
||||
networks = relationship('Network', back_populates='cloud')
|
||||
|
||||
|
||||
class Region(Base, VariableMixin):
|
||||
__tablename__ = 'regions'
|
||||
__table_args__ = (
|
||||
UniqueConstraint("cloud_id", "name",
|
||||
name="uq_region0cloudid0name"),
|
||||
)
|
||||
id = Column(Integer, primary_key=True)
|
||||
project_id = Column(
|
||||
UUIDType(binary=False), ForeignKey('projects.id'), index=True,
|
||||
nullable=False)
|
||||
cloud_id = Column(
|
||||
Integer, ForeignKey('clouds.id'), index=True, nullable=False)
|
||||
name = Column(String(255))
|
||||
note = Column(Text)
|
||||
_repr_columns = [id, name]
|
||||
|
||||
project = relationship('Project', back_populates='regions')
|
||||
cloud = relationship('Cloud', back_populates='regions')
|
||||
cells = relationship('Cell', back_populates='region')
|
||||
devices = relationship('Device', back_populates='region')
|
||||
networks = relationship('Network', back_populates='region')
|
||||
|
||||
@property
|
||||
def resolution_order(self):
|
||||
return list(itertools.chain(
|
||||
[self],
|
||||
[self.cloud],
|
||||
[self.project]))
|
||||
|
||||
|
||||
class Cell(Base, VariableMixin):
|
||||
__tablename__ = 'cells'
|
||||
__table_args__ = (
|
||||
UniqueConstraint("region_id", "name",
|
||||
name="uq_cell0regionid0name"),
|
||||
)
|
||||
id = Column(Integer, primary_key=True)
|
||||
region_id = Column(
|
||||
Integer, ForeignKey('regions.id'), index=True, nullable=False)
|
||||
cloud_id = Column(
|
||||
Integer, ForeignKey('clouds.id'), index=True, nullable=False)
|
||||
project_id = Column(
|
||||
UUIDType(binary=False), ForeignKey('projects.id'), index=True,
|
||||
nullable=False)
|
||||
name = Column(String(255))
|
||||
note = Column(Text)
|
||||
_repr_columns = [id, name]
|
||||
|
||||
project = relationship('Project', back_populates='cells')
|
||||
cloud = relationship('Cloud', back_populates='cells')
|
||||
region = relationship('Region', back_populates='cells')
|
||||
devices = relationship('Device', back_populates='cell')
|
||||
networks = relationship('Network', back_populates='cell')
|
||||
|
||||
@property
|
||||
def resolution_order(self):
|
||||
return list(itertools.chain(
|
||||
[self],
|
||||
[self.region],
|
||||
[self.cloud],
|
||||
[self.project]))
|
||||
|
||||
|
||||
class Device(Base, VariableMixin):
|
||||
"""Base class for all devices."""
|
||||
|
||||
__tablename__ = 'devices'
|
||||
__table_args__ = (
|
||||
UniqueConstraint("region_id", "name",
|
||||
name="uq_device0regionid0name"),
|
||||
)
|
||||
id = Column(Integer, primary_key=True)
|
||||
type = Column(String(50)) # discriminant for joined table inheritance
|
||||
name = Column(String(255), nullable=False)
|
||||
cloud_id = Column(
|
||||
Integer, ForeignKey('clouds.id'), index=True, nullable=False)
|
||||
region_id = Column(
|
||||
Integer, ForeignKey('regions.id'), index=True, nullable=False)
|
||||
cell_id = Column(
|
||||
Integer, ForeignKey('cells.id'), index=True, nullable=True)
|
||||
project_id = Column(
|
||||
UUIDType(binary=False), ForeignKey('projects.id'), index=True,
|
||||
nullable=False)
|
||||
parent_id = Column(Integer, ForeignKey('devices.id'))
|
||||
ip_address = Column(IPAddressType, nullable=False)
|
||||
device_type = Column(String(255), nullable=False)
|
||||
# TODO(jimbaker) generalize `note` for supporting governance
|
||||
active = Column(Boolean, default=True)
|
||||
note = Column(Text)
|
||||
|
||||
_repr_columns = [id, name]
|
||||
|
||||
project = relationship('Project', back_populates='devices')
|
||||
cloud = relationship('Cloud', back_populates='devices')
|
||||
region = relationship('Region', back_populates='devices')
|
||||
cell = relationship('Cell', back_populates='devices')
|
||||
related_labels = relationship(
|
||||
'Label', back_populates='device', collection_class=set,
|
||||
cascade='all, delete-orphan', lazy='joined')
|
||||
labels = association_proxy('related_labels', 'label')
|
||||
interfaces = relationship('NetworkInterface', back_populates='device')
|
||||
children = relationship(
|
||||
'Device', backref=backref('parent', remote_side=[id]))
|
||||
|
||||
@validates("parent_id")
|
||||
def validate_parent_id(self, _, parent_id):
|
||||
if parent_id is None:
|
||||
return parent_id
|
||||
elif parent_id == self.id:
|
||||
msg = (
|
||||
"A device cannot be its own parent. The id for '{name}'"
|
||||
" cannot be used as its parent_id."
|
||||
).format(name=self.name)
|
||||
raise exceptions.ParentIDError(msg)
|
||||
elif parent_id in (descendant.id for descendant in self.descendants):
|
||||
msg = (
|
||||
"A device cannot have a descendant as its parent. The"
|
||||
" parent_id for '{name}' cannot be set to the id '{bad_id}'."
|
||||
).format(name=self.name, bad_id=parent_id)
|
||||
raise exceptions.ParentIDError(msg)
|
||||
else:
|
||||
return parent_id
|
||||
|
||||
@property
|
||||
def ancestors(self):
|
||||
lineage = []
|
||||
ancestor = self.parent
|
||||
while ancestor:
|
||||
lineage.append(ancestor)
|
||||
ancestor = ancestor.parent
|
||||
return lineage
|
||||
|
||||
@property
|
||||
def descendants(self):
|
||||
marked = OrderedDict()
|
||||
descent = deque(self.children)
|
||||
while descent:
|
||||
descendant = descent.popleft()
|
||||
marked[descendant] = True
|
||||
descent.extend(
|
||||
child for child in descendant.children if child not in marked
|
||||
)
|
||||
return list(marked.keys())
|
||||
|
||||
@property
|
||||
def resolution_order(self):
|
||||
return list(itertools.chain(
|
||||
[self],
|
||||
self.ancestors,
|
||||
[self.cell] if self.cell else [],
|
||||
[self.region],
|
||||
[self.cloud],
|
||||
[self.project]))
|
||||
|
||||
__mapper_args__ = {
|
||||
'polymorphic_on': type,
|
||||
'polymorphic_identity': 'devices',
|
||||
'with_polymorphic': '*'
|
||||
}
|
||||
|
||||
|
||||
class Host(Device):
|
||||
__tablename__ = 'hosts'
|
||||
id = Column(Integer, ForeignKey('devices.id'), primary_key=True)
|
||||
hostname = Device.name
|
||||
|
||||
__mapper_args__ = {
|
||||
'polymorphic_identity': 'hosts',
|
||||
'inherit_condition': (id == Device.id)
|
||||
}
|
||||
|
||||
|
||||
class NetworkInterface(Base, VariableMixin):
|
||||
__tablename__ = 'network_interfaces'
|
||||
__table_args__ = (
|
||||
UniqueConstraint("device_id", "name",
|
||||
name="uq_netinter0deviceid0name"),
|
||||
)
|
||||
id = Column(Integer, primary_key=True)
|
||||
name = Column(String(255), nullable=True)
|
||||
interface_type = Column(String(255), nullable=True)
|
||||
vlan_id = Column(Integer, nullable=True)
|
||||
port = Column(Integer, nullable=True)
|
||||
vlan = Column(String(255), nullable=True)
|
||||
duplex = Column(String(255), nullable=True)
|
||||
speed = Column(String(255), nullable=True)
|
||||
link = Column(String(255), nullable=True)
|
||||
cdp = Column(String(255), nullable=True)
|
||||
security = Column(String(255), nullable=True)
|
||||
ip_address = Column(IPAddressType, nullable=False)
|
||||
project_id = Column(UUIDType(binary=False), ForeignKey('projects.id'),
|
||||
index=True, nullable=False)
|
||||
device_id = Column(Integer, ForeignKey('devices.id'))
|
||||
network_id = Column(Integer, ForeignKey('networks.id'), nullable=True)
|
||||
|
||||
project = relationship(
|
||||
'Project', back_populates='interfaces', cascade='all', lazy='joined')
|
||||
network = relationship(
|
||||
'Network', back_populates='interfaces', cascade='all', lazy='joined')
|
||||
device = relationship(
|
||||
'Device', back_populates='interfaces', cascade='all', lazy='joined')
|
||||
|
||||
|
||||
class Network(Base, VariableMixin):
|
||||
__tablename__ = 'networks'
|
||||
__table_args__ = (
|
||||
UniqueConstraint("name", "project_id", "region_id",
|
||||
name="uq_name0projectid0regionid"),
|
||||
)
|
||||
id = Column(Integer, primary_key=True)
|
||||
name = Column(String(255), nullable=True)
|
||||
cidr = Column(String(255), nullable=True)
|
||||
gateway = Column(String(255), nullable=True)
|
||||
netmask = Column(String(255), nullable=True)
|
||||
ip_block_type = Column(String(255), nullable=True)
|
||||
nss = Column(String(255), nullable=True)
|
||||
cloud_id = Column(
|
||||
Integer, ForeignKey('clouds.id'), index=True, nullable=False)
|
||||
region_id = Column(
|
||||
Integer, ForeignKey('regions.id'), index=True, nullable=False)
|
||||
cell_id = Column(
|
||||
Integer, ForeignKey('cells.id'), index=True, nullable=True)
|
||||
project_id = Column(
|
||||
UUIDType(binary=False), ForeignKey('projects.id'), index=True,
|
||||
nullable=False)
|
||||
|
||||
project = relationship('Project', back_populates='networks')
|
||||
cloud = relationship('Cloud', back_populates='networks')
|
||||
region = relationship('Region', back_populates='networks')
|
||||
cell = relationship('Cell', back_populates='networks')
|
||||
interfaces = relationship('NetworkInterface', back_populates='network')
|
||||
|
||||
|
||||
class NetworkDevice(Device):
|
||||
__tablename__ = 'network_devices'
|
||||
id = Column(Integer, ForeignKey('devices.id'), primary_key=True)
|
||||
hostname = Device.name
|
||||
# network device specific properties
|
||||
model_name = Column(String(255), nullable=True)
|
||||
os_version = Column(String(255), nullable=True)
|
||||
vlans = Column(JSON)
|
||||
|
||||
__mapper_args__ = {
|
||||
'polymorphic_identity': 'network_devices',
|
||||
'inherit_condition': (id == Device.id)
|
||||
}
|
||||
|
||||
|
||||
class Label(Base):
|
||||
"""Models arbitrary labeling (aka tagging) of devices."""
|
||||
__tablename__ = 'labels'
|
||||
device_id = Column(
|
||||
Integer,
|
||||
ForeignKey(Device.id, name='fk_labels_devices'),
|
||||
primary_key=True)
|
||||
label = Column(String(255), primary_key=True)
|
||||
_repr_columns = [device_id, label]
|
||||
|
||||
def __init__(self, label):
|
||||
self.label = label
|
||||
|
||||
device = relationship("Device", back_populates="related_labels")
|
@ -1,100 +0,0 @@
|
||||
"""Exceptions for Craton Inventory system."""
|
||||
from oslo_log import log as logging
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Base(Exception):
|
||||
"""Base Exception for Craton Inventory."""
|
||||
code = 500
|
||||
message = "An unknown exception occurred"
|
||||
|
||||
def __str__(self):
|
||||
return self.message
|
||||
|
||||
def __init__(self, code=None, message=None, **kwargs):
|
||||
if code:
|
||||
self.code = code
|
||||
|
||||
if not message:
|
||||
try:
|
||||
message = self.msg % kwargs
|
||||
except Exception:
|
||||
LOG.exception('Error in formatting exception message')
|
||||
message = self.msg
|
||||
|
||||
self.message = message
|
||||
|
||||
super(Base, self).__init__(
|
||||
'%s: %s' % (self.code, self.message))
|
||||
|
||||
|
||||
class DuplicateCloud(Base):
|
||||
code = 409
|
||||
msg = "A cloud with the given name already exists."
|
||||
|
||||
|
||||
class DuplicateRegion(Base):
|
||||
code = 409
|
||||
msg = "A region with the given name already exists."
|
||||
|
||||
|
||||
class DuplicateCell(Base):
|
||||
code = 409
|
||||
msg = "A cell with the given name already exists."
|
||||
|
||||
|
||||
class DuplicateDevice(Base):
|
||||
code = 409
|
||||
msg = "A device with the given name already exists."
|
||||
|
||||
|
||||
class DuplicateNetwork(Base):
|
||||
code = 409
|
||||
msg = "Network with the given name already exists in this region."
|
||||
|
||||
|
||||
class NetworkNotFound(Base):
|
||||
code = 404
|
||||
msg = "Network not found for ID %(id)s."
|
||||
|
||||
|
||||
class DeviceNotFound(Base):
|
||||
code = 404
|
||||
msg = "%(device_type)s device not found for ID %(id)s."
|
||||
|
||||
|
||||
class AuthenticationError(Base):
|
||||
code = 401
|
||||
msg = "The request could not be authenticated."
|
||||
|
||||
|
||||
class AdminRequired(Base):
|
||||
code = 401
|
||||
msg = "This action requires the 'admin' role"
|
||||
|
||||
|
||||
class BadRequest(Base):
|
||||
code = 400
|
||||
|
||||
|
||||
class InvalidJSONPath(BadRequest):
|
||||
msg = "The query contains an invalid JSON Path expression."
|
||||
|
||||
|
||||
class InvalidJSONValue(BadRequest):
|
||||
msg = "An invalid JSON value was specified."
|
||||
|
||||
|
||||
class NotFound(Base):
|
||||
code = 404
|
||||
msg = "Not Found"
|
||||
|
||||
|
||||
class UnknownException(Base):
|
||||
code = 500
|
||||
|
||||
|
||||
class ParentIDError(ValueError):
|
||||
pass
|
@ -1,30 +0,0 @@
|
||||
import mock
|
||||
import testtools
|
||||
from oslo_middleware import base
|
||||
|
||||
|
||||
class TestContext(base.Middleware):
|
||||
def __init__(self, auth_token=None, user=None, tenant=None,
|
||||
is_admin=False, is_admin_project=False):
|
||||
self.auth_token = auth_token
|
||||
self.user = user
|
||||
self.tenant = tenant
|
||||
self.is_admin = is_admin
|
||||
self.is_admin_project = is_admin_project
|
||||
|
||||
|
||||
def make_context(*args, **kwargs):
|
||||
return TestContext(*args, **kwargs)
|
||||
|
||||
|
||||
class TestCase(testtools.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestCase, self).setUp()
|
||||
self.addCleanup(mock.patch.stopall)
|
||||
|
||||
self.context = make_context(auth_token='fake-token',
|
||||
user='fake-user',
|
||||
tenant='fake-tenant',
|
||||
is_admin=True,
|
||||
is_admin_project=True)
|
@ -1,493 +0,0 @@
|
||||
import contextlib
|
||||
import copy
|
||||
import json
|
||||
import threading
|
||||
|
||||
import docker
|
||||
from oslo_log import log as logging
|
||||
from oslo_utils import uuidutils
|
||||
import requests
|
||||
from retrying import retry
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy import MetaData
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
import testtools
|
||||
|
||||
from craton.db.sqlalchemy import models
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
FAKE_DATA_GEN_USERNAME = 'demo'
|
||||
FAKE_DATA_GEN_TOKEN = 'demo'
|
||||
FAKE_DATA_GEN_PROJECT_ID = 'b9f10eca66ac4c279c139d01e65f96b4'
|
||||
|
||||
FAKE_DATA_GEN_BOOTSTRAP_USERNAME = 'bootstrap'
|
||||
FAKE_DATA_GEN_BOOTSTRAP_TOKEN = 'bootstrap'
|
||||
|
||||
HEADER_TOKEN = 'X-Auth-Token'
|
||||
HEADER_USERNAME = 'X-Auth-User'
|
||||
HEADER_PROJECT = 'X-Auth-Project'
|
||||
|
||||
|
||||
def get_root_headers():
|
||||
return {
|
||||
HEADER_USERNAME: FAKE_DATA_GEN_BOOTSTRAP_USERNAME,
|
||||
HEADER_TOKEN: FAKE_DATA_GEN_BOOTSTRAP_TOKEN
|
||||
}
|
||||
|
||||
|
||||
class DockerSetup(threading.Thread):
|
||||
|
||||
def __init__(self):
|
||||
self.container = None
|
||||
self.container_is_ready = threading.Event()
|
||||
self.error = None
|
||||
self.client = None
|
||||
self.repo_dir = './'
|
||||
super(DockerSetup, self).__init__()
|
||||
|
||||
def run(self):
|
||||
"""Build a docker container from the given Dockerfile and start
|
||||
the container in a separate thread."""
|
||||
try:
|
||||
self.client = docker.Client(version='auto')
|
||||
is_ok = self.client.ping()
|
||||
if is_ok != 'OK':
|
||||
msg = 'Docker daemon ping failed.'
|
||||
self.error = msg
|
||||
LOG.error(self.error)
|
||||
self.container_is_ready.set()
|
||||
return
|
||||
except Exception as err:
|
||||
self.error = err
|
||||
LOG.error(self.error)
|
||||
self.container_is_ready.set()
|
||||
return
|
||||
|
||||
# Create Docker image for Craton
|
||||
build_output = self.client.build(path=self.repo_dir,
|
||||
tag='craton-functional-testing-api',
|
||||
dockerfile='Dockerfile',
|
||||
pull=True,
|
||||
forcerm=True)
|
||||
LOG.debug(build_output)
|
||||
output_last_line = ""
|
||||
for output_last_line in build_output:
|
||||
pass
|
||||
|
||||
message = output_last_line.decode("utf-8")
|
||||
if "Successfully built" not in message:
|
||||
msg = 'Failed to build docker image.'
|
||||
self.error = msg
|
||||
self.container_is_ready.set()
|
||||
return
|
||||
|
||||
# create and start the container
|
||||
container_tag = 'craton-functional-testing-api'
|
||||
self.container = self.client.create_container(container_tag)
|
||||
self.client.start(self.container)
|
||||
self.container_data = self.client.inspect_container(self.container)
|
||||
if self.container_data['State']['Status'] != 'running':
|
||||
msg = 'Container is not running.'
|
||||
self.error = msg
|
||||
self.container_is_ready.set()
|
||||
return
|
||||
|
||||
self.container_is_ready.set()
|
||||
|
||||
def stop(self):
|
||||
"""Stop a running container."""
|
||||
if self.container is not None:
|
||||
self.client.stop(self.container, timeout=30)
|
||||
|
||||
def remove(self):
|
||||
"""Remove/Delete a stopped container."""
|
||||
if self.container is not None:
|
||||
self.client.remove_container(self.container)
|
||||
|
||||
def remove_image(self):
|
||||
"""Remove the image we created."""
|
||||
if self.client:
|
||||
self.client.remove_image('craton-functional-testing-api')
|
||||
|
||||
|
||||
@retry(wait_fixed=1000, stop_max_attempt_number=20)
|
||||
def ensure_running_endpoint(container_data):
|
||||
service_ip = container_data['NetworkSettings']['IPAddress']
|
||||
url = 'http://{}:7780/v1'.format(service_ip)
|
||||
headers = {"Content-Type": "application/json"}
|
||||
requests.get(url, headers=headers)
|
||||
|
||||
|
||||
_container = None
|
||||
|
||||
|
||||
def setup_container():
|
||||
global _container
|
||||
|
||||
_container = DockerSetup()
|
||||
_container.daemon = True
|
||||
_container.start()
|
||||
_container.container_is_ready.wait()
|
||||
|
||||
if _container.error:
|
||||
teardown_container()
|
||||
else:
|
||||
try:
|
||||
ensure_running_endpoint(_container.container_data)
|
||||
except Exception:
|
||||
msg = 'Error during data generation script run.'
|
||||
_container.error = msg
|
||||
teardown_container()
|
||||
|
||||
|
||||
def teardown_container():
|
||||
if _container:
|
||||
_container.stop()
|
||||
_container.remove()
|
||||
_container.remove_image()
|
||||
|
||||
|
||||
def setUpModule():
|
||||
setup_container()
|
||||
|
||||
|
||||
def tearDownModule():
|
||||
teardown_container()
|
||||
|
||||
|
||||
def setup_database(container_ip):
|
||||
mysqldb = "mysql+pymysql://craton:craton@{}/craton".format(container_ip)
|
||||
engine = create_engine(mysqldb)
|
||||
meta = MetaData()
|
||||
meta.reflect(engine)
|
||||
|
||||
# NOTE(sulo, jimbaker): First clean the db up for tests, and do
|
||||
# our own bootstrapping to isolate all test from any external
|
||||
# scripts.
|
||||
with contextlib.closing(engine.connect()) as conn:
|
||||
transaction = conn.begin()
|
||||
conn.execute("SET foreign_key_checks = 0")
|
||||
for table in reversed(meta.sorted_tables):
|
||||
conn.execute(table.delete())
|
||||
conn.execute("SET foreign_key_checks = 1")
|
||||
transaction.commit()
|
||||
|
||||
# NOTE(sulo, jimbaker): now bootstrap user and project; using the
|
||||
# SA model allows us to respect the additional constraints in the
|
||||
# model, vs having to duplicate logic if working against the
|
||||
# database directly.
|
||||
Session = sessionmaker(bind=engine)
|
||||
session = Session()
|
||||
project = models.Project(
|
||||
name=FAKE_DATA_GEN_USERNAME,
|
||||
id=FAKE_DATA_GEN_PROJECT_ID)
|
||||
bootstrap_user = models.User(
|
||||
project=project,
|
||||
username=FAKE_DATA_GEN_BOOTSTRAP_USERNAME,
|
||||
api_key=FAKE_DATA_GEN_BOOTSTRAP_TOKEN,
|
||||
is_admin=True,
|
||||
is_root=True)
|
||||
demo_user = models.User(
|
||||
project=project,
|
||||
username=FAKE_DATA_GEN_USERNAME,
|
||||
api_key=FAKE_DATA_GEN_TOKEN,
|
||||
is_admin=True)
|
||||
|
||||
session.add(project)
|
||||
session.add(bootstrap_user)
|
||||
session.add(demo_user)
|
||||
|
||||
# NOTE(jimbaker) simple assumption: either this commit succeeds,
|
||||
# or we need to fail fast - there's no recovery allowed in this
|
||||
# testing setup.
|
||||
session.commit()
|
||||
|
||||
|
||||
class TestCase(testtools.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
"""Base setup provides container data back individual tests."""
|
||||
super(TestCase, self).setUp()
|
||||
self.container_setup_error = _container.error
|
||||
self.session = requests.Session()
|
||||
|
||||
if not self.container_setup_error:
|
||||
data = _container.container_data
|
||||
self.service_ip = data['NetworkSettings']['IPAddress']
|
||||
self.url = 'http://{}:7780'.format(self.service_ip)
|
||||
self.session.headers[HEADER_PROJECT] = FAKE_DATA_GEN_PROJECT_ID
|
||||
self.session.headers[HEADER_USERNAME] = FAKE_DATA_GEN_USERNAME
|
||||
self.session.headers[HEADER_TOKEN] = FAKE_DATA_GEN_TOKEN
|
||||
|
||||
self.root_headers = copy.deepcopy(self.session.headers)
|
||||
self.root_headers.update(get_root_headers())
|
||||
|
||||
setup_database(self.service_ip)
|
||||
|
||||
def tearDown(self):
|
||||
super(TestCase, self).tearDown()
|
||||
|
||||
def assertSuccessOk(self, response):
|
||||
self.assertEqual(requests.codes.OK, response.status_code)
|
||||
|
||||
def assertSuccessCreated(self, response):
|
||||
self.assertEqual(requests.codes.CREATED, response.status_code)
|
||||
|
||||
def assertNoContent(self, response):
|
||||
self.assertEqual(requests.codes.NO_CONTENT, response.status_code)
|
||||
|
||||
def assertBadRequest(self, response):
|
||||
self.assertEqual(requests.codes.BAD_REQUEST, response.status_code)
|
||||
|
||||
def assertJSON(self, response):
|
||||
if response.text:
|
||||
try:
|
||||
data = json.loads(response.text)
|
||||
except json.JSONDecodeError:
|
||||
self.fail("Response data is not JSON.")
|
||||
else:
|
||||
reference = "{formatted_data}\n".format(
|
||||
formatted_data=json.dumps(
|
||||
data, indent=2, sort_keys=True, separators=(',', ': ')
|
||||
)
|
||||
)
|
||||
self.assertEqual(
|
||||
reference,
|
||||
response.text
|
||||
)
|
||||
|
||||
def assertFailureFormat(self, response):
|
||||
if response.status_code >= 400:
|
||||
body = response.json()
|
||||
self.assertEqual(2, len(body))
|
||||
self.assertEqual(response.status_code, body["status"])
|
||||
self.assertIn("message", body)
|
||||
|
||||
def get(self, url, headers=None, **params):
|
||||
resp = self.session.get(
|
||||
url, verify=False, headers=headers, params=params,
|
||||
)
|
||||
self.assertJSON(resp)
|
||||
self.assertFailureFormat(resp)
|
||||
return resp
|
||||
|
||||
def post(self, url, headers=None, data=None):
|
||||
resp = self.session.post(
|
||||
url, verify=False, headers=headers, json=data,
|
||||
)
|
||||
self.assertJSON(resp)
|
||||
self.assertFailureFormat(resp)
|
||||
return resp
|
||||
|
||||
def put(self, url, headers=None, data=None):
|
||||
resp = self.session.put(
|
||||
url, verify=False, headers=headers, json=data,
|
||||
)
|
||||
self.assertJSON(resp)
|
||||
self.assertFailureFormat(resp)
|
||||
return resp
|
||||
|
||||
def delete(self, url, headers=None, body=None):
|
||||
resp = self.session.delete(
|
||||
url, verify=False, headers=headers, json=body,
|
||||
)
|
||||
self.assertJSON(resp)
|
||||
self.assertFailureFormat(resp)
|
||||
return resp
|
||||
|
||||
def create_project(self, name, variables=None):
|
||||
url = self.url + '/v1/projects'
|
||||
payload = {'name': name}
|
||||
if variables:
|
||||
payload['variables'] = variables
|
||||
response = self.post(url, headers=self.root_headers, data=payload)
|
||||
self.assertEqual(201, response.status_code)
|
||||
self.assertIn('Location', response.headers)
|
||||
project = response.json()
|
||||
self.assertTrue(uuidutils.is_uuid_like(project['id']))
|
||||
self.assertEqual(
|
||||
response.headers['Location'],
|
||||
"{}/{}".format(url, project['id'])
|
||||
)
|
||||
return project
|
||||
|
||||
def create_cloud(self, name, variables=None):
|
||||
url = self.url + '/v1/clouds'
|
||||
|
||||
values = {'name': name}
|
||||
if variables:
|
||||
values['variables'] = variables
|
||||
resp = self.post(url, data=values)
|
||||
self.assertSuccessCreated(resp)
|
||||
self.assertIn('Location', resp.headers)
|
||||
json = resp.json()
|
||||
self.assertEqual(
|
||||
resp.headers['Location'],
|
||||
"{}/{}".format(url, json['id'])
|
||||
)
|
||||
return json
|
||||
|
||||
def delete_clouds(self, clouds):
|
||||
base_url = self.url + '/v1/clouds/{}'
|
||||
for cloud in clouds:
|
||||
url = base_url.format(cloud['id'])
|
||||
resp = self.delete(url)
|
||||
self.assertNoContent(resp)
|
||||
|
||||
def create_region(self, name, cloud, variables=None):
|
||||
url = self.url + '/v1/regions'
|
||||
|
||||
values = {'name': name, 'cloud_id': cloud['id']}
|
||||
if variables:
|
||||
values['variables'] = variables
|
||||
resp = self.post(url, data=values)
|
||||
self.assertSuccessCreated(resp)
|
||||
self.assertIn('Location', resp.headers)
|
||||
json = resp.json()
|
||||
self.assertEqual(
|
||||
resp.headers['Location'],
|
||||
"{}/{}".format(url, json['id'])
|
||||
)
|
||||
return json
|
||||
|
||||
def delete_regions(self, regions):
|
||||
base_url = self.url + '/v1/regions/{}'
|
||||
for region in regions:
|
||||
url = base_url.format(region['id'])
|
||||
resp = self.delete(url)
|
||||
self.assertNoContent(resp)
|
||||
|
||||
def create_cell(self, name, cloud, region, variables=None):
|
||||
url = self.url + '/v1/cells'
|
||||
payload = {'name': name, 'region_id': region['id'],
|
||||
'cloud_id': cloud['id']}
|
||||
if variables:
|
||||
payload['variables'] = variables
|
||||
cell = self.post(url, data=payload)
|
||||
self.assertEqual(201, cell.status_code)
|
||||
self.assertIn('Location', cell.headers)
|
||||
self.assertEqual(
|
||||
cell.headers['Location'],
|
||||
"{}/{}".format(url, cell.json()['id'])
|
||||
)
|
||||
return cell.json()
|
||||
|
||||
def create_network(
|
||||
self, name, cloud, region, cidr, gateway, netmask, variables=None
|
||||
):
|
||||
|
||||
url = self.url + '/v1/networks'
|
||||
payload = {
|
||||
'name': name,
|
||||
'cidr': cidr,
|
||||
'gateway': gateway,
|
||||
'netmask': netmask,
|
||||
'region_id': region['id'],
|
||||
'cloud_id': cloud['id'],
|
||||
}
|
||||
if variables:
|
||||
payload['variables'] = variables
|
||||
|
||||
network = self.post(url, data=payload)
|
||||
self.assertEqual(201, network.status_code)
|
||||
self.assertIn('Location', network.headers)
|
||||
self.assertEqual(
|
||||
network.headers['Location'],
|
||||
"{}/{}".format(url, network.json()['id'])
|
||||
)
|
||||
return network.json()
|
||||
|
||||
def create_host(self, name, cloud, region, hosttype, ip_address,
|
||||
parent_id=None, **variables):
|
||||
url = self.url + '/v1/hosts'
|
||||
payload = {
|
||||
'name': name,
|
||||
'device_type': hosttype,
|
||||
'ip_address': ip_address,
|
||||
'region_id': region['id'],
|
||||
'cloud_id': cloud['id']
|
||||
}
|
||||
if parent_id:
|
||||
payload['parent_id'] = parent_id
|
||||
if variables:
|
||||
payload['variables'] = variables
|
||||
|
||||
host = self.post(url, data=payload)
|
||||
self.assertEqual(201, host.status_code)
|
||||
self.assertIn('Location', host.headers)
|
||||
self.assertEqual(
|
||||
host.headers['Location'],
|
||||
"{}/{}".format(url, host.json()['id'])
|
||||
)
|
||||
return host.json()
|
||||
|
||||
def create_network_device(
|
||||
self, name, cloud, region, device_type, ip_address, parent_id=None,
|
||||
**variables
|
||||
):
|
||||
|
||||
url = self.url + '/v1/network-devices'
|
||||
payload = {
|
||||
'name': name,
|
||||
'device_type': device_type,
|
||||
'ip_address': ip_address,
|
||||
'region_id': region['id'],
|
||||
'cloud_id': cloud['id'],
|
||||
}
|
||||
if parent_id:
|
||||
payload['parent_id'] = parent_id
|
||||
if variables:
|
||||
payload['variables'] = variables
|
||||
|
||||
network_device = self.post(url, data=payload)
|
||||
self.assertEqual(201, network_device.status_code)
|
||||
self.assertIn('Location', network_device.headers)
|
||||
self.assertEqual(
|
||||
network_device.headers['Location'],
|
||||
"{}/{}".format(url, network_device.json()['id'])
|
||||
)
|
||||
return network_device.json()
|
||||
|
||||
|
||||
class DeviceTestBase(TestCase):
|
||||
def setUp(self):
|
||||
super(DeviceTestBase, self).setUp()
|
||||
self.cloud = self.create_cloud()
|
||||
self.region = self.create_region()
|
||||
|
||||
def create_cloud(self, name='cloud-1'):
|
||||
return super(DeviceTestBase, self).create_cloud(name=name)
|
||||
|
||||
def create_region(self, name='region-1', cloud=None, variables=None):
|
||||
return super(DeviceTestBase, self).create_region(
|
||||
name=name,
|
||||
cloud=cloud if cloud else self.cloud,
|
||||
variables=variables,
|
||||
)
|
||||
|
||||
def create_network_device(self, name, device_type, ip_address, region=None,
|
||||
cloud=None, parent_id=None, **variables):
|
||||
return super(DeviceTestBase, self).create_network_device(
|
||||
name=name,
|
||||
cloud=cloud if cloud else self.cloud,
|
||||
region=region if region else self.region,
|
||||
device_type=device_type,
|
||||
ip_address=ip_address,
|
||||
parent_id=parent_id,
|
||||
**variables
|
||||
)
|
||||
|
||||
def create_host(self, name, hosttype, ip_address, region=None, cloud=None,
|
||||
parent_id=None, **variables):
|
||||
return super(DeviceTestBase, self).create_host(
|
||||
name=name,
|
||||
cloud=cloud if cloud else self.cloud,
|
||||
region=region if region else self.region,
|
||||
hosttype=hosttype,
|
||||
ip_address=ip_address,
|
||||
parent_id=parent_id,
|
||||
**variables
|
||||
)
|
@ -1,216 +0,0 @@
|
||||
from craton.tests.functional.test_variable_calls import \
|
||||
APIV1ResourceWithVariablesTestCase
|
||||
|
||||
|
||||
class APIV1CellTest(APIV1ResourceWithVariablesTestCase):
|
||||
|
||||
resource = 'cells'
|
||||
|
||||
def setUp(self):
|
||||
super(APIV1CellTest, self).setUp()
|
||||
self.cloud = self.create_cloud()
|
||||
self.region = self.create_region()
|
||||
|
||||
def tearDown(self):
|
||||
super(APIV1CellTest, self).tearDown()
|
||||
|
||||
def create_cloud(self):
|
||||
return super(APIV1CellTest, self).create_cloud(name='cloud-1')
|
||||
|
||||
def create_region(self):
|
||||
return super(APIV1CellTest, self).create_region(
|
||||
name='region-1',
|
||||
cloud=self.cloud,
|
||||
variables={"region": "one"},
|
||||
)
|
||||
|
||||
def create_cell(self, name, variables=None):
|
||||
return super(APIV1CellTest, self).create_cell(
|
||||
name=name,
|
||||
cloud=self.cloud,
|
||||
region=self.region,
|
||||
variables=variables
|
||||
)
|
||||
|
||||
def test_cell_create_with_variables(self):
|
||||
variables = {'a': 'b'}
|
||||
cell = self.create_cell('cell-a', variables=variables)
|
||||
self.assertEqual('cell-a', cell['name'])
|
||||
self.assertEqual(variables, cell['variables'])
|
||||
|
||||
def test_create_cell_supports_vars_ops(self):
|
||||
cell = self.create_cell('new-cell', {'a': 'b'})
|
||||
self.assert_vars_get_expected(cell['id'], {'a': 'b'})
|
||||
self.assert_vars_can_be_set(cell['id'])
|
||||
self.assert_vars_can_be_deleted(cell['id'])
|
||||
|
||||
def test_cell_create_with_no_name_fails(self):
|
||||
url = self.url + '/v1/cells'
|
||||
payload = {'region_id': self.region['id']}
|
||||
cell = self.post(url, data=payload)
|
||||
self.assertEqual(400, cell.status_code)
|
||||
|
||||
def test_cell_create_with_duplicate_name_fails(self):
|
||||
self.create_cell('test-cell')
|
||||
url = self.url + '/v1/cells'
|
||||
payload = {'name': 'test-cell', 'region_id': self.region['id'],
|
||||
"cloud_id": self.cloud['id']}
|
||||
cell = self.post(url, data=payload)
|
||||
self.assertEqual(409, cell.status_code)
|
||||
|
||||
def test_cell_create_with_extra_id_property_fails(self):
|
||||
url = self.url + '/v1/cells'
|
||||
payload = {'region_id': self.region['id'],
|
||||
'cloud_id': self.cloud['id'], 'name': 'a', 'id': 3}
|
||||
cell = self.post(url, data=payload)
|
||||
self.assertEqual(400, cell.status_code)
|
||||
msg = (
|
||||
"The request included the following errors:\n"
|
||||
"- Additional properties are not allowed ('id' was unexpected)"
|
||||
)
|
||||
self.assertEqual(cell.json()['message'], msg)
|
||||
|
||||
def test_cell_create_with_extra_created_at_property_fails(self):
|
||||
url = self.url + '/v1/cells'
|
||||
payload = {'region_id': self.region['id'],
|
||||
'cloud_id': self.cloud['id'], 'name': 'a',
|
||||
'created_at': "some date"}
|
||||
cell = self.post(url, data=payload)
|
||||
self.assertEqual(400, cell.status_code)
|
||||
msg = (
|
||||
"The request included the following errors:\n"
|
||||
"- Additional properties are not allowed "
|
||||
"('created_at' was unexpected)"
|
||||
)
|
||||
self.assertEqual(cell.json()['message'], msg)
|
||||
|
||||
def test_cell_create_with_extra_updated_at_property_fails(self):
|
||||
url = self.url + '/v1/cells'
|
||||
payload = {'region_id': self.region['id'],
|
||||
'cloud_id': self.cloud['id'], 'name': 'a',
|
||||
'updated_at': "some date"}
|
||||
cell = self.post(url, data=payload)
|
||||
self.assertEqual(400, cell.status_code)
|
||||
msg = (
|
||||
"The request included the following errors:\n"
|
||||
"- Additional properties are not allowed "
|
||||
"('updated_at' was unexpected)"
|
||||
)
|
||||
self.assertEqual(cell.json()['message'], msg)
|
||||
|
||||
def test_cell_create_missing_all_properties_fails(self):
|
||||
url = self.url + '/v1/cells'
|
||||
cell = self.post(url, data={})
|
||||
self.assertEqual(400, cell.status_code)
|
||||
msg = (
|
||||
"The request included the following errors:\n"
|
||||
"- 'cloud_id' is a required property\n"
|
||||
"- 'name' is a required property\n"
|
||||
"- 'region_id' is a required property"
|
||||
)
|
||||
self.assertEqual(cell.json()['message'], msg)
|
||||
|
||||
def test_cells_get_all_with_details(self):
|
||||
self.create_cell('cell1', variables={'a': 'b'})
|
||||
self.create_cell('cell2', variables={'c': 'd'})
|
||||
url = self.url + '/v1/cells?details=all'
|
||||
resp = self.get(url)
|
||||
cells = resp.json()['cells']
|
||||
self.assertEqual(2, len(cells))
|
||||
for cell in cells:
|
||||
self.assertTrue('variables' in cell)
|
||||
|
||||
for cell in cells:
|
||||
if cell['name'] == 'cell1':
|
||||
expected = {'a': 'b', "region": "one"}
|
||||
self.assertEqual(expected, cell['variables'])
|
||||
if cell['name'] == 'cell2':
|
||||
expected = {'c': 'd', "region": "one"}
|
||||
self.assertEqual(expected, cell['variables'])
|
||||
|
||||
def test_cells_get_all_for_region(self):
|
||||
# Create a cell first
|
||||
self.create_cell('cell-1')
|
||||
url = self.url + '/v1/cells?region_id={}'.format(self.region['id'])
|
||||
resp = self.get(url)
|
||||
cells = resp.json()['cells']
|
||||
self.assertEqual(1, len(cells))
|
||||
self.assertEqual(['cell-1'], [i['name'] for i in cells])
|
||||
|
||||
def test_cells_get_all_for_cloud(self):
|
||||
# Create a cell first
|
||||
for i in range(2):
|
||||
self.create_cell('cell-{}'.format(str(i)))
|
||||
url = self.url + '/v1/cells?cloud_id={}'.format(self.cloud['id'])
|
||||
resp = self.get(url)
|
||||
cells = resp.json()['cells']
|
||||
self.assertEqual(2, len(cells))
|
||||
self.assertEqual(['cell-0', 'cell-1'], [i['name'] for i in cells])
|
||||
|
||||
def test_cell_get_all_with_name_filter(self):
|
||||
self.create_cell('cell1')
|
||||
self.create_cell('cell2')
|
||||
url = self.url + '/v1/cells?name=cell2'
|
||||
resp = self.get(url)
|
||||
cells = resp.json()['cells']
|
||||
self.assertEqual(1, len(cells))
|
||||
self.assertEqual({'cell2'}, {cell['name'] for cell in cells})
|
||||
|
||||
def test_get_cell_details(self):
|
||||
cellvars = {"who": "that"}
|
||||
cell = self.create_cell('cell1', variables=cellvars)
|
||||
url = self.url + '/v1/cells/{}'.format(cell['id'])
|
||||
resp = self.get(url)
|
||||
cell_with_detail = resp.json()
|
||||
self.assertEqual('cell1', cell_with_detail['name'])
|
||||
|
||||
def test_get_cell_resolved_vars(self):
|
||||
cellvars = {"who": "that"}
|
||||
cell = self.create_cell('cell1', variables=cellvars)
|
||||
url = self.url + '/v1/cells/{}'.format(cell['id'])
|
||||
resp = self.get(url)
|
||||
cell_with_detail = resp.json()
|
||||
self.assertEqual('cell1', cell_with_detail['name'])
|
||||
self.assertEqual({"who": "that", "region": "one"},
|
||||
cell_with_detail['variables'])
|
||||
|
||||
def test_get_cell_unresolved_vars(self):
|
||||
cellvars = {"who": "that"}
|
||||
cell = self.create_cell('cell1', variables=cellvars)
|
||||
cell_id = cell['id']
|
||||
url = self.url + '/v1/cells/{}?resolved-values=false'.format(cell_id)
|
||||
resp = self.get(url)
|
||||
cell_with_detail = resp.json()
|
||||
self.assertEqual('cell1', cell_with_detail['name'])
|
||||
self.assertEqual({"who": "that"}, cell_with_detail['variables'])
|
||||
|
||||
def test_cell_update(self):
|
||||
cell = self.create_cell('cell-1')
|
||||
url = self.url + '/v1/cells/{}'.format(cell['id'])
|
||||
data = {'note': 'Updated cell note.'}
|
||||
resp = self.put(url, data=data)
|
||||
self.assertEqual(200, resp.status_code)
|
||||
cell = resp.json()
|
||||
self.assertEqual(data['note'], cell['note'])
|
||||
|
||||
def test_cell_delete(self):
|
||||
cell1 = self.create_cell('cell-1')
|
||||
self.create_cell('cell-2')
|
||||
url = self.url + '/v1/cells'
|
||||
resp = self.get(url)
|
||||
self.assertEqual(200, resp.status_code)
|
||||
cells = resp.json()['cells']
|
||||
self.assertEqual(2, len(cells))
|
||||
self.assertEqual({'cell-1', 'cell-2'},
|
||||
{cell['name'] for cell in cells})
|
||||
|
||||
delurl = self.url + '/v1/cells/{}'.format(cell1['id'])
|
||||
resp = self.delete(delurl)
|
||||
self.assertEqual(204, resp.status_code)
|
||||
|
||||
resp = self.get(url)
|
||||
self.assertEqual(200, resp.status_code)
|
||||
cells = resp.json()['cells']
|
||||
self.assertEqual(1, len(cells))
|
||||
self.assertEqual({'cell-2'},
|
||||
{cell['name'] for cell in cells})
|
@ -1,204 +0,0 @@
|
||||
import urllib.parse
|
||||
|
||||
from craton.tests.functional import TestCase
|
||||
|
||||
|
||||
class APIV1CloudTest(TestCase):
|
||||
"""Test cases for /cloud calls.
|
||||
"""
|
||||
|
||||
def test_create_cloud_full_data(self):
|
||||
# Test with full set of allowed parameters
|
||||
values = {"name": "cloud-new",
|
||||
"note": "This is cloud-new.",
|
||||
"variables": {"a": "b"}}
|
||||
url = self.url + '/v1/clouds'
|
||||
resp = self.post(url, data=values)
|
||||
self.assertEqual(201, resp.status_code)
|
||||
self.assertIn('Location', resp.headers)
|
||||
self.assertEqual(
|
||||
resp.headers['Location'],
|
||||
"{}/{}".format(url, resp.json()['id'])
|
||||
)
|
||||
self.assertEqual(values['name'], resp.json()['name'])
|
||||
|
||||
def test_create_cloud_without_variables(self):
|
||||
values = {"name": "cloud-two",
|
||||
"note": "This is cloud-two"}
|
||||
url = self.url + '/v1/clouds'
|
||||
resp = self.post(url, data=values)
|
||||
self.assertEqual(201, resp.status_code)
|
||||
self.assertIn('Location', resp.headers)
|
||||
self.assertEqual(
|
||||
resp.headers['Location'],
|
||||
"{}/{}".format(url, resp.json()['id'])
|
||||
)
|
||||
self.assertEqual("cloud-two", resp.json()['name'])
|
||||
|
||||
def test_create_cloud_with_no_name_fails(self):
|
||||
values = {"note": "This is cloud one."}
|
||||
url = self.url + '/v1/clouds'
|
||||
resp = self.post(url, data=values)
|
||||
self.assertEqual(resp.status_code, 400)
|
||||
err_msg = (
|
||||
"The request included the following errors:\n"
|
||||
"- 'name' is a required property"
|
||||
)
|
||||
self.assertEqual(resp.json()['message'], err_msg)
|
||||
|
||||
def test_create_cloud_with_duplicate_name_fails(self):
|
||||
self.create_cloud("ORD135")
|
||||
|
||||
values = {"name": "ORD135"}
|
||||
url = self.url + '/v1/clouds'
|
||||
resp = self.post(url, data=values)
|
||||
self.assertEqual(409, resp.status_code)
|
||||
|
||||
def test_create_region_with_extra_id_property_fails(self):
|
||||
values = {"name": "test", "id": 101}
|
||||
url = self.url + '/v1/clouds'
|
||||
resp = self.post(url, data=values)
|
||||
self.assertEqual(resp.status_code, 400)
|
||||
msg = (
|
||||
"The request included the following errors:\n"
|
||||
"- Additional properties are not allowed ('id' was unexpected)"
|
||||
)
|
||||
self.assertEqual(resp.json()['message'], msg)
|
||||
|
||||
def test_create_region_with_extra_created_at_property_fails(self):
|
||||
values = {"name": "test", "created_at": "some date"}
|
||||
url = self.url + '/v1/clouds'
|
||||
resp = self.post(url, data=values)
|
||||
self.assertEqual(resp.status_code, 400)
|
||||
msg = (
|
||||
"The request included the following errors:\n"
|
||||
"- Additional properties are not allowed "
|
||||
"('created_at' was unexpected)"
|
||||
)
|
||||
self.assertEqual(resp.json()['message'], msg)
|
||||
|
||||
def test_create_region_with_extra_updated_at_property_fails(self):
|
||||
values = {"name": "test", "updated_at": "some date"}
|
||||
url = self.url + '/v1/clouds'
|
||||
resp = self.post(url, data=values)
|
||||
self.assertEqual(resp.status_code, 400)
|
||||
msg = (
|
||||
"The request included the following errors:\n"
|
||||
"- Additional properties are not allowed "
|
||||
"('updated_at' was unexpected)"
|
||||
)
|
||||
self.assertEqual(resp.json()['message'], msg)
|
||||
|
||||
def test_cloud_create_missing_all_properties_fails(self):
|
||||
url = self.url + '/v1/clouds'
|
||||
cloud = self.post(url, data={})
|
||||
self.assertEqual(400, cloud.status_code)
|
||||
msg = (
|
||||
"The request included the following errors:\n"
|
||||
"- 'name' is a required property"
|
||||
)
|
||||
self.assertEqual(cloud.json()['message'], msg)
|
||||
|
||||
def test_clouds_get_all(self):
|
||||
self.create_cloud("ORD1")
|
||||
self.create_cloud("ORD2")
|
||||
url = self.url + '/v1/clouds'
|
||||
resp = self.get(url)
|
||||
self.assertEqual(200, resp.status_code)
|
||||
self.assertEqual(2, len(resp.json()))
|
||||
|
||||
def test_clouds_get_all_with_details_filter(self):
|
||||
c1 = self.create_cloud("ORD1", variables={'a': 'b'})
|
||||
c2 = self.create_cloud("ORD2", variables={'c': 'd'})
|
||||
url = self.url + '/v1/clouds?details=all'
|
||||
resp = self.get(url)
|
||||
self.assertEqual(200, resp.status_code)
|
||||
clouds = resp.json()['clouds']
|
||||
self.assertEqual(2, len(clouds))
|
||||
for cloud in clouds:
|
||||
self.assertTrue('variables' in cloud)
|
||||
|
||||
for cloud in clouds:
|
||||
if cloud['name'] == 'ORD1':
|
||||
self.assertEqual(c1['variables'], {'a': 'b'})
|
||||
if cloud['name'] == 'ORD2':
|
||||
self.assertEqual(c2['variables'], {'c': 'd'})
|
||||
|
||||
def test_clouds_get_all_with_name_filter(self):
|
||||
self.create_cloud("ORD1")
|
||||
self.create_cloud("ORD2")
|
||||
url = self.url + '/v1/clouds?name=ORD1'
|
||||
resp = self.get(url)
|
||||
self.assertEqual(200, resp.status_code)
|
||||
clouds = resp.json()['clouds']
|
||||
self.assertEqual(1, len(clouds))
|
||||
self.assertEqual('ORD1', clouds[0]['name'])
|
||||
|
||||
def test_cloud_with_non_existing_filters(self):
|
||||
self.create_cloud("ORD1")
|
||||
url = self.url + '/v1/clouds?name=idontexist'
|
||||
resp = self.get(url)
|
||||
self.assertEqual(404, resp.status_code)
|
||||
|
||||
def test_cloud_get_details_for_cloud(self):
|
||||
regvars = {"a": "b", "one": "two"}
|
||||
cloud = self.create_cloud("ORD1", variables=regvars)
|
||||
url = self.url + '/v1/clouds/{}'.format(cloud['id'])
|
||||
resp = self.get(url)
|
||||
cloud = resp.json()
|
||||
self.assertEqual(cloud['name'], 'ORD1')
|
||||
self.assertEqual(regvars, cloud['variables'])
|
||||
|
||||
|
||||
class TestPagination(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestPagination, self).setUp()
|
||||
self.clouds = [self.create_cloud('cloud-{}'.format(i))
|
||||
for i in range(0, 61)]
|
||||
self.addCleanup(self.delete_clouds, self.clouds)
|
||||
|
||||
def test_list_first_thirty_clouds(self):
|
||||
url = self.url + '/v1/clouds'
|
||||
response = self.get(url)
|
||||
self.assertSuccessOk(response)
|
||||
json = response.json()
|
||||
self.assertIn('clouds', json)
|
||||
self.assertEqual(30, len(json['clouds']))
|
||||
self.assertListEqual([r['id'] for r in self.clouds[:30]],
|
||||
[r['id'] for r in json['clouds']])
|
||||
|
||||
def test_get_returns_correct_next_link(self):
|
||||
url = self.url + '/v1/clouds'
|
||||
thirtieth_cloud = self.clouds[29]
|
||||
response = self.get(url)
|
||||
self.assertSuccessOk(response)
|
||||
json = response.json()
|
||||
self.assertIn('links', json)
|
||||
for link_rel in json['links']:
|
||||
if link_rel['rel'] == 'next':
|
||||
break
|
||||
else:
|
||||
self.fail("No 'next' link was returned in response")
|
||||
|
||||
parsed_next = urllib.parse.urlparse(link_rel['href'])
|
||||
self.assertIn('marker={}'.format(thirtieth_cloud['id']),
|
||||
parsed_next.query)
|
||||
|
||||
def test_get_returns_correct_prev_link(self):
|
||||
first_cloud = self.clouds[0]
|
||||
thirtieth_cloud = self.clouds[29]
|
||||
url = self.url + '/v1/clouds?marker={}'.format(thirtieth_cloud['id'])
|
||||
response = self.get(url)
|
||||
self.assertSuccessOk(response)
|
||||
json = response.json()
|
||||
self.assertIn('links', json)
|
||||
for link_rel in json['links']:
|
||||
if link_rel['rel'] == 'prev':
|
||||
break
|
||||
else:
|
||||
self.fail("No 'prev' link was returned in response")
|
||||
|
||||
parsed_prev = urllib.parse.urlparse(link_rel['href'])
|
||||
self.assertIn('marker={}'.format(first_cloud['id']),
|
||||
parsed_prev.query)
|
@ -1,190 +0,0 @@
|
||||
from itertools import count, cycle
|
||||
import urllib.parse
|
||||
|
||||
from craton.tests.functional import DeviceTestBase
|
||||
|
||||
|
||||
class DeviceTests(DeviceTestBase):
|
||||
|
||||
def count_devices(self, devices):
|
||||
num_devices = (
|
||||
len(devices['hosts']) +
|
||||
len(devices['network-devices'])
|
||||
)
|
||||
return num_devices
|
||||
|
||||
|
||||
class APIV1DeviceTest(DeviceTests):
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.net_device1 = self.create_network_device(
|
||||
'network_device1', 'switch', '192.168.1.1'
|
||||
)
|
||||
self.net_device2 = self.create_network_device(
|
||||
'network_device2', 'switch', '192.168.1.2',
|
||||
parent_id=self.net_device1['id'],
|
||||
)
|
||||
self.host1 = self.create_host(
|
||||
'host1', 'server', '192.168.1.3', parent_id=self.net_device2['id']
|
||||
)
|
||||
self.container1 = self.create_host(
|
||||
'host1container1', 'container', '192.168.1.4',
|
||||
parent_id=self.host1['id'],
|
||||
)
|
||||
url = self.url + '/v1/devices'
|
||||
resp = self.get(url)
|
||||
self.assertEqual(200, resp.status_code)
|
||||
devices = resp.json()['devices']
|
||||
self.assertEqual(4, self.count_devices(devices))
|
||||
|
||||
def test_device_get_by_parent_id_no_descendants(self):
|
||||
url = '{}/v1/devices?parent_id={}'.format(
|
||||
self.url, self.net_device1['id']
|
||||
)
|
||||
resp = self.get(url)
|
||||
self.assertEqual(200, resp.status_code)
|
||||
devices = resp.json()['devices']
|
||||
self.assertEqual(1, self.count_devices(devices))
|
||||
self.assertEqual(
|
||||
self.net_device1['id'], devices['network-devices'][0]['parent_id']
|
||||
)
|
||||
|
||||
def test_device_get_by_parent_id_with_descendants(self):
|
||||
url = '{}/v1/devices?parent_id={}&descendants=true'.format(
|
||||
self.url, self.net_device1['id']
|
||||
)
|
||||
resp = self.get(url)
|
||||
self.assertEqual(200, resp.status_code)
|
||||
devices = resp.json()['devices']
|
||||
self.assertEqual(3, self.count_devices(devices))
|
||||
self.assertEqual(
|
||||
self.net_device1['id'], devices['network-devices'][0]['parent_id']
|
||||
)
|
||||
self.assertEqual(
|
||||
self.net_device2['id'], devices['hosts'][0]['parent_id']
|
||||
)
|
||||
self.assertEqual(self.host1['id'], devices['hosts'][1]['parent_id'])
|
||||
|
||||
def test_device_by_missing_filter(self):
|
||||
url = self.url + '/v1/devices?active=false'
|
||||
resp = self.get(url)
|
||||
self.assertEqual(200, resp.status_code)
|
||||
devices = resp.json()['devices']
|
||||
self.assertEqual(0, self.count_devices(devices))
|
||||
|
||||
|
||||
class TestPagination(DeviceTests):
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.devices = []
|
||||
last_octet = count(1)
|
||||
|
||||
first_network_device = self.create_network_device(
|
||||
'network-device0',
|
||||
'switch',
|
||||
'192.168.1.{}'.format(next(last_octet)),
|
||||
)
|
||||
self.devices.append(first_network_device)
|
||||
|
||||
for i in range(1, 3):
|
||||
network_device = self.create_network_device(
|
||||
'network-device{}'.format(i),
|
||||
'switch',
|
||||
'192.168.1.{}'.format(next(last_octet)),
|
||||
)
|
||||
self.devices.append(network_device)
|
||||
host_parents = (
|
||||
self.devices[1],
|
||||
self.devices[2],
|
||||
)
|
||||
for i, host_parent in zip(range(12), cycle(host_parents)):
|
||||
host = self.create_host(
|
||||
'host{}'.format(i),
|
||||
'server',
|
||||
'192.168.1.{}'.format(next(last_octet)),
|
||||
parent_id=host_parent['id'],
|
||||
)
|
||||
self.devices.append(host)
|
||||
|
||||
for j in range(4):
|
||||
container = self.create_host(
|
||||
'host{}container{}'.format(i, j),
|
||||
'container',
|
||||
'192.168.1.{}'.format(next(last_octet)),
|
||||
parent_id=host['id'],
|
||||
)
|
||||
self.devices.append(container)
|
||||
|
||||
def test_get_returns_a_default_list_of_thirty_devices(self):
|
||||
response = self.get(self.url + '/v1/devices')
|
||||
self.assertSuccessOk(response)
|
||||
devices = response.json()
|
||||
self.assertIn('devices', devices)
|
||||
self.assertEqual(30, self.count_devices(devices['devices']))
|
||||
returned_device_ids = sorted(
|
||||
device['id']
|
||||
for dt in devices['devices'].values()
|
||||
for device in dt
|
||||
)
|
||||
self.assertListEqual(
|
||||
[d['id'] for d in self.devices[:30]],
|
||||
returned_device_ids
|
||||
)
|
||||
|
||||
def test_get_returns_correct_next_link(self):
|
||||
thirtieth_device = self.devices[29]
|
||||
response = self.get(self.url + '/v1/devices')
|
||||
self.assertSuccessOk(response)
|
||||
devices = response.json()
|
||||
self.assertIn('links', devices)
|
||||
for link_rel in devices['links']:
|
||||
if link_rel['rel'] == 'next':
|
||||
break
|
||||
else:
|
||||
self.fail("No 'next' link was returned in response")
|
||||
|
||||
parsed_next = urllib.parse.urlparse(link_rel['href'])
|
||||
self.assertIn('marker={}'.format(thirtieth_device['id']),
|
||||
parsed_next.query)
|
||||
|
||||
def test_get_returns_correct_prev_link(self):
|
||||
first_device = self.devices[0]
|
||||
thirtieth_device = self.devices[29]
|
||||
url = self.url + '/v1/devices?marker={}'.format(thirtieth_device['id'])
|
||||
response = self.get(url)
|
||||
self.assertSuccessOk(response)
|
||||
devices = response.json()
|
||||
self.assertIn('links', devices)
|
||||
for link_rel in devices['links']:
|
||||
if link_rel['rel'] == 'prev':
|
||||
break
|
||||
else:
|
||||
self.fail("No 'prev' link was returned in response")
|
||||
|
||||
parsed_prev = urllib.parse.urlparse(link_rel['href'])
|
||||
self.assertIn(
|
||||
'marker={}'.format(first_device['id']), parsed_prev.query
|
||||
)
|
||||
|
||||
def test_ascending_sort_by_name(self):
|
||||
response = self.get(self.url + '/v1/devices',
|
||||
sort_keys='name', sort_dir='asc')
|
||||
self.assertSuccessOk(response)
|
||||
devices = response.json()['devices']
|
||||
self.assertEqual(30, self.count_devices(devices))
|
||||
|
||||
def test_ascending_sort_by_name_and_id(self):
|
||||
response = self.get(self.url + '/v1/devices',
|
||||
sort_keys='name,id', sort_dir='asc')
|
||||
self.assertSuccessOk(response)
|
||||
devices = response.json()['devices']
|
||||
self.assertEqual(30, self.count_devices(devices))
|
||||
|
||||
def test_ascending_sort_by_name_and_id_space_separated(self):
|
||||
response = self.get(self.url + '/v1/devices',
|
||||
sort_keys='name id', sort_dir='asc')
|
||||
self.assertSuccessOk(response)
|
||||
devices = response.json()['devices']
|
||||
self.assertEqual(30, self.count_devices(devices))
|
@ -1,552 +0,0 @@
|
||||
import urllib.parse
|
||||
|
||||
from craton.tests.functional import DeviceTestBase
|
||||
from craton.tests.functional.test_variable_calls import \
|
||||
APIV1ResourceWithVariablesTestCase
|
||||
|
||||
|
||||
class APIV1HostTest(DeviceTestBase, APIV1ResourceWithVariablesTestCase):
|
||||
|
||||
resource = 'hosts'
|
||||
|
||||
def test_create_host_supports_vars_ops(self):
|
||||
host = self.create_host('host1', 'server', '192.168.1.1')
|
||||
self.assert_vars_get_expected(host['id'], {})
|
||||
self.assert_vars_can_be_set(host['id'])
|
||||
self.assert_vars_can_be_deleted(host['id'])
|
||||
|
||||
def test_host_get_by_vars_filter(self):
|
||||
vars1 = {"a": "b", "host": "one"}
|
||||
self.create_host('host1', 'server', '192.168.1.1', **vars1)
|
||||
vars2 = {"a": "b"}
|
||||
self.create_host('host2', 'server', '192.168.1.2', **vars2)
|
||||
|
||||
url = self.url + '/v1/hosts'
|
||||
resp = self.get(url, vars='a:"b"')
|
||||
self.assertEqual(200, resp.status_code)
|
||||
hosts = resp.json()['hosts']
|
||||
self.assertEqual(2, len(hosts))
|
||||
self.assertEqual({'192.168.1.1', '192.168.1.2'},
|
||||
{host['ip_address'] for host in hosts})
|
||||
|
||||
url = self.url + '/v1/hosts'
|
||||
resp = self.get(url, vars='host:"one"')
|
||||
self.assertEqual(200, resp.status_code)
|
||||
hosts = resp.json()['hosts']
|
||||
self.assertEqual(1, len(hosts))
|
||||
self.assertEqual('192.168.1.1', hosts[0]['ip_address'])
|
||||
self.assert_vars_get_expected(hosts[0]['id'], vars1)
|
||||
|
||||
def test_create_host(self):
|
||||
host = self.create_host('host1', 'server', '192.168.1.1')
|
||||
self.assertEqual('host1', host['name'])
|
||||
|
||||
def test_create_with_missing_name_fails(self):
|
||||
url = self.url + '/v1/hosts'
|
||||
payload = {'device_type': 'server', 'ip_address': '192.168.1.1',
|
||||
'region_id': self.region['id']}
|
||||
host = self.post(url, data=payload)
|
||||
self.assertEqual(400, host.status_code)
|
||||
|
||||
def test_create_with_missing_ip_fails(self):
|
||||
url = self.url + '/v1/hosts'
|
||||
payload = {'name': 'test', 'device_type': 'server',
|
||||
'region_id': self.region['id']}
|
||||
host = self.post(url, data=payload)
|
||||
self.assertEqual(400, host.status_code)
|
||||
|
||||
def test_create_with_missing_type_fails(self):
|
||||
url = self.url + '/v1/hosts'
|
||||
payload = {'name': 'who', 'ip_address': '192.168.1.1',
|
||||
'region_id': self.region['id']}
|
||||
host = self.post(url, data=payload)
|
||||
self.assertEqual(400, host.status_code)
|
||||
|
||||
def test_create_with_extra_id_property_fails(self):
|
||||
url = self.url + '/v1/hosts'
|
||||
payload = {'device_type': 'server', 'ip_address': '192.168.1.1',
|
||||
'region_id': self.region['id'],
|
||||
'cloud_id': self.cloud['id'], 'name': 'a', 'id': 1}
|
||||
host = self.post(url, data=payload)
|
||||
self.assertEqual(400, host.status_code)
|
||||
msg = (
|
||||
"The request included the following errors:\n"
|
||||
"- Additional properties are not allowed ('id' was unexpected)"
|
||||
)
|
||||
self.assertEqual(host.json()['message'], msg)
|
||||
|
||||
def test_create_with_extra_created_at_property_fails(self):
|
||||
url = self.url + '/v1/hosts'
|
||||
payload = {'device_type': 'server', 'ip_address': '192.168.1.1',
|
||||
'region_id': self.region['id'],
|
||||
'cloud_id': self.cloud['id'], 'name': 'a',
|
||||
'created_at': 'some date'}
|
||||
host = self.post(url, data=payload)
|
||||
self.assertEqual(400, host.status_code)
|
||||
msg = (
|
||||
"The request included the following errors:\n"
|
||||
"- Additional properties are not allowed "
|
||||
"('created_at' was unexpected)"
|
||||
)
|
||||
self.assertEqual(host.json()['message'], msg)
|
||||
|
||||
def test_create_with_extra_updated_at_property_fails(self):
|
||||
url = self.url + '/v1/hosts'
|
||||
payload = {'device_type': 'server', 'ip_address': '192.168.1.1',
|
||||
'region_id': self.region['id'],
|
||||
'cloud_id': self.cloud['id'], 'name': 'a',
|
||||
'updated_at': 'some date'}
|
||||
host = self.post(url, data=payload)
|
||||
self.assertEqual(400, host.status_code)
|
||||
msg = (
|
||||
"The request included the following errors:\n"
|
||||
"- Additional properties are not allowed "
|
||||
"('updated_at' was unexpected)"
|
||||
)
|
||||
self.assertEqual(host.json()['message'], msg)
|
||||
|
||||
def test_create_missing_all_properties_fails(self):
|
||||
url = self.url + '/v1/hosts'
|
||||
host = self.post(url, data={})
|
||||
self.assertEqual(400, host.status_code)
|
||||
msg = (
|
||||
"The request included the following errors:\n"
|
||||
"- 'cloud_id' is a required property\n"
|
||||
"- 'device_type' is a required property\n"
|
||||
"- 'ip_address' is a required property\n"
|
||||
"- 'name' is a required property\n"
|
||||
"- 'region_id' is a required property"
|
||||
)
|
||||
self.assertEqual(host.json()['message'], msg)
|
||||
|
||||
def test_create_with_parent_id(self):
|
||||
parent = self.create_host(
|
||||
name='test1',
|
||||
cloud=self.cloud,
|
||||
region=self.region,
|
||||
hosttype='server',
|
||||
ip_address='192.168.1.1',
|
||||
)
|
||||
child = self.create_host(
|
||||
name='test2',
|
||||
cloud=self.cloud,
|
||||
region=self.region,
|
||||
hosttype='server',
|
||||
ip_address='192.168.1.2',
|
||||
parent_id=parent['id'],
|
||||
)
|
||||
self.assertEqual(parent['id'], child['parent_id'])
|
||||
|
||||
def test_update_with_parent_id(self):
|
||||
parent = self.create_host(
|
||||
name='test1',
|
||||
cloud=self.cloud,
|
||||
region=self.region,
|
||||
hosttype='server',
|
||||
ip_address='192.168.1.1',
|
||||
)
|
||||
|
||||
child = self.create_host(
|
||||
name='test2',
|
||||
cloud=self.cloud,
|
||||
region=self.region,
|
||||
hosttype='server',
|
||||
ip_address='192.168.1.2',
|
||||
)
|
||||
self.assertIsNone(child['parent_id'])
|
||||
|
||||
url = '{}/v1/hosts/{}'.format(self.url, child['id'])
|
||||
child_update_resp = self.put(
|
||||
url, data={'parent_id': parent['id']}
|
||||
)
|
||||
self.assertEqual(200, child_update_resp.status_code)
|
||||
child_update = child_update_resp.json()
|
||||
self.assertEqual(parent['id'], child_update['parent_id'])
|
||||
|
||||
def test_update_with_parent_id_equal_id_fails(self):
|
||||
host = self.create_host(
|
||||
name='test1',
|
||||
cloud=self.cloud,
|
||||
region=self.region,
|
||||
hosttype='server',
|
||||
ip_address='192.168.1.1',
|
||||
)
|
||||
|
||||
url = '{}/v1/hosts/{}'.format(self.url, host['id'])
|
||||
host_update_resp = self.put(
|
||||
url, data={'parent_id': host['id']}
|
||||
)
|
||||
self.assertEqual(400, host_update_resp.status_code)
|
||||
|
||||
def test_update_with_parent_id_equal_descendant_id_fails(self):
|
||||
parent = self.create_host(
|
||||
name='test1',
|
||||
cloud=self.cloud,
|
||||
region=self.region,
|
||||
hosttype='server',
|
||||
ip_address='192.168.1.1',
|
||||
)
|
||||
self.assertIsNone(parent['parent_id'])
|
||||
|
||||
child = self.create_host(
|
||||
name='test2',
|
||||
cloud=self.cloud,
|
||||
region=self.region,
|
||||
hosttype='server',
|
||||
ip_address='192.168.1.2',
|
||||
parent_id=parent['id'],
|
||||
)
|
||||
self.assertEqual(parent['id'], child['parent_id'])
|
||||
|
||||
grandchild = self.create_host(
|
||||
name='test3',
|
||||
cloud=self.cloud,
|
||||
region=self.region,
|
||||
hosttype='server',
|
||||
ip_address='192.168.1.3',
|
||||
parent_id=child['id'],
|
||||
)
|
||||
self.assertEqual(child['id'], grandchild['parent_id'])
|
||||
|
||||
url = '{}/v1/hosts/{}'.format(self.url, parent['id'])
|
||||
parent_update_resp = self.put(
|
||||
url, data={'parent_id': grandchild['id']}
|
||||
)
|
||||
self.assertEqual(400, parent_update_resp.status_code)
|
||||
|
||||
def test_get_all_hosts_with_details(self):
|
||||
region_vars = {'x': 'y'}
|
||||
region = self.create_region(name='region1', variables=region_vars)
|
||||
variables = {"a": "b"}
|
||||
self.create_host('host1', 'server', '192.168.1.1', region=region,
|
||||
**variables)
|
||||
self.create_host('host2', 'server', '192.168.1.2', region=region,
|
||||
**variables)
|
||||
url = self.url + '/v1/hosts?details=all'
|
||||
resp = self.get(url)
|
||||
self.assertEqual(200, resp.status_code)
|
||||
hosts = resp.json()['hosts']
|
||||
self.assertEqual(2, len(hosts))
|
||||
for host in hosts:
|
||||
self.assertTrue('variables' in host)
|
||||
self.assertEqual({'a': 'b', 'x': 'y'}, host['variables'])
|
||||
|
||||
def test_host_get_by_ip_filter(self):
|
||||
self.create_host('host1', 'server', '192.168.1.1')
|
||||
self.create_host('host2', 'server', '192.168.1.2')
|
||||
url = self.url + '/v1/hosts?ip_address=192.168.1.1'
|
||||
resp = self.get(url)
|
||||
self.assertEqual(200, resp.status_code)
|
||||
hosts = resp.json()['hosts']
|
||||
self.assertEqual(1, len(hosts))
|
||||
self.assertEqual('192.168.1.1', hosts[0]['ip_address'])
|
||||
|
||||
def test_host_by_missing_filter(self):
|
||||
self.create_host('host1', 'server', '192.168.1.1')
|
||||
url = self.url + '/v1/hosts?ip_address=192.168.1.2'
|
||||
resp = self.get(url)
|
||||
self.assertEqual(200, resp.status_code)
|
||||
self.assertEqual(0, len(resp.json()['hosts']))
|
||||
|
||||
def test_host_create_labels(self):
|
||||
res = self.create_host('host1', 'server', '192.168.1.1')
|
||||
url = self.url + '/v1/hosts/{}/labels'.format(res['id'])
|
||||
|
||||
data = {"labels": ["compute"]}
|
||||
resp = self.put(url, data=data)
|
||||
self.assertEqual(200, resp.status_code)
|
||||
|
||||
resp = self.get(url)
|
||||
self.assertEqual(data, resp.json())
|
||||
|
||||
def test_host_by_label_filter_match_one(self):
|
||||
labels_route_mask = '/v1/hosts/{}/labels'
|
||||
host1 = self.create_host('host1', 'server', '192.168.1.1')
|
||||
host2 = self.create_host('host2', 'server', '192.168.1.2')
|
||||
host3 = self.create_host('host3', 'server', '192.168.1.3')
|
||||
|
||||
# set labels on hosts
|
||||
data = {"labels": ["compute"]}
|
||||
for host in (host1, host2, host3):
|
||||
url = self.url + labels_route_mask.format(host['id'])
|
||||
resp = self.put(url, data=data)
|
||||
self.assertEqual(200, resp.status_code)
|
||||
|
||||
# set one of them with extra labels
|
||||
data = {"labels": ["compute", "scheduler"]}
|
||||
url = self.url + labels_route_mask.format(host3['id'])
|
||||
resp = self.put(url, data=data)
|
||||
self.assertEqual(200, resp.status_code)
|
||||
|
||||
# get hosts by its label
|
||||
url = self.url + '/v1/hosts?label=scheduler'
|
||||
resp = self.get(url)
|
||||
self.assertEqual(200, resp.status_code)
|
||||
hosts = resp.json()['hosts']
|
||||
self.assertEqual(1, len(hosts))
|
||||
self.assertEqual(host3['id'], hosts[0]['id'])
|
||||
|
||||
def test_host_by_label_filters_match_all(self):
|
||||
labels_route_mask = '/v1/hosts/{}/labels'
|
||||
host1 = self.create_host('host1', 'server', '192.168.1.1')
|
||||
host2 = self.create_host('host2', 'server', '192.168.1.2')
|
||||
host3 = self.create_host('host3', 'server', '192.168.1.3')
|
||||
|
||||
# set labels on hosts
|
||||
data = {"labels": ["compute"]}
|
||||
for host in (host1, host2, host3):
|
||||
url = self.url + labels_route_mask.format(host['id'])
|
||||
resp = self.put(url, data=data)
|
||||
self.assertEqual(200, resp.status_code)
|
||||
|
||||
# set one of them with extra labels
|
||||
data = {"labels": ["compute", "scheduler"]}
|
||||
url = self.url + labels_route_mask.format(host2['id'])
|
||||
resp = self.put(url, data=data)
|
||||
self.assertEqual(200, resp.status_code)
|
||||
|
||||
# get hosts by its label
|
||||
url = self.url + '/v1/hosts?label=scheduler&label=compute'
|
||||
resp = self.get(url)
|
||||
self.assertEqual(200, resp.status_code)
|
||||
hosts = resp.json()['hosts']
|
||||
self.assertEqual(1, len(hosts))
|
||||
self.assertEqual(host2['id'], hosts[0]['id'])
|
||||
|
||||
def test_host_by_label_filters_match_one_common(self):
|
||||
labels_route_mask = '/v1/hosts/{}/labels'
|
||||
test_hosts = [
|
||||
self.create_host('host1', 'server', '192.168.1.1'),
|
||||
self.create_host('host2', 'server', '192.168.1.2'),
|
||||
self.create_host('host3', 'server', '192.168.1.3'),
|
||||
]
|
||||
|
||||
# set labels on hosts
|
||||
data = {"labels": ["compute"]}
|
||||
for host in test_hosts:
|
||||
url = self.url + labels_route_mask.format(host['id'])
|
||||
resp = self.put(url, data=data)
|
||||
self.assertEqual(200, resp.status_code)
|
||||
|
||||
# set one of them with extra labels
|
||||
data = {"labels": ["compute", "scheduler"]}
|
||||
url = self.url + labels_route_mask.format(test_hosts[1]['id'])
|
||||
resp = self.put(url, data=data)
|
||||
self.assertEqual(200, resp.status_code)
|
||||
|
||||
# get hosts by its label
|
||||
url = self.url + '/v1/hosts?label=compute'
|
||||
resp = self.get(url)
|
||||
self.assertEqual(200, resp.status_code)
|
||||
hosts = resp.json()['hosts']
|
||||
self.assertEqual(3, len(hosts))
|
||||
self.assertEqual(sorted([host['id'] for host in test_hosts]),
|
||||
sorted([host['id'] for host in hosts]))
|
||||
|
||||
def test_host_get_all_vars_filter_resolved_region(self):
|
||||
region_vars = {'foo': 'bar'}
|
||||
region = self.create_region(name='region-2', variables=region_vars)
|
||||
host_vars = {'baz': 'zoo'}
|
||||
self.create_host('host1', 'server', '192.168.1.1', **host_vars)
|
||||
host2 = self.create_host('host2', 'server', '192.168.1.2',
|
||||
region=region, **host_vars)
|
||||
url = self.url + '/v1/hosts'
|
||||
|
||||
resp = self.get(url, vars='foo:"bar",baz:"zoo"')
|
||||
hosts = resp.json()['hosts']
|
||||
self.assertEqual(1, len(hosts))
|
||||
self.assertEqual(host2['id'], hosts[0]['id'])
|
||||
|
||||
def test_host_get_all_vars_filter_resolved_region_and_host(self):
|
||||
region_vars = {'foo': 'bar'}
|
||||
region = self.create_region(name='region-2', variables=region_vars)
|
||||
host_vars = {'baz': 'zoo'}
|
||||
host1 = self.create_host('host1', 'server', '192.168.1.1',
|
||||
**region_vars)
|
||||
host2 = self.create_host('host2', 'server', '192.168.1.2',
|
||||
region=region, **host_vars)
|
||||
url = self.url + '/v1/hosts'
|
||||
|
||||
resp = self.get(url, vars='foo:"bar"')
|
||||
hosts = resp.json()['hosts']
|
||||
self.assertEqual(2, len(hosts))
|
||||
self.assertListEqual(sorted([host1['id'], host2['id']]),
|
||||
sorted([host['id'] for host in hosts]))
|
||||
|
||||
def test_host_get_all_vars_filter_resolved_region_child_override(self):
|
||||
region_vars = {'foo': 'bar'}
|
||||
region = self.create_region(name='region-2', variables=region_vars)
|
||||
host1 = self.create_host('host1', 'server', '192.168.1.1',
|
||||
region=region, foo='baz')
|
||||
host2 = self.create_host('host2', 'server', '192.168.1.2',
|
||||
region=region)
|
||||
url = self.url + '/v1/hosts'
|
||||
|
||||
resp = self.get(url, vars='foo:"baz"')
|
||||
hosts = resp.json()['hosts']
|
||||
self.assertEqual(1, len(hosts))
|
||||
self.assertEqual(host1['id'], hosts[0]['id'])
|
||||
|
||||
resp = self.get(url, vars='foo:"bar"')
|
||||
hosts = resp.json()['hosts']
|
||||
self.assertEqual(1, len(hosts))
|
||||
self.assertEqual(host2['id'], hosts[0]['id'])
|
||||
|
||||
def test_host_get_all_vars_filter_resolved_host_child_override(self):
|
||||
host1 = self.create_host('host1', 'server', '192.168.1.1',
|
||||
baz='zoo')
|
||||
host2 = self.create_host('host2', 'server', '192.168.1.2',
|
||||
parent_id=host1['id'], baz='boo')
|
||||
url = self.url + '/v1/hosts'
|
||||
|
||||
resp = self.get(url, vars='baz:"zoo"')
|
||||
hosts = resp.json()['hosts']
|
||||
self.assertEqual(1, len(hosts))
|
||||
self.assertEqual(host1['id'], hosts[0]['id'])
|
||||
|
||||
resp = self.get(url, vars='baz:"boo"')
|
||||
hosts = resp.json()['hosts']
|
||||
self.assertEqual(1, len(hosts))
|
||||
self.assertEqual(host2['id'], hosts[0]['id'])
|
||||
|
||||
def test_host_get_all_vars_filter_unresolved(self):
|
||||
host1 = self.create_host('host1', 'server', '192.168.1.1',
|
||||
foo='bar', baz='zoo')
|
||||
self.create_host('host2', 'server', '192.168.1.2', foo='bar')
|
||||
|
||||
# NOTE(thomasem): Unfortunately, we use resolved-values instead of
|
||||
# resolved_values, so we can't pass this in as kwargs to self.get(...),
|
||||
# see https://bugs.launchpad.net/craton/+bug/1672880.
|
||||
url = self.url + \
|
||||
'/v1/hosts?resolved-values=false&vars=foo:"bar",baz:"zoo"'
|
||||
|
||||
resp = self.get(url)
|
||||
hosts = resp.json()['hosts']
|
||||
self.assertEqual(1, len(hosts))
|
||||
self.assertEqual(host1['id'], hosts[0]['id'])
|
||||
|
||||
def test_host_delete(self):
|
||||
host = self.create_host('host1', 'server', '192.168.1.1')
|
||||
url = self.url + '/v1/hosts/{}'.format(host['id'])
|
||||
resp = self.delete(url)
|
||||
self.assertEqual(204, resp.status_code)
|
||||
|
||||
resp = self.get(url)
|
||||
self.assertEqual(404, resp.status_code)
|
||||
self.assertEqual({'status': 404, 'message': 'Not Found'},
|
||||
resp.json())
|
||||
|
||||
|
||||
class TestPagination(DeviceTestBase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestPagination, self).setUp()
|
||||
self.hosts = [
|
||||
self.create_host('host{}'.format(i), 'server',
|
||||
'192.168.1.{}'.format(i + 1))
|
||||
for i in range(0, 61)
|
||||
]
|
||||
|
||||
def test_get_returns_a_default_list_of_thirty_hosts(self):
|
||||
response = self.get(self.url + '/v1/hosts')
|
||||
self.assertSuccessOk(response)
|
||||
hosts = response.json()
|
||||
self.assertIn('hosts', hosts)
|
||||
self.assertEqual(30, len(hosts['hosts']))
|
||||
self.assertListEqual([h['id'] for h in self.hosts[:30]],
|
||||
[h['id'] for h in hosts['hosts']])
|
||||
|
||||
def test_get_returns_correct_next_link(self):
|
||||
thirtieth_host = self.hosts[29]
|
||||
response = self.get(self.url + '/v1/hosts')
|
||||
self.assertSuccessOk(response)
|
||||
hosts = response.json()
|
||||
self.assertIn('links', hosts)
|
||||
for link_rel in hosts['links']:
|
||||
if link_rel['rel'] == 'next':
|
||||
break
|
||||
else:
|
||||
self.fail("No 'next' link was returned in response")
|
||||
|
||||
parsed_next = urllib.parse.urlparse(link_rel['href'])
|
||||
self.assertIn('marker={}'.format(thirtieth_host['id']),
|
||||
parsed_next.query)
|
||||
|
||||
def test_get_returns_correct_prev_link(self):
|
||||
first_host = self.hosts[0]
|
||||
thirtieth_host = self.hosts[29]
|
||||
url = self.url + '/v1/hosts?marker={}'.format(thirtieth_host['id'])
|
||||
response = self.get(url)
|
||||
self.assertSuccessOk(response)
|
||||
hosts = response.json()
|
||||
self.assertIn('links', hosts)
|
||||
for link_rel in hosts['links']:
|
||||
if link_rel['rel'] == 'prev':
|
||||
break
|
||||
else:
|
||||
self.fail("No 'prev' link was returned in response")
|
||||
|
||||
parsed_prev = urllib.parse.urlparse(link_rel['href'])
|
||||
self.assertIn('marker={}'.format(first_host['id']), parsed_prev.query)
|
||||
|
||||
def test_get_all_for_region(self):
|
||||
region = self.create_region('region-2')
|
||||
self.create_host('host1', 'server', '192.168.1.1', region=region)
|
||||
self.create_host('host2', 'server', '192.168.1.2', region=region)
|
||||
url = self.url + '/v1/hosts?region_id={}'.format(region['id'])
|
||||
resp = self.get(url)
|
||||
self.assertSuccessOk(resp)
|
||||
hosts = resp.json()
|
||||
self.assertEqual(2, len(hosts['hosts']))
|
||||
|
||||
def test_get_all_for_cloud(self):
|
||||
cloud = self.create_cloud('cloud-2')
|
||||
region = self.create_region(cloud=cloud)
|
||||
self.create_host('host1', 'server', '192.168.1.1', cloud=cloud,
|
||||
region=region)
|
||||
self.create_host('host2', 'server', '192.168.1.2', cloud=cloud,
|
||||
region=region)
|
||||
url = self.url + '/v1/hosts?cloud_id={}'.format(cloud['id'])
|
||||
resp = self.get(url)
|
||||
self.assertSuccessOk(resp)
|
||||
hosts = resp.json()['hosts']
|
||||
self.assertEqual(2, len(hosts))
|
||||
self.assertEqual(['host1', 'host2'], [h['name'] for h in hosts])
|
||||
|
||||
def test_ascending_sort_by_name(self):
|
||||
response = self.get(self.url + '/v1/hosts',
|
||||
sort_keys='name', sort_dir='asc')
|
||||
self.assertSuccessOk(response)
|
||||
hosts = response.json()['hosts']
|
||||
self.assertEqual(30, len(hosts))
|
||||
|
||||
def test_ascending_sort_by_name_and_id(self):
|
||||
response = self.get(self.url + '/v1/hosts',
|
||||
sort_keys='name,id', sort_dir='asc')
|
||||
self.assertSuccessOk(response)
|
||||
hosts = response.json()['hosts']
|
||||
self.assertEqual(30, len(hosts))
|
||||
|
||||
def test_ascending_sort_by_name_and_id_space_separated(self):
|
||||
response = self.get(self.url + '/v1/hosts',
|
||||
sort_keys='name id', sort_dir='asc')
|
||||
self.assertSuccessOk(response)
|
||||
hosts = response.json()['hosts']
|
||||
self.assertEqual(30, len(hosts))
|
||||
|
||||
def test_follows_next_link(self):
|
||||
url = self.url + '/v1/hosts'
|
||||
response = self.get(url)
|
||||
self.assertSuccessOk(response)
|
||||
json = response.json()
|
||||
hosts = json['hosts']
|
||||
while hosts:
|
||||
for link in json['links']:
|
||||
if link['rel'] == 'next':
|
||||
break
|
||||
else:
|
||||
break
|
||||
response = self.get(link['href'])
|
||||
self.assertSuccessOk(response)
|
||||
json = response.json()
|
||||
hosts = json['hosts']
|
@ -1,550 +0,0 @@
|
||||
from craton import exceptions
|
||||
from craton.tests import functional
|
||||
|
||||
TEST_STRING = "I'm just a string"
|
||||
|
||||
TEST_ARRAY = [
|
||||
1,
|
||||
23.4,
|
||||
True,
|
||||
False,
|
||||
'false',
|
||||
TEST_STRING,
|
||||
{
|
||||
'bumbleywump': 'cucumberpatch',
|
||||
'literal_boolean': 'true'
|
||||
},
|
||||
['sub', 'array', True]
|
||||
]
|
||||
|
||||
TEST_DICT = {
|
||||
'foo': {
|
||||
'nested_string': 'Bumbleywump Cucumberpatch',
|
||||
'nested_bool': True,
|
||||
'nested_null': None,
|
||||
'nested_int': 1,
|
||||
'nested_float': 3.14,
|
||||
'nested_boolstr': 'false',
|
||||
'hyphenated-key': 'look-at-all-these-hyphens!',
|
||||
},
|
||||
'bar': TEST_ARRAY,
|
||||
'baz': 'zoo'
|
||||
}
|
||||
|
||||
|
||||
def _get_variables_for(name):
|
||||
return {
|
||||
'{}_dict'.format(name): TEST_DICT,
|
||||
'{}_array'.format(name): TEST_ARRAY,
|
||||
'{}_string'.format(name): TEST_STRING,
|
||||
}
|
||||
|
||||
|
||||
class JSONPathResolvedSearchTestCase(functional.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(JSONPathResolvedSearchTestCase, self).setUp()
|
||||
self.cloud = self.create_cloud(
|
||||
name='cloud1',
|
||||
variables=_get_variables_for('cloud1'),
|
||||
)
|
||||
self.region = self.create_region(
|
||||
name='region1',
|
||||
cloud=self.cloud,
|
||||
variables=_get_variables_for('region1'),
|
||||
)
|
||||
self.cell = self.create_cell(
|
||||
name='cell1',
|
||||
cloud=self.cloud,
|
||||
region=self.region,
|
||||
variables=_get_variables_for('cell1')
|
||||
)
|
||||
self.switches = []
|
||||
for i in range(2):
|
||||
name = 'netdev{}'.format(str(i))
|
||||
self.switches.append(self.create_network_device(
|
||||
name=name,
|
||||
cloud=self.cloud,
|
||||
region=self.region,
|
||||
cell=self.cell,
|
||||
device_type='switch',
|
||||
ip_address='192.168.{}.1'.format(i),
|
||||
**_get_variables_for(name)
|
||||
))
|
||||
|
||||
self.hosts = []
|
||||
for i in range(len(self.switches) * 3):
|
||||
name = 'host{}'.format(i)
|
||||
self.hosts.append(self.create_host(
|
||||
name=name,
|
||||
cloud=self.cloud,
|
||||
region=self.region,
|
||||
cell=self.cell,
|
||||
hosttype='server',
|
||||
ip_address='192.168.{}.2'.format(i),
|
||||
parent_id=self.switches[i % len(self.switches)]['id'],
|
||||
**_get_variables_for(name)
|
||||
))
|
||||
|
||||
def test_jsonpath_search_device_parent(self):
|
||||
url = self.url + '/v1/hosts'
|
||||
queries = [
|
||||
'netdev1_dict.foo."hyphenated-key":"look-at-all-these-hyphens!"',
|
||||
]
|
||||
expected_names = ['host1', 'host3', 'host5']
|
||||
|
||||
resp = self.get(url, vars=','.join(queries))
|
||||
hosts = resp.json()['hosts']
|
||||
parent_ids = set([h['parent_id'] for h in hosts])
|
||||
|
||||
self.assertEqual(3, len(hosts))
|
||||
self.assertEqual(1, len(parent_ids))
|
||||
self.assertEqual(self.switches[1]['id'], parent_ids.pop())
|
||||
self.assertListEqual(
|
||||
sorted(expected_names),
|
||||
sorted([h['name'] for h in hosts])
|
||||
)
|
||||
|
||||
def test_jsonpath_search_device_parent_override(self):
|
||||
url = self.url + '/v1/hosts'
|
||||
queries = [
|
||||
'netdev1_dict.foo."hyphenated-key":"look-at-all-these-hyphens!"',
|
||||
]
|
||||
variables_put = {
|
||||
'netdev1_dict': {
|
||||
'foo': {
|
||||
'hyphenated-key': 'look-at-all-these-hyphens'
|
||||
}
|
||||
}
|
||||
}
|
||||
self.put('{}/{}/variables'.format(url, self.hosts[3]['id']),
|
||||
data=variables_put)
|
||||
resp = self.get(url, vars=','.join(queries))
|
||||
hosts = resp.json()['hosts']
|
||||
parent_ids = set([h['parent_id'] for h in hosts])
|
||||
|
||||
self.assertEqual(2, len(hosts))
|
||||
self.assertEqual(1, len(parent_ids))
|
||||
self.assertEqual(self.switches[1]['id'], parent_ids.pop())
|
||||
|
||||
def test_jsonpath_search_device_child_vars_included(self):
|
||||
url = self.url + '/v1/hosts'
|
||||
queries = [
|
||||
'netdev1_dict.foo."hyphenated-key":"look-at-all-these-hyphens!"',
|
||||
]
|
||||
modified_id = self.hosts[0]['id']
|
||||
variables_put = {
|
||||
'netdev1_dict': {
|
||||
'foo': {
|
||||
'hyphenated-key': 'look-at-all-these-hyphens!'
|
||||
}
|
||||
}
|
||||
}
|
||||
self.put('{}/{}/variables'.format(url, modified_id),
|
||||
data=variables_put)
|
||||
expected_names = ['host0', 'host1', 'host3', 'host5']
|
||||
|
||||
resp = self.get(url, vars=','.join(queries))
|
||||
hosts = resp.json()['hosts']
|
||||
|
||||
self.assertEqual(4, len(hosts))
|
||||
self.assertListEqual(
|
||||
sorted(expected_names),
|
||||
sorted([h['name'] for h in hosts])
|
||||
)
|
||||
|
||||
def test_jsonpath_search_device_conjunctive_parent_vars(self):
|
||||
url = self.url + '/v1/hosts'
|
||||
queries = [
|
||||
'netdev1_dict.foo."hyphenated-key":"look-at-all-these-hyphens!"',
|
||||
'region1_array[2]:true',
|
||||
'cloud1_dict.bar[3]:false',
|
||||
]
|
||||
resp = self.get(url, vars=','.join(queries))
|
||||
hosts = resp.json()['hosts']
|
||||
parent_ids = set([h['parent_id'] for h in hosts])
|
||||
|
||||
self.assertEqual(3, len(hosts))
|
||||
self.assertEqual(1, len(parent_ids))
|
||||
self.assertEqual(self.switches[1]['id'], parent_ids.pop())
|
||||
|
||||
|
||||
class JSONPathSearchTestCaseMixin(object):
|
||||
|
||||
resource = '<resource>'
|
||||
|
||||
def get_resource_url(self):
|
||||
return '{}/v1/{}'.format(self.url, self.resource)
|
||||
|
||||
def setup_projects(self, projects):
|
||||
created = []
|
||||
for name, variables in projects:
|
||||
created.append(self.create_project(
|
||||
name=name,
|
||||
variables=variables
|
||||
))
|
||||
return created
|
||||
|
||||
def setup_clouds(self, clouds):
|
||||
created = []
|
||||
for name, variables in clouds:
|
||||
created.append(self.create_cloud(
|
||||
name=name,
|
||||
variables=variables
|
||||
))
|
||||
return created
|
||||
|
||||
def setup_regions(self, regions):
|
||||
created = []
|
||||
cloud = self.create_cloud(name='cloud1')
|
||||
for name, variables in regions:
|
||||
created.append(self.create_region(
|
||||
name=name,
|
||||
cloud=cloud,
|
||||
variables=variables
|
||||
))
|
||||
return created
|
||||
|
||||
def setup_cells(self, cells):
|
||||
created = []
|
||||
cloud = self.create_cloud(name='cloud1')
|
||||
region = self.create_region(
|
||||
name='region1',
|
||||
cloud=cloud
|
||||
)
|
||||
for name, variables in cells:
|
||||
created.append(self.create_cell(
|
||||
name=name,
|
||||
cloud=cloud,
|
||||
region=region,
|
||||
variables=variables
|
||||
))
|
||||
return created
|
||||
|
||||
def setup_networks(self, networks):
|
||||
created = []
|
||||
cloud = self.create_cloud(name='cloud1')
|
||||
region = self.create_region(
|
||||
name='region1',
|
||||
cloud=cloud
|
||||
)
|
||||
for name, variables in networks:
|
||||
created.append(self.create_network(
|
||||
name=name,
|
||||
cloud=cloud,
|
||||
region=region,
|
||||
cidr='192.168.0.0/24',
|
||||
gateway='192.168.0.1',
|
||||
netmask='255.255.255.0',
|
||||
variables=variables
|
||||
))
|
||||
return created
|
||||
|
||||
def setup_network_devices(self, network_devices):
|
||||
created = []
|
||||
cloud = self.create_cloud(name='cloud1')
|
||||
region = self.create_region(
|
||||
name='region1',
|
||||
cloud=cloud
|
||||
)
|
||||
for name, variables in network_devices:
|
||||
created.append(self.create_network_device(
|
||||
name=name,
|
||||
cloud=cloud,
|
||||
region=region,
|
||||
device_type='switch',
|
||||
ip_address='192.168.0.1',
|
||||
**variables
|
||||
))
|
||||
return created
|
||||
|
||||
def setup_hosts(self, hosts):
|
||||
created = []
|
||||
cloud = self.create_cloud(name='cloud1')
|
||||
region = self.create_region(
|
||||
name='region1',
|
||||
cloud=cloud
|
||||
)
|
||||
for name, variables in hosts:
|
||||
created.append(self.create_host(
|
||||
name=name,
|
||||
cloud=cloud,
|
||||
region=region,
|
||||
hosttype='server',
|
||||
ip_address='192.168.0.1',
|
||||
**variables
|
||||
))
|
||||
return created
|
||||
|
||||
def setup_resources(self, resources):
|
||||
setup_fn = {
|
||||
"projects": self.setup_projects,
|
||||
"clouds": self.setup_clouds,
|
||||
"regions": self.setup_regions,
|
||||
"cells": self.setup_cells,
|
||||
"networks": self.setup_networks,
|
||||
"network-devices": self.setup_network_devices,
|
||||
"hosts": self.setup_hosts,
|
||||
}
|
||||
return setup_fn[self.resource](resources)
|
||||
|
||||
def resources_from_response(self, resp):
|
||||
return resp.json()[self.resource.replace('-', '_')]
|
||||
|
||||
def get_resources(self, **params):
|
||||
headers = None
|
||||
if self.resource in ('projects',):
|
||||
headers = self.root_headers
|
||||
resp = self.get(self.get_resource_url(), headers=headers,
|
||||
details='all', **params)
|
||||
return resp
|
||||
|
||||
def test_jsonpath_search_nested_string(self):
|
||||
resources = (
|
||||
('resource1', {'foo': TEST_DICT}),
|
||||
('resource2', {'foo': {'baz': 'nope'}})
|
||||
)
|
||||
created = self.setup_resources(resources)
|
||||
|
||||
found = self.resources_from_response(self.get_resources(
|
||||
vars='foo.foo.nested_string:"Bumbleywump Cucumberpatch"'))
|
||||
|
||||
self.assertEqual(1, len(found))
|
||||
self.assertEqual(created[0]['id'], found[0]['id'])
|
||||
self.assertEqual(created[0]['variables'], found[0]['variables'])
|
||||
|
||||
def test_jsonpath_search_nested_string_wildcard(self):
|
||||
resources = (
|
||||
('resource1', {'foo': TEST_DICT}),
|
||||
('resource2', {'foo': {"baz": "zoom"}})
|
||||
)
|
||||
created = self.setup_resources(resources)
|
||||
|
||||
found = self.resources_from_response(
|
||||
self.get_resources(vars='foo.*:"zoo"'))
|
||||
|
||||
self.assertEqual(1, len(found))
|
||||
self.assertEqual(created[0]['id'], found[0]['id'])
|
||||
self.assertEqual(created[0]['variables'], found[0]['variables'])
|
||||
|
||||
def test_jsonpath_search_array_string(self):
|
||||
resources = (
|
||||
('resource1', {'foo': TEST_ARRAY}),
|
||||
('resource2', {'foo': TEST_ARRAY}),
|
||||
('resource3', {'foo': ["I'm just a string", 1, 2, 3, 4, 'foo']}),
|
||||
)
|
||||
created = self.setup_resources(resources)
|
||||
|
||||
found = self.resources_from_response(
|
||||
self.get_resources(vars='foo[5]:"I\'m just a string"'))
|
||||
|
||||
self.assertEqual(2, len(found))
|
||||
self.assertListEqual(sorted([c['id'] for c in created[:2]]),
|
||||
sorted([f['id'] for f in found]))
|
||||
|
||||
def test_jsonpath_search_array_string_wildcard(self):
|
||||
resources = (
|
||||
('resource1', {'foo': TEST_ARRAY}),
|
||||
('resource2', {'foo': TEST_ARRAY}),
|
||||
('resource3', {'foo': ["I'm just a string", True]}),
|
||||
('resource4', {'foo': ['Bumbleywump Cucumberpatch']}),
|
||||
)
|
||||
created = self.setup_resources(resources)
|
||||
|
||||
found = self.resources_from_response(
|
||||
self.get_resources(vars='foo[*]:"I\'m just a string"'))
|
||||
|
||||
self.assertEqual(3, len(found))
|
||||
self.assertListEqual(sorted([c['id'] for c in created[:3]]),
|
||||
sorted([f['id'] for f in found]))
|
||||
|
||||
def test_jsonpath_search_nested_array_string(self):
|
||||
resources = (
|
||||
('resource1', {'foo': TEST_DICT}),
|
||||
('resource2', {'foo': TEST_DICT}),
|
||||
('resource3', {'foo': {"bar": ["I'm just a string", True]}}),
|
||||
('resource4', {'foo': TEST_ARRAY}),
|
||||
)
|
||||
created = self.setup_resources(resources)
|
||||
|
||||
found = self.resources_from_response(
|
||||
self.get_resources(vars='foo.bar[*]:"I\'m just a string"'))
|
||||
|
||||
self.assertEqual(3, len(found))
|
||||
self.assertListEqual(sorted([c['id'] for c in created[:3]]),
|
||||
sorted([f['id'] for f in found]))
|
||||
|
||||
def test_jsonpath_search_nested_int(self):
|
||||
resources = (
|
||||
('resource1', {'foo': TEST_DICT}),
|
||||
('resource2', {'foo': {"foo": {"nested_int": "1"}}})
|
||||
)
|
||||
created = self.setup_resources(resources)
|
||||
|
||||
found = self.resources_from_response(
|
||||
self.get_resources(vars='foo.foo.nested_int:1'))
|
||||
|
||||
self.assertEqual(1, len(found))
|
||||
self.assertEqual(created[0]['id'], found[0]['id'])
|
||||
self.assertEqual(created[0]['variables'], found[0]['variables'])
|
||||
|
||||
def test_jsonpath_search_nested_float(self):
|
||||
resources = (
|
||||
('resource1', {'foo': TEST_DICT}),
|
||||
('resource2', {'foo': {"foo": {"nested_float": 3}}})
|
||||
)
|
||||
created = self.setup_resources(resources)
|
||||
|
||||
found = self.resources_from_response(
|
||||
self.get_resources(vars='foo.foo.nested_float:3.14'))
|
||||
|
||||
self.assertEqual(1, len(found))
|
||||
self.assertEqual(created[0]['id'], found[0]['id'])
|
||||
self.assertEqual(created[0]['variables'], found[0]['variables'])
|
||||
|
||||
def test_jsonpath_search_nested_bool(self):
|
||||
resources = (
|
||||
('resource1', {'foo': TEST_DICT}),
|
||||
('resource2', {'foo': {"foo": {"nested_bool": 'true'}}})
|
||||
)
|
||||
created = self.setup_resources(resources)
|
||||
|
||||
found = self.resources_from_response(
|
||||
self.get_resources(vars='foo.foo.nested_bool:true'))
|
||||
|
||||
self.assertEqual(1, len(found))
|
||||
self.assertEqual(created[0]['id'], found[0]['id'])
|
||||
self.assertEqual(created[0]['variables'], found[0]['variables'])
|
||||
|
||||
def test_jsonpath_search_nested_boolstr(self):
|
||||
resources = (
|
||||
('resource1', {'foo': TEST_DICT}),
|
||||
('resource2', {'foo': {"foo": {"nested_boolstr": False}}})
|
||||
)
|
||||
created = self.setup_resources(resources)
|
||||
|
||||
found = self.resources_from_response(
|
||||
self.get_resources(vars='foo.foo.nested_boolstr:"false"'))
|
||||
|
||||
self.assertEqual(1, len(found))
|
||||
self.assertEqual(created[0]['id'], found[0]['id'])
|
||||
self.assertEqual(created[0]['variables'], found[0]['variables'])
|
||||
|
||||
def test_jsonpath_search_nested_null(self):
|
||||
resources = (
|
||||
('resource1', {'foo': TEST_DICT}),
|
||||
('resource2', {'foo': {"foo": {"nested_null": 'test'}}})
|
||||
)
|
||||
created = self.setup_resources(resources)
|
||||
|
||||
found = self.resources_from_response(
|
||||
self.get_resources(vars='foo.foo.nested_null:null'))
|
||||
|
||||
self.assertEqual(1, len(found))
|
||||
self.assertEqual(created[0]['id'], found[0]['id'])
|
||||
self.assertEqual(created[0]['variables'], found[0]['variables'])
|
||||
|
||||
def test_jsonpath_search_hyphenated(self):
|
||||
resources = (
|
||||
('resource1', {'foo': TEST_DICT}),
|
||||
('resource2', {'foo': {"foo": {"hyphenated-key": 'test-test'}}})
|
||||
)
|
||||
created = self.setup_resources(resources)
|
||||
|
||||
found = self.resources_from_response(self.get_resources(
|
||||
vars='foo.foo."hyphenated-key":"look-at-all-these-hyphens!"'))
|
||||
|
||||
self.assertEqual(1, len(found))
|
||||
self.assertEqual(created[0]['id'], found[0]['id'])
|
||||
self.assertEqual(created[0]['variables'], found[0]['variables'])
|
||||
|
||||
def test_jsonpath_search_key_with_period(self):
|
||||
resources = (
|
||||
('resource1', {'v3.0': TEST_DICT}),
|
||||
('resource2', {'v3.0': {"foo": {"hyphenated-key": 'test-test'}}})
|
||||
)
|
||||
created = self.setup_resources(resources)
|
||||
|
||||
found = self.resources_from_response(self.get_resources(
|
||||
vars='"v3.0".foo."hyphenated-key":"look-at-all-these-hyphens!"'))
|
||||
|
||||
self.assertEqual(1, len(found))
|
||||
self.assertEqual(created[0]['id'], found[0]['id'])
|
||||
self.assertEqual(created[0]['variables'], found[0]['variables'])
|
||||
|
||||
def test_jsonpath_search_non_string_member(self):
|
||||
self.setup_resources((
|
||||
('resource1', {'v3.0': TEST_DICT}),
|
||||
))
|
||||
|
||||
resp = self.get_resources(
|
||||
vars='v3.0.foo."hyphenated-key":"look-at-all-these-hyphens!"')
|
||||
self.assertBadRequest(resp)
|
||||
self.assertEqual(exceptions.InvalidJSONPath.msg,
|
||||
resp.json()['message'])
|
||||
|
||||
def test_jsonpath_search_hyphenated_without_quotes(self):
|
||||
self.setup_resources((
|
||||
('resource1', {'v3.0': TEST_DICT}),
|
||||
))
|
||||
|
||||
resp = self.get_resources(
|
||||
vars='foo.hyphenated-key:"look-at-all-these-hyphens!"')
|
||||
self.assertBadRequest(resp)
|
||||
self.assertEqual(exceptions.InvalidJSONPath.msg,
|
||||
resp.json()['message'])
|
||||
|
||||
def test_jsonpath_search_invalid_first_key(self):
|
||||
self.setup_resources((
|
||||
('resource1', {'v3.0': TEST_DICT}),
|
||||
))
|
||||
|
||||
resp = self.get_resources(vars='[*]foo.bar:"string"')
|
||||
self.assertBadRequest(resp)
|
||||
self.assertEqual(exceptions.InvalidJSONPath.msg,
|
||||
resp.json()['message'])
|
||||
|
||||
def test_jsonpath_search_bad_json_string_value(self):
|
||||
self.setup_resources((
|
||||
('resource1', {'v3.0': TEST_DICT}),
|
||||
))
|
||||
|
||||
resp = self.get_resources(vars='foo.bar:string')
|
||||
self.assertBadRequest(resp)
|
||||
self.assertEqual(exceptions.InvalidJSONValue.msg,
|
||||
resp.json()['message'])
|
||||
|
||||
|
||||
class ProjectsJSONPathSearchTestCase(functional.TestCase,
|
||||
JSONPathSearchTestCaseMixin):
|
||||
resource = 'projects'
|
||||
|
||||
|
||||
class CloudsJSONPathSearchTestCase(functional.TestCase,
|
||||
JSONPathSearchTestCaseMixin):
|
||||
resource = 'clouds'
|
||||
|
||||
|
||||
class RegionsJSONPathSearchTestCase(functional.TestCase,
|
||||
JSONPathSearchTestCaseMixin):
|
||||
resource = 'regions'
|
||||
|
||||
|
||||
class CellsJSONPathSearchTestCase(functional.TestCase,
|
||||
JSONPathSearchTestCaseMixin):
|
||||
resource = 'cells'
|
||||
|
||||
|
||||
class NetworksJSONPathSearchTestCase(functional.TestCase,
|
||||
JSONPathSearchTestCaseMixin):
|
||||
resource = 'networks'
|
||||
|
||||
|
||||
class NetworkDevicesJSONPathSearchTestCase(functional.TestCase,
|
||||
JSONPathSearchTestCaseMixin):
|
||||
resource = 'network-devices'
|
||||
|
||||
|
||||
class HostsJSONPathSearchTestCase(functional.TestCase,
|
||||
JSONPathSearchTestCaseMixin):
|
||||
resource = 'hosts'
|
@ -1,162 +0,0 @@
|
||||
from craton.tests.functional import TestCase
|
||||
|
||||
|
||||
class APIV1NetworkSchemaTest(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(APIV1NetworkSchemaTest, self).setUp()
|
||||
self.cloud = self.create_cloud(name='cloud-1')
|
||||
self.region = self.create_region(name='region-1', cloud=self.cloud)
|
||||
self.networks_url = self.url + '/v1/networks'
|
||||
self.cidr = '192.168.0.0/24'
|
||||
self.netmask = '255.255.255.0'
|
||||
self.gateway = '192.168.0.1'
|
||||
|
||||
def test_network_create_with_required_works(self):
|
||||
payload = {
|
||||
'cloud_id': self.cloud['id'],
|
||||
'region_id': self.region['id'],
|
||||
'name': 'a',
|
||||
'cidr': self.cidr,
|
||||
'netmask': self.netmask,
|
||||
'gateway': self.gateway,
|
||||
}
|
||||
resp = self.post(self.networks_url, data=payload)
|
||||
self.assertEqual(201, resp.status_code)
|
||||
|
||||
network = resp.json()
|
||||
self.assertEqual('a', network['name'])
|
||||
self.assertEqual(self.cloud['id'], network['cloud_id'])
|
||||
self.assertEqual(self.region['id'], network['region_id'])
|
||||
self.assertEqual(self.cidr, network['cidr'])
|
||||
self.assertEqual(self.gateway, network['gateway'])
|
||||
self.assertEqual(self.netmask, network['netmask'])
|
||||
|
||||
def test_network_create_without_region_id_fails(self):
|
||||
payload = {
|
||||
'cloud_id': self.cloud['id'],
|
||||
'name': 'a',
|
||||
'cidr': self.cidr,
|
||||
'netmask': self.netmask,
|
||||
'gateway': self.gateway,
|
||||
}
|
||||
network = self.post(self.networks_url, data=payload)
|
||||
self.assertEqual(400, network.status_code)
|
||||
msg = (
|
||||
"The request included the following errors:\n"
|
||||
"- 'region_id' is a required property"
|
||||
)
|
||||
self.assertEqual(network.json()['message'], msg)
|
||||
|
||||
def test_network_create_without_cloud_id_fails(self):
|
||||
payload = {
|
||||
'region_id': self.region['id'],
|
||||
'name': 'a',
|
||||
'cidr': self.cidr,
|
||||
'netmask': self.netmask,
|
||||
'gateway': self.gateway,
|
||||
}
|
||||
network = self.post(self.networks_url, data=payload)
|
||||
self.assertEqual(400, network.status_code)
|
||||
msg = (
|
||||
"The request included the following errors:\n"
|
||||
"- 'cloud_id' is a required property"
|
||||
)
|
||||
self.assertEqual(network.json()['message'], msg)
|
||||
|
||||
def test_network_create_with_extra_id_property_fails(self):
|
||||
payload = {
|
||||
'region_id': self.region['id'],
|
||||
'cloud_id': self.cloud['id'],
|
||||
'name': 'a',
|
||||
'cidr': self.cidr,
|
||||
'netmask': self.netmask,
|
||||
'gateway': self.gateway,
|
||||
'id': 3
|
||||
}
|
||||
network = self.post(self.networks_url, data=payload)
|
||||
self.assertEqual(400, network.status_code)
|
||||
msg = (
|
||||
"The request included the following errors:\n"
|
||||
"- Additional properties are not allowed ('id' was unexpected)"
|
||||
)
|
||||
self.assertEqual(network.json()['message'], msg)
|
||||
|
||||
def test_network_create_with_extra_created_at_property_fails(self):
|
||||
payload = {
|
||||
'region_id': self.region['id'],
|
||||
'cloud_id': self.cloud['id'],
|
||||
'name': 'a',
|
||||
'cidr': self.cidr,
|
||||
'netmask': self.netmask,
|
||||
'gateway': self.gateway,
|
||||
'created_at': 'This should not work'
|
||||
}
|
||||
network = self.post(self.networks_url, data=payload)
|
||||
self.assertEqual(400, network.status_code)
|
||||
msg = (
|
||||
"The request included the following errors:\n"
|
||||
"- Additional properties are not allowed ('created_at' was "
|
||||
"unexpected)"
|
||||
)
|
||||
self.assertEqual(network.json()['message'], msg)
|
||||
|
||||
def test_network_create_with_extra_updated_at_property_fails(self):
|
||||
payload = {
|
||||
'region_id': self.region['id'],
|
||||
'cloud_id': self.cloud['id'],
|
||||
'name': 'a',
|
||||
'cidr': self.cidr,
|
||||
'netmask': self.netmask,
|
||||
'gateway': self.gateway,
|
||||
'updated_at': 'This should not work'
|
||||
}
|
||||
network = self.post(self.networks_url, data=payload)
|
||||
self.assertEqual(400, network.status_code)
|
||||
msg = (
|
||||
"The request included the following errors:\n"
|
||||
"- Additional properties are not allowed ('updated_at' was "
|
||||
"unexpected)"
|
||||
)
|
||||
self.assertEqual(network.json()['message'], msg)
|
||||
|
||||
def test_network_create_missing_all_properties_fails(self):
|
||||
url = self.url + '/v1/networks'
|
||||
network = self.post(url, data={})
|
||||
self.assertEqual(400, network.status_code)
|
||||
msg = (
|
||||
"The request included the following errors:\n"
|
||||
"- 'cidr' is a required property\n"
|
||||
"- 'cloud_id' is a required property\n"
|
||||
"- 'gateway' is a required property\n"
|
||||
"- 'name' is a required property\n"
|
||||
"- 'netmask' is a required property\n"
|
||||
"- 'region_id' is a required property"
|
||||
)
|
||||
self.assertEqual(network.json()['message'], msg)
|
||||
|
||||
def test_network_get_all_with_details(self):
|
||||
payload = {
|
||||
'cloud_id': self.cloud['id'],
|
||||
'region_id': self.region['id'],
|
||||
'name': 'a',
|
||||
'cidr': self.cidr,
|
||||
'netmask': self.netmask,
|
||||
'gateway': self.gateway,
|
||||
'variables': {'a': 'b'},
|
||||
}
|
||||
resp = self.post(self.networks_url, data=payload)
|
||||
self.assertEqual(201, resp.status_code)
|
||||
|
||||
payload['name'] = 'b'
|
||||
resp = self.post(self.networks_url, data=payload)
|
||||
self.assertEqual(201, resp.status_code)
|
||||
|
||||
url = self.networks_url + '?details=all'
|
||||
resp = self.get(url)
|
||||
self.assertEqual(200, resp.status_code)
|
||||
networks = resp.json()['networks']
|
||||
|
||||
for network in networks:
|
||||
self.assertTrue('variables' in network)
|
||||
self.assertEqual({'a': 'b'}, network['variables'])
|
@ -1,115 +0,0 @@
|
||||
from craton.tests.functional import DeviceTestBase
|
||||
|
||||
|
||||
class APIV1NetworkDeviceTest(DeviceTestBase):
|
||||
|
||||
resource = 'network-devices'
|
||||
|
||||
def test_create_with_parent_id(self):
|
||||
parent = self.create_network_device(
|
||||
name='test1',
|
||||
cloud=self.cloud,
|
||||
region=self.region,
|
||||
device_type='switch',
|
||||
ip_address='192.168.1.1',
|
||||
)
|
||||
child = self.create_network_device(
|
||||
name='test2',
|
||||
cloud=self.cloud,
|
||||
region=self.region,
|
||||
device_type='switch',
|
||||
ip_address='192.168.1.2',
|
||||
parent_id=parent['id'],
|
||||
)
|
||||
self.assertEqual(parent['id'], child['parent_id'])
|
||||
|
||||
def test_update_with_parent_id(self):
|
||||
parent = self.create_network_device(
|
||||
name='test1',
|
||||
cloud=self.cloud,
|
||||
region=self.region,
|
||||
device_type='switch',
|
||||
ip_address='192.168.1.1',
|
||||
)
|
||||
|
||||
child = self.create_network_device(
|
||||
name='test2',
|
||||
cloud=self.cloud,
|
||||
region=self.region,
|
||||
device_type='switch',
|
||||
ip_address='192.168.1.2',
|
||||
)
|
||||
self.assertIsNone(child['parent_id'])
|
||||
|
||||
url = '{}/v1/network-devices/{}'.format(self.url, child['id'])
|
||||
child_update_resp = self.put(
|
||||
url, data={'parent_id': parent['id']}
|
||||
)
|
||||
self.assertEqual(200, child_update_resp.status_code)
|
||||
child_update = child_update_resp.json()
|
||||
self.assertEqual(parent['id'], child_update['parent_id'])
|
||||
|
||||
def test_update_with_parent_id_equal_id_fails(self):
|
||||
network_device = self.create_network_device(
|
||||
name='test1',
|
||||
cloud=self.cloud,
|
||||
region=self.region,
|
||||
device_type='switch',
|
||||
ip_address='192.168.1.1',
|
||||
)
|
||||
|
||||
url = '{}/v1/network-devices/{}'.format(self.url, network_device['id'])
|
||||
network_device_update_resp = self.put(
|
||||
url, data={'parent_id': network_device['id']}
|
||||
)
|
||||
self.assertEqual(400, network_device_update_resp.status_code)
|
||||
|
||||
def test_update_with_parent_id_equal_descendant_id_fails(self):
|
||||
parent = self.create_network_device(
|
||||
name='test1',
|
||||
cloud=self.cloud,
|
||||
region=self.region,
|
||||
device_type='switch',
|
||||
ip_address='192.168.1.1',
|
||||
)
|
||||
self.assertIsNone(parent['parent_id'])
|
||||
|
||||
child = self.create_network_device(
|
||||
name='test2',
|
||||
cloud=self.cloud,
|
||||
region=self.region,
|
||||
device_type='switch',
|
||||
ip_address='192.168.1.2',
|
||||
parent_id=parent['id'],
|
||||
)
|
||||
self.assertEqual(parent['id'], child['parent_id'])
|
||||
|
||||
grandchild = self.create_network_device(
|
||||
name='test3',
|
||||
cloud=self.cloud,
|
||||
region=self.region,
|
||||
device_type='switch',
|
||||
ip_address='192.168.1.3',
|
||||
parent_id=child['id'],
|
||||
)
|
||||
self.assertEqual(child['id'], grandchild['parent_id'])
|
||||
|
||||
url = '{}/v1/network-devices/{}'.format(self.url, parent['id'])
|
||||
parent_update_resp = self.put(
|
||||
url, data={'parent_id': grandchild['id']}
|
||||
)
|
||||
self.assertEqual(400, parent_update_resp.status_code)
|
||||
|
||||
def test_network_device_create_missing_all_properties_fails(self):
|
||||
url = self.url + '/v1/network-devices'
|
||||
network_device = self.post(url, data={})
|
||||
self.assertEqual(400, network_device.status_code)
|
||||
msg = (
|
||||
"The request included the following errors:\n"
|
||||
"- 'cloud_id' is a required property\n"
|
||||
"- 'device_type' is a required property\n"
|
||||
"- 'ip_address' is a required property\n"
|
||||
"- 'name' is a required property\n"
|
||||
"- 'region_id' is a required property"
|
||||
)
|
||||
self.assertEqual(network_device.json()['message'], msg)
|
@ -1,70 +0,0 @@
|
||||
from craton.tests import functional
|
||||
|
||||
|
||||
class APIv1NetworkInterfacesTest(functional.DeviceTestBase):
|
||||
def setUp(self):
|
||||
super(APIv1NetworkInterfacesTest, self).setUp()
|
||||
self.interfaces_url = self.url + '/v1/network-interfaces'
|
||||
|
||||
def test_associate_network_device_with_a_host(self):
|
||||
host = self.create_host('host-0', 'server', '127.0.0.1')
|
||||
|
||||
payload = {
|
||||
'name': 'lo',
|
||||
'ip_address': '127.0.0.1',
|
||||
'device_id': host['id'],
|
||||
'interface_type': 'loopback',
|
||||
}
|
||||
response = self.post(self.interfaces_url, data=payload)
|
||||
self.assertSuccessCreated(response)
|
||||
self.assertIn('Location', response.headers)
|
||||
interface = response.json()
|
||||
self.assertEqual(
|
||||
'{}/{}'.format(self.interfaces_url, interface['id']),
|
||||
response.headers['Location']
|
||||
)
|
||||
|
||||
def test_port_must_be_an_integer_on_create(self):
|
||||
host = self.create_host('host-0', 'server', '127.0.0.1')
|
||||
|
||||
payload = {
|
||||
'name': 'lo',
|
||||
'ip_address': '127.0.0.1',
|
||||
'device_id': host['id'],
|
||||
'interface_type': 'loopback',
|
||||
'port': 'asdf',
|
||||
}
|
||||
response = self.post(self.interfaces_url, data=payload)
|
||||
self.assertBadRequest(response)
|
||||
|
||||
def test_port_must_be_an_integer_on_update(self):
|
||||
host = self.create_host('host-0', 'server', '127.0.0.1')
|
||||
|
||||
payload = {
|
||||
'name': 'lo',
|
||||
'ip_address': '127.0.0.1',
|
||||
'device_id': host['id'],
|
||||
'interface_type': 'loopback',
|
||||
'port': 80,
|
||||
}
|
||||
response = self.post(self.interfaces_url, data=payload)
|
||||
self.assertSuccessCreated(response)
|
||||
interface = response.json()
|
||||
|
||||
url = self.interfaces_url + '/{}'.format(interface['id'])
|
||||
payload = {'port': 'asdf'}
|
||||
response = self.put(url, data=payload)
|
||||
self.assertBadRequest(response)
|
||||
|
||||
def test_network_interface_create_missing_all_properties_fails(self):
|
||||
url = self.url + '/v1/network-interfaces'
|
||||
network_interface = self.post(url, data={})
|
||||
self.assertEqual(400, network_interface.status_code)
|
||||
msg = (
|
||||
"The request included the following errors:\n"
|
||||
"- 'device_id' is a required property\n"
|
||||
"- 'interface_type' is a required property\n"
|
||||
"- 'ip_address' is a required property\n"
|
||||
"- 'name' is a required property"
|
||||
)
|
||||
self.assertEqual(network_interface.json()['message'], msg)
|
@ -1,129 +0,0 @@
|
||||
from craton.tests import functional
|
||||
from craton.tests.functional.test_variable_calls import \
|
||||
APIV1ResourceWithVariablesTestCase
|
||||
|
||||
|
||||
class TestPaginationOfProjects(functional.TestCase):
|
||||
def setUp(self):
|
||||
super(TestPaginationOfProjects, self).setUp()
|
||||
self.projects = [
|
||||
self.create_project('project-{}'.format(i))
|
||||
for i in range(0, 61)
|
||||
]
|
||||
|
||||
def test_lists_first_thirty_projects(self):
|
||||
response = self.get(self.url + '/v1/projects',
|
||||
headers=self.root_headers)
|
||||
self.assertSuccessOk(response)
|
||||
json = response.json()
|
||||
self.assertIn('projects', json)
|
||||
projects = json['projects']
|
||||
self.assertEqual(30, len(projects))
|
||||
|
||||
def test_lists_projects_with_the_same_name(self):
|
||||
self.create_project('project-0')
|
||||
|
||||
response = self.get(self.url + '/v1/projects',
|
||||
name='project-0',
|
||||
headers=self.root_headers)
|
||||
self.assertSuccessOk(response)
|
||||
projects = response.json()['projects']
|
||||
self.assertEqual(2, len(projects))
|
||||
|
||||
|
||||
class APIV1ProjectTest(APIV1ResourceWithVariablesTestCase):
|
||||
|
||||
resource = 'projects'
|
||||
|
||||
def test_project_create_with_variables(self):
|
||||
variables = {'a': 'b'}
|
||||
project_name = 'test'
|
||||
project = self.create_project(project_name, variables=variables)
|
||||
self.assertEqual(project_name, project['name'])
|
||||
self.assertEqual(variables, project['variables'])
|
||||
|
||||
def test_create_project_supports_vars_ops(self):
|
||||
project = self.create_project('test', variables={'a': 'b'})
|
||||
self.assert_vars_get_expected(project['id'], {'a': 'b'})
|
||||
self.assert_vars_can_be_set(project['id'])
|
||||
self.assert_vars_can_be_deleted(project['id'])
|
||||
|
||||
def test_project_create_with_duplicate_name_works(self):
|
||||
project_name = 'test'
|
||||
self.create_project(project_name)
|
||||
url = self.url + '/v1/projects'
|
||||
payload = {'name': project_name}
|
||||
project = self.post(url, headers=self.root_headers, data=payload)
|
||||
self.assertEqual(201, project.status_code)
|
||||
|
||||
def test_project_get_all_with_name_filter(self):
|
||||
proj1 = 'test1'
|
||||
proj2 = 'test2'
|
||||
self.create_project(proj2)
|
||||
for i in range(3):
|
||||
self.create_project(proj1)
|
||||
url = self.url + '/v1/projects?name={}'.format(proj1)
|
||||
resp = self.get(url, headers=self.root_headers)
|
||||
projects = resp.json()['projects']
|
||||
self.assertEqual(3, len(projects))
|
||||
for project in projects:
|
||||
self.assertEqual(proj1, project['name'])
|
||||
|
||||
def test_get_project_details(self):
|
||||
project_name = 'test'
|
||||
project_vars = {"who": "that"}
|
||||
project = self.create_project(project_name, variables=project_vars)
|
||||
url = self.url + '/v1/projects/{}'.format(project['id'])
|
||||
project_with_detail = self.get(url, headers=self.root_headers)
|
||||
self.assertEqual(project_name, project_with_detail.json()['name'])
|
||||
self.assertEqual(project_vars, project_with_detail.json()['variables'])
|
||||
|
||||
def test_project_delete(self):
|
||||
project1 = self.create_project('test1')
|
||||
url = self.url + '/v1/projects'
|
||||
projects = self.get(url, headers=self.root_headers)
|
||||
# NOTE(thomasem): Have to include the default project created by
|
||||
# test setup.
|
||||
self.assertEqual(2, len(projects.json()['projects']))
|
||||
|
||||
delurl = self.url + '/v1/projects/{}'.format(project1['id'])
|
||||
self.delete(delurl, headers=self.root_headers)
|
||||
|
||||
projects = self.get(url, headers=self.root_headers)
|
||||
self.assertEqual(1, len(projects.json()['projects']))
|
||||
|
||||
def test_project_variables_update(self):
|
||||
project_name = 'test'
|
||||
project = self.create_project(project_name)
|
||||
variables = {"bumbleywump": "cucumberpatch"}
|
||||
|
||||
put_url = self.url + '/v1/projects/{}/variables'.format(project['id'])
|
||||
resp = self.put(put_url, headers=self.root_headers, data=variables)
|
||||
self.assertEqual(200, resp.status_code)
|
||||
|
||||
get_url = self.url + '/v1/projects/{}'.format(project['id'])
|
||||
project = self.get(get_url, headers=self.root_headers)
|
||||
self.assertEqual(variables, project.json()['variables'])
|
||||
|
||||
def test_project_variables_delete(self):
|
||||
project_name = 'test'
|
||||
delete_key = 'bumbleywump'
|
||||
variables = {
|
||||
delete_key: 'cucumberpatch'
|
||||
}
|
||||
expected_vars = {'foo': 'bar'}
|
||||
variables.update(expected_vars)
|
||||
|
||||
project = self.create_project(project_name, variables=variables)
|
||||
self.assert_vars_get_expected(project['id'], variables)
|
||||
self.assert_vars_can_be_deleted(project['id'])
|
||||
|
||||
def test_project_create_missing_all_properties_fails(self):
|
||||
url = self.url + '/v1/projects'
|
||||
project = self.post(url, data={})
|
||||
self.assertEqual(400, project.status_code)
|
||||
msg = (
|
||||
"The request included the following errors:\n"
|
||||
"- 'name' is a required property"
|
||||
)
|
||||
self.assertEqual(project.json()['message'], msg)
|
@ -1,288 +0,0 @@
|
||||
import urllib.parse
|
||||
|
||||
from craton.tests.functional import TestCase
|
||||
|
||||
|
||||
class RegionTests(TestCase):
|
||||
def setUp(self):
|
||||
super(RegionTests, self).setUp()
|
||||
self.cloud = self.create_cloud()
|
||||
|
||||
def create_cloud(self):
|
||||
return super(RegionTests, self).create_cloud(
|
||||
name='cloud-1',
|
||||
variables={'version': 'x'},
|
||||
)
|
||||
|
||||
def create_region(self, name, variables=None):
|
||||
return super(RegionTests, self).create_region(
|
||||
name=name,
|
||||
cloud=self.cloud,
|
||||
variables=variables
|
||||
)
|
||||
|
||||
|
||||
class APIV1RegionTest(RegionTests):
|
||||
"""Test cases for /region calls.
|
||||
One set of data for the test is generated by fake data generateion
|
||||
script during test module setup.
|
||||
"""
|
||||
|
||||
def test_create_region_full_data(self):
|
||||
# Test with full set of allowed parameters
|
||||
values = {"name": "region-new",
|
||||
"note": "This is region-new.",
|
||||
"cloud_id": self.cloud['id'],
|
||||
"variables": {"a": "b"}}
|
||||
url = self.url + '/v1/regions'
|
||||
resp = self.post(url, data=values)
|
||||
self.assertEqual(201, resp.status_code)
|
||||
self.assertIn('Location', resp.headers)
|
||||
self.assertEqual(
|
||||
resp.headers['Location'],
|
||||
"{}/{}".format(url, resp.json()['id'])
|
||||
)
|
||||
self.assertEqual(values['name'], resp.json()['name'])
|
||||
|
||||
def test_create_region_without_variables(self):
|
||||
values = {"name": "region-two",
|
||||
"note": "This is region-two",
|
||||
"cloud_id": self.cloud['id']}
|
||||
url = self.url + '/v1/regions'
|
||||
resp = self.post(url, data=values)
|
||||
self.assertEqual(201, resp.status_code)
|
||||
self.assertIn('Location', resp.headers)
|
||||
self.assertEqual(
|
||||
resp.headers['Location'],
|
||||
"{}/{}".format(url, resp.json()['id'])
|
||||
)
|
||||
self.assertEqual("region-two", resp.json()['name'])
|
||||
|
||||
def test_create_region_with_no_name_fails(self):
|
||||
values = {"note": "This is region one.", "cloud_id": self.cloud['id']}
|
||||
url = self.url + '/v1/regions'
|
||||
resp = self.post(url, data=values)
|
||||
self.assertEqual(resp.status_code, 400)
|
||||
err_msg = (
|
||||
"The request included the following errors:\n"
|
||||
"- 'name' is a required property"
|
||||
)
|
||||
self.assertEqual(resp.json()['message'], err_msg)
|
||||
|
||||
def test_create_region_with_no_cloud_id_fails(self):
|
||||
values = {"name": "I don't work at all, you know."}
|
||||
url = self.url + '/v1/regions'
|
||||
resp = self.post(url, data=values)
|
||||
self.assertEqual(resp.status_code, 400)
|
||||
err_msg = (
|
||||
"The request included the following errors:\n"
|
||||
"- 'cloud_id' is a required property"
|
||||
)
|
||||
self.assertEqual(resp.json()['message'], err_msg)
|
||||
|
||||
def test_create_region_with_duplicate_name_fails(self):
|
||||
self.create_region("ORD135")
|
||||
|
||||
values = {"name": "ORD135", "cloud_id": self.cloud['id']}
|
||||
url = self.url + '/v1/regions'
|
||||
resp = self.post(url, data=values)
|
||||
self.assertEqual(409, resp.status_code)
|
||||
|
||||
def test_create_region_with_extra_id_property_fails(self):
|
||||
values = {"name": "test", 'cloud_id': self.cloud['id'], "id": 101}
|
||||
url = self.url + '/v1/regions'
|
||||
resp = self.post(url, data=values)
|
||||
self.assertEqual(resp.status_code, 400)
|
||||
msg = (
|
||||
"The request included the following errors:\n"
|
||||
"- Additional properties are not allowed ('id' was unexpected)"
|
||||
)
|
||||
self.assertEqual(resp.json()['message'], msg)
|
||||
|
||||
def test_create_region_with_extra_created_at_property_fails(self):
|
||||
values = {"name": "test", 'cloud_id': self.cloud['id'],
|
||||
"created_at": "some date"}
|
||||
url = self.url + '/v1/regions'
|
||||
resp = self.post(url, data=values)
|
||||
self.assertEqual(resp.status_code, 400)
|
||||
msg = (
|
||||
"The request included the following errors:\n"
|
||||
"- Additional properties are not allowed "
|
||||
"('created_at' was unexpected)"
|
||||
)
|
||||
self.assertEqual(resp.json()['message'], msg)
|
||||
|
||||
def test_create_region_with_extra_updated_at_property_fails(self):
|
||||
values = {"name": "test", 'cloud_id': self.cloud['id'],
|
||||
"updated_at": "some date"}
|
||||
url = self.url + '/v1/regions'
|
||||
resp = self.post(url, data=values)
|
||||
self.assertEqual(resp.status_code, 400)
|
||||
msg = (
|
||||
"The request included the following errors:\n"
|
||||
"- Additional properties are not allowed "
|
||||
"('updated_at' was unexpected)"
|
||||
)
|
||||
self.assertEqual(resp.json()['message'], msg)
|
||||
|
||||
def test_region_create_missing_all_properties_fails(self):
|
||||
url = self.url + '/v1/regions'
|
||||
region = self.post(url, data={})
|
||||
self.assertEqual(400, region.status_code)
|
||||
msg = (
|
||||
"The request included the following errors:\n"
|
||||
"- 'cloud_id' is a required property\n"
|
||||
"- 'name' is a required property"
|
||||
)
|
||||
self.assertEqual(region.json()['message'], msg)
|
||||
|
||||
def test_regions_get_all(self):
|
||||
self.create_region("ORD1")
|
||||
self.create_region("ORD2")
|
||||
url = self.url + '/v1/regions'
|
||||
resp = self.get(url)
|
||||
self.assertEqual(200, resp.status_code)
|
||||
self.assertEqual(2, len(resp.json()))
|
||||
|
||||
def test_regions_get_all_with_details(self):
|
||||
self.create_region('ORD1', variables={'a': 'b'})
|
||||
self.create_region('ORD2', variables={'c': 'd'})
|
||||
url = self.url + '/v1/regions?details=all'
|
||||
resp = self.get(url)
|
||||
self.assertEqual(200, resp.status_code)
|
||||
regions = resp.json()['regions']
|
||||
self.assertEqual(2, len(regions))
|
||||
for region in regions:
|
||||
self.assertTrue('variables' in region)
|
||||
for region in regions:
|
||||
if region['name'] == 'ORD1':
|
||||
self.assertEqual({'a': 'b', 'version': 'x'},
|
||||
region['variables'])
|
||||
if region['name'] == 'ORD2':
|
||||
self.assertEqual({'c': 'd', 'version': 'x'},
|
||||
region['variables'])
|
||||
|
||||
def test_regions_get_all_with_name_filter(self):
|
||||
self.create_region("ORD1")
|
||||
self.create_region("ORD2")
|
||||
url = self.url + '/v1/regions?name=ORD1'
|
||||
resp = self.get(url)
|
||||
self.assertEqual(200, resp.status_code)
|
||||
regions = resp.json()['regions']
|
||||
self.assertEqual(1, len(regions))
|
||||
self.assertEqual('ORD1', regions[0]['name'])
|
||||
|
||||
def test_regions_get_all_for_cloud(self):
|
||||
for i in range(2):
|
||||
self.create_region("ORD{}".format(str(i)))
|
||||
url = self.url + '/v1/regions?cloud_id={}'.format(self.cloud['id'])
|
||||
resp = self.get(url)
|
||||
self.assertEqual(200, resp.status_code)
|
||||
regions = resp.json()['regions']
|
||||
self.assertEqual(2, len(regions))
|
||||
self.assertEqual(['ORD0', 'ORD1'], [r['name'] for r in regions])
|
||||
|
||||
def test_region_with_non_existing_filters(self):
|
||||
self.create_region("ORD1")
|
||||
url = self.url + '/v1/regions?name=idontexist'
|
||||
resp = self.get(url)
|
||||
self.assertEqual(404, resp.status_code)
|
||||
|
||||
def test_region_get_details_for_region(self):
|
||||
regvars = {"a": "b", "one": "two"}
|
||||
region = self.create_region("ORD1", variables=regvars)
|
||||
url = self.url + '/v1/regions/{}'.format(region['id'])
|
||||
resp = self.get(url)
|
||||
region = resp.json()
|
||||
self.assertEqual(region['name'], 'ORD1')
|
||||
|
||||
def test_region_get_details_has_resolved_vars(self):
|
||||
regvars = {"a": "b", "one": "two"}
|
||||
region = self.create_region("ORD1", variables=regvars)
|
||||
url = self.url + '/v1/regions/{}'.format(region['id'])
|
||||
resp = self.get(url)
|
||||
region = resp.json()
|
||||
self.assertEqual(region['name'], 'ORD1')
|
||||
expected = {"a": "b", "one": "two", "version": "x"}
|
||||
self.assertEqual(expected, region['variables'])
|
||||
|
||||
def test_region_get_details_with_unresolved_vars(self):
|
||||
regvars = {"a": "b", "one": "two"}
|
||||
region = self.create_region("ORD1", variables=regvars)
|
||||
r_id = region['id']
|
||||
url = self.url + '/v1/regions/{}?resolved-values=false'.format(r_id)
|
||||
resp = self.get(url)
|
||||
region = resp.json()
|
||||
self.assertEqual(region['name'], 'ORD1')
|
||||
self.assertEqual(regvars, region['variables'])
|
||||
|
||||
|
||||
class TestPagination(RegionTests):
|
||||
|
||||
def setUp(self):
|
||||
super(TestPagination, self).setUp()
|
||||
self.regions = [self.create_region('region-{}'.format(i))
|
||||
for i in range(0, 61)]
|
||||
self.addCleanup(self.delete_regions, self.regions)
|
||||
|
||||
def test_list_first_thirty_regions(self):
|
||||
url = self.url + '/v1/regions'
|
||||
response = self.get(url)
|
||||
self.assertSuccessOk(response)
|
||||
json = response.json()
|
||||
self.assertIn('regions', json)
|
||||
self.assertEqual(30, len(json['regions']))
|
||||
self.assertListEqual([r['id'] for r in self.regions[:30]],
|
||||
[r['id'] for r in json['regions']])
|
||||
|
||||
def test_get_returns_correct_next_link(self):
|
||||
url = self.url + '/v1/regions'
|
||||
thirtieth_region = self.regions[29]
|
||||
response = self.get(url)
|
||||
self.assertSuccessOk(response)
|
||||
json = response.json()
|
||||
self.assertIn('links', json)
|
||||
for link_rel in json['links']:
|
||||
if link_rel['rel'] == 'next':
|
||||
break
|
||||
else:
|
||||
self.fail("No 'next' link was returned in response")
|
||||
|
||||
parsed_next = urllib.parse.urlparse(link_rel['href'])
|
||||
self.assertIn('marker={}'.format(thirtieth_region['id']),
|
||||
parsed_next.query)
|
||||
|
||||
def test_get_returns_correct_prev_link(self):
|
||||
first_region = self.regions[0]
|
||||
thirtieth_region = self.regions[29]
|
||||
url = self.url + '/v1/regions?marker={}'.format(thirtieth_region['id'])
|
||||
response = self.get(url)
|
||||
self.assertSuccessOk(response)
|
||||
json = response.json()
|
||||
self.assertIn('links', json)
|
||||
for link_rel in json['links']:
|
||||
if link_rel['rel'] == 'prev':
|
||||
break
|
||||
else:
|
||||
self.fail("No 'prev' link was returned in response")
|
||||
|
||||
parsed_prev = urllib.parse.urlparse(link_rel['href'])
|
||||
self.assertIn('marker={}'.format(first_region['id']),
|
||||
parsed_prev.query)
|
||||
|
||||
def test_follow_all_region_links(self):
|
||||
url = self.url + '/v1/regions'
|
||||
response = self.get(url)
|
||||
self.assertSuccessOk(response)
|
||||
json = response.json()
|
||||
regions = json['regions']
|
||||
while regions:
|
||||
for link in json['links']:
|
||||
if link['rel'] == 'next':
|
||||
break
|
||||
else:
|
||||
break
|
||||
response = self.get(link['href'])
|
||||
self.assertSuccessOk(response)
|
||||
json = response.json()
|
||||
regions = json['regions']
|
@ -1,29 +0,0 @@
|
||||
from craton.tests import functional
|
||||
|
||||
|
||||
class UserTests(functional.TestCase):
|
||||
|
||||
def test_create_user(self):
|
||||
project = self.create_project('test')
|
||||
url = self.url + '/v1/users'
|
||||
payload = {'username': 'testuser', 'project_id': project['id']}
|
||||
user = self.post(url, data=payload)
|
||||
self.assertEqual(201, user.status_code)
|
||||
self.assertEqual(payload['username'], user.json()['username'])
|
||||
self.assertEqual(payload['project_id'], user.json()['project_id'])
|
||||
|
||||
def test_create_user_with_admin_priv(self):
|
||||
project = self.create_project('test')
|
||||
url = self.url + '/v1/users'
|
||||
payload = {'username': 'testuser', 'project_id': project['id'],
|
||||
'is_admin': True}
|
||||
user = self.post(url, headers=self.root_headers, data=payload)
|
||||
self.assertEqual(201, user.status_code)
|
||||
self.assertEqual(payload['username'], user.json()['username'])
|
||||
self.assertEqual(payload['is_admin'], user.json()['is_admin'])
|
||||
|
||||
def test_create_user_with_no_project_id_fails(self):
|
||||
url = self.url + '/v1/users'
|
||||
payload = {'username': 'testuser'}
|
||||
user = self.post(url, headers=self.root_headers, data=payload)
|
||||
self.assertEqual(400, user.status_code)
|
@ -1,57 +0,0 @@
|
||||
from craton.tests.functional import TestCase
|
||||
|
||||
|
||||
class APIV1ResourceWithVariablesTestCase(TestCase):
|
||||
"""Base test case for resources that have variables mixed in"""
|
||||
|
||||
resource = '<resource>' # Test classes that mix in should set
|
||||
path = '/v1/{resource}/{resource_id}/variables'
|
||||
|
||||
def get_vars_url(self, resource_id):
|
||||
return self.url + self.path.format(
|
||||
resource=self.resource, resource_id=resource_id)
|
||||
|
||||
def get_current_vars(self, resource_id):
|
||||
url = self.get_vars_url(resource_id)
|
||||
response = self.get(url)
|
||||
self.assertEqual(200, response.status_code)
|
||||
return response.json()['variables']
|
||||
|
||||
def assert_vars_get_expected(self, resource_id, expected_vars):
|
||||
self.assertEqual(expected_vars, self.get_current_vars(resource_id))
|
||||
|
||||
def assert_vars_can_be_set(self, resource_id):
|
||||
"""Asserts new vars can be added to the existing vars, if any"""
|
||||
# track the expected current state of vars for this resource,
|
||||
# verifying expectations
|
||||
current_vars = self.get_current_vars(resource_id)
|
||||
payload = {'string-key': 'string-value', 'num-key': 47,
|
||||
'bookean-key': False, 'none-key': None,
|
||||
'object-key': {'a': 1, 'b': 2},
|
||||
'list-key': ['a', 'b', 1, 2, 3, True, None]}
|
||||
|
||||
url = self.get_vars_url(resource_id)
|
||||
response = self.put(url, data=payload)
|
||||
current_vars.update(payload)
|
||||
self.assertEqual(200, response.status_code)
|
||||
self.assertEqual(current_vars, response.json()['variables'])
|
||||
self.assertEqual(current_vars, self.get_current_vars(resource_id))
|
||||
|
||||
def assert_vars_can_be_deleted(self, resource_id):
|
||||
"""Asserts that new vars can be added, then deleted"""
|
||||
# track the expected current state of vars for this resource,
|
||||
# verifying expectations
|
||||
current_vars = self.get_current_vars(resource_id)
|
||||
|
||||
url = self.get_vars_url(resource_id)
|
||||
added_vars = {'will-keep': 42, 'will-delete': 47}
|
||||
response = self.put(url, data=added_vars)
|
||||
current_vars.update(added_vars)
|
||||
self.assertEqual(200, response.status_code)
|
||||
self.assertEqual(current_vars, response.json()['variables'])
|
||||
self.assertEqual(current_vars, self.get_current_vars(resource_id))
|
||||
|
||||
response = self.delete(url, body=['will-delete', 'non-existent-key'])
|
||||
del current_vars['will-delete']
|
||||
self.assertEqual(204, response.status_code)
|
||||
self.assertEqual(current_vars, self.get_current_vars(resource_id))
|
@ -1,38 +0,0 @@
|
||||
import fixtures
|
||||
|
||||
from craton.db.sqlalchemy import api as sa_api
|
||||
from craton.db.sqlalchemy import models
|
||||
from craton.tests import TestCase
|
||||
|
||||
|
||||
_DB_SCHEMA = None
|
||||
|
||||
|
||||
class Database(fixtures.Fixture):
|
||||
def __init__(self):
|
||||
self.engine = sa_api.get_engine()
|
||||
self.engine.dispose()
|
||||
conn = self.engine.connect()
|
||||
self.setup_sqlite()
|
||||
self._DB = "".join(line for line in conn.connection.iterdump())
|
||||
self.engine.dispose()
|
||||
|
||||
def setup_sqlite(self):
|
||||
# NOTE(sulo): there is no version here. We will be using
|
||||
# Alembic in the near future to manage migrations.
|
||||
models.Base.metadata.create_all(self.engine)
|
||||
|
||||
def _setUp(self):
|
||||
conn = self.engine.connect()
|
||||
conn.connection.executescript(self._DB)
|
||||
self.addCleanup(self.engine.dispose)
|
||||
|
||||
|
||||
class DBTestCase(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(DBTestCase, self).setUp()
|
||||
global _DB_SCHEMA
|
||||
if not _DB_SCHEMA:
|
||||
_DB_SCHEMA = Database()
|
||||
self.useFixture(_DB_SCHEMA)
|
@ -1,15 +0,0 @@
|
||||
from craton.db.sqlalchemy import api as dbapi
|
||||
from craton.tests.unit.db import base
|
||||
|
||||
|
||||
class TestProjectsGetAll(base.DBTestCase):
|
||||
|
||||
def test_link_params_dictionary(self):
|
||||
_, links = dbapi.projects_get_all(
|
||||
self.context,
|
||||
filters={'name': None, 'id': None,
|
||||
'sort_keys': ['id', 'created_at'], 'sort_dir': 'asc'},
|
||||
pagination_params={'limit': 30, 'marker': None},
|
||||
)
|
||||
self.assertNotIn('name', links)
|
||||
self.assertNotIn('id', links)
|
@ -1,116 +0,0 @@
|
||||
import uuid
|
||||
|
||||
from craton import exceptions
|
||||
from craton.db import api as dbapi
|
||||
from craton.tests.unit.db import base
|
||||
|
||||
project_id1 = uuid.uuid4().hex
|
||||
cloud_id1 = uuid.uuid4().hex
|
||||
|
||||
cell1 = {'region_id': 1, 'project_id': project_id1, 'name': 'cell1',
|
||||
"cloud_id": cloud_id1}
|
||||
cell1_region2 = {'region_id': 2, 'project_id': project_id1, 'name': 'cell1',
|
||||
"cloud_id": cloud_id1}
|
||||
cell2 = {'region_id': 1, 'project_id': project_id1, 'name': 'cell2',
|
||||
"cloud_id": cloud_id1}
|
||||
|
||||
cells = (cell1, cell1_region2, cell2)
|
||||
default_pagination = {'limit': 30, 'marker': None}
|
||||
|
||||
|
||||
class CellsDBTestCase(base.DBTestCase):
|
||||
|
||||
def test_cells_create(self):
|
||||
try:
|
||||
dbapi.cells_create(self.context, cell1)
|
||||
except Exception:
|
||||
self.fail("Cell create raised unexpected exception")
|
||||
|
||||
def test_duplicate_cell_create_raises_409(self):
|
||||
dbapi.cells_create(self.context, cell1)
|
||||
self.assertRaises(exceptions.DuplicateCell, dbapi.cells_create,
|
||||
self.context, cell1)
|
||||
|
||||
def test_cells_get_all(self):
|
||||
dbapi.cells_create(self.context, cell1)
|
||||
filters = {
|
||||
"region_id": cell1["region_id"],
|
||||
}
|
||||
res, _ = dbapi.cells_get_all(self.context, filters, default_pagination)
|
||||
self.assertEqual(len(res), 1)
|
||||
self.assertEqual(res[0]['name'], 'cell1')
|
||||
|
||||
def test_cells_get_all_filter_name(self):
|
||||
for cell in cells:
|
||||
dbapi.cells_create(self.context, cell)
|
||||
setup_res, _ = dbapi.cells_get_all(self.context, {},
|
||||
default_pagination)
|
||||
self.assertGreater(len(setup_res), 2)
|
||||
|
||||
filters = {
|
||||
"name": cell1["name"],
|
||||
}
|
||||
res, _ = dbapi.cells_get_all(self.context, filters, default_pagination)
|
||||
self.assertEqual(len(res), 2)
|
||||
for cell in res:
|
||||
self.assertEqual(cell['name'], 'cell1')
|
||||
|
||||
def test_cells_get_all_filter_id(self):
|
||||
for cell in cells:
|
||||
dbapi.cells_create(self.context, cell)
|
||||
setup_res, _ = dbapi.cells_get_all(self.context, {},
|
||||
default_pagination)
|
||||
self.assertGreater(len(setup_res), 2)
|
||||
self.assertEqual(
|
||||
len([cell for cell in setup_res if cell['id'] == 1]), 1
|
||||
)
|
||||
|
||||
filters = {
|
||||
"id": 1,
|
||||
}
|
||||
res, _ = dbapi.cells_get_all(self.context, filters, default_pagination)
|
||||
self.assertEqual(len(res), 1)
|
||||
self.assertEqual(res[0]['id'], 1)
|
||||
|
||||
def test_cells_get_all_with_filters(self):
|
||||
res = dbapi.cells_create(self.context, cell1)
|
||||
variables = {"key1": "value1", "key2": "value2"}
|
||||
dbapi.variables_update_by_resource_id(
|
||||
self.context, "cells", res.id, variables
|
||||
)
|
||||
filters = {
|
||||
"vars": "key2:value2",
|
||||
"region_id": cell1["region_id"],
|
||||
}
|
||||
res, _ = dbapi.cells_get_all(self.context, filters, default_pagination)
|
||||
self.assertEqual(len(res), 1)
|
||||
self.assertEqual(res[0]['name'], 'cell1')
|
||||
|
||||
def test_cells_get_all_with_filters_noexist(self):
|
||||
res = dbapi.cells_create(self.context, cell1)
|
||||
variables = {"key1": "value1", "key2": "value2"}
|
||||
dbapi.variables_update_by_resource_id(
|
||||
self.context, "cells", res.id, variables
|
||||
)
|
||||
filters = {}
|
||||
filters["vars"] = "key2:value5"
|
||||
res, _ = dbapi.cells_get_all(self.context, filters, default_pagination)
|
||||
self.assertEqual(len(res), 0)
|
||||
|
||||
def test_cell_delete(self):
|
||||
create_res = dbapi.cells_create(self.context, cell1)
|
||||
# First make sure we have the cell
|
||||
res = dbapi.cells_get_by_id(self.context, create_res.id)
|
||||
self.assertEqual(res.name, 'cell1')
|
||||
|
||||
dbapi.cells_delete(self.context, res.id)
|
||||
self.assertRaises(exceptions.NotFound, dbapi.cells_get_by_id,
|
||||
self.context, res.id)
|
||||
|
||||
def test_cell_update(self):
|
||||
create_res = dbapi.cells_create(self.context, cell1)
|
||||
res = dbapi.cells_get_by_id(self.context, create_res.id)
|
||||
self.assertEqual(res.name, 'cell1')
|
||||
new_name = 'cell1_New'
|
||||
res = dbapi.cells_update(self.context, res.id, {'name': 'cell1_New'})
|
||||
self.assertEqual(res.name, new_name)
|
@ -1,89 +0,0 @@
|
||||
import uuid
|
||||
|
||||
from craton.db import api as dbapi
|
||||
from craton.tests.unit.db import base
|
||||
from craton import exceptions
|
||||
|
||||
default_pagination = {'limit': 30, 'marker': None}
|
||||
|
||||
project_id1 = uuid.uuid4().hex
|
||||
cloud1 = {'project_id': project_id1, 'name': 'cloud1'}
|
||||
|
||||
|
||||
class CloudsDBTestCase(base.DBTestCase):
|
||||
|
||||
def test_cloud_create(self):
|
||||
try:
|
||||
dbapi.clouds_create(self.context, cloud1)
|
||||
except Exception:
|
||||
self.fail("Cloud create raised unexpected exception")
|
||||
|
||||
def test_cloud_create_duplicate_name_raises(self):
|
||||
dbapi.clouds_create(self.context, cloud1)
|
||||
self.assertRaises(exceptions.DuplicateCloud, dbapi.clouds_create,
|
||||
self.context, cloud1)
|
||||
|
||||
def test_clouds_get_all(self):
|
||||
dbapi.clouds_create(self.context, cloud1)
|
||||
filters = {}
|
||||
res, _ = dbapi.clouds_get_all(self.context, filters,
|
||||
default_pagination)
|
||||
self.assertEqual(len(res), 1)
|
||||
self.assertEqual(res[0]['name'], 'cloud1')
|
||||
|
||||
def test_clouds_get_all_with_var_filters(self):
|
||||
res = dbapi.clouds_create(self.context, cloud1)
|
||||
variables = {"key1": "value1", "key2": "value2"}
|
||||
dbapi.variables_update_by_resource_id(
|
||||
self.context, "clouds", res.id, variables
|
||||
)
|
||||
filters = {}
|
||||
filters["vars"] = "key1:value1"
|
||||
clouds, _ = dbapi.clouds_get_all(
|
||||
self.context, filters, default_pagination,
|
||||
)
|
||||
self.assertEqual(len(clouds), 1)
|
||||
self.assertEqual(clouds[0].name, cloud1['name'])
|
||||
|
||||
def test_clouds_get_all_with_var_filters_noexist(self):
|
||||
res = dbapi.clouds_create(self.context, cloud1)
|
||||
variables = {"key1": "value1", "key2": "value2"}
|
||||
dbapi.variables_update_by_resource_id(
|
||||
self.context, "clouds", res.id, variables
|
||||
)
|
||||
filters = {}
|
||||
filters["vars"] = "key1:value12"
|
||||
clouds, _ = dbapi.clouds_get_all(
|
||||
self.context, filters, default_pagination,
|
||||
)
|
||||
self.assertEqual(len(clouds), 0)
|
||||
|
||||
def test_cloud_get_by_name(self):
|
||||
dbapi.clouds_create(self.context, cloud1)
|
||||
res = dbapi.clouds_get_by_name(self.context, cloud1['name'])
|
||||
self.assertEqual(res.name, 'cloud1')
|
||||
|
||||
def test_cloud_get_by_id(self):
|
||||
dbapi.clouds_create(self.context, cloud1)
|
||||
res = dbapi.clouds_get_by_id(self.context, 1)
|
||||
self.assertEqual(res.name, 'cloud1')
|
||||
|
||||
def test_cloud_update(self):
|
||||
dbapi.clouds_create(self.context, cloud1)
|
||||
res = dbapi.clouds_get_by_id(self.context, 1)
|
||||
self.assertEqual(res.name, 'cloud1')
|
||||
new_name = "cloud_New1"
|
||||
res = dbapi.clouds_update(self.context, res.id,
|
||||
{'name': 'cloud_New1'})
|
||||
self.assertEqual(res.name, new_name)
|
||||
|
||||
def test_cloud_delete(self):
|
||||
dbapi.clouds_create(self.context, cloud1)
|
||||
# First make sure we have the cloud
|
||||
res = dbapi.clouds_get_by_name(self.context, cloud1['name'])
|
||||
self.assertEqual(res.name, 'cloud1')
|
||||
|
||||
dbapi.clouds_delete(self.context, res.id)
|
||||
self.assertRaises(exceptions.NotFound,
|
||||
dbapi.clouds_get_by_name,
|
||||
self.context, 'fake-cloud')
|
@ -1,704 +0,0 @@
|
||||
import uuid
|
||||
|
||||
from netaddr import IPAddress
|
||||
|
||||
from craton import exceptions
|
||||
from craton.db import api as dbapi
|
||||
from craton.tests.unit.db import base
|
||||
|
||||
default_pagination = {'limit': 30, 'marker': None}
|
||||
|
||||
|
||||
class BaseDevicesDBTestCase(base.DBTestCase):
|
||||
|
||||
mock_project_id = uuid.uuid4().hex
|
||||
|
||||
def make_project(self, name, **variables):
|
||||
project = dbapi.projects_create(
|
||||
self.context,
|
||||
{"name": name,
|
||||
"variables": variables})
|
||||
return str(project.id)
|
||||
|
||||
def make_cloud(self, project_id, name, **variables):
|
||||
cloud = dbapi.clouds_create(
|
||||
self.context,
|
||||
{'name': name,
|
||||
'project_id': project_id,
|
||||
'variables': variables})
|
||||
return cloud.id
|
||||
|
||||
def make_region(self, project_id, cloud_id, name, **variables):
|
||||
region = dbapi.regions_create(
|
||||
self.context,
|
||||
{'name': name,
|
||||
'project_id': project_id,
|
||||
'cloud_id': cloud_id,
|
||||
'variables': variables})
|
||||
return region.id
|
||||
|
||||
def make_cell(self, project_id, cloud_id, region_id, name, **variables):
|
||||
cell = dbapi.cells_create(
|
||||
self.context,
|
||||
{'name': name,
|
||||
'project_id': project_id,
|
||||
'cloud_id': cloud_id,
|
||||
'region_id': region_id,
|
||||
'variables': variables})
|
||||
return cell.id
|
||||
|
||||
def make_host(self, project_id, cloud_id, region_id, name, ip_address,
|
||||
host_type, cell_id=None, parent_id=None, labels=None,
|
||||
**variables):
|
||||
if cell_id:
|
||||
host = {'name': name,
|
||||
'project_id': project_id,
|
||||
'cloud_id': cloud_id,
|
||||
'region_id': region_id,
|
||||
'cell_id': cell_id,
|
||||
'ip_address': ip_address,
|
||||
'parent_id': parent_id,
|
||||
'device_type': host_type,
|
||||
'active': True,
|
||||
'labels': set() if labels is None else labels,
|
||||
'variables': variables}
|
||||
else:
|
||||
host = {'name': name,
|
||||
'project_id': project_id,
|
||||
'cloud_id': cloud_id,
|
||||
'region_id': region_id,
|
||||
'ip_address': ip_address,
|
||||
'parent_id': parent_id,
|
||||
'device_type': host_type,
|
||||
'active': True,
|
||||
'labels': set() if labels is None else labels,
|
||||
'variables': variables}
|
||||
|
||||
host = dbapi.hosts_create(self.context, host)
|
||||
return host.id
|
||||
|
||||
def make_network_device(
|
||||
self, project_id, cloud_id, region_id, name, ip_address,
|
||||
device_type, cell_id=None, parent_id=None, **variables
|
||||
):
|
||||
network_device_data = {
|
||||
'name': name,
|
||||
'project_id': project_id,
|
||||
'cloud_id': cloud_id,
|
||||
'region_id': region_id,
|
||||
'cell_id': cell_id,
|
||||
'ip_address': ip_address,
|
||||
'parent_id': parent_id,
|
||||
'device_type': device_type,
|
||||
'active': True,
|
||||
'variables': variables,
|
||||
}
|
||||
|
||||
network_device = dbapi.network_devices_create(
|
||||
self.context, network_device_data
|
||||
)
|
||||
return network_device.id
|
||||
|
||||
|
||||
class DevicesDBTestCase(BaseDevicesDBTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
project_id = self.make_project('project_1')
|
||||
cloud_id = self.make_cloud(project_id, 'cloud_1')
|
||||
region_id = self.make_region(project_id, cloud_id, 'region_1')
|
||||
net_device1_id = self.make_network_device(
|
||||
project_id, cloud_id, region_id, 'switch1.example.com',
|
||||
IPAddress('10.1.2.101'), 'switch'
|
||||
)
|
||||
net_device2_id = self.make_network_device(
|
||||
project_id, cloud_id, region_id, 'switch2.example.com',
|
||||
IPAddress('10.1.2.102'), 'switch', parent_id=net_device1_id
|
||||
)
|
||||
host1_id = self.make_host(
|
||||
project_id, cloud_id, region_id, 'www1.example.com',
|
||||
IPAddress(u'10.1.2.103'), 'server', parent_id=net_device2_id
|
||||
)
|
||||
host2_id = self.make_host(
|
||||
project_id, cloud_id, region_id, 'www2.example.com',
|
||||
IPAddress(u'10.1.2.104'), 'container', parent_id=host1_id
|
||||
)
|
||||
host3_id = self.make_host(
|
||||
project_id, cloud_id, region_id, 'www3.example.com',
|
||||
IPAddress(u'10.1.2.105'), 'server'
|
||||
)
|
||||
|
||||
self.parent = net_device1_id
|
||||
self.children = [net_device2_id]
|
||||
self.descendants = [net_device2_id, host1_id, host2_id]
|
||||
self.all = [
|
||||
net_device1_id, net_device2_id, host1_id, host2_id, host3_id
|
||||
]
|
||||
|
||||
def test_devices_get_all(self):
|
||||
devices, _ = dbapi.devices_get_all(
|
||||
self.context, {}, default_pagination
|
||||
)
|
||||
|
||||
self.assertEqual(self.all, [device.id for device in devices])
|
||||
|
||||
def test_devices_get_all_children(self):
|
||||
devices, _ = dbapi.devices_get_all(
|
||||
self.context, {'parent_id': self.parent}, default_pagination
|
||||
)
|
||||
|
||||
self.assertEqual(self.children, [device.id for device in devices])
|
||||
|
||||
def test_devices_get_all_descendants(self):
|
||||
devices, _ = dbapi.devices_get_all(
|
||||
self.context,
|
||||
{'parent_id': self.parent, 'descendants': True},
|
||||
default_pagination
|
||||
)
|
||||
|
||||
self.assertEqual(self.descendants, [device.id for device in devices])
|
||||
|
||||
|
||||
class HostsDBTestCase(BaseDevicesDBTestCase):
|
||||
|
||||
def make_very_small_cloud(self, with_cell=False):
|
||||
project_id = self.make_project('project_1', foo='P1', zoo='P2',
|
||||
boo='P3')
|
||||
cloud_id = self.make_cloud(project_id, 'cloud_1', zoo='CL1')
|
||||
region_id = self.make_region(
|
||||
project_id,
|
||||
cloud_id,
|
||||
'region_1',
|
||||
foo='R1', bar='R2', bax='R3')
|
||||
if with_cell:
|
||||
cell_id = self.make_cell(project_id, cloud_id, region_id, 'cell_1',
|
||||
bar='C2')
|
||||
else:
|
||||
cell_id = None
|
||||
host_id = self.make_host(project_id, cloud_id, region_id,
|
||||
'www1.example.com',
|
||||
IPAddress(u'10.1.2.101'), 'server',
|
||||
cell_id=cell_id, foo='H1', baz='H3')
|
||||
return project_id, cloud_id, region_id, cell_id, host_id
|
||||
|
||||
def test_hosts_create(self):
|
||||
# Need to do this query despite creation above because other
|
||||
# elements (cell, region) were in separate committed sessions
|
||||
# when the host was created. Verify these linked elements load
|
||||
# correctly
|
||||
project_id, cloud_id, region_id, cell_id, host_id = \
|
||||
self.make_very_small_cloud(with_cell=True)
|
||||
host = dbapi.hosts_get_by_id(self.context, host_id)
|
||||
self.assertEqual(host.region.id, region_id)
|
||||
self.assertEqual(host.region.name, 'region_1')
|
||||
self.assertEqual(host.cell.id, cell_id)
|
||||
self.assertEqual(host.cell.name, 'cell_1')
|
||||
|
||||
# Verify resolved variables/blames override properly
|
||||
self.assertEqual(
|
||||
[obj.id for obj in host.resolution_order],
|
||||
[host_id, cell_id, region_id, cloud_id, uuid.UUID(project_id)])
|
||||
|
||||
self.assertEqual(
|
||||
[variables for variables in host.resolution_order_variables],
|
||||
[{'foo': 'H1', 'baz': 'H3'},
|
||||
{'bar': 'C2'},
|
||||
{'foo': 'R1', 'bar': 'R2', 'bax': 'R3'},
|
||||
{'zoo': 'CL1'},
|
||||
{'foo': 'P1', 'zoo': 'P2', 'boo': 'P3'}])
|
||||
|
||||
self.assertEqual(
|
||||
host.resolved,
|
||||
{'foo': 'H1', 'bar': 'C2', 'baz': 'H3', 'bax': 'R3', 'zoo': 'CL1',
|
||||
'boo': 'P3'})
|
||||
|
||||
blame = host.blame(['foo', 'bar', 'zoo', 'boo'])
|
||||
self.assertEqual(blame['foo'].source.name, 'www1.example.com')
|
||||
self.assertEqual(blame['foo'].variable.value, 'H1')
|
||||
self.assertEqual(blame['bar'].source.name, 'cell_1')
|
||||
self.assertEqual(blame['bar'].variable.value, 'C2')
|
||||
self.assertEqual(blame['zoo'].source.name, 'cloud_1')
|
||||
self.assertEqual(blame['zoo'].variable.value, 'CL1')
|
||||
self.assertEqual(blame['boo'].source.name, 'project_1')
|
||||
self.assertEqual(blame['boo'].variable.value, 'P3')
|
||||
|
||||
def test_hosts_create_duplicate_raises(self):
|
||||
cloud_id = self.make_cloud(self.mock_project_id, 'cloud_1')
|
||||
region_id = self.make_region(self.mock_project_id, cloud_id,
|
||||
'region_1')
|
||||
self.make_host(self.mock_project_id, cloud_id, region_id,
|
||||
'www1.example.com',
|
||||
IPAddress(u'10.1.2.101'), 'server')
|
||||
new_host = {'name': 'www1.example.com', 'region_id': region_id,
|
||||
'ip_address': IPAddress(u'10.1.2.101'),
|
||||
'device_type': 'server',
|
||||
'cloud_id': cloud_id, 'project_id': self.mock_project_id}
|
||||
self.assertRaises(exceptions.DuplicateDevice, dbapi.hosts_create,
|
||||
self.context, new_host)
|
||||
|
||||
def test_hosts_create_without_cell(self):
|
||||
project_id, cloud_id, region_id, _, host_id = \
|
||||
self.make_very_small_cloud()
|
||||
host = dbapi.hosts_get_by_id(self.context, host_id)
|
||||
self.assertEqual(host.cloud_id, cloud_id)
|
||||
self.assertEqual(host.region.id, region_id)
|
||||
self.assertEqual(host.region.name, 'region_1')
|
||||
self.assertIsNone(host.cell)
|
||||
|
||||
# Verify resolved variables/blames override properly
|
||||
self.assertEqual(
|
||||
[obj.id for obj in host.resolution_order],
|
||||
[host_id, region_id, cloud_id, uuid.UUID(project_id)])
|
||||
|
||||
self.assertEqual(
|
||||
[variables for variables in host.resolution_order_variables],
|
||||
[{'foo': 'H1', 'baz': 'H3'},
|
||||
{'foo': 'R1', 'bar': 'R2', 'bax': 'R3'},
|
||||
{'zoo': 'CL1'},
|
||||
{'foo': 'P1', 'zoo': 'P2', 'boo': 'P3'}])
|
||||
|
||||
self.assertEqual(
|
||||
host.resolved,
|
||||
{'foo': 'H1', 'bar': 'R2', 'baz': 'H3', 'bax': 'R3', 'zoo': 'CL1',
|
||||
'boo': 'P3'})
|
||||
|
||||
blame = host.blame(['foo', 'bar', 'zoo', 'boo'])
|
||||
self.assertEqual(blame['foo'].source.name, 'www1.example.com')
|
||||
self.assertEqual(blame['foo'].variable.value, 'H1')
|
||||
self.assertEqual(blame['bar'].source.name, 'region_1')
|
||||
self.assertEqual(blame['bar'].variable.value, 'R2')
|
||||
self.assertEqual(blame['zoo'].source.name, 'cloud_1')
|
||||
self.assertEqual(blame['zoo'].variable.value, 'CL1')
|
||||
self.assertEqual(blame['boo'].source.name, 'project_1')
|
||||
self.assertEqual(blame['boo'].variable.value, 'P3')
|
||||
|
||||
def test_hosts_update(self):
|
||||
cloud_id = self.make_cloud(self.mock_project_id, 'cloud_1')
|
||||
region_id = self.make_region(self.mock_project_id, cloud_id,
|
||||
'region_1')
|
||||
host_id = self.make_host(self.mock_project_id, cloud_id, region_id,
|
||||
'example',
|
||||
IPAddress(u'10.1.2.101'), 'server',
|
||||
bar='bar2')
|
||||
name = "Host_New"
|
||||
res = dbapi.hosts_update(self.context, host_id, {'name': 'Host_New'})
|
||||
self.assertEqual(res.name, name)
|
||||
|
||||
def test_hosts_variable_resolved_with_parent(self):
|
||||
project_id = self.make_project(
|
||||
'project_1',
|
||||
foo='P1', zoo='P2', boo='P3')
|
||||
cloud_id = self.make_cloud(
|
||||
project_id,
|
||||
'cloud_1',
|
||||
zoo='CL1', zab='CL2')
|
||||
region_id = self.make_region(
|
||||
project_id,
|
||||
cloud_id,
|
||||
'region_1',
|
||||
foo='R1', bar='R2', bax='R3')
|
||||
cell_id = self.make_cell(project_id, cloud_id, region_id, 'cell_1',
|
||||
bar='C2')
|
||||
host1_id = self.make_host(project_id, cloud_id, region_id,
|
||||
'www1.example.com',
|
||||
IPAddress(u'10.1.2.101'), 'server',
|
||||
cell_id=cell_id, foo='H1', baz='H3')
|
||||
host2_id = self.make_host(project_id, cloud_id, region_id,
|
||||
'www1.example2.com',
|
||||
IPAddress(u'10.1.2.102'), 'server',
|
||||
cell_id=cell_id, parent_id=host1_id)
|
||||
host2 = dbapi.hosts_get_by_id(self.context, host2_id)
|
||||
|
||||
# Verify resolved variables/blames override properly
|
||||
self.assertEqual(
|
||||
[obj.id for obj in host2.resolution_order],
|
||||
[host2_id, host1_id, cell_id, region_id, cloud_id,
|
||||
uuid.UUID(project_id)])
|
||||
|
||||
self.assertEqual(
|
||||
[variables for variables in host2.resolution_order_variables],
|
||||
[{},
|
||||
{'baz': 'H3', 'foo': 'H1'},
|
||||
{'bar': 'C2'},
|
||||
{'bar': 'R2', 'foo': 'R1', 'bax': 'R3'},
|
||||
{'zoo': 'CL1', 'zab': 'CL2'},
|
||||
{'foo': 'P1', 'zoo': 'P2', 'boo': 'P3'}])
|
||||
|
||||
self.assertEqual(
|
||||
host2.resolved,
|
||||
{'foo': 'H1', 'bar': 'C2', 'baz': 'H3', 'bax': 'R3', 'zoo': 'CL1',
|
||||
'boo': 'P3', 'zab': 'CL2'})
|
||||
|
||||
blame = host2.blame(['foo', 'bar', 'zoo', 'boo', 'zab'])
|
||||
self.assertEqual(blame['foo'].source.name, 'www1.example.com')
|
||||
self.assertEqual(blame['foo'].variable.value, 'H1')
|
||||
self.assertEqual(blame['bar'].source.name, 'cell_1')
|
||||
self.assertEqual(blame['bar'].variable.value, 'C2')
|
||||
self.assertEqual(blame['zoo'].source.name, 'cloud_1')
|
||||
self.assertEqual(blame['zoo'].variable.value, 'CL1')
|
||||
self.assertEqual(blame['zab'].source.name, 'cloud_1')
|
||||
self.assertEqual(blame['zab'].variable.value, 'CL2')
|
||||
self.assertEqual(blame['boo'].source.name, 'project_1')
|
||||
self.assertEqual(blame['boo'].variable.value, 'P3')
|
||||
|
||||
def test_hosts_variables_no_resolved(self):
|
||||
project_id = self.make_project('project_1', zoo='P2')
|
||||
cloud_id = self.make_cloud(project_id, 'cloud_1')
|
||||
region_id = self.make_region(project_id, cloud_id, 'region_1',
|
||||
foo='R1')
|
||||
host_id = self.make_host(project_id, cloud_id, region_id,
|
||||
'www.example.xyz',
|
||||
IPAddress(u'10.1.2.101'),
|
||||
'server', bar='bar2')
|
||||
host = dbapi.hosts_get_by_id(self.context, host_id)
|
||||
self.assertEqual(host.name, 'www.example.xyz')
|
||||
self.assertEqual(host.variables, {'bar': 'bar2'})
|
||||
|
||||
def test_hosts_resolved_vars_no_cells(self):
|
||||
project_id = self.make_project('project_1')
|
||||
cloud_id = self.make_cloud(project_id, 'cloud_1')
|
||||
region_id = self.make_region(project_id, cloud_id, 'region_1',
|
||||
foo='R1')
|
||||
host_id = self.make_host(project_id, cloud_id, region_id,
|
||||
'www.example.xyz',
|
||||
IPAddress(u'10.1.2.101'),
|
||||
'server', bar='bar2')
|
||||
host = dbapi.hosts_get_by_id(self.context, host_id)
|
||||
self.assertEqual(host.name, 'www.example.xyz')
|
||||
self.assertEqual(host.resolved, {'bar': 'bar2', 'foo': 'R1'})
|
||||
|
||||
def test_host_labels_create(self):
|
||||
cloud_id = self.make_cloud(self.mock_project_id, 'cloud_1')
|
||||
region_id = self.make_region(self.mock_project_id, cloud_id,
|
||||
'region_1',
|
||||
foo='R1')
|
||||
host_id = self.make_host(self.mock_project_id, cloud_id, region_id,
|
||||
'www.example.xyz',
|
||||
IPAddress(u'10.1.2.101'),
|
||||
'server', bar='bar2')
|
||||
labels = {"labels": ["tom", "jerry"]}
|
||||
dbapi.hosts_labels_update(self.context, host_id, labels)
|
||||
|
||||
def test_host_labels_delete(self):
|
||||
cloud_id = self.make_cloud(self.mock_project_id, 'cloud_1')
|
||||
region_id = self.make_region(self.mock_project_id, cloud_id,
|
||||
'region_1',
|
||||
foo='R1')
|
||||
host_id = self.make_host(self.mock_project_id, cloud_id, region_id,
|
||||
'www.example.xyz',
|
||||
IPAddress(u'10.1.2.101'),
|
||||
'server', bar='bar2')
|
||||
_labels = {"labels": ["tom", "jerry", "jones"]}
|
||||
dbapi.hosts_labels_update(self.context, host_id, _labels)
|
||||
host = dbapi.hosts_get_by_id(self.context, host_id)
|
||||
self.assertEqual(sorted(host.labels), sorted(_labels["labels"]))
|
||||
_dlabels = {"labels": ["tom"]}
|
||||
dbapi.hosts_labels_delete(self.context, host_id, _dlabels)
|
||||
host = dbapi.hosts_get_by_id(self.context, host_id)
|
||||
self.assertEqual(host.labels, {"jerry", "jones"})
|
||||
|
||||
def test_hosts_get_all_with_label_filters(self):
|
||||
cloud_id = self.make_cloud(self.mock_project_id, 'cloud_1')
|
||||
region_id = self.make_region(self.mock_project_id, cloud_id,
|
||||
'region_1')
|
||||
labels = {"labels": ["compute"]}
|
||||
host1 = self.make_host(
|
||||
self.mock_project_id,
|
||||
cloud_id,
|
||||
region_id,
|
||||
'www1.example.com',
|
||||
IPAddress(u'10.1.2.101'),
|
||||
'server',
|
||||
)
|
||||
dbapi.hosts_labels_update(self.context, host1, labels)
|
||||
|
||||
self.make_host(
|
||||
self.mock_project_id,
|
||||
cloud_id,
|
||||
region_id,
|
||||
'www1.example2.com',
|
||||
IPAddress(u'10.1.2.102'),
|
||||
'server',
|
||||
)
|
||||
res, _ = dbapi.hosts_get_all(self.context, {"label": "compute"},
|
||||
default_pagination)
|
||||
|
||||
self.assertEqual(len(res), 1)
|
||||
self.assertEqual(res[0].name, 'www1.example.com')
|
||||
|
||||
def test_hosts_get_all_with_filter_cell_id(self):
|
||||
project_id = self.make_project('project_1', foo='P1', zoo='P2')
|
||||
cloud_id = self.make_cloud(project_id, 'cloud_1')
|
||||
region_id = self.make_region(project_id, cloud_id, 'region_1',
|
||||
foo='R1')
|
||||
cell_id1 = self.make_cell(project_id, cloud_id, region_id, 'cell_1',
|
||||
bar='C2')
|
||||
cell_id2 = self.make_cell(project_id, cloud_id, region_id, 'cell_2',
|
||||
bar='C2')
|
||||
self.assertNotEqual(cell_id1, cell_id2)
|
||||
|
||||
self.make_host(
|
||||
project_id,
|
||||
cloud_id,
|
||||
region_id,
|
||||
'www.example.xyz',
|
||||
IPAddress(u'10.1.2.101'),
|
||||
'server',
|
||||
cell_id=cell_id1,
|
||||
)
|
||||
self.make_host(
|
||||
project_id,
|
||||
cloud_id,
|
||||
region_id,
|
||||
'www.example.abc',
|
||||
IPAddress(u'10.1.2.102'),
|
||||
'server',
|
||||
cell_id=cell_id2,
|
||||
)
|
||||
|
||||
all_res, _ = dbapi.hosts_get_all(self.context, {}, default_pagination)
|
||||
self.assertEqual(len(all_res), 2)
|
||||
self.assertEqual(
|
||||
len([host for host in all_res if host['cell_id'] == cell_id1]), 1
|
||||
)
|
||||
|
||||
filters = {
|
||||
"cell_id": cell_id1,
|
||||
}
|
||||
res, _ = dbapi.hosts_get_all(self.context, filters,
|
||||
default_pagination)
|
||||
self.assertEqual(len(res), 1)
|
||||
self.assertEqual(res[0].name, 'www.example.xyz')
|
||||
|
||||
def test_hosts_get_all_with_filters(self):
|
||||
project_id = self.make_project('project_1', foo='P1', zoo='P2')
|
||||
cloud_id = self.make_cloud(project_id, 'cloud_1')
|
||||
region_id = self.make_region(project_id, cloud_id, 'region_1',
|
||||
foo='R1')
|
||||
host_id = self.make_host(project_id, cloud_id, region_id,
|
||||
'www.example.xyz',
|
||||
IPAddress(u'10.1.2.101'),
|
||||
'server')
|
||||
variables = {"key1": "value1", "key2": "value2"}
|
||||
dbapi.variables_update_by_resource_id(
|
||||
self.context, "hosts", host_id, variables
|
||||
)
|
||||
filters = {
|
||||
"region_id": region_id,
|
||||
"vars": "key2:value2",
|
||||
}
|
||||
res, _ = dbapi.hosts_get_all(self.context, filters,
|
||||
default_pagination)
|
||||
self.assertEqual(len(res), 1)
|
||||
self.assertEqual(res[0].name, 'www.example.xyz')
|
||||
|
||||
def test_hosts_get_with_key_value_filters(self):
|
||||
project_id = self.make_project('project_1', foo='P1', zoo='P2')
|
||||
cloud_id = self.make_cloud(project_id, 'cloud_1')
|
||||
region_id = self.make_region(project_id, cloud_id, 'region_1',
|
||||
foo='R1')
|
||||
host1 = self.make_host(project_id, cloud_id, region_id,
|
||||
'www.example.xyz',
|
||||
IPAddress(u'10.1.2.101'),
|
||||
'server')
|
||||
variables = {"key1": "example1", "key2": "Tom"}
|
||||
dbapi.variables_update_by_resource_id(
|
||||
self.context, "hosts", host1, variables
|
||||
)
|
||||
# Second host with own variables
|
||||
host2 = self.make_host(project_id, cloud_id, region_id,
|
||||
'www.example2.xyz',
|
||||
IPAddress(u'10.1.2.102'),
|
||||
'server')
|
||||
variables = {"key1": "example2", "key2": "Tom"}
|
||||
dbapi.variables_update_by_resource_id(
|
||||
self.context, "hosts", host2, variables
|
||||
)
|
||||
filters = {"vars": "key1:example2"}
|
||||
|
||||
res, _ = dbapi.hosts_get_all(self.context, filters, default_pagination)
|
||||
self.assertEqual(len(res), 1)
|
||||
self.assertEqual('www.example2.xyz', res[0].name)
|
||||
|
||||
filters = {"vars": "key2:Tom"}
|
||||
res, _ = dbapi.hosts_get_all(self.context, filters, default_pagination)
|
||||
self.assertEqual(len(res), 2)
|
||||
|
||||
def test_hosts_get_all_with_filters_noexist(self):
|
||||
project_id = self.make_project('project_1', foo='P1', zoo='P2')
|
||||
cloud_id = self.make_cloud(project_id, 'cloud_1')
|
||||
region_id = self.make_region(project_id, cloud_id, 'region_1',
|
||||
foo='R1')
|
||||
host_id = self.make_host(project_id, cloud_id, region_id,
|
||||
'www.example.xyz',
|
||||
IPAddress(u'10.1.2.101'),
|
||||
'server')
|
||||
variables = {"key1": "value1", "key2": "value2"}
|
||||
dbapi.variables_update_by_resource_id(
|
||||
self.context, "hosts", host_id, variables
|
||||
)
|
||||
filters = {
|
||||
"region_id": 1,
|
||||
"vars": "key1:value5",
|
||||
}
|
||||
res, _ = dbapi.hosts_get_all(self.context, filters,
|
||||
default_pagination)
|
||||
self.assertEqual(len(res), 0)
|
||||
|
||||
def test_hosts_create_sets_parent_id(self):
|
||||
project_id = self.make_project('project_1')
|
||||
cloud_id = self.make_cloud(project_id, 'cloud_1')
|
||||
region_id = self.make_region(project_id, cloud_id, 'region_1')
|
||||
parent_id = self.make_host(
|
||||
project_id, cloud_id, region_id, '1.www.example.com',
|
||||
IPAddress(u'10.1.2.101'), 'server'
|
||||
)
|
||||
child = dbapi.hosts_create(
|
||||
self.context,
|
||||
{
|
||||
'project_id': project_id,
|
||||
'cloud_id': cloud_id,
|
||||
'region_id': region_id,
|
||||
'hostname': '2.www.example.com',
|
||||
'ip_address': IPAddress(u'10.1.2.102'),
|
||||
'device_type': 'server',
|
||||
'parent_id': parent_id,
|
||||
}
|
||||
)
|
||||
self.assertEqual(parent_id, child.parent_id)
|
||||
|
||||
def test_hosts_update_sets_parent_id(self):
|
||||
project_id = self.make_project('project_1')
|
||||
cloud_id = self.make_cloud(project_id, 'cloud_1')
|
||||
region_id = self.make_region(project_id, cloud_id, 'region_1')
|
||||
parent_id = self.make_host(
|
||||
project_id, cloud_id, region_id, '1.www.example.com',
|
||||
IPAddress(u'10.1.2.101'), 'server'
|
||||
)
|
||||
child = dbapi.hosts_create(
|
||||
self.context,
|
||||
{
|
||||
'project_id': project_id,
|
||||
'cloud_id': cloud_id,
|
||||
'region_id': region_id,
|
||||
'hostname': '2.www.example.com',
|
||||
'ip_address': IPAddress(u'10.1.2.102'),
|
||||
'device_type': 'server',
|
||||
'parent_id': None,
|
||||
}
|
||||
)
|
||||
self.assertIsNone(child.parent_id)
|
||||
child_update = dbapi.hosts_update(
|
||||
self.context,
|
||||
child.id,
|
||||
{
|
||||
'parent_id': parent_id,
|
||||
}
|
||||
)
|
||||
self.assertEqual(parent_id, child_update.parent_id)
|
||||
|
||||
def test_hosts_update_fails_when_parent_id_set_to_own_id(self):
|
||||
project_id = self.make_project('project_1')
|
||||
cloud_id = self.make_cloud(project_id, 'cloud_1')
|
||||
region_id = self.make_region(project_id, cloud_id, 'region_1')
|
||||
host1 = dbapi.hosts_create(
|
||||
self.context,
|
||||
{
|
||||
'project_id': project_id,
|
||||
'cloud_id': cloud_id,
|
||||
'region_id': region_id,
|
||||
'hostname': '1.www.example.com',
|
||||
'ip_address': IPAddress(u'10.1.2.101'),
|
||||
'device_type': 'server',
|
||||
'parent_id': None,
|
||||
}
|
||||
)
|
||||
self.assertRaises(
|
||||
exceptions.BadRequest,
|
||||
dbapi.hosts_update,
|
||||
self.context,
|
||||
host1.id,
|
||||
{
|
||||
'parent_id': host1.id,
|
||||
}
|
||||
)
|
||||
|
||||
def test_hosts_update_fails_when_parent_set_to_descendant(self):
|
||||
project_id = self.make_project('project_1')
|
||||
cloud_id = self.make_cloud(project_id, 'cloud_1')
|
||||
region_id = self.make_region(project_id, cloud_id, 'region_1')
|
||||
parent = dbapi.hosts_create(
|
||||
self.context,
|
||||
{
|
||||
'project_id': project_id,
|
||||
'cloud_id': cloud_id,
|
||||
'region_id': region_id,
|
||||
'hostname': '1.www.example.com',
|
||||
'ip_address': IPAddress(u'10.1.2.101'),
|
||||
'device_type': 'server',
|
||||
'parent_id': None,
|
||||
}
|
||||
)
|
||||
child = dbapi.hosts_create(
|
||||
self.context,
|
||||
{
|
||||
'project_id': project_id,
|
||||
'cloud_id': cloud_id,
|
||||
'region_id': region_id,
|
||||
'hostname': '2.www.example.com',
|
||||
'ip_address': IPAddress(u'10.1.2.102'),
|
||||
'device_type': 'server',
|
||||
'parent_id': parent.id,
|
||||
}
|
||||
)
|
||||
grandchild = dbapi.hosts_create(
|
||||
self.context,
|
||||
{
|
||||
'project_id': project_id,
|
||||
'cloud_id': cloud_id,
|
||||
'region_id': region_id,
|
||||
'hostname': '3.www.example.com',
|
||||
'ip_address': IPAddress(u'10.1.2.103'),
|
||||
'device_type': 'server',
|
||||
'parent_id': child.id,
|
||||
}
|
||||
)
|
||||
self.assertRaises(
|
||||
exceptions.BadRequest,
|
||||
dbapi.hosts_update,
|
||||
self.context,
|
||||
parent.id,
|
||||
{
|
||||
'parent_id': grandchild.id,
|
||||
}
|
||||
)
|
||||
|
||||
def test_hosts_get_all_with_resolved_var_filters(self):
|
||||
project_id = self.make_project('project_1', foo='P1', zoo='P2')
|
||||
cloud_id = self.make_cloud(project_id, 'cloud_1')
|
||||
region_id = self.make_region(
|
||||
project_id, cloud_id, 'region_1', foo='R1')
|
||||
switch_id = self.make_network_device(
|
||||
project_id, cloud_id, region_id,
|
||||
'switch1.example.com', IPAddress('10.1.2.101'), 'switch',
|
||||
zoo='S1', bar='S2')
|
||||
self.make_host(
|
||||
project_id, cloud_id, region_id,
|
||||
'www.example.xyz', IPAddress(u'10.1.2.101'), 'server',
|
||||
parent_id=switch_id,
|
||||
key1="value1", key2="value2")
|
||||
self.make_host(
|
||||
project_id, cloud_id, region_id,
|
||||
'www2.example.xyz', IPAddress(u'10.1.2.102'), 'server',
|
||||
parent_id=switch_id,
|
||||
key1="value-will-not-match", key2="value2")
|
||||
|
||||
filters = {
|
||||
"region_id": 1,
|
||||
"vars": "key1:value1,zoo:S1,foo:R1",
|
||||
"resolved-values": True,
|
||||
}
|
||||
res, _ = dbapi.hosts_get_all(
|
||||
self.context, filters, default_pagination)
|
||||
self.assertEqual(len(res), 1)
|
||||
self.assertEqual(res[0].name, 'www.example.xyz')
|
@ -1,522 +0,0 @@
|
||||
import uuid
|
||||
|
||||
from craton import exceptions
|
||||
from craton.db import api as dbapi
|
||||
from craton.tests.unit.db import base
|
||||
|
||||
|
||||
default_pagination = {'limit': 30, 'marker': None}
|
||||
|
||||
project_id1 = uuid.uuid4().hex
|
||||
cloud_id1 = uuid.uuid4().hex
|
||||
network1 = {"name": "test network",
|
||||
"cidr": "192.168.1.0/24",
|
||||
"gateway": "192.168.1.1",
|
||||
"netmask": "255.255.255.0",
|
||||
"region_id": 1,
|
||||
"project_id": project_id1,
|
||||
"cloud_id": cloud_id1}
|
||||
|
||||
network2 = {"name": "test network2",
|
||||
"cidr": "192.168.1.0/24",
|
||||
"gateway": "192.168.1.1",
|
||||
"netmask": "255.255.255.0",
|
||||
"region_id": 2,
|
||||
"project_id": project_id1,
|
||||
"cloud_id": cloud_id1}
|
||||
|
||||
device1 = {"hostname": "switch1",
|
||||
"model_name": "Model-X",
|
||||
"region_id": 1,
|
||||
"project_id": project_id1,
|
||||
"device_type": "switch",
|
||||
"ip_address": "192.168.1.1",
|
||||
"cloud_id": cloud_id1}
|
||||
|
||||
device2 = {"hostname": "switch2",
|
||||
"model_name": "Model-X",
|
||||
"region_id": 2,
|
||||
"project_id": project_id1,
|
||||
"device_type": "switch",
|
||||
"ip_address": "192.168.1.1",
|
||||
"cloud_id": cloud_id1}
|
||||
|
||||
device3 = {"hostname": "foo1",
|
||||
"model_name": "Model-Bar",
|
||||
"region_id": 1,
|
||||
"project_id": project_id1,
|
||||
"device_type": "foo",
|
||||
"ip_address": "192.168.1.2",
|
||||
"cloud_id": cloud_id1}
|
||||
|
||||
network_interface1 = {"device_id": 1,
|
||||
"project_id": project_id1,
|
||||
"name": "eth1",
|
||||
"ip_address": "192.168.0.2",
|
||||
"interface_type": "ethernet"}
|
||||
|
||||
network_interface2 = {"device_id": 2,
|
||||
"project_id": project_id1,
|
||||
"name": "eth1",
|
||||
"ip_address": "192.168.0.3",
|
||||
"interface_type": "ethernet"}
|
||||
|
||||
|
||||
class NetworksDBTestCase(base.DBTestCase):
|
||||
|
||||
def test_networks_create(self):
|
||||
try:
|
||||
dbapi.networks_create(self.context, network1)
|
||||
except Exception:
|
||||
self.fail("Networks create raised unexpected exception")
|
||||
|
||||
def test_network_create_duplicate_name_raises(self):
|
||||
dbapi.networks_create(self.context, network1)
|
||||
self.assertRaises(exceptions.DuplicateNetwork, dbapi.networks_create,
|
||||
self.context, network1)
|
||||
|
||||
def test_networks_get_all(self):
|
||||
dbapi.networks_create(self.context, network1)
|
||||
dbapi.networks_create(self.context, network2)
|
||||
filters = {}
|
||||
res, _ = dbapi.networks_get_all(self.context, filters,
|
||||
default_pagination)
|
||||
self.assertEqual(len(res), 2)
|
||||
|
||||
def test_networks_get_all_filter_region(self):
|
||||
dbapi.networks_create(self.context, network1)
|
||||
dbapi.networks_create(self.context, network2)
|
||||
filters = {
|
||||
'region_id': network1['region_id'],
|
||||
}
|
||||
res, _ = dbapi.networks_get_all(self.context, filters,
|
||||
default_pagination)
|
||||
self.assertEqual(len(res), 1)
|
||||
self.assertEqual(res[0]['name'], 'test network')
|
||||
|
||||
def test_networks_get_by_id(self):
|
||||
network = dbapi.networks_create(self.context, network1)
|
||||
res = dbapi.networks_get_by_id(self.context, network.id)
|
||||
self.assertEqual(res.name, 'test network')
|
||||
|
||||
def test_networks_get_by_name_filter_no_exit(self):
|
||||
dbapi.networks_create(self.context, network1)
|
||||
filters = {"name": "foo", "region_id": network1['region_id']}
|
||||
res, _ = dbapi.networks_get_all(self.context, filters,
|
||||
default_pagination)
|
||||
self.assertEqual(res, [])
|
||||
|
||||
def test_network_update(self):
|
||||
network = dbapi.networks_create(self.context, network1)
|
||||
res = dbapi.networks_get_by_id(self.context, network.id)
|
||||
self.assertEqual(res.name, 'test network')
|
||||
new_name = 'test_network1'
|
||||
res = dbapi.networks_update(self.context, res.id,
|
||||
{'name': 'test_network1'})
|
||||
self.assertEqual(res.name, new_name)
|
||||
|
||||
def test_networks_get_by_id_no_exist_raises(self):
|
||||
# Since no network is created, any id should raise
|
||||
self.assertRaises(exceptions.NotFound, dbapi.networks_get_by_id,
|
||||
self.context, 4)
|
||||
|
||||
def test_networks_delete(self):
|
||||
network = dbapi.networks_create(self.context, network1)
|
||||
# First make sure we have the network created
|
||||
res = dbapi.networks_get_by_id(self.context, network.id)
|
||||
self.assertEqual(res.id, network.id)
|
||||
# Delete the network
|
||||
dbapi.networks_delete(self.context, res.id)
|
||||
self.assertRaises(exceptions.NotFound, dbapi.networks_get_by_id,
|
||||
self.context, res.id)
|
||||
|
||||
|
||||
class NetworkDevicesDBTestCase(base.DBTestCase):
|
||||
|
||||
def test_network_devices_create(self):
|
||||
try:
|
||||
dbapi.network_devices_create(self.context, device1)
|
||||
except Exception:
|
||||
self.fail("Network device create raised unexpected exception")
|
||||
|
||||
def test_network_devices_get_all(self):
|
||||
dbapi.network_devices_create(self.context, device1)
|
||||
dbapi.network_devices_create(self.context, device2)
|
||||
filters = {}
|
||||
res, _ = dbapi.network_devices_get_all(self.context, filters,
|
||||
default_pagination)
|
||||
self.assertEqual(len(res), 2)
|
||||
|
||||
def test_network_device_get_all_filter_region(self):
|
||||
dbapi.network_devices_create(self.context, device1)
|
||||
dbapi.network_devices_create(self.context, device2)
|
||||
filters = {
|
||||
'region_id': device1['region_id'],
|
||||
}
|
||||
res, _ = dbapi.network_devices_get_all(self.context, filters,
|
||||
default_pagination)
|
||||
self.assertEqual(len(res), 1)
|
||||
self.assertEqual(res[0]['hostname'], 'switch1')
|
||||
|
||||
def test_network_device_get_all_filter_name(self):
|
||||
dbapi.network_devices_create(self.context, device1)
|
||||
dbapi.network_devices_create(self.context, device2)
|
||||
|
||||
name = device1['hostname']
|
||||
setup_res, _ = dbapi.network_devices_get_all(self.context, {},
|
||||
default_pagination)
|
||||
|
||||
self.assertEqual(len(setup_res), 2)
|
||||
matches = [dev for dev in setup_res if dev['hostname'] == name]
|
||||
self.assertEqual(len(matches), 1)
|
||||
|
||||
filters = {
|
||||
'name': name,
|
||||
}
|
||||
res, _ = dbapi.network_devices_get_all(self.context, filters,
|
||||
default_pagination)
|
||||
self.assertEqual(len(res), 1)
|
||||
self.assertEqual(res[0]['hostname'], name)
|
||||
|
||||
def test_network_device_get_all_filter_cell_id(self):
|
||||
region_id = 1
|
||||
cell1 = dbapi.cells_create(
|
||||
self.context,
|
||||
{
|
||||
'name': 'cell1',
|
||||
'project_id': project_id1,
|
||||
'cloud_id': cloud_id1,
|
||||
'region_id': region_id,
|
||||
}
|
||||
)
|
||||
cell2 = dbapi.cells_create(
|
||||
self.context,
|
||||
{
|
||||
'name': 'cell2',
|
||||
'project_id': project_id1,
|
||||
'cloud_id': cloud_id1,
|
||||
'region_id': region_id,
|
||||
}
|
||||
)
|
||||
dbapi.network_devices_create(
|
||||
self.context, dict(cell_id=cell1.id, **device1)
|
||||
)
|
||||
dbapi.network_devices_create(
|
||||
self.context, dict(cell_id=cell2.id, **device2)
|
||||
)
|
||||
|
||||
setup_res, _ = dbapi.network_devices_get_all(self.context, {},
|
||||
default_pagination)
|
||||
|
||||
self.assertEqual(len(setup_res), 2)
|
||||
matches = [dev for dev in setup_res if dev['cell_id'] == cell1.id]
|
||||
self.assertEqual(len(matches), 1)
|
||||
|
||||
filters = {
|
||||
'cell_id': cell1.id,
|
||||
}
|
||||
res, _ = dbapi.network_devices_get_all(self.context, filters,
|
||||
default_pagination)
|
||||
self.assertEqual(len(res), 1)
|
||||
self.assertEqual(res[0]['cell_id'], cell1.id)
|
||||
|
||||
def test_network_device_get_all_filter_device_type(self):
|
||||
dbapi.network_devices_create(self.context, device1)
|
||||
dbapi.network_devices_create(self.context, device3)
|
||||
|
||||
dev_type = device1['device_type']
|
||||
setup_res, _ = dbapi.network_devices_get_all(self.context, {},
|
||||
default_pagination)
|
||||
|
||||
self.assertEqual(len(setup_res), 2)
|
||||
matches = [dev for dev in setup_res if dev['device_type'] == dev_type]
|
||||
self.assertEqual(len(matches), 1)
|
||||
|
||||
filters = {
|
||||
'device_type': dev_type,
|
||||
}
|
||||
res, _ = dbapi.network_devices_get_all(self.context, filters,
|
||||
default_pagination)
|
||||
self.assertEqual(len(res), 1)
|
||||
self.assertEqual(res[0]['device_type'], dev_type)
|
||||
|
||||
def test_network_device_get_all_filter_id(self):
|
||||
dbapi.network_devices_create(self.context, device1)
|
||||
dbapi.network_devices_create(self.context, device2)
|
||||
|
||||
setup_res, _ = dbapi.network_devices_get_all(self.context, {},
|
||||
default_pagination)
|
||||
|
||||
self.assertEqual(len(setup_res), 2)
|
||||
|
||||
dev_id = setup_res[0]['id']
|
||||
self.assertNotEqual(dev_id, setup_res[1]['id'])
|
||||
|
||||
filters = {
|
||||
'id': dev_id
|
||||
}
|
||||
res, _ = dbapi.network_devices_get_all(self.context, filters,
|
||||
default_pagination)
|
||||
self.assertEqual(len(res), 1)
|
||||
self.assertEqual(res[0]['id'], dev_id)
|
||||
|
||||
def test_network_device_get_all_filter_ip_address(self):
|
||||
dbapi.network_devices_create(self.context, device1)
|
||||
dbapi.network_devices_create(self.context, device3)
|
||||
|
||||
ip = device1['ip_address']
|
||||
setup_res, _ = dbapi.network_devices_get_all(self.context, {},
|
||||
default_pagination)
|
||||
|
||||
self.assertEqual(len(setup_res), 2)
|
||||
matches = [dev for dev in setup_res if str(dev['ip_address']) == ip]
|
||||
self.assertEqual(len(matches), 1)
|
||||
|
||||
filters = {
|
||||
'ip_address': ip,
|
||||
}
|
||||
res, _ = dbapi.network_devices_get_all(self.context, filters,
|
||||
default_pagination)
|
||||
self.assertEqual(len(res), 1)
|
||||
self.assertEqual(str(res[0]['ip_address']), ip)
|
||||
|
||||
def test_network_devices_get_by_id(self):
|
||||
device = dbapi.network_devices_create(self.context, device1)
|
||||
res = dbapi.network_devices_get_by_id(self.context, device.id)
|
||||
self.assertEqual(res.hostname, 'switch1')
|
||||
|
||||
def test_network_devices_get_by_filter_no_exit(self):
|
||||
dbapi.network_devices_create(self.context, device1)
|
||||
filters = {"hostname": "foo"}
|
||||
res, _ = dbapi.networks_get_all(self.context, filters,
|
||||
default_pagination)
|
||||
self.assertEqual(res, [])
|
||||
|
||||
def test_network_devices_delete(self):
|
||||
device = dbapi.network_devices_create(self.context, device1)
|
||||
# First make sure we have the device
|
||||
res = dbapi.network_devices_get_by_id(self.context, device.id)
|
||||
self.assertEqual(res.id, device.id)
|
||||
# Delete the device
|
||||
dbapi.network_devices_delete(self.context, res.id)
|
||||
self.assertRaises(exceptions.NotFound, dbapi.network_devices_get_by_id,
|
||||
self.context, res.id)
|
||||
|
||||
def test_network_devices_labels_create(self):
|
||||
device = dbapi.network_devices_create(self.context, device1)
|
||||
labels = {"labels": ["tom", "jerry"]}
|
||||
dbapi.network_devices_labels_update(self.context, device.id, labels)
|
||||
|
||||
def test_network_devices_update(self):
|
||||
device = dbapi.network_devices_create(self.context, device1)
|
||||
res = dbapi.network_devices_get_by_id(self.context, device.id)
|
||||
self.assertEqual(res.hostname, 'switch1')
|
||||
new_name = 'switch2'
|
||||
res = dbapi.network_devices_update(self.context, res.id,
|
||||
{'name': 'switch2'})
|
||||
self.assertEqual(res.name, new_name)
|
||||
|
||||
def test_network_devices_labels_delete(self):
|
||||
device = dbapi.network_devices_create(self.context, device1)
|
||||
_labels = {"labels": ["tom", "jerry"]}
|
||||
dbapi.network_devices_labels_update(self.context, device.id, _labels)
|
||||
ndevice = dbapi.network_devices_get_by_id(self.context, device.id)
|
||||
self.assertEqual(sorted(ndevice.labels), sorted(_labels["labels"]))
|
||||
_dlabels = {"labels": ["tom"]}
|
||||
dbapi.network_devices_labels_delete(self.context, ndevice.id, _dlabels)
|
||||
ndevice = dbapi.network_devices_get_by_id(self.context, ndevice.id)
|
||||
self.assertEqual(ndevice.labels, {"jerry"})
|
||||
|
||||
def test_network_devices_create_sets_parent_id(self):
|
||||
parent = dbapi.network_devices_create(
|
||||
self.context,
|
||||
{
|
||||
'project_id': project_id1,
|
||||
'cloud_id': cloud_id1,
|
||||
'region_id': 1,
|
||||
'name': '1.www.example.com',
|
||||
'ip_address': '10.1.2.102',
|
||||
'device_type': 'switch',
|
||||
}
|
||||
)
|
||||
child = dbapi.network_devices_create(
|
||||
self.context,
|
||||
{
|
||||
'project_id': project_id1,
|
||||
'cloud_id': cloud_id1,
|
||||
'region_id': 1,
|
||||
'name': '2.www.example.com',
|
||||
'ip_address': '10.1.2.102',
|
||||
'device_type': 'switch',
|
||||
'parent_id': parent.id,
|
||||
}
|
||||
)
|
||||
self.assertEqual(parent.id, child.parent_id)
|
||||
|
||||
def test_network_devices_update_sets_parent_id(self):
|
||||
parent = dbapi.network_devices_create(
|
||||
self.context,
|
||||
{
|
||||
'project_id': project_id1,
|
||||
'cloud_id': cloud_id1,
|
||||
'region_id': 1,
|
||||
'name': '1.www.example.com',
|
||||
'ip_address': '10.1.2.102',
|
||||
'device_type': 'switch',
|
||||
}
|
||||
)
|
||||
child = dbapi.network_devices_create(
|
||||
self.context,
|
||||
{
|
||||
'project_id': project_id1,
|
||||
'cloud_id': cloud_id1,
|
||||
'region_id': 1,
|
||||
'name': '2.www.example.com',
|
||||
'ip_address': '10.1.2.102',
|
||||
'device_type': 'switch',
|
||||
'parent_id': None,
|
||||
}
|
||||
)
|
||||
self.assertIsNone(child.parent_id)
|
||||
child_update = dbapi.network_devices_update(
|
||||
self.context,
|
||||
child.id,
|
||||
{
|
||||
'parent_id': parent.id,
|
||||
}
|
||||
)
|
||||
self.assertEqual(parent.id, child_update.parent_id)
|
||||
|
||||
def test_network_devices_update_fails_when_parent_id_set_to_own_id(self):
|
||||
network_device1 = dbapi.network_devices_create(
|
||||
self.context,
|
||||
{
|
||||
'project_id': project_id1,
|
||||
'cloud_id': cloud_id1,
|
||||
'region_id': 1,
|
||||
'name': '1.www.example.com',
|
||||
'ip_address': '10.1.2.101',
|
||||
'device_type': 'switch',
|
||||
'parent_id': None,
|
||||
}
|
||||
)
|
||||
self.assertRaises(
|
||||
exceptions.BadRequest,
|
||||
dbapi.network_devices_update,
|
||||
self.context,
|
||||
network_device1.id,
|
||||
{
|
||||
'parent_id': network_device1.id,
|
||||
}
|
||||
)
|
||||
|
||||
def test_network_devices_update_fails_when_parent_set_to_descendant(self):
|
||||
parent = dbapi.network_devices_create(
|
||||
self.context,
|
||||
{
|
||||
'project_id': project_id1,
|
||||
'cloud_id': cloud_id1,
|
||||
'region_id': 1,
|
||||
'name': '1.www.example.com',
|
||||
'ip_address': '10.1.2.101',
|
||||
'device_type': 'switch',
|
||||
'parent_id': None,
|
||||
}
|
||||
)
|
||||
child = dbapi.network_devices_create(
|
||||
self.context,
|
||||
{
|
||||
'project_id': project_id1,
|
||||
'cloud_id': cloud_id1,
|
||||
'region_id': 1,
|
||||
'name': '2.www.example.com',
|
||||
'ip_address': '10.1.2.102',
|
||||
'device_type': 'switch',
|
||||
'parent_id': parent.id,
|
||||
}
|
||||
)
|
||||
grandchild = dbapi.network_devices_create(
|
||||
self.context,
|
||||
{
|
||||
'project_id': project_id1,
|
||||
'cloud_id': cloud_id1,
|
||||
'region_id': 1,
|
||||
'name': '3.www.example.com',
|
||||
'ip_address': '10.1.2.103',
|
||||
'device_type': 'switch',
|
||||
'parent_id': child.id,
|
||||
}
|
||||
)
|
||||
self.assertRaises(
|
||||
exceptions.BadRequest,
|
||||
dbapi.network_devices_update,
|
||||
self.context,
|
||||
parent.id,
|
||||
{
|
||||
'parent_id': grandchild.id,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class NetworkInterfacesDBTestCase(base.DBTestCase):
|
||||
|
||||
def test_network_interfaces_create(self):
|
||||
try:
|
||||
dbapi.network_interfaces_create(self.context, network_interface1)
|
||||
except Exception:
|
||||
self.fail("Network interface create raised unexpected exception")
|
||||
|
||||
def test_network_interfaces_get_all(self):
|
||||
dbapi.network_interfaces_create(self.context, network_interface1)
|
||||
dbapi.network_interfaces_create(self.context, network_interface2)
|
||||
filters = {}
|
||||
res, _ = dbapi.network_interfaces_get_all(self.context, filters,
|
||||
default_pagination)
|
||||
self.assertEqual(len(res), 2)
|
||||
self.assertEqual(
|
||||
str(res[0]['ip_address']), network_interface1['ip_address']
|
||||
)
|
||||
self.assertEqual(
|
||||
str(res[1]['ip_address']), network_interface2['ip_address']
|
||||
)
|
||||
|
||||
def test_interface_get_all_filter_device_id(self):
|
||||
dbapi.network_interfaces_create(self.context, network_interface1)
|
||||
dbapi.network_interfaces_create(self.context, network_interface2)
|
||||
filters = {
|
||||
"device_id": 1,
|
||||
}
|
||||
res, _ = dbapi.network_interfaces_get_all(self.context, filters,
|
||||
default_pagination)
|
||||
self.assertEqual(len(res), 1)
|
||||
self.assertEqual(res[0]['name'], 'eth1')
|
||||
|
||||
def test_network_interfaces_get_by_id(self):
|
||||
interface = dbapi.network_interfaces_create(self.context,
|
||||
network_interface1)
|
||||
res = dbapi.network_interfaces_get_by_id(self.context, interface.id)
|
||||
self.assertEqual(res.name, 'eth1')
|
||||
self.assertEqual(str(res.ip_address), network_interface1['ip_address'])
|
||||
|
||||
def test_network_interfaces_update(self):
|
||||
interface = dbapi.network_interfaces_create(self.context,
|
||||
network_interface1)
|
||||
res = dbapi.network_interfaces_get_by_id(self.context, interface.id)
|
||||
self.assertEqual(res.name, 'eth1')
|
||||
new_name = 'eth2'
|
||||
res = dbapi.network_interfaces_update(self.context, interface.id,
|
||||
{'name': 'eth2'})
|
||||
self.assertEqual(res.name, new_name)
|
||||
self.assertEqual(str(res.ip_address), network_interface1['ip_address'])
|
||||
|
||||
def test_network_interfaces_delete(self):
|
||||
interface = dbapi.network_interfaces_create(self.context,
|
||||
network_interface1)
|
||||
# First make sure we have the interface created
|
||||
res = dbapi.network_interfaces_get_by_id(self.context, interface.id)
|
||||
self.assertEqual(res.id, interface.id)
|
||||
# Delete the device
|
||||
dbapi.network_interfaces_delete(self.context, res.id)
|
||||
self.assertRaises(exceptions.NotFound,
|
||||
dbapi.network_interfaces_get_by_id,
|
||||
self.context, res.id)
|
@ -1,99 +0,0 @@
|
||||
import copy
|
||||
import uuid
|
||||
|
||||
from craton import exceptions
|
||||
from craton.db import api as dbapi
|
||||
from craton.tests.unit.db import base
|
||||
|
||||
default_pagination = {'limit': 30, 'marker': None}
|
||||
|
||||
project1 = {'name': 'project1'}
|
||||
project2 = {'name': 'project2'}
|
||||
|
||||
|
||||
class ProjectsDBTestCase(base.DBTestCase):
|
||||
|
||||
def test_create_project(self):
|
||||
# Set root, as only admin project can create other projects
|
||||
project = dbapi.projects_create(self.context, project1)
|
||||
self.assertEqual(project['name'], project1['name'])
|
||||
|
||||
def test_create_project_no_root_fails(self):
|
||||
context = copy.deepcopy(self.context)
|
||||
context.is_admin_project = False
|
||||
self.assertRaises(exceptions.AdminRequired,
|
||||
dbapi.projects_create,
|
||||
context,
|
||||
project1)
|
||||
|
||||
def test_project_get_all(self):
|
||||
dbapi.projects_create(self.context, project1)
|
||||
dbapi.projects_create(self.context, project2)
|
||||
|
||||
res, _ = dbapi.projects_get_all(self.context, {}, default_pagination)
|
||||
self.assertEqual(len(res), 2)
|
||||
|
||||
def test_project_get_no_admin_project_raises(self):
|
||||
self.context.is_admin_project = True
|
||||
dbapi.projects_create(self.context, project1)
|
||||
dbapi.projects_create(self.context, project2)
|
||||
|
||||
# Now set admin_project = false to become normal project user
|
||||
self.context.is_admin_project = False
|
||||
self.assertRaises(exceptions.AdminRequired,
|
||||
dbapi.projects_get_all,
|
||||
self.context,
|
||||
{}, default_pagination)
|
||||
|
||||
def test_project_get_by_name(self):
|
||||
dbapi.projects_create(self.context, project1)
|
||||
dbapi.projects_create(self.context, project2)
|
||||
|
||||
res, _ = dbapi.projects_get_by_name(self.context, project1['name'], {},
|
||||
default_pagination)
|
||||
self.assertEqual(len(res), 1)
|
||||
self.assertEqual(res[0].name, project1['name'])
|
||||
|
||||
def test_project_get_by_id(self):
|
||||
project = dbapi.projects_create(self.context, project1)
|
||||
res = dbapi.projects_get_by_id(self.context, project['id'])
|
||||
self.assertEqual(str(res['id']), str(project['id']))
|
||||
|
||||
def test_project_create_id_uuid_type(self):
|
||||
project = dbapi.projects_create(self.context, project1)
|
||||
self.assertEqual(type(project['id']), uuid.UUID)
|
||||
|
||||
def test_project_get_id_uuid_type(self):
|
||||
project = dbapi.projects_create(self.context, project1)
|
||||
res = dbapi.projects_get_by_id(self.context, project['id'])
|
||||
self.assertEqual(type(res['id']), uuid.UUID)
|
||||
|
||||
def test_project_variables_update_does_update_variables(self):
|
||||
create_res = dbapi.projects_create(self.context, project1)
|
||||
res = dbapi.projects_get_by_id(self.context, create_res.id)
|
||||
self.assertEqual(res.variables, {})
|
||||
variables = {"key1": "value1", "key2": "value2"}
|
||||
res = dbapi.variables_update_by_resource_id(
|
||||
self.context, "projects", res.id, variables
|
||||
)
|
||||
self.assertEqual(res.variables, variables)
|
||||
new_variables = {"key1": "tom", "key2": "cat"}
|
||||
res = dbapi.variables_update_by_resource_id(
|
||||
self.context, "projects", res.id, new_variables
|
||||
)
|
||||
self.assertEqual(res.variables, new_variables)
|
||||
|
||||
def test_project_variables_delete(self):
|
||||
create_res = dbapi.projects_create(self.context, project1)
|
||||
res = dbapi.projects_get_by_id(self.context, create_res.id)
|
||||
self.assertEqual(res.variables, {})
|
||||
variables = {"key1": "value1", "key2": "value2"}
|
||||
res = dbapi.variables_update_by_resource_id(
|
||||
self.context, "projects", res.id, variables
|
||||
)
|
||||
self.assertEqual(res.variables, variables)
|
||||
# NOTE(sulo): we delete variables by their key
|
||||
res = dbapi.variables_delete_by_resource_id(
|
||||
self.context, "projects", res.id, {"key1": "key1"}
|
||||
)
|
||||
self.assertEqual(res.variables, {"key2": "value2"})
|
@ -1,98 +0,0 @@
|
||||
import uuid
|
||||
|
||||
from craton.db import api as dbapi
|
||||
from craton.tests.unit.db import base
|
||||
from craton import exceptions
|
||||
|
||||
default_pagination = {'limit': 30, 'marker': None}
|
||||
|
||||
project_id1 = uuid.uuid4().hex
|
||||
cloud_id1 = uuid.uuid4().hex
|
||||
region1 = {'project_id': project_id1, 'cloud_id': cloud_id1, 'name': 'region1'}
|
||||
|
||||
|
||||
class RegionsDBTestCase(base.DBTestCase):
|
||||
|
||||
def test_region_create(self):
|
||||
try:
|
||||
dbapi.regions_create(self.context, region1)
|
||||
except Exception:
|
||||
self.fail("Region create raised unexpected exception")
|
||||
|
||||
def test_region_create_duplicate_name_raises(self):
|
||||
dbapi.regions_create(self.context, region1)
|
||||
self.assertRaises(exceptions.DuplicateRegion, dbapi.regions_create,
|
||||
self.context, region1)
|
||||
|
||||
def test_regions_get_all(self):
|
||||
dbapi.regions_create(self.context, region1)
|
||||
filters = {}
|
||||
res, _ = dbapi.regions_get_all(self.context, filters,
|
||||
default_pagination)
|
||||
self.assertEqual(len(res), 1)
|
||||
self.assertEqual(res[0]['name'], 'region1')
|
||||
|
||||
def test_regions_get_all_with_var_filters(self):
|
||||
res = dbapi.regions_create(self.context, region1)
|
||||
variables = {"key1": "value1", "key2": "value2"}
|
||||
dbapi.variables_update_by_resource_id(
|
||||
self.context, "regions", res.id, variables
|
||||
)
|
||||
filters = {}
|
||||
filters["vars"] = "key1:value1"
|
||||
regions, _ = dbapi.regions_get_all(
|
||||
self.context, filters, default_pagination,
|
||||
)
|
||||
self.assertEqual(len(regions), 1)
|
||||
self.assertEqual(regions[0].name, region1['name'])
|
||||
|
||||
def test_regions_get_all_with_var_filters_noexist(self):
|
||||
res = dbapi.regions_create(self.context, region1)
|
||||
variables = {"key1": "value1", "key2": "value2"}
|
||||
dbapi.variables_update_by_resource_id(
|
||||
self.context, "regions", res.id, variables
|
||||
)
|
||||
filters = {}
|
||||
filters["vars"] = "key1:value12"
|
||||
regions, _ = dbapi.regions_get_all(
|
||||
self.context, filters, default_pagination,
|
||||
)
|
||||
self.assertEqual(len(regions), 0)
|
||||
|
||||
def test_region_get_by_name(self):
|
||||
dbapi.regions_create(self.context, region1)
|
||||
res = dbapi.regions_get_by_name(self.context, region1['name'])
|
||||
self.assertEqual(res.name, 'region1')
|
||||
|
||||
def test_region_get_by_id(self):
|
||||
dbapi.regions_create(self.context, region1)
|
||||
res = dbapi.regions_get_by_id(self.context, 1)
|
||||
self.assertEqual(res.name, 'region1')
|
||||
|
||||
def test_region_get_by_name_no_exit_raises(self):
|
||||
# TODO(sulo): fix sqlalchemy api first
|
||||
pass
|
||||
|
||||
def test_region_get_by_id_no_exist_raises(self):
|
||||
# TODO(sulo): fix sqlalchemy api first
|
||||
pass
|
||||
|
||||
def test_region_update(self):
|
||||
dbapi.regions_create(self.context, region1)
|
||||
res = dbapi.regions_get_by_id(self.context, 1)
|
||||
self.assertEqual(res.name, 'region1')
|
||||
new_name = "region_New1"
|
||||
res = dbapi.regions_update(self.context, res.id,
|
||||
{'name': 'region_New1'})
|
||||
self.assertEqual(res.name, new_name)
|
||||
|
||||
def test_region_delete(self):
|
||||
dbapi.regions_create(self.context, region1)
|
||||
# First make sure we have the region
|
||||
res = dbapi.regions_get_by_name(self.context, region1['name'])
|
||||
self.assertEqual(res.name, 'region1')
|
||||
|
||||
dbapi.regions_delete(self.context, res.id)
|
||||
self.assertRaises(exceptions.NotFound,
|
||||
dbapi.regions_get_by_name,
|
||||
self.context, 'fake-region')
|
@ -1,74 +0,0 @@
|
||||
import uuid
|
||||
|
||||
from craton import exceptions
|
||||
from craton.db import api as dbapi
|
||||
from craton.tests.unit.db import base
|
||||
|
||||
default_pagination = {'limit': 30, 'marker': None}
|
||||
|
||||
project_id1 = uuid.uuid4().hex
|
||||
project_id2 = uuid.uuid4().hex
|
||||
root = {'project_id': project_id1, 'username': 'root', "is_admin": True,
|
||||
"is_root": True}
|
||||
user1 = {'project_id': project_id2, 'username': 'user1', "is_admin": True}
|
||||
user2 = {'project_id': project_id2, 'username': 'user2', "is_admin": False}
|
||||
|
||||
|
||||
class UsersDBTestCase(base.DBTestCase):
|
||||
|
||||
def make_user(self, user, is_admin=True, is_root=False):
|
||||
# Set admin context first
|
||||
self.context.is_admin = is_admin
|
||||
self.context.is_admin_project = is_root
|
||||
user = dbapi.users_create(self.context, user)
|
||||
return user
|
||||
|
||||
def test_user_create(self):
|
||||
user = self.make_user(user1)
|
||||
self.assertEqual(user['username'], 'user1')
|
||||
|
||||
def test_user_create_no_admin_context_fails(self):
|
||||
self.assertRaises(exceptions.AdminRequired,
|
||||
self.make_user,
|
||||
user1,
|
||||
is_admin=False)
|
||||
|
||||
def test_users_get_all(self):
|
||||
# Ensure context tenant is the same one as the
|
||||
# one that will make request, test context has
|
||||
# fake-tenant set by default.
|
||||
self.context.tenant = user1['project_id']
|
||||
dbapi.users_create(self.context, user1)
|
||||
dbapi.users_create(self.context, user2)
|
||||
res = dbapi.users_get_all(self.context, {}, default_pagination)
|
||||
self.assertEqual(len(res), 2)
|
||||
|
||||
def test_user_get_all_no_project_context(self):
|
||||
# Ensure when request has no root context and the request
|
||||
# is not for the same project no user info is given back.
|
||||
self.make_user(user1)
|
||||
self.context.tenant = uuid.uuid4().hex
|
||||
res, _ = dbapi.users_get_all(self.context, {}, default_pagination)
|
||||
self.assertEqual(len(res), 0)
|
||||
|
||||
def test_user_get_no_admin_context_raises(self):
|
||||
self.make_user(user1)
|
||||
self.context.is_admin = False
|
||||
self.assertRaises(exceptions.AdminRequired,
|
||||
dbapi.users_get_all,
|
||||
self.context,
|
||||
{}, default_pagination)
|
||||
|
||||
def test_user_get_by_name(self):
|
||||
dbapi.users_create(self.context, user1)
|
||||
dbapi.users_create(self.context, user2)
|
||||
self.context.tenant = user1['project_id']
|
||||
res, _ = dbapi.users_get_by_name(self.context, user1['username'], {},
|
||||
default_pagination)
|
||||
self.assertEqual(len(res), 1)
|
||||
self.assertEqual(res[0]['username'], user1['username'])
|
||||
|
||||
def test_user_get_by_id(self):
|
||||
user = self.make_user(user1)
|
||||
res = dbapi.users_get_by_id(self.context, user["id"])
|
||||
self.assertEqual(res["username"], user["username"])
|
@ -1,473 +0,0 @@
|
||||
from copy import deepcopy
|
||||
|
||||
from craton import exceptions
|
||||
from craton.db import api as dbapi
|
||||
from craton.tests.unit.db import base
|
||||
|
||||
|
||||
class VariablesDBTestCase:
|
||||
|
||||
def _get_mock_resource_id(self):
|
||||
# NOTE(thomasem): Project IDs are UUIDs not integers
|
||||
if self.resources_type in ("projects",):
|
||||
return "5a4e32e1-8571-4c2c-a088-a11f98900355"
|
||||
return 1
|
||||
|
||||
def create_project(self, name, variables=None):
|
||||
project = dbapi.projects_create(
|
||||
self.context,
|
||||
{
|
||||
"name": name,
|
||||
"variables": variables or {},
|
||||
},
|
||||
)
|
||||
return project.id
|
||||
|
||||
def create_cloud(self, name, project_id, variables=None):
|
||||
cloud = dbapi.clouds_create(
|
||||
self.context,
|
||||
{
|
||||
'name': name,
|
||||
'project_id': project_id,
|
||||
'variables': variables or {},
|
||||
},
|
||||
)
|
||||
return cloud.id
|
||||
|
||||
def create_region(self, name, project_id, cloud_id, variables=None):
|
||||
region = dbapi.regions_create(
|
||||
self.context,
|
||||
{
|
||||
'name': name,
|
||||
'project_id': project_id,
|
||||
'cloud_id': cloud_id,
|
||||
'variables': variables or {},
|
||||
},
|
||||
)
|
||||
return region.id
|
||||
|
||||
def create_cell(self, name, project_id, cloud_id, region_id,
|
||||
variables=None):
|
||||
cell = dbapi.cells_create(
|
||||
self.context,
|
||||
{
|
||||
'name': name,
|
||||
'project_id': project_id,
|
||||
'cloud_id': cloud_id,
|
||||
'region_id': region_id,
|
||||
'variables': variables or {}
|
||||
},
|
||||
)
|
||||
return cell.id
|
||||
|
||||
def create_host(
|
||||
self, name, project_id, cloud_id, region_id, ip_address, host_type,
|
||||
cell_id=None, parent_id=None, labels=None, variables=None,
|
||||
):
|
||||
host = {
|
||||
'name': name,
|
||||
'project_id': project_id,
|
||||
'cloud_id': cloud_id,
|
||||
'region_id': region_id,
|
||||
'cell_id': cell_id,
|
||||
'ip_address': ip_address,
|
||||
'parent_id': parent_id,
|
||||
'device_type': host_type,
|
||||
'active': True,
|
||||
'labels': labels or (),
|
||||
'variables': variables or {},
|
||||
}
|
||||
|
||||
host = dbapi.hosts_create(self.context, host)
|
||||
self.assertEqual(variables, host.variables)
|
||||
|
||||
return host.id
|
||||
|
||||
def create_network(
|
||||
self, name, project_id, cloud_id, region_id, cidr, gateway,
|
||||
netmask, cell_id=None, variables=None,
|
||||
):
|
||||
network = {
|
||||
'name': name,
|
||||
'project_id': project_id,
|
||||
'cloud_id': cloud_id,
|
||||
'region_id': region_id,
|
||||
'cell_id': cell_id,
|
||||
'cidr': cidr,
|
||||
'gateway': gateway,
|
||||
'netmask': netmask,
|
||||
'variables': variables or {},
|
||||
}
|
||||
|
||||
network = dbapi.networks_create(self.context, network)
|
||||
self.assertEqual(variables, network.variables)
|
||||
|
||||
return network.id
|
||||
|
||||
def create_network_device(
|
||||
self, name, project_id, cloud_id, region_id, ip_address,
|
||||
network_device_type, cell_id=None, parent_id=None, labels=None,
|
||||
variables=None,
|
||||
):
|
||||
network_device = {
|
||||
'name': name,
|
||||
'project_id': project_id,
|
||||
'cloud_id': cloud_id,
|
||||
'region_id': region_id,
|
||||
'cell_id': cell_id,
|
||||
'ip_address': ip_address,
|
||||
'parent_id': parent_id,
|
||||
'device_type': network_device_type,
|
||||
'active': True,
|
||||
'labels': labels or (),
|
||||
'variables': variables or {},
|
||||
}
|
||||
|
||||
network_device = dbapi.network_devices_create(
|
||||
self.context, network_device
|
||||
)
|
||||
self.assertEqual(variables, network_device.variables)
|
||||
|
||||
return network_device.id
|
||||
|
||||
def setup_host(self, variables):
|
||||
project_id = self.create_project(name='project1')
|
||||
cloud_id = self.create_cloud(name='cloud1', project_id=project_id)
|
||||
region_id = self.create_region(
|
||||
name='region1',
|
||||
project_id=project_id,
|
||||
cloud_id=cloud_id,
|
||||
)
|
||||
cell_id = self.create_cell(
|
||||
name="cell1",
|
||||
project_id=project_id,
|
||||
cloud_id=cloud_id,
|
||||
region_id=region_id,
|
||||
)
|
||||
host_id = self.create_host(
|
||||
name="host1",
|
||||
project_id=project_id,
|
||||
cloud_id=cloud_id,
|
||||
region_id=region_id,
|
||||
ip_address="192.168.2.1",
|
||||
host_type="server",
|
||||
cell_id=cell_id,
|
||||
parent_id=None,
|
||||
labels=None,
|
||||
variables=variables,
|
||||
)
|
||||
|
||||
return host_id
|
||||
|
||||
def setup_network_device(self, variables):
|
||||
project_id = self.create_project(name='project1')
|
||||
cloud_id = self.create_cloud(name='cloud1', project_id=project_id)
|
||||
region_id = self.create_region(
|
||||
name='region1',
|
||||
project_id=project_id,
|
||||
cloud_id=cloud_id,
|
||||
)
|
||||
cell_id = self.create_cell(
|
||||
name="cell1",
|
||||
project_id=project_id,
|
||||
cloud_id=cloud_id,
|
||||
region_id=region_id
|
||||
)
|
||||
network_device_id = self.create_network_device(
|
||||
name="network_device1",
|
||||
project_id=project_id,
|
||||
cloud_id=cloud_id,
|
||||
region_id=region_id,
|
||||
ip_address="192.168.2.1",
|
||||
network_device_type="switch",
|
||||
cell_id=cell_id,
|
||||
parent_id=None,
|
||||
labels=None,
|
||||
variables=variables,
|
||||
)
|
||||
|
||||
return network_device_id
|
||||
|
||||
def setup_network(self, variables):
|
||||
project_id = self.create_project(name='project1')
|
||||
cloud_id = self.create_cloud(name='cloud1', project_id=project_id)
|
||||
region_id = self.create_region(
|
||||
name='region1',
|
||||
project_id=project_id,
|
||||
cloud_id=cloud_id,
|
||||
)
|
||||
cell_id = self.create_cell(
|
||||
name="cell1",
|
||||
project_id=project_id,
|
||||
cloud_id=cloud_id,
|
||||
region_id=region_id,
|
||||
)
|
||||
network_id = self.create_network(
|
||||
name="network1",
|
||||
project_id=project_id,
|
||||
cloud_id=cloud_id,
|
||||
region_id=region_id,
|
||||
cell_id=cell_id,
|
||||
cidr="192.168.2.0/24",
|
||||
gateway="192.168.2.1",
|
||||
netmask="255.255.255.0",
|
||||
variables=variables,
|
||||
)
|
||||
|
||||
return network_id
|
||||
|
||||
def setup_cell(self, variables):
|
||||
project_id = self.create_project(name='project1')
|
||||
cloud_id = self.create_cloud(name='cloud1', project_id=project_id)
|
||||
region_id = self.create_region(
|
||||
name='region1',
|
||||
project_id=project_id,
|
||||
cloud_id=cloud_id,
|
||||
)
|
||||
cell_id = self.create_cell(
|
||||
name="cell1",
|
||||
project_id=project_id,
|
||||
cloud_id=cloud_id,
|
||||
region_id=region_id,
|
||||
variables=variables,
|
||||
)
|
||||
|
||||
return cell_id
|
||||
|
||||
def setup_region(self, variables):
|
||||
project_id = self.create_project(name='project1')
|
||||
cloud_id = self.create_cloud(name='cloud1', project_id=project_id)
|
||||
region_id = self.create_region(
|
||||
name='region1',
|
||||
project_id=project_id,
|
||||
cloud_id=cloud_id,
|
||||
variables=variables,
|
||||
)
|
||||
|
||||
return region_id
|
||||
|
||||
def setup_cloud(self, variables):
|
||||
project_id = self.create_project(name='project1')
|
||||
cloud_id = self.create_cloud(
|
||||
name='cloud1',
|
||||
project_id=project_id,
|
||||
variables=variables,
|
||||
)
|
||||
|
||||
return cloud_id
|
||||
|
||||
def setup_project(self, variables):
|
||||
project_id = self.create_project(name='project1', variables=variables)
|
||||
return project_id
|
||||
|
||||
def setup_resource(self, *args, **kwargs):
|
||||
setup_fn = {
|
||||
"cells": self.setup_cell,
|
||||
"hosts": self.setup_host,
|
||||
"networks": self.setup_network,
|
||||
"network-devices": self.setup_network_device,
|
||||
"regions": self.setup_region,
|
||||
"clouds": self.setup_cloud,
|
||||
"projects": self.setup_project,
|
||||
}
|
||||
|
||||
return setup_fn[self.resources_type](*args, *kwargs)
|
||||
|
||||
def test_get_resource_by_id_with_variables(self):
|
||||
variables = {
|
||||
"key1": "value1",
|
||||
"key2": "value2",
|
||||
"key3": "value3",
|
||||
}
|
||||
|
||||
resource_id = self.setup_resource(deepcopy(variables))
|
||||
|
||||
test = dbapi.resource_get_by_id(
|
||||
self.context, self.resources_type, resource_id
|
||||
)
|
||||
|
||||
self.assertEqual(resource_id, test.id)
|
||||
self.assertEqual(variables, test.variables)
|
||||
|
||||
def test_get_resource_by_id_not_found(self):
|
||||
|
||||
self.assertRaises(
|
||||
exceptions.NotFound,
|
||||
dbapi.resource_get_by_id,
|
||||
context=self.context,
|
||||
resources=self.resources_type,
|
||||
resource_id=self._get_mock_resource_id(),
|
||||
)
|
||||
|
||||
def test_variables_update_by_resource_id_existing_empty(self):
|
||||
existing_variables = {}
|
||||
|
||||
resource_id = self.setup_resource(existing_variables)
|
||||
|
||||
variables = {
|
||||
"key1": "value1",
|
||||
"key2": "value2",
|
||||
"key3": "value3",
|
||||
}
|
||||
|
||||
test = dbapi.variables_update_by_resource_id(
|
||||
self.context, self.resources_type, resource_id, deepcopy(variables)
|
||||
)
|
||||
|
||||
self.assertEqual(resource_id, test.id)
|
||||
self.assertEqual(variables, test.variables)
|
||||
|
||||
validate = dbapi.resource_get_by_id(
|
||||
self.context, self.resources_type, resource_id
|
||||
)
|
||||
|
||||
self.assertEqual(resource_id, validate.id)
|
||||
self.assertEqual(variables, validate.variables)
|
||||
|
||||
def test_variables_update_by_resource_id_not_found(self):
|
||||
self.assertRaises(
|
||||
exceptions.NotFound,
|
||||
dbapi.variables_update_by_resource_id,
|
||||
context=self.context,
|
||||
resources=self.resources_type,
|
||||
resource_id=self._get_mock_resource_id(),
|
||||
data={"key1": "value1"},
|
||||
)
|
||||
|
||||
def test_variables_update_by_resource_id_modify_existing(self):
|
||||
existing_variables = {
|
||||
"key1": "value1",
|
||||
"key2": "value2",
|
||||
"key3": "value3",
|
||||
}
|
||||
|
||||
update_variables = {
|
||||
"key3": "newvalue3",
|
||||
"key4": "value4",
|
||||
}
|
||||
|
||||
result_variables = deepcopy(existing_variables)
|
||||
result_variables.update(deepcopy(update_variables))
|
||||
|
||||
resource_id = self.setup_resource(existing_variables)
|
||||
|
||||
test = dbapi.variables_update_by_resource_id(
|
||||
context=self.context,
|
||||
resources=self.resources_type,
|
||||
resource_id=resource_id,
|
||||
data=deepcopy(update_variables)
|
||||
)
|
||||
|
||||
self.assertEqual(resource_id, test.id)
|
||||
self.assertEqual(result_variables, test.variables)
|
||||
|
||||
validate = dbapi.resource_get_by_id(
|
||||
self.context, self.resources_type, resource_id
|
||||
)
|
||||
|
||||
self.assertEqual(resource_id, validate.id)
|
||||
self.assertEqual(result_variables, validate.variables)
|
||||
|
||||
def test_variables_delete_by_resource_id(self):
|
||||
existing_variables = {
|
||||
"key1": "value1",
|
||||
"key2": "value2",
|
||||
"key3": "value3",
|
||||
}
|
||||
|
||||
delete_variables = [
|
||||
"key2",
|
||||
"key3",
|
||||
]
|
||||
|
||||
result_variables = {"key1": "value1"}
|
||||
|
||||
resource_id = self.setup_resource(existing_variables)
|
||||
|
||||
test = dbapi.variables_delete_by_resource_id(
|
||||
context=self.context,
|
||||
resources=self.resources_type,
|
||||
resource_id=resource_id,
|
||||
data=delete_variables
|
||||
)
|
||||
|
||||
self.assertEqual(resource_id, test.id)
|
||||
self.assertEqual(result_variables, test.variables)
|
||||
|
||||
validate = dbapi.resource_get_by_id(
|
||||
self.context, self.resources_type, resource_id
|
||||
)
|
||||
|
||||
self.assertEqual(resource_id, validate.id)
|
||||
self.assertEqual(result_variables, validate.variables)
|
||||
|
||||
def test_variables_delete_by_resource_id_resource_not_found(self):
|
||||
|
||||
self.assertRaises(
|
||||
exceptions.NotFound,
|
||||
dbapi.variables_delete_by_resource_id,
|
||||
context=self.context,
|
||||
resources=self.resources_type,
|
||||
resource_id=self._get_mock_resource_id(),
|
||||
data={"key1": "value1"},
|
||||
)
|
||||
|
||||
def test_variables_delete_by_resource_id_variable_not_found(self):
|
||||
existing_variables = {
|
||||
"key1": "value1",
|
||||
"key2": "value2",
|
||||
"key3": "value3",
|
||||
}
|
||||
|
||||
delete_variables = [
|
||||
"key4",
|
||||
]
|
||||
|
||||
result_variables = deepcopy(existing_variables)
|
||||
|
||||
resource_id = self.setup_resource(existing_variables)
|
||||
|
||||
test = dbapi.variables_delete_by_resource_id(
|
||||
context=self.context,
|
||||
resources=self.resources_type,
|
||||
resource_id=resource_id,
|
||||
data=delete_variables
|
||||
)
|
||||
|
||||
self.assertEqual(resource_id, test.id)
|
||||
self.assertEqual(result_variables, test.variables)
|
||||
|
||||
validate = dbapi.resource_get_by_id(
|
||||
self.context, self.resources_type, resource_id
|
||||
)
|
||||
|
||||
self.assertEqual(resource_id, validate.id)
|
||||
self.assertEqual(result_variables, validate.variables)
|
||||
|
||||
|
||||
class HostsVariablesDBTestCase(VariablesDBTestCase, base.DBTestCase):
|
||||
resources_type = "hosts"
|
||||
|
||||
|
||||
class NetworkDevicesVariablesDBTestCase(VariablesDBTestCase, base.DBTestCase):
|
||||
resources_type = "network-devices"
|
||||
|
||||
|
||||
class CellsVariablesDBTestCase(VariablesDBTestCase, base.DBTestCase):
|
||||
resources_type = "cells"
|
||||
|
||||
|
||||
class RegionsVariablesDBTestCase(VariablesDBTestCase, base.DBTestCase):
|
||||
resources_type = "regions"
|
||||
|
||||
|
||||
class NetworksVariablesDBTestCase(VariablesDBTestCase, base.DBTestCase):
|
||||
resources_type = "networks"
|
||||
|
||||
|
||||
class ProjectsVariablesDBTestCase(VariablesDBTestCase, base.DBTestCase):
|
||||
resources_type = "projects"
|
||||
|
||||
|
||||
class CloudsVariablesDBTestCase(VariablesDBTestCase, base.DBTestCase):
|
||||
resources_type = "clouds"
|
@ -1,232 +0,0 @@
|
||||
import copy
|
||||
import uuid
|
||||
|
||||
|
||||
"""
|
||||
Provides some fake resources - region, cell, host and other related
|
||||
objects for test.
|
||||
"""
|
||||
|
||||
|
||||
class Project(object):
|
||||
def __init__(self, id, name, variables):
|
||||
self.id = uuid.UUID(id)
|
||||
self.name = name
|
||||
self.variables = variables
|
||||
|
||||
def items(self):
|
||||
return iter(self.__dict__.items())
|
||||
|
||||
|
||||
PROJECT1 = Project("4534dcb4-dacd-474f-8afc-8bd5ab2d26e8",
|
||||
"project1", {"key1": "value1", "key2": "value2"})
|
||||
PROJECT2 = Project("77c527cb-837d-4fcb-bafb-af37ba3d13a4",
|
||||
"project2", {"key1": "value1", "key2": "value2"})
|
||||
|
||||
|
||||
class Cloud(object):
|
||||
def __init__(self, id, name, project_id, variables, labels=None):
|
||||
self.id = id
|
||||
self.name = name
|
||||
self.project_id = project_id
|
||||
self.variables = variables
|
||||
self.labels = labels
|
||||
|
||||
def items(self):
|
||||
return iter(self.__dict__.items())
|
||||
|
||||
|
||||
CLOUD1 = Cloud(1, "cloud1", "abcd", {"key1": "value1", "key2": "value2"})
|
||||
CLOUD2 = Cloud(2, "cloud2", "abcd", {"key3": "value3", "key4": "value4"})
|
||||
CLOUDS_LIST = [CLOUD1, CLOUD2]
|
||||
|
||||
|
||||
class User(object):
|
||||
def __init__(self, id, username, project_id, is_admin, is_root,
|
||||
api_key, roles=None):
|
||||
self.id = id
|
||||
self.username = username
|
||||
self.project_id = project_id
|
||||
self.is_admin = is_admin
|
||||
self.is_root = is_root
|
||||
self.api_key = api_key
|
||||
self.roles = roles
|
||||
|
||||
def items(self):
|
||||
return iter(self.__dict__.items())
|
||||
|
||||
|
||||
USER1 = User(1, 'user1', "2757a1b4-cd90-4891-886c-a246fd4e7064", True, False,
|
||||
'xx-yy-zz')
|
||||
USER2 = User(2, 'user2', "05d081ca-dcf5-4e96-b132-23b94d665799", False, False,
|
||||
'aa-bb-cc')
|
||||
|
||||
|
||||
class Cell(object):
|
||||
def __init__(self, id, name, status, region_id, cloud_id, project_id,
|
||||
variables, labels=None):
|
||||
self.id = id
|
||||
self.name = name
|
||||
self.status = status
|
||||
self.region_id = region_id
|
||||
self.cloud_id = cloud_id
|
||||
self.project_id = project_id
|
||||
self.variables = variables
|
||||
self.resolved = variables
|
||||
self.labels = labels
|
||||
|
||||
def items(self):
|
||||
return iter(self.__dict__.items())
|
||||
|
||||
|
||||
CELL1 = Cell(1, "cell1", "active", 1, 1, 1, {"key1": "value1",
|
||||
"key2": "value2"})
|
||||
CELL2 = Cell(2, "cell2", "active", "2", "1", "abcd", {"key3": "value3",
|
||||
"key4": "value4"})
|
||||
CELL3 = Cell(3, "cell1", "active", 2, 1, 1, {"key1": "value1",
|
||||
"key2": "value2"})
|
||||
|
||||
CELL_LIST = [CELL1, CELL2]
|
||||
CELL_LIST2 = [CELL1, CELL3]
|
||||
|
||||
|
||||
class Region(object):
|
||||
def __init__(self, id, name, project_id, cloud_id, variables, labels=None):
|
||||
self.id = id
|
||||
self.name = name
|
||||
self.project_id = project_id
|
||||
self.cloud_id = cloud_id
|
||||
self.variables = variables
|
||||
self.resolved = variables
|
||||
self.labels = labels
|
||||
|
||||
def items(self):
|
||||
return iter(self.__dict__.items())
|
||||
|
||||
|
||||
REGION1 = Region(1, "region1", "abcd", 1, {"key1": "value1", "key2": "value2"})
|
||||
REGION2 = Region(2, "region2", "abcd", 1, {"key3": "value3", "key4": "value4"})
|
||||
REGIONS_LIST = [REGION1, REGION2]
|
||||
|
||||
|
||||
class Host(object):
|
||||
def __init__(self, id, name, project_id, cloud_id, region_id, ip_address,
|
||||
device_type, variables, labels=None, cell_id=None,
|
||||
parent_id=None):
|
||||
self.id = id
|
||||
self.name = name
|
||||
self.project_id = project_id
|
||||
self.cloud_id = cloud_id
|
||||
self.region_id = region_id
|
||||
self.ip_address = ip_address
|
||||
self.variables = variables
|
||||
self.resolved = copy.copy(variables)
|
||||
self.device_type = device_type
|
||||
self.labels = labels
|
||||
self.cell_id = cell_id
|
||||
self.parent_id = parent_id
|
||||
|
||||
def items(self):
|
||||
return iter(self.__dict__.items())
|
||||
|
||||
|
||||
HOST1 = Host(1, "www.craton.com", 1, 1, 1, "192.168.1.1", "server",
|
||||
{"key1": "value1", "key2": "value2"})
|
||||
HOST2 = Host(2, "www.example.com", "1", "1", "1", "192.168.1.2", "server",
|
||||
{"key1": "value1", "key2": "value2"})
|
||||
HOST3 = Host(3, "www.example.net", "1", "!", "2", "10.10.0.1", "server",
|
||||
{"key1": "value1", "key2": "value2"})
|
||||
HOST4 = Host(4, "www.example.net", "1", "1", "2", "10.10.0.1", "server",
|
||||
{"key1": "value1", "key2": "value2"}, labels=["a", "b"])
|
||||
HOSTS_LIST_R1 = [HOST1, HOST2]
|
||||
HOSTS_LIST_R2 = [HOST3]
|
||||
HOSTS_LIST_R3 = [HOST1, HOST2, HOST3]
|
||||
|
||||
|
||||
class Networks(object):
|
||||
def __init__(self, id, name, project_id, cidr, gateway, netmask,
|
||||
variables, cloud_id, region_id, labels=None):
|
||||
self.id = id
|
||||
self.name = name
|
||||
self.project_id = project_id
|
||||
self.cidr = cidr
|
||||
self.gateway = gateway
|
||||
self.netmask = netmask
|
||||
self.variables = variables
|
||||
self.resolved = copy.copy(variables)
|
||||
self.labels = labels
|
||||
self.cloud_id = cloud_id
|
||||
self.region_id = region_id
|
||||
|
||||
def items(self):
|
||||
return iter(self.__dict__.items())
|
||||
|
||||
|
||||
NETWORK1 = Networks(1, "PrivateNetwork", 1, "192.168.1.0/24", "192.168.1.1",
|
||||
"255.255.255.0", {"key1": "value1"}, 1, 1)
|
||||
NETWORK2 = Networks(2, "PublicNetwork", 1, "10.10.1.0/24", "10.10.1.1",
|
||||
"255.255.255.0", {"pkey1": "pvalue1"}, 1, 1)
|
||||
NETWORK3 = Networks(3, "OtherNetwork", 1, "10.10.1.0/24", "10.10.1.2",
|
||||
"255.255.255.0", {"okey1": "ovalue1"}, 1, 2)
|
||||
NETWORKS_LIST = [NETWORK1, NETWORK2]
|
||||
NETWORKS_LIST2 = [NETWORK1, NETWORK2, NETWORK3]
|
||||
|
||||
|
||||
class NetworkDevice():
|
||||
def __init__(self, id, name, project_id, cloud_id, region_id, device_type,
|
||||
ip_address, variables, labels=None, cell_id=None,
|
||||
parent_id=None):
|
||||
self.name = name
|
||||
self.id = id
|
||||
self.project_id = project_id
|
||||
self.region_id = region_id
|
||||
self.device_type = device_type
|
||||
self.ip_address = ip_address
|
||||
self.variables = variables
|
||||
self.resolved = copy.copy(variables)
|
||||
self.labels = labels
|
||||
self.cloud_id = cloud_id
|
||||
self.cell_id = cell_id
|
||||
self.parent_id = parent_id
|
||||
|
||||
def items(self):
|
||||
return iter(self.__dict__.items())
|
||||
|
||||
|
||||
NETWORK_DEVICE1 = NetworkDevice(1, "NetDevices1", 1, 1, 1, "Server",
|
||||
"10.10.0.1",
|
||||
{"key1": "value1", "key2": "value2"},
|
||||
labels=["a", "b"])
|
||||
NETWORK_DEVICE2 = NetworkDevice(2, "NetDevices2", 1, 1, 2, "Server",
|
||||
"10.10.0.2",
|
||||
{"key1": "value1", "key2": "value2"},
|
||||
labels=["a", "b"])
|
||||
|
||||
NETWORK_DEVICE_LIST1 = [NETWORK_DEVICE1]
|
||||
NETWORK_DEVICE_LIST2 = [NETWORK_DEVICE1, NETWORK_DEVICE2]
|
||||
|
||||
|
||||
class NetworkInterface():
|
||||
def __init__(self, id, name, device_id, project_id, interface_type,
|
||||
ip_address, variables):
|
||||
self.id = id
|
||||
self.name = name
|
||||
self.device_id = device_id
|
||||
self.project_id = project_id
|
||||
self.interface_type = interface_type
|
||||
self.ip_address = ip_address
|
||||
self.variables = variables
|
||||
|
||||
def items(self):
|
||||
return iter(self.__dict__.items())
|
||||
|
||||
|
||||
NETWORK_INTERFACE1 = NetworkInterface(1, "NetInterface", 1, 1,
|
||||
"interface_type1", "10.10.0.1",
|
||||
{"key1": "value1", "key2": "value2"})
|
||||
NETWORK_INTERFACE2 = NetworkInterface(2, "NetInterface", 2, 1,
|
||||
"interface_type2", "10.10.0.2",
|
||||
{"key1": "value1", "key2": "value2"})
|
||||
|
||||
NETWORK_INTERFACE_LIST1 = [NETWORK_INTERFACE1]
|
||||
NETWORK_INTERFACE_LIST2 = [NETWORK_INTERFACE1, NETWORK_INTERFACE2]
|
File diff suppressed because it is too large
Load Diff
@ -1,27 +0,0 @@
|
||||
from craton import api
|
||||
from craton.tests import TestCase
|
||||
|
||||
|
||||
class TestRouteURLNaming(TestCase):
|
||||
pass
|
||||
|
||||
|
||||
def generate_route_naming_functions(cls):
|
||||
def gen_test(endpoint, url):
|
||||
def test(self):
|
||||
pattern = (
|
||||
"^/v1/([a-z-]+|<any\('[a-z-]+'(, '[a-z-]+')*\):resources>)"
|
||||
"(/<id>(/[a-z-]+)?)?"
|
||||
)
|
||||
self.assertRegex(url, pattern)
|
||||
test_name = 'test_route_naming_{}'.format(endpoint)
|
||||
setattr(cls, test_name, test)
|
||||
|
||||
app = api.setup_app()
|
||||
for rule in app.url_map.iter_rules():
|
||||
endpoint = rule.endpoint[3:]
|
||||
url = rule.rule
|
||||
gen_test(endpoint, url)
|
||||
|
||||
|
||||
generate_route_naming_functions(TestRouteURLNaming)
|
@ -1,207 +0,0 @@
|
||||
import jsonschema
|
||||
|
||||
from craton import api
|
||||
from craton.api.v1.schemas import filters, validators
|
||||
from craton.tests import TestCase
|
||||
|
||||
|
||||
VALIDATORS = {
|
||||
"with_schema": [
|
||||
('ansible_inventory', 'GET'),
|
||||
('cells', 'GET'),
|
||||
('cells', 'POST'),
|
||||
('cells_id', 'GET'),
|
||||
('cells_id', 'PUT'),
|
||||
('devices', 'GET'),
|
||||
('hosts', 'GET'),
|
||||
('hosts', 'POST'),
|
||||
('hosts_id', 'GET'),
|
||||
('hosts_id', 'PUT'),
|
||||
('hosts_labels', 'DELETE'),
|
||||
('hosts_labels', 'GET'),
|
||||
('hosts_labels', 'PUT'),
|
||||
('network_devices', 'GET'),
|
||||
('network_devices', 'POST'),
|
||||
('network_devices_id', 'GET'),
|
||||
('network_devices_id', 'PUT'),
|
||||
('network_devices_labels', 'GET'),
|
||||
('network_devices_labels', 'PUT'),
|
||||
('network_devices_labels', 'DELETE'),
|
||||
('network_interfaces', 'GET'),
|
||||
('network_interfaces', 'POST'),
|
||||
("network_interfaces_id", "GET"),
|
||||
('network_interfaces_id', 'PUT'),
|
||||
('networks', 'GET'),
|
||||
('networks', 'POST'),
|
||||
("networks_id", "GET"),
|
||||
('networks_id', 'PUT'),
|
||||
('projects', 'GET'),
|
||||
('projects', 'POST'),
|
||||
("projects_id", "GET"),
|
||||
('regions', 'GET'),
|
||||
('regions', 'POST'),
|
||||
("regions_id", "GET"),
|
||||
('regions_id', 'PUT'),
|
||||
('clouds', 'GET'),
|
||||
('clouds', 'POST'),
|
||||
("clouds_id", "GET"),
|
||||
('clouds_id', 'PUT'),
|
||||
('users', 'GET'),
|
||||
('users', 'POST'),
|
||||
("users_id", "GET"),
|
||||
('variables_with_resolve', 'DELETE'),
|
||||
('variables_with_resolve', 'GET'),
|
||||
('variables_with_resolve', 'PUT'),
|
||||
('variables_without_resolve', 'DELETE'),
|
||||
('variables_without_resolve', 'GET'),
|
||||
('variables_without_resolve', 'PUT'),
|
||||
],
|
||||
"without_schema": [
|
||||
('cells_id', 'DELETE'),
|
||||
('hosts_id', 'DELETE'),
|
||||
('network_devices_id', 'DELETE'),
|
||||
("network_interfaces_id", "DELETE"),
|
||||
("networks_id", "DELETE"),
|
||||
("projects_id", "DELETE"),
|
||||
("users_id", "DELETE"),
|
||||
("regions_id", "DELETE"),
|
||||
("clouds_id", "DELETE"),
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
class TestAPISchema(TestCase):
|
||||
"""Confirm that valid schema are defined."""
|
||||
def test_all_validators_have_test(self):
|
||||
known = set(VALIDATORS["with_schema"] + VALIDATORS["without_schema"])
|
||||
defined = set(validators.keys())
|
||||
self.assertSetEqual(known, defined)
|
||||
|
||||
|
||||
def generate_schema_validation_functions(cls):
|
||||
def gen_validator_schema_test(endpoint, method):
|
||||
def test(self):
|
||||
try:
|
||||
loc_schema = validators[(endpoint, method)]
|
||||
except KeyError:
|
||||
self.fail(
|
||||
'The validator {} is missing from the schemas '
|
||||
'validators object.'.format((endpoint, method))
|
||||
)
|
||||
|
||||
self.assertEqual(len(loc_schema), 1)
|
||||
locations = {
|
||||
'GET': 'args',
|
||||
'DELETE': 'json',
|
||||
'PUT': 'json',
|
||||
'POST': 'json',
|
||||
}
|
||||
location, schema = loc_schema.popitem()
|
||||
self.assertIn(method, locations)
|
||||
self.assertEqual(locations[method], location)
|
||||
self.assertIs(
|
||||
jsonschema.Draft4Validator.check_schema(schema), None
|
||||
)
|
||||
if 'type' not in schema or schema['type'] == 'object':
|
||||
self.assertFalse(schema['additionalProperties'])
|
||||
|
||||
name = '_'.join(('validator', endpoint, method))
|
||||
setattr(cls, 'test_valid_schema_{}'.format(name), test)
|
||||
|
||||
for (endpoint, method) in VALIDATORS["with_schema"]:
|
||||
gen_validator_schema_test(endpoint, method)
|
||||
|
||||
def gen_no_validator_schema_test(endpoint, method):
|
||||
def test(self):
|
||||
try:
|
||||
loc_schema = validators[(endpoint, method)]
|
||||
except KeyError:
|
||||
self.fail(
|
||||
'The validator {} is missing from the schemas '
|
||||
'validators object.'.format((endpoint, method))
|
||||
)
|
||||
self.assertEqual({}, loc_schema)
|
||||
name = '_'.join(('validator', endpoint, method))
|
||||
setattr(cls, 'test_no_schema_{}'.format(name), test)
|
||||
|
||||
for (endpoint, method) in VALIDATORS["without_schema"]:
|
||||
gen_no_validator_schema_test(endpoint, method)
|
||||
|
||||
def gen_filter_test(name, schema):
|
||||
def test(self):
|
||||
self.assertIs(
|
||||
jsonschema.Draft4Validator.check_schema(schema), None
|
||||
)
|
||||
if 'type' not in schema or schema['type'] == 'object':
|
||||
self.assertFalse(schema['additionalProperties'])
|
||||
setattr(cls, 'test_valid_schema_{}'.format(name), test)
|
||||
|
||||
for (endpoint, method), responses in filters.items():
|
||||
for return_code, json in responses.items():
|
||||
if json['schema']:
|
||||
name = '_'.join(('filter', endpoint, method, str(return_code)))
|
||||
gen_filter_test(name, json['schema'])
|
||||
|
||||
|
||||
generate_schema_validation_functions(TestAPISchema)
|
||||
|
||||
|
||||
class TestSchemaLocationInRoute(TestCase):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.app = api.setup_app()
|
||||
|
||||
|
||||
def generate_endpoint_method_validation_functions(cls):
|
||||
def gen_test(test_type, endpoint, method):
|
||||
def test(self):
|
||||
rules = [
|
||||
rule for rule in self.app.url_map.iter_rules()
|
||||
if rule.endpoint == endpoint and method in rule.methods
|
||||
]
|
||||
self.assertEqual(len(rules), 1)
|
||||
test_name = 'test_{}_endpoint_method_in_routes_{}_{}'.format(
|
||||
test_type, endpoint, method
|
||||
)
|
||||
setattr(cls, test_name, test)
|
||||
|
||||
for (_endpoint, method) in validators:
|
||||
endpoint = "v1.{}".format(_endpoint)
|
||||
gen_test('validators', endpoint, method)
|
||||
|
||||
for (_endpoint, method) in filters:
|
||||
endpoint = "v1.{}".format(_endpoint)
|
||||
gen_test('filters', endpoint, method)
|
||||
|
||||
|
||||
generate_endpoint_method_validation_functions(TestSchemaLocationInRoute)
|
||||
|
||||
|
||||
class TestRoutesInValidators(TestCase):
|
||||
pass
|
||||
|
||||
|
||||
def generate_route_validation_functions(cls):
|
||||
def gen_test(test_type, checker, endpoint, method):
|
||||
def test(self):
|
||||
self.assertIn((endpoint, method), checker)
|
||||
test_name = 'test_route_in_{}_{}_{}'.format(
|
||||
test_type, endpoint, method
|
||||
)
|
||||
setattr(cls, test_name, test)
|
||||
|
||||
app = api.setup_app()
|
||||
for rule in app.url_map.iter_rules():
|
||||
# remove 'v1.' from start of endpoint
|
||||
endpoint = rule.endpoint[3:]
|
||||
for method in rule.methods:
|
||||
if method == 'OPTIONS':
|
||||
continue
|
||||
elif method == 'HEAD' and 'GET' in rule.methods:
|
||||
continue
|
||||
else:
|
||||
gen_test('validators', validators, endpoint, method)
|
||||
gen_test('filters', filters, endpoint, method)
|
||||
|
||||
|
||||
generate_route_validation_functions(TestRoutesInValidators)
|
@ -1,22 +0,0 @@
|
||||
"""Tests for craton.util module."""
|
||||
import uuid
|
||||
|
||||
from craton import tests
|
||||
from craton import util
|
||||
|
||||
|
||||
class TestProjectIdUtilities(tests.TestCase):
|
||||
"""Unit tests for the copy_project_id_into_json function."""
|
||||
|
||||
def test_adds_project_id_to_json(self):
|
||||
"""Verify we add the project_id to the json body."""
|
||||
project_id = uuid.uuid4().hex
|
||||
self.context.tenant = project_id
|
||||
json = util.copy_project_id_into_json(self.context, {})
|
||||
self.assertDictEqual({'project_id': project_id}, json)
|
||||
|
||||
def test_defaults_project_id_to_zero(self):
|
||||
"""Verify if there's no tenant attribute on the context we use 0."""
|
||||
del self.context.tenant
|
||||
json = util.copy_project_id_into_json(self.context, {})
|
||||
self.assertDictEqual({'project_id': ''}, json)
|
@ -1,82 +0,0 @@
|
||||
"""Module containing generic utilies for Craton."""
|
||||
from datetime import date
|
||||
from decorator import decorator
|
||||
from flask import json, Response
|
||||
import werkzeug.exceptions
|
||||
|
||||
from oslo_log import log
|
||||
|
||||
import craton.exceptions as exceptions
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
||||
def copy_project_id_into_json(context, json, project_id_key='project_id'):
|
||||
"""Copy the project_id from the context into the JSON request body.
|
||||
|
||||
:param context:
|
||||
The request context object.
|
||||
:param json:
|
||||
The parsed JSON request body.
|
||||
:returns:
|
||||
The JSON with the project-id from the headers added as the
|
||||
"project_id" value in the JSON.
|
||||
:rtype:
|
||||
dict
|
||||
"""
|
||||
json[project_id_key] = getattr(context, 'tenant', '')
|
||||
return json
|
||||
|
||||
|
||||
class JSONEncoder(json.JSONEncoder):
|
||||
|
||||
def default(self, o):
|
||||
if isinstance(o, date):
|
||||
return o.isoformat()
|
||||
return json.JSONEncoder.default(self, o)
|
||||
|
||||
|
||||
JSON_KWARGS = {
|
||||
"indent": 2,
|
||||
"sort_keys": True,
|
||||
"cls": JSONEncoder,
|
||||
"separators": (",", ": "),
|
||||
}
|
||||
|
||||
|
||||
def handle_all_exceptions(e):
|
||||
"""Generate error Flask response object from exception."""
|
||||
headers = [("Content-Type", "application/json")]
|
||||
if isinstance(e, exceptions.Base):
|
||||
message = e.message
|
||||
status = e.code
|
||||
elif isinstance(e, werkzeug.exceptions.HTTPException):
|
||||
message = e.description
|
||||
status = e.code
|
||||
# Werkzeug exceptions can include additional headers, those should be
|
||||
# kept unless the header is "Content-Type" which is set by this
|
||||
# function.
|
||||
headers.extend(
|
||||
h for h in e.get_headers(None) if h[0].lower() != "content-type"
|
||||
)
|
||||
else:
|
||||
LOG.exception(e)
|
||||
e_ = exceptions.UnknownException
|
||||
message = e_.message
|
||||
status = e_.code
|
||||
|
||||
body = {
|
||||
"message": message,
|
||||
"status": status,
|
||||
}
|
||||
|
||||
body_ = "{}\n".format(json.dumps(body, **JSON_KWARGS))
|
||||
return Response(body_, status, headers)
|
||||
|
||||
|
||||
@decorator
|
||||
def handle_all_exceptions_decorator(fn, *args, **kwargs):
|
||||
try:
|
||||
return fn(*args, **kwargs)
|
||||
except Exception as e:
|
||||
return handle_all_exceptions(e)
|
@ -1,4 +0,0 @@
|
||||
"""Tasks for managing the execution of Ansible playbooks
|
||||
|
||||
Takes in account failure.
|
||||
"""
|
@ -1,11 +0,0 @@
|
||||
import abc
|
||||
|
||||
|
||||
class WorkflowFactory(object, metaclass=abc.ABCMeta):
|
||||
|
||||
@abc.abstractmethod
|
||||
def workflow(self):
|
||||
"""Construct appropriate taskflow flow object.
|
||||
|
||||
:returns: A flow.Flow subclass
|
||||
"""
|
@ -1,40 +0,0 @@
|
||||
import time
|
||||
|
||||
from oslo_log import log as logging
|
||||
from taskflow import task
|
||||
from taskflow.patterns import linear_flow
|
||||
|
||||
from craton.workflow import base
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Sleep(task.Task):
|
||||
def __init__(self, delay=10, **kwargs):
|
||||
super(Sleep, self).__init__(**kwargs)
|
||||
self.delay = delay
|
||||
|
||||
def execute(self):
|
||||
LOG.info('Doing task %s', self)
|
||||
time.sleep(self.delay)
|
||||
|
||||
|
||||
class Fail(task.Task):
|
||||
def execute(self):
|
||||
LOG.info('Failing task %s', self)
|
||||
raise RuntimeError('failure in task %s' % self)
|
||||
|
||||
|
||||
class TestFlow(base.WorkflowFactory):
|
||||
def __init__(self, task_delay=5):
|
||||
super(TestFlow, self).__init__()
|
||||
self.task_delay = task_delay
|
||||
|
||||
def workflow(self):
|
||||
f = linear_flow.Flow('example')
|
||||
f.add(
|
||||
Sleep(name='step 1', delay=self.task_delay),
|
||||
Sleep(name='step 2', delay=self.task_delay),
|
||||
Fail(name='step 3'),
|
||||
)
|
||||
return f
|
@ -1,74 +0,0 @@
|
||||
import contextlib
|
||||
import threading
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
from oslo_utils import uuidutils
|
||||
from taskflow.conductors import backends as conductors
|
||||
from taskflow.jobs import backends as boards
|
||||
from taskflow.persistence import backends as persistence_backends
|
||||
from zake import fake_client
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
CONF = cfg.CONF
|
||||
|
||||
OPTS = [
|
||||
cfg.StrOpt('job_board_name', default='craton_jobs',
|
||||
help='Name of job board used to store outstanding jobs.'),
|
||||
cfg.IntOpt('max_simultaneous_jobs', default=9,
|
||||
help='Number of tasks to run in parallel on this worker.'),
|
||||
]
|
||||
CONF.register_opts(OPTS)
|
||||
|
||||
TASKFLOW_OPTS = [
|
||||
cfg.StrOpt('connection', default='memory',
|
||||
help='Taskflow backend used for persisting taskstate.'),
|
||||
cfg.StrOpt('job_board_url',
|
||||
default='zookeeper://localhost?path=/taskflow/craton/jobs',
|
||||
help='URL used to store outstanding jobs'),
|
||||
cfg.BoolOpt('db_upgrade', default=True,
|
||||
help='Upgrade DB schema on startup.'),
|
||||
]
|
||||
CONF.register_opts(TASKFLOW_OPTS, group='taskflow')
|
||||
|
||||
|
||||
def _get_persistence_backend(conf):
|
||||
return persistence_backends.fetch({
|
||||
'connection': conf.taskflow.connection,
|
||||
})
|
||||
|
||||
|
||||
def _get_jobboard_backend(conf, persistence=None):
|
||||
client = None
|
||||
if conf.taskflow.connection == 'memory':
|
||||
client = fake_client.FakeClient()
|
||||
return boards.fetch(conf.job_board_name,
|
||||
{'board': conf.taskflow.job_board_url},
|
||||
client=client, persistence=persistence)
|
||||
|
||||
|
||||
def start(conf):
|
||||
persistence = _get_persistence_backend(conf)
|
||||
|
||||
if conf.taskflow.db_upgrade:
|
||||
with contextlib.closing(persistence.get_connection()) as conn:
|
||||
LOG.info('Checking for database schema upgrade')
|
||||
conn.upgrade()
|
||||
|
||||
my_name = uuidutils.generate_uuid()
|
||||
LOG.info('I am %s', my_name)
|
||||
|
||||
board = _get_jobboard_backend(conf, persistence=persistence)
|
||||
|
||||
conductor = conductors.fetch(
|
||||
'nonblocking', my_name, board,
|
||||
engine='parallel',
|
||||
max_simultaneous_jobs=conf.max_simultaneous_jobs,
|
||||
persistence=persistence)
|
||||
|
||||
board.connect()
|
||||
LOG.debug('Starting taskflow conductor loop')
|
||||
threading.Thread(target=conductor.run).start()
|
||||
|
||||
return persistence, board, conductor
|
@ -1,21 +0,0 @@
|
||||
Craton's API Reference Guide
|
||||
============================
|
||||
Resources:
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
cells
|
||||
devices
|
||||
hosts
|
||||
networks
|
||||
net-devices
|
||||
net-interfaces
|
||||
regions
|
||||
|
||||
API Usage:
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
filtering-by-variables
|
@ -1,167 +0,0 @@
|
||||
digraph structs {
|
||||
node [shape=plaintext]
|
||||
|
||||
# overlap=false;
|
||||
# splines=true;
|
||||
# layout="neato";
|
||||
|
||||
Cli [label=<
|
||||
<TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0">
|
||||
<TR><TD PORT="Cli"><font face="Helvetica" point-size="12">CLI<br/></font>
|
||||
</TD></TR>
|
||||
</TABLE>>];
|
||||
|
||||
PythonApi [label=<
|
||||
<TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0">
|
||||
<TR><TD PORT="PythonApi"><font face="Helvetica" point-size="12">Python API<br/></font>
|
||||
</TD></TR>
|
||||
</TABLE>>];
|
||||
|
||||
|
||||
CratonCore [label=<
|
||||
<TABLE BORDER="0" CELLBORDER="0" CELLSPACING="4">
|
||||
<TR>
|
||||
<TD>
|
||||
<TABLE BORDER="0" CELLBORDER="1" CELLSPACING="8">
|
||||
|
||||
<TR>
|
||||
<TD PORT="Horizon"><font face="Helvetica" point-size="12">
|
||||
<font face="Helvetica" point-size="12">Horizon UI<br/><font point-size="8">
|
||||
Inventory,<br/>Workflow Panels</font></font>
|
||||
</font></TD>
|
||||
</TR>
|
||||
<TR><TD PORT="Keystone"><font face="Helvetica" point-size="12">Keystone<br/><font point-size="8">
|
||||
Principals, roles,<br/>privileges,<br/>catalog endpoints</font></font>
|
||||
</TD></TR>
|
||||
<TR><TD PORT="Barbican"><font face="Helvetica" point-size="12">Barbican<br/><font point-size="8">
|
||||
Key Storage for<br/>TaskFlow Workers</font></font>
|
||||
</TD></TR>
|
||||
</TABLE>
|
||||
</TD>
|
||||
<TD>
|
||||
<TABLE BORDER="1" CELLBORDER="1" CELLSPACING="4">
|
||||
<!--font face="Helvetica"-->
|
||||
<TR>
|
||||
<TD rowspan="5" PORT="Rbac"><font face="Helvetica" point-size="12">RBAC</font></TD>
|
||||
<TD colspan="4" PORT="RestApi"><font face="Helvetica" point-size="12">REST API Service (Flask)</font></TD>
|
||||
</TR>
|
||||
<TR>
|
||||
<TD colspan="3" PORT="PythonObjectModel"><font face="Helvetica" point-size="12">Python Object Model</font></TD>
|
||||
<TD colspan="1" PORT="OsloCache"><font face="Helvetica" point-size="12">oslo.cache</font></TD>
|
||||
</TR>
|
||||
<TR>
|
||||
<TD colspan="2" PORT="InventoryFabric"><font face="Helvetica" point-size="12">Inventory Fabric</font></TD>
|
||||
<TD colspan="2" PORT="Workflows"><font face="Helvetica" point-size="12">Workflows</font></TD>
|
||||
</TR>
|
||||
<TR>
|
||||
<TD colspan="1" PORT="VirtualizedVariables"><font face="Helvetica" point-size="12">Virtualized <br/>Variables</font></TD>
|
||||
<TD colspan="2" PORT="DefaultInventoryModel"><font face="Helvetica" point-size="12">Default<br/>Inventory<br/>Model</font></TD>
|
||||
<TD colspan="1" PORT="TaskFlowController"><font face="Helvetica" point-size="12">TaskFlow<br/>Controller</font></TD>
|
||||
</TR>
|
||||
<TR>
|
||||
<TD colspan="1" PORT="VariablePlugin"><font face="Helvetica" point-size="12">Variable<br/>Plugin<br/>(Stevedore)</font></TD>
|
||||
<TD colspan="2" PORT="SqlAlchemy"><font face="Helvetica" point-size="12">SQL<br/>Alchemy</font></TD>
|
||||
<TD colspan="1" PORT="WorkflowPlugin"><font face="Helvetica" point-size="12">Workflow<br/>Plugin<br/>(Stevedore)</font></TD>
|
||||
</TR>
|
||||
<!--/font-->
|
||||
</TABLE>
|
||||
</TD>
|
||||
<TD>
|
||||
<TABLE BORDER="0" CELLBORDER="1" CELLSPACING="8">
|
||||
|
||||
<TR><TD COLSPAN="2" PORT="Redis"><font face="Helvetica" point-size="12">REDIS<br/></font>
|
||||
</TD></TR>
|
||||
|
||||
<TR><TD COLSPAN="2" PORT="MySqlGalera"><font face="Helvetica" point-size="12">MySQL/Galera<br/></font>
|
||||
</TD></TR>
|
||||
<TR>
|
||||
<TD PORT="TfJobBoard"><font face="Helvetica" point-size="12">TF<br/>JobBoard<br/></font>
|
||||
</TD>
|
||||
<TD PORT="WaLogCapture" bgcolor="#D6DBDF"><font face="Helvetica" point-size="12">WA Log<br/>Capture<br/></font>
|
||||
</TD>
|
||||
</TR>
|
||||
<TR>
|
||||
<TD ><font face="Helvetica" point-size="12">TF<br/>Worker<br/>Pool<br/></font>
|
||||
</TD>
|
||||
<TD bgcolor="#D7BDE2"><font face="Helvetica" point-size="12" >ZooKeeper<br/></font>
|
||||
</TD>
|
||||
</TR>
|
||||
|
||||
</TABLE>
|
||||
</TD>
|
||||
</TR>
|
||||
</TABLE>
|
||||
|
||||
>];
|
||||
|
||||
|
||||
NovaPlugin [label=<
|
||||
<TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0">
|
||||
<TR><TD PORT="NovaPlugin"><font face="Helvetica" point-size="12">Nova Plugin<br/><font point-size="8">
|
||||
(Inventory)</font></font>
|
||||
</TD></TR>
|
||||
</TABLE>>];
|
||||
|
||||
HistoryPlugin [label=<
|
||||
<TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0">
|
||||
<TR><TD PORT="HistoryPlugin" bgcolor="#D6DBDF"><font face="Helvetica" point-size="12">History Plugin<br/><font point-size="8">
|
||||
(Inventory)</font></font>
|
||||
</TD></TR>
|
||||
</TABLE>>];
|
||||
|
||||
AnsiblePlugin [label=<
|
||||
<TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0">
|
||||
<TR><TD PORT="AnsiblePlugin"><font face="Helvetica" point-size="12">Ansible Plugin<br/><font point-size="8">
|
||||
(Workflow)</font></font>
|
||||
</TD></TR>
|
||||
</TABLE>>];
|
||||
|
||||
HistoricalData [label=<
|
||||
<TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0">
|
||||
<TR><TD PORT="HistoricalData" bgcolor="#D6DBDF"><font face="Helvetica" point-size="12">Historica lData</font>
|
||||
</TD></TR>
|
||||
</TABLE>>];
|
||||
|
||||
|
||||
Legend [label=<
|
||||
<TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0">
|
||||
<TR><TD><font face="Helvetica" point-size="12">Legend</font>
|
||||
</TD></TR>
|
||||
<TR><TD border="1" bgcolor="#D7BDE2"><font face="Helvetica" point-size="10">Used For Scaling</font>
|
||||
</TD></TR>
|
||||
<TR><TD border="1" bgcolor="#D6DBDF"><font face="Helvetica" point-size="10">Future Work</font>
|
||||
</TD></TR>
|
||||
</TABLE>>];
|
||||
|
||||
|
||||
|
||||
//UndercloudIntegrations [pos="1,1"];
|
||||
#subgraph cluster1 {
|
||||
# style=invis;
|
||||
# Barbican;
|
||||
# Horizon;
|
||||
# Keystone;
|
||||
# }
|
||||
|
||||
ranksep=.25;
|
||||
#size = "8,8";
|
||||
#{ rank = same; Horizon; CratonCore:PythonObjectModel; }
|
||||
|
||||
#{ rank = same; UndercloudIntegrations; CratonCore; }
|
||||
#Horizon -> Keystone [style=invis]
|
||||
NovaPlugin -> Legend [style=invis];
|
||||
CratonCore:Barbican -> Legend [style=invis];
|
||||
|
||||
CratonCore:WaLogCapture -> HistoricalData:HistoricalData;
|
||||
HistoryPlugin:HistoryPlugin -> HistoricalData:HistoricalData;
|
||||
CratonCore:Horizon -> PythonApi:PythonApi [constraint=false];
|
||||
CratonCore:RBAC -> CratonCore:Keystone;
|
||||
PythonApi:PythonApi -> CratonCore:RestApi;
|
||||
Cli:Cli -> PythonApi:PythonApi;
|
||||
CratonCore:VariablePlugin -> NovaPlugin:NovaPlugin;
|
||||
CratonCore:VariablePlugin -> HistoryPlugin:HistoryPlugin;
|
||||
CratonCore:WorkflowPlugin -> AnsiblePlugin:AnsiblePlugin;
|
||||
CratonCore:OsloCache -> CratonCore:Redis [constraint=false];
|
||||
CratonCore:SqlAlchemy -> CratonCore:MySqlGalera;
|
||||
|
||||
}
|
@ -1,75 +0,0 @@
|
||||
Architecture
|
||||
============
|
||||
|
||||
|
||||
.. graphviz:: arch-diagram.dot
|
||||
|
||||
CLI
|
||||
---
|
||||
TODO: Add Documentation
|
||||
|
||||
Python API
|
||||
----------
|
||||
TODO: Add Documentation
|
||||
|
||||
RBAC
|
||||
----
|
||||
TODO: Add Documentation
|
||||
|
||||
REST API Service (Flask)
|
||||
------------------------
|
||||
TODO: Add Documentation
|
||||
|
||||
Python Object Model
|
||||
-------------------
|
||||
TODO: Add Documentation
|
||||
|
||||
oslo.cache
|
||||
----------
|
||||
TODO: Add Documentation
|
||||
|
||||
Inventory Fabric
|
||||
----------------
|
||||
TODO: Add Documentation
|
||||
|
||||
Workflows
|
||||
---------
|
||||
TODO: Add Documentation
|
||||
|
||||
Virtualized Variables
|
||||
---------------------
|
||||
TODO: Add Documentation
|
||||
|
||||
Default Inventory Mode
|
||||
----------------------
|
||||
TODO: Add Documentation
|
||||
|
||||
TaskFlow Controller
|
||||
-------------------
|
||||
TODO: Add Documentation
|
||||
|
||||
Variable Plugin (Stevedore)
|
||||
---------------------------
|
||||
TODO: Add Documentation
|
||||
|
||||
SQL Alchemy
|
||||
-----------
|
||||
TODO: Add Documentation
|
||||
|
||||
Workflow Plugin (Stevedore)
|
||||
---------------------------
|
||||
TODO: Add Documentation
|
||||
|
||||
Nova Plugin
|
||||
-----------
|
||||
TODO: Add Documentation
|
||||
|
||||
History Plugin
|
||||
--------------
|
||||
TODO: Add Documentation
|
||||
|
||||
Ansible Plugin
|
||||
--------------
|
||||
TODO: Add Documentation
|
||||
|
||||
|
@ -1,402 +0,0 @@
|
||||
.. _cells:
|
||||
|
||||
=====
|
||||
Cells
|
||||
=====
|
||||
|
||||
Definition of cell
|
||||
|
||||
Create Cell
|
||||
===========
|
||||
:POST: /v1/cells
|
||||
|
||||
Create a new Cell
|
||||
|
||||
Normal response codes: OK(201)
|
||||
|
||||
Error response codes: invalid request(400), validation exception(405)
|
||||
|
||||
Request
|
||||
-------
|
||||
|
||||
+------------+------+---------+-------------------------+
|
||||
| Name | In | Type | Description |
|
||||
+============+======+=========+=========================+
|
||||
| name | body | string | Unique name of the cell |
|
||||
+------------+------+---------+-------------------------+
|
||||
| region_id | body | integer | Unique ID of the region |
|
||||
+------------+------+---------+-------------------------+
|
||||
| labels | body | string | User defined labels |
|
||||
+------------+------+---------+-------------------------+
|
||||
| note | body | string | Note used for governance|
|
||||
+------------+------+---------+-------------------------+
|
||||
| variables | body | object | User defined variables |
|
||||
+------------+------+---------+-------------------------+
|
||||
|
||||
Required Header
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
- Content-Type: application/json
|
||||
- X-Auth-Token
|
||||
- X-Auth-User
|
||||
- X-Auth-Project
|
||||
|
||||
Example Cell Create
|
||||
*******************
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
curl -i "http://${MY_IP}:7780/v1/cells" \
|
||||
-d '{"name": "myCell", "region_id": 1}' \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "X-Auth-Token: demo" \
|
||||
-H "X-Auth-User: demo" \
|
||||
-H "X-Auth-Project: 717e9a216e2d44e0bc848398563bda06"
|
||||
|
||||
Response
|
||||
--------
|
||||
|
||||
+-----------+------+---------+-------------------------------+
|
||||
| Name | In | Type | Description |
|
||||
+===========+======+=========+===============================+
|
||||
| cell | body | object | - id |
|
||||
| | | | - name |
|
||||
| | | | - region_id |
|
||||
| | | | - labels |
|
||||
| | | | - note |
|
||||
| | | | - variables |
|
||||
+-----------+------+---------+-------------------------------+
|
||||
| id | body | integer | Unique ID of the cell |
|
||||
+-----------+------+---------+-------------------------------+
|
||||
| name | body | string | Unique name of the cell |
|
||||
+-----------+------+---------+-------------------------------+
|
||||
| region_id | body | integer | Unique ID of the cell's region|
|
||||
+-----------+------+---------+-------------------------------+
|
||||
| labels | body | string | User defined labels |
|
||||
+-----------+------+---------+-------------------------------+
|
||||
| note | body | string | Note used for governance |
|
||||
+-----------+------+---------+-------------------------------+
|
||||
| variables | body | object | User defined variables |
|
||||
+-----------+------+---------+-------------------------------+
|
||||
|
||||
Example Cell Create
|
||||
*******************
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"id": 1,
|
||||
"name": "myCell",
|
||||
"note": null,
|
||||
"region_id": 1
|
||||
}
|
||||
|
||||
List Cells
|
||||
==========
|
||||
|
||||
:GET: /v1/cells?region_id=
|
||||
|
||||
Gets all Cells
|
||||
|
||||
Normal response codes: OK(200)
|
||||
|
||||
Error response codes: invalid request(400), cell not found(404), validation exception(405)
|
||||
|
||||
Default response: unexpected error
|
||||
|
||||
Request
|
||||
-------
|
||||
|
||||
+-----------+-------+--------+---------+----------------------------------+
|
||||
| Name | In | Type | Required| Description |
|
||||
+===========+=======+========+=========+==================================+
|
||||
| region_id | query | string | Yes | ID of the region to get cells for|
|
||||
+-----------+-------+--------+---------+----------------------------------+
|
||||
|
||||
Required Header
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
- Content-Type: application/json
|
||||
- X-Auth-Token
|
||||
- X-Auth-User
|
||||
- X-Auth-Project
|
||||
|
||||
Example Cell List
|
||||
*****************
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
curl -i "http://${MY_IP}:7780/v1/cells?region_id=1" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "X-Auth-Token: demo" \
|
||||
-H "X-Auth-User: demo" \
|
||||
-H "X-Auth-Project: 717e9a216e2d44e0bc848398563bda06"
|
||||
|
||||
Response
|
||||
--------
|
||||
|
||||
+------------+------+---------+-------------------------------+
|
||||
| Name | In | Type | Description |
|
||||
+============+======+=========+===============================+
|
||||
| cells | body | array | Array of cell objects |
|
||||
+------------+------+---------+-------------------------------+
|
||||
| id | body | integer | Unique ID of the cell |
|
||||
+------------+------+---------+-------------------------------+
|
||||
| name | body | string | Unique name of the cell |
|
||||
+------------+------+---------+-------------------------------+
|
||||
| region_id | body | integer | Unique ID of the cell's region|
|
||||
+------------+------+---------+-------------------------------+
|
||||
| labels | body | string | User defined labels |
|
||||
+------------+------+---------+-------------------------------+
|
||||
| note | body | string | Note used for governance |
|
||||
+------------+------+---------+-------------------------------+
|
||||
| variables | body | object | User defined variables |
|
||||
+------------+------+---------+-------------------------------+
|
||||
|
||||
Example Cell List
|
||||
*****************
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
[
|
||||
{
|
||||
"id": 2,
|
||||
"name": "cellJr",
|
||||
"note": null,
|
||||
"region_id": 1
|
||||
},
|
||||
{
|
||||
"id": 1,
|
||||
"name": "myCell",
|
||||
"note": null,
|
||||
"region_id": 1
|
||||
}
|
||||
]
|
||||
|
||||
.. todo:: **Example Unexpected Error**
|
||||
|
||||
..literalinclude:: ./api_samples/errors/errors-unexpected-resp.json
|
||||
:language: javascript
|
||||
|
||||
Update Cells
|
||||
============
|
||||
|
||||
:PUT: /v1/cells/{id}
|
||||
|
||||
Update an existing cell
|
||||
|
||||
Normal response codes: OK(200)
|
||||
|
||||
Error response codes: invalid request(400), cell not found(404), validation exception(405)
|
||||
|
||||
Request
|
||||
-------
|
||||
|
||||
+----------+------+---------+------------------------------------+
|
||||
| Name | In | Type | Description |
|
||||
+==========+======+=========+====================================+
|
||||
| name | body | string | Unique name of the cell |
|
||||
+----------+------+---------+------------------------------------+
|
||||
| labels | body | string | User defined labels |
|
||||
+----------+------+---------+------------------------------------+
|
||||
| note | body | string | Note used for governance |
|
||||
+----------+------+---------+------------------------------------+
|
||||
|
||||
|
||||
Required Header
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
- Content-Type: application/json
|
||||
- X-Auth-Token
|
||||
- X-Auth-User
|
||||
- X-Auth-Project
|
||||
|
||||
Example Cell Update
|
||||
*******************
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
curl -i "http://${MY_IP}:7780/v1/cells/1" \
|
||||
-XPUT \
|
||||
-d '{"name": "changedName"}' \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "X-Auth-Token: demo" \
|
||||
-H "X-Auth-User: demo" \
|
||||
-H "X-Auth-Project: 717e9a216e2d44e0bc848398563bda06"
|
||||
|
||||
Response
|
||||
--------
|
||||
|
||||
+----------+------+---------+-------------------------------+
|
||||
| Name | In | Type | Description |
|
||||
+==========+======+=========+===============================+
|
||||
| cell | body | object | - id |
|
||||
| | | | - name |
|
||||
| | | | - region_id |
|
||||
| | | | - labels |
|
||||
| | | | - note |
|
||||
| | | | - variables |
|
||||
+----------+------+---------+-------------------------------+
|
||||
| id | body | integer | Unique ID of the cell |
|
||||
+----------+------+---------+-------------------------------+
|
||||
| name | body | string | Unique name of the cell |
|
||||
+----------+------+---------+-------------------------------+
|
||||
| region_id| body | integer | Unique ID of the cell's region|
|
||||
+----------+------+---------+-------------------------------+
|
||||
| labels | body | string | User defined labels |
|
||||
+----------+------+---------+-------------------------------+
|
||||
| note | body | string | Note used for governance |
|
||||
+----------+------+---------+-------------------------------+
|
||||
| variables| body | object | User defined variables |
|
||||
+----------+------+---------+-------------------------------+
|
||||
|
||||
Examples Cell Update
|
||||
********************
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"id": 1,
|
||||
"name": "changedName",
|
||||
"note": null,
|
||||
"project_id": "717e9a21-6e2d-44e0-bc84-8398563bda06",
|
||||
"region_id": 1
|
||||
}
|
||||
|
||||
Update Cell Variables
|
||||
=====================
|
||||
|
||||
:PUT: /v1/cells/{id}/variables
|
||||
|
||||
Update user defined variables for the cell
|
||||
|
||||
Normal response codes: OK(200)
|
||||
|
||||
Error response codes: invalid request(400), cell not found(404), validation exception(405)
|
||||
|
||||
Request
|
||||
-------
|
||||
|
||||
+--------+------+---------+------------------------------------+
|
||||
| Name | In | Type | Description |
|
||||
+========+======+=========+====================================+
|
||||
| key | body | string | Identifier |
|
||||
+--------+------+---------+------------------------------------+
|
||||
| value | body | object | Data |
|
||||
+--------+------+---------+------------------------------------+
|
||||
| id | path | integer | Unique ID of the cell to be updated|
|
||||
+--------+------+---------+------------------------------------+
|
||||
|
||||
Required Header
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
- Content-Type: application/json
|
||||
- X-Auth-Token
|
||||
- X-Auth-User
|
||||
- X-Auth-Project
|
||||
|
||||
Example Cell Update Variables
|
||||
*****************************
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
curl -i "http://${MY_IP}:7780/v1/cells/1/variables" \
|
||||
-XPUT \
|
||||
-d '{"newKey": "sampleKey"}' \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "X-Auth-Token: demo" \
|
||||
-H "X-Auth-User: demo" \
|
||||
-H "X-Auth-Project: 717e9a216e2d44e0bc848398563bda06"
|
||||
|
||||
Response
|
||||
--------
|
||||
|
||||
+--------+------+---------+-------------------------+
|
||||
| Name | In | Type | Description |
|
||||
+========+======+=========+=========================+
|
||||
| key | body | string | Identifier |
|
||||
+--------+------+---------+-------------------------+
|
||||
| value | body | object | Data |
|
||||
+--------+------+---------+-------------------------+
|
||||
|
||||
Example Cell Update Variables
|
||||
*****************************
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"variables":
|
||||
{
|
||||
"newKey": “sampleKey”
|
||||
}
|
||||
}
|
||||
|
||||
Delete Cell
|
||||
===========
|
||||
|
||||
:DELETE: /v1/cells/{id}
|
||||
|
||||
Deletes an existing record of a Cell
|
||||
|
||||
Normal response codes: no content(204)
|
||||
|
||||
Error response codes: invalid request(400), cell not found(404)
|
||||
|
||||
Request
|
||||
-------
|
||||
|
||||
+--------+------+---------+------------------------------------+
|
||||
| Name | In | Type | Description |
|
||||
+========+======+=========+====================================+
|
||||
| id | path | integer | Unique ID of the cell to be deleted|
|
||||
+--------+------+---------+------------------------------------+
|
||||
|
||||
Required Header
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
- Content-Type: application/json
|
||||
- X-Auth-Token
|
||||
- X-Auth-User
|
||||
- X-Auth-Project
|
||||
|
||||
Response
|
||||
--------
|
||||
|
||||
No body content is returned on a successful DELETE
|
||||
|
||||
Delete Cell Variables
|
||||
=====================
|
||||
|
||||
:DELETE: /v1/cells/{id}/variables
|
||||
|
||||
Delete existing key/value variables for the cell
|
||||
|
||||
Normal response codes: no content(204)
|
||||
|
||||
Error response codes: invalid request(400), cell not found(404) validation exception(405)
|
||||
|
||||
Request
|
||||
-------
|
||||
|
||||
+--------+------+---------+-------------------------+
|
||||
| Name | In | Type | Description |
|
||||
+========+======+=========+=========================+
|
||||
| id | path | integer | Unique ID of the cell |
|
||||
+--------+------+---------+-------------------------+
|
||||
| key | body | string | Identifier to be deleted|
|
||||
+--------+------+---------+-------------------------+
|
||||
| value | body | object | Data to be deleted |
|
||||
+--------+------+---------+-------------------------+
|
||||
|
||||
Required Header
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
- Content-Type: application/json
|
||||
- X-Auth-Token
|
||||
- X-Auth-User
|
||||
- X-Auth-Project
|
||||
|
||||
Response
|
||||
--------
|
||||
|
||||
No body content is returned on a successful DELETE
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user