Retire Packaging Deb project repos
This commit is part of a series to retire the Packaging Deb project. Step 2 is to remove all content from the project repos, replacing it with a README notification where to find ongoing work, and how to recover the repo if needed at some future point (as in https://docs.openstack.org/infra/manual/drivers.html#retiring-a-project). Change-Id: I7d3174127fb51713fd590f6be2edd875a1a6e8d8
This commit is contained in:
parent
e1a1fa4621
commit
edeb6f11f3
42
.gitignore
vendored
42
.gitignore
vendored
@ -1,42 +0,0 @@
|
|||||||
*.py[co]
|
|
||||||
*.egg
|
|
||||||
*.egg-info
|
|
||||||
dist
|
|
||||||
build
|
|
||||||
eggs
|
|
||||||
parts
|
|
||||||
var
|
|
||||||
sdist
|
|
||||||
develop-eggs
|
|
||||||
.installed.cfg
|
|
||||||
pip-log.txt
|
|
||||||
.tox
|
|
||||||
*.mo
|
|
||||||
.mr.developer.cfg
|
|
||||||
.DS_Store
|
|
||||||
Thumbs.db
|
|
||||||
.venv
|
|
||||||
.idea
|
|
||||||
out
|
|
||||||
target
|
|
||||||
*.iml
|
|
||||||
*.ipr
|
|
||||||
*.iws
|
|
||||||
doc/html
|
|
||||||
doc/source/apidoc
|
|
||||||
doc/source/api
|
|
||||||
doc/build
|
|
||||||
*.db
|
|
||||||
.coverage
|
|
||||||
nosetests.xml
|
|
||||||
pylint-report.txt
|
|
||||||
ChangeLog
|
|
||||||
cscope.out
|
|
||||||
horizon
|
|
||||||
trove_dashboard/test/configs/config.conf
|
|
||||||
trove_dashboard/test/.secret_key_store
|
|
||||||
AUTHORS
|
|
||||||
*.lock
|
|
||||||
|
|
||||||
# Files created by releasenotes build
|
|
||||||
releasenotes/build
|
|
@ -1,4 +0,0 @@
|
|||||||
[gerrit]
|
|
||||||
host=review.openstack.org
|
|
||||||
port=29418
|
|
||||||
project=openstack/trove-dashboard.git
|
|
@ -1,20 +0,0 @@
|
|||||||
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
|
|
||||||
|
|
||||||
You can find more trove specific info:
|
|
||||||
|
|
||||||
http://docs.openstack.org/developer/trove/
|
|
||||||
|
|
||||||
Once those steps have been completed, changes to OpenStack
|
|
||||||
should be submitted for review via the Gerrit tool, following
|
|
||||||
the workflow documented at:
|
|
||||||
|
|
||||||
http://docs.openstack.org/infra/manual/developers.html#development-workflow
|
|
||||||
|
|
||||||
Pull requests submitted through GitHub will be ignored.
|
|
||||||
|
|
||||||
Bugs should be filed on Launchpad, not GitHub:
|
|
||||||
|
|
||||||
https://bugs.launchpad.net/trove
|
|
12
HACKING.rst
12
HACKING.rst
@ -1,12 +0,0 @@
|
|||||||
Trove Style Commandments
|
|
||||||
==========================
|
|
||||||
|
|
||||||
- Step 1: Read the OpenStack Style Commandments
|
|
||||||
http://docs.openstack.org/developer/hacking/
|
|
||||||
- Step 2: Read on
|
|
||||||
|
|
||||||
Trove Specific Commandments
|
|
||||||
-----------------------------
|
|
||||||
|
|
||||||
None so far
|
|
||||||
|
|
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.
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
|||||||
recursive-include trove_dashboard *.html *.scss *.js
|
|
||||||
|
|
||||||
include AUTHORS
|
|
||||||
include ChangeLog
|
|
14
README
Normal file
14
README
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
This project is no longer maintained.
|
||||||
|
|
||||||
|
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".
|
||||||
|
|
||||||
|
For ongoing work on maintaining OpenStack packages in the Debian
|
||||||
|
distribution, please see the Debian OpenStack packaging team at
|
||||||
|
https://wiki.debian.org/OpenStack/.
|
||||||
|
|
||||||
|
For any further questions, please email
|
||||||
|
openstack-dev@lists.openstack.org or join #openstack-dev on
|
||||||
|
Freenode.
|
99
README.rst
99
README.rst
@ -1,99 +0,0 @@
|
|||||||
OpenStack Dashboard plugin for Trove project
|
|
||||||
============================================
|
|
||||||
|
|
||||||
.. image:: http://governance.openstack.org/badges/trove-dashboard.svg
|
|
||||||
:target: http://governance.openstack.org/reference/tags/index.html
|
|
||||||
|
|
||||||
|
|
||||||
How to use with Horizon on server:
|
|
||||||
----------------------------------
|
|
||||||
|
|
||||||
Use pip to install the package on the server running Horizon. Then either copy
|
|
||||||
or link the files in trove_dashboard/enabled to
|
|
||||||
openstack_dashboard/local/enabled. This step will cause the Horizon service to
|
|
||||||
pick up the trove plugin when it starts.
|
|
||||||
|
|
||||||
How to use with devstack:
|
|
||||||
-------------------------
|
|
||||||
|
|
||||||
Add the following to your devstack ``local.conf`` file::
|
|
||||||
|
|
||||||
enable_plugin trove-dashboard git://git.openstack.org/openstack/trove-dashboard
|
|
||||||
|
|
||||||
|
|
||||||
To run unit tests:
|
|
||||||
------------------
|
|
||||||
::
|
|
||||||
|
|
||||||
./run_tests.sh
|
|
||||||
|
|
||||||
Editing Code
|
|
||||||
------------
|
|
||||||
|
|
||||||
Apache
|
|
||||||
~~~~~~
|
|
||||||
|
|
||||||
Make a change to trove-dashboard then goto to the horizon directory and
|
|
||||||
compress the code with django and then restart apache.::
|
|
||||||
|
|
||||||
# rsync code to /opt/stack/trove-dashboard
|
|
||||||
# copy or link files from trove-dashboard/enabled/* to horizon/openstack_dashboard/local/enabled/
|
|
||||||
cd /opt/stack/horizon
|
|
||||||
python manage.py compress
|
|
||||||
python manage.py collectstatic --noinput
|
|
||||||
sudo service apache2 restart
|
|
||||||
|
|
||||||
|
|
||||||
Django
|
|
||||||
~~~~~~
|
|
||||||
|
|
||||||
You can also speed up development time using the django test server instead of
|
|
||||||
apache.::
|
|
||||||
|
|
||||||
/opt/stack/horizon/run_tests.sh --runserver
|
|
||||||
|
|
||||||
If you set COMPRESS_ENABLED and COMPRESS_OFFLINE to False in local_settings.py
|
|
||||||
that allows you to bypass the compress and collectstatic as well.
|
|
||||||
|
|
||||||
|
|
||||||
Settings
|
|
||||||
~~~~~~~~
|
|
||||||
|
|
||||||
The use of a cross-process cache such as Memcached is required.
|
|
||||||
|
|
||||||
Install Memcached itself and a Memcached binding such as python-memcached.
|
|
||||||
|
|
||||||
For a single horizon instance use the CACHES setting like the example below.
|
|
||||||
|
|
||||||
CACHES = {
|
|
||||||
'default': {
|
|
||||||
'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
|
|
||||||
'LOCATION': '127.0.0.1:11211',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
For multiple horizon instances behind a load balancer configure each instance
|
|
||||||
to use the same cache like the example below.
|
|
||||||
|
|
||||||
CACHES = {
|
|
||||||
'default': {
|
|
||||||
'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
|
|
||||||
'LOCATION': [u'10.2.100.133:11211', u'10.2.100.134:11211'']
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
NOTE:
|
|
||||||
=====
|
|
||||||
|
|
||||||
As of the Mitaka release, the dashboard for trove is now maintained outside of
|
|
||||||
the Horizon codebase, in this repository.
|
|
||||||
|
|
||||||
Links:
|
|
||||||
------
|
|
||||||
|
|
||||||
Trove project: https://git.openstack.org/openstack/trove
|
|
||||||
|
|
||||||
Trove at wiki.openstack.org: https://wiki.openstack.org/wiki/Trove
|
|
||||||
|
|
||||||
Launchpad project: https://launchpad.net/trove-dashboard
|
|
@ -1,5 +0,0 @@
|
|||||||
[extractors]
|
|
||||||
django = django_babel.extract:extract_django
|
|
||||||
|
|
||||||
[python: **.py]
|
|
||||||
[django: **/templates/**.html]
|
|
@ -1,14 +0,0 @@
|
|||||||
[extractors]
|
|
||||||
# We use a custom extractor to find translatable strings in AngularJS
|
|
||||||
# templates. The extractor is included in horizon.utils for now.
|
|
||||||
# See http://babel.pocoo.org/docs/messages/#referencing-extraction-methods for
|
|
||||||
# details on how this works.
|
|
||||||
angular = horizon.utils.babel_extract_angular:extract_angular
|
|
||||||
|
|
||||||
[javascript: **.js]
|
|
||||||
|
|
||||||
# We need to look into all static folders for HTML files.
|
|
||||||
# The **/static ensures that we also search within
|
|
||||||
# /openstack_dashboard/dashboards/XYZ/static which will ensure
|
|
||||||
# that plugins are also translated.
|
|
||||||
[angular: **/static/**.html]
|
|
@ -1,52 +0,0 @@
|
|||||||
# plugin.sh - DevStack plugin.sh dispatch script trove-dashboard
|
|
||||||
|
|
||||||
TROVE_DASHBOARD_DIR=$(cd $(dirname $BASH_SOURCE)/.. && pwd)
|
|
||||||
|
|
||||||
function install_trove_dashboard {
|
|
||||||
setup_develop ${TROVE_DASHBOARD_DIR}
|
|
||||||
}
|
|
||||||
|
|
||||||
function configure_trove_dashboard {
|
|
||||||
cp -a ${TROVE_DASHBOARD_DIR}/trove_dashboard/enabled/* ${DEST}/horizon/openstack_dashboard/local/enabled/
|
|
||||||
# NOTE: If locale directory does not exist, compilemessages will fail,
|
|
||||||
# so check for an existence of locale directory is required.
|
|
||||||
if [ -d ${TROVE_DASHBOARD_DIR}/trove_dashboard/locale ]; then
|
|
||||||
(cd ${TROVE_DASHBOARD_DIR}/trove_dashboard; DJANGO_SETTINGS_MODULE=openstack_dashboard.settings ../manage.py compilemessages)
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# check for service enabled
|
|
||||||
if is_service_enabled trove-dashboard; then
|
|
||||||
|
|
||||||
if [[ "$1" == "stack" && "$2" == "pre-install" ]]; then
|
|
||||||
# Set up system services
|
|
||||||
# no-op
|
|
||||||
:
|
|
||||||
|
|
||||||
elif [[ "$1" == "stack" && "$2" == "install" ]]; then
|
|
||||||
# Perform installation of service source
|
|
||||||
echo_summary "Installing Trove UI"
|
|
||||||
install_trove_dashboard
|
|
||||||
|
|
||||||
elif [[ "$1" == "stack" && "$2" == "post-config" ]]; then
|
|
||||||
# Configure after the other layer 1 and 2 services have been configured
|
|
||||||
echo_summary "Configurng Trove UI"
|
|
||||||
configure_trove_dashboard
|
|
||||||
|
|
||||||
elif [[ "$1" == "stack" && "$2" == "extra" ]]; then
|
|
||||||
# no-op
|
|
||||||
:
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ "$1" == "unstack" ]]; then
|
|
||||||
# no-op
|
|
||||||
:
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ "$1" == "clean" ]]; then
|
|
||||||
# Remove state and transient data
|
|
||||||
# Remember clean.sh first calls unstack.sh
|
|
||||||
# no-op
|
|
||||||
:
|
|
||||||
fi
|
|
||||||
fi
|
|
@ -1,2 +0,0 @@
|
|||||||
# settings file for trove-dashboard plugin
|
|
||||||
enable_service trove-dashboard
|
|
23
manage.py
23
manage.py
@ -1,23 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
|
|
||||||
from django.core.management import execute_from_command_line
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE",
|
|
||||||
"trove_dashboard.test.settings")
|
|
||||||
execute_from_command_line(sys.argv)
|
|
@ -1,4 +0,0 @@
|
|||||||
---
|
|
||||||
fixes:
|
|
||||||
- Display all endpoints in the dashboard's cluster
|
|
||||||
details endpoint list.
|
|
@ -1,7 +0,0 @@
|
|||||||
---
|
|
||||||
features:
|
|
||||||
- Support configuration groups in the dashboard. This
|
|
||||||
includes creating and deleting groups; adding,
|
|
||||||
editing and removing parameters; attaching and
|
|
||||||
detaching groups to running instances; and specifying
|
|
||||||
a group during instance creation.
|
|
@ -1,3 +0,0 @@
|
|||||||
---
|
|
||||||
features:
|
|
||||||
- Enable cluster support for MariaDB and Cassandra.
|
|
@ -1,3 +0,0 @@
|
|||||||
---
|
|
||||||
fixes:
|
|
||||||
- Enable cluster grow and shrink panels for pxc.
|
|
@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
fixes:
|
|
||||||
- The current caching initializes the instance list for the grow
|
|
||||||
cluster manager each time a new process would instantiate the
|
|
||||||
manager. Changed to manager to only initialize on creation.
|
|
@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
features:
|
|
||||||
- Added support for logging features in the dashboard.
|
|
||||||
This includes listing logs that can be retrieved and
|
|
||||||
viewing, publishing and stop collection of log contents.
|
|
@ -1,293 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
# Trove Dashboard Release Notes documentation build configuration file, created by
|
|
||||||
# sphinx-quickstart on Thu Apr 14 18:07:45 2016.
|
|
||||||
#
|
|
||||||
# This file is execfile()d with the current directory set to its
|
|
||||||
# containing dir.
|
|
||||||
#
|
|
||||||
# Note that not all possible configuration values are present in this
|
|
||||||
# autogenerated file.
|
|
||||||
#
|
|
||||||
# All configuration values have a default; values that are commented out
|
|
||||||
# serve to show the default.
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
|
|
||||||
# If extensions (or modules to document with autodoc) are in another directory,
|
|
||||||
# add these directories to sys.path here. If the directory is relative to the
|
|
||||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
|
||||||
#sys.path.insert(0, os.path.abspath('.'))
|
|
||||||
|
|
||||||
# -- General configuration ------------------------------------------------
|
|
||||||
|
|
||||||
# If your documentation needs a minimal Sphinx version, state it here.
|
|
||||||
#needs_sphinx = '1.0'
|
|
||||||
|
|
||||||
# Add any Sphinx extension module names here, as strings. They can be
|
|
||||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
|
||||||
# ones.
|
|
||||||
extensions = [
|
|
||||||
'oslosphinx',
|
|
||||||
'reno.sphinxext',
|
|
||||||
]
|
|
||||||
|
|
||||||
# Add any paths that contain templates here, relative to this directory.
|
|
||||||
templates_path = ['_templates']
|
|
||||||
|
|
||||||
# The suffix(es) of source filenames.
|
|
||||||
# You can specify multiple suffix as a list of string:
|
|
||||||
# source_suffix = ['.rst', '.md']
|
|
||||||
source_suffix = '.rst'
|
|
||||||
|
|
||||||
# The encoding of source files.
|
|
||||||
#source_encoding = 'utf-8-sig'
|
|
||||||
|
|
||||||
# The master toctree document.
|
|
||||||
master_doc = 'index'
|
|
||||||
|
|
||||||
# General information about the project.
|
|
||||||
project = u'Trove Dashboard Release Notes'
|
|
||||||
copyright = u'2016, Trove developers'
|
|
||||||
author = u'Trove developers'
|
|
||||||
|
|
||||||
# The version info for the project you're documenting, acts as replacement for
|
|
||||||
# |version| and |release|, also used in various other places throughout the
|
|
||||||
# built documents.
|
|
||||||
#
|
|
||||||
from trove_dashboard.version import version_info as trove_version
|
|
||||||
# The short X.Y version.
|
|
||||||
version = trove_version.canonical_version_string()
|
|
||||||
# The full version, including alpha/beta/rc tags.
|
|
||||||
release = trove_version.version_string_with_vcs()
|
|
||||||
|
|
||||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
|
||||||
# for a list of supported languages.
|
|
||||||
#
|
|
||||||
# This is also used if you do content translation via gettext catalogs.
|
|
||||||
# Usually you set "language" from the command line for these cases.
|
|
||||||
language = None
|
|
||||||
|
|
||||||
# There are two options for replacing |today|: either, you set today to some
|
|
||||||
# non-false value, then it is used:
|
|
||||||
#today = ''
|
|
||||||
# Else, today_fmt is used as the format for a strftime call.
|
|
||||||
#today_fmt = '%B %d, %Y'
|
|
||||||
|
|
||||||
# List of patterns, relative to source directory, that match files and
|
|
||||||
# directories to ignore when looking for source files.
|
|
||||||
# This patterns also effect to html_static_path and html_extra_path
|
|
||||||
exclude_patterns = []
|
|
||||||
|
|
||||||
# The reST default role (used for this markup: `text`) to use for all
|
|
||||||
# documents.
|
|
||||||
#default_role = None
|
|
||||||
|
|
||||||
# If true, '()' will be appended to :func: etc. cross-reference text.
|
|
||||||
#add_function_parentheses = True
|
|
||||||
|
|
||||||
# If true, the current module name will be prepended to all description
|
|
||||||
# unit titles (such as .. function::).
|
|
||||||
#add_module_names = True
|
|
||||||
|
|
||||||
# If true, sectionauthor and moduleauthor directives will be shown in the
|
|
||||||
# output. They are ignored by default.
|
|
||||||
#show_authors = False
|
|
||||||
|
|
||||||
# The name of the Pygments (syntax highlighting) style to use.
|
|
||||||
pygments_style = 'sphinx'
|
|
||||||
|
|
||||||
# A list of ignored prefixes for module index sorting.
|
|
||||||
#modindex_common_prefix = []
|
|
||||||
|
|
||||||
# If true, keep warnings as "system message" paragraphs in the built documents.
|
|
||||||
#keep_warnings = False
|
|
||||||
|
|
||||||
# If true, `todo` and `todoList` produce output, else they produce nothing.
|
|
||||||
todo_include_todos = False
|
|
||||||
|
|
||||||
|
|
||||||
# -- Options for HTML output ----------------------------------------------
|
|
||||||
|
|
||||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
|
||||||
# a list of builtin themes.
|
|
||||||
html_theme = 'default'
|
|
||||||
|
|
||||||
# Theme options are theme-specific and customize the look and feel of a theme
|
|
||||||
# further. For a list of options available for each theme, see the
|
|
||||||
# documentation.
|
|
||||||
#html_theme_options = {}
|
|
||||||
|
|
||||||
# Add any paths that contain custom themes here, relative to this directory.
|
|
||||||
#html_theme_path = []
|
|
||||||
|
|
||||||
# The name for this set of Sphinx documents.
|
|
||||||
# "<project> v<release> documentation" by default.
|
|
||||||
#html_title = u'Trove Dashboard Release Notes vblah'
|
|
||||||
|
|
||||||
# A shorter title for the navigation bar. Default is the same as html_title.
|
|
||||||
#html_short_title = None
|
|
||||||
|
|
||||||
# The name of an image file (relative to this directory) to place at the top
|
|
||||||
# of the sidebar.
|
|
||||||
#html_logo = None
|
|
||||||
|
|
||||||
# The name of an image file (relative to this directory) to use as a favicon of
|
|
||||||
# the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
|
||||||
# pixels large.
|
|
||||||
#html_favicon = None
|
|
||||||
|
|
||||||
# Add any paths that contain custom static files (such as style sheets) here,
|
|
||||||
# relative to this directory. They are copied after the builtin static files,
|
|
||||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
|
||||||
html_static_path = ['_static']
|
|
||||||
|
|
||||||
# Add any extra paths that contain custom files (such as robots.txt or
|
|
||||||
# .htaccess) here, relative to this directory. These files are copied
|
|
||||||
# directly to the root of the documentation.
|
|
||||||
#html_extra_path = []
|
|
||||||
|
|
||||||
# If not None, a 'Last updated on:' timestamp is inserted at every page
|
|
||||||
# bottom, using the given strftime format.
|
|
||||||
# The empty string is equivalent to '%b %d, %Y'.
|
|
||||||
#html_last_updated_fmt = None
|
|
||||||
|
|
||||||
# If true, SmartyPants will be used to convert quotes and dashes to
|
|
||||||
# typographically correct entities.
|
|
||||||
#html_use_smartypants = True
|
|
||||||
|
|
||||||
# Custom sidebar templates, maps document names to template names.
|
|
||||||
#html_sidebars = {}
|
|
||||||
|
|
||||||
# Additional templates that should be rendered to pages, maps page names to
|
|
||||||
# template names.
|
|
||||||
#html_additional_pages = {}
|
|
||||||
|
|
||||||
# If false, no module index is generated.
|
|
||||||
#html_domain_indices = True
|
|
||||||
|
|
||||||
# If false, no index is generated.
|
|
||||||
#html_use_index = True
|
|
||||||
|
|
||||||
# If true, the index is split into individual pages for each letter.
|
|
||||||
#html_split_index = False
|
|
||||||
|
|
||||||
# If true, links to the reST sources are added to the pages.
|
|
||||||
#html_show_sourcelink = True
|
|
||||||
|
|
||||||
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
|
|
||||||
#html_show_sphinx = True
|
|
||||||
|
|
||||||
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
|
|
||||||
#html_show_copyright = True
|
|
||||||
|
|
||||||
# If true, an OpenSearch description file will be output, and all pages will
|
|
||||||
# contain a <link> tag referring to it. The value of this option must be the
|
|
||||||
# base URL from which the finished HTML is served.
|
|
||||||
#html_use_opensearch = ''
|
|
||||||
|
|
||||||
# This is the file name suffix for HTML files (e.g. ".xhtml").
|
|
||||||
#html_file_suffix = None
|
|
||||||
|
|
||||||
# Language to be used for generating the HTML full-text search index.
|
|
||||||
# Sphinx supports the following languages:
|
|
||||||
# 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja'
|
|
||||||
# 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr', 'zh'
|
|
||||||
#html_search_language = 'en'
|
|
||||||
|
|
||||||
# A dictionary with options for the search language support, empty by default.
|
|
||||||
# 'ja' uses this config value.
|
|
||||||
# 'zh' user can custom change `jieba` dictionary path.
|
|
||||||
#html_search_options = {'type': 'default'}
|
|
||||||
|
|
||||||
# The name of a javascript file (relative to the configuration directory) that
|
|
||||||
# implements a search results scorer. If empty, the default will be used.
|
|
||||||
#html_search_scorer = 'scorer.js'
|
|
||||||
|
|
||||||
# Output file base name for HTML help builder.
|
|
||||||
htmlhelp_basename = 'TroveDashboardReleaseNotesdoc'
|
|
||||||
|
|
||||||
# -- Options for LaTeX output ---------------------------------------------
|
|
||||||
|
|
||||||
latex_elements = {
|
|
||||||
# The paper size ('letterpaper' or 'a4paper').
|
|
||||||
#'papersize': 'letterpaper',
|
|
||||||
|
|
||||||
# The font size ('10pt', '11pt' or '12pt').
|
|
||||||
#'pointsize': '10pt',
|
|
||||||
|
|
||||||
# Additional stuff for the LaTeX preamble.
|
|
||||||
#'preamble': '',
|
|
||||||
|
|
||||||
# Latex figure (float) alignment
|
|
||||||
#'figure_align': 'htbp',
|
|
||||||
}
|
|
||||||
|
|
||||||
# Grouping the document tree into LaTeX files. List of tuples
|
|
||||||
# (source start file, target name, title,
|
|
||||||
# author, documentclass [howto, manual, or own class]).
|
|
||||||
latex_documents = [
|
|
||||||
(master_doc, 'TroveDashboardReleaseNotes.tex', u'Trove Dashboard Release Notes Documentation',
|
|
||||||
u'Trove developers', 'manual'),
|
|
||||||
]
|
|
||||||
|
|
||||||
# The name of an image file (relative to this directory) to place at the top of
|
|
||||||
# the title page.
|
|
||||||
#latex_logo = None
|
|
||||||
|
|
||||||
# For "manual" documents, if this is true, then toplevel headings are parts,
|
|
||||||
# not chapters.
|
|
||||||
#latex_use_parts = False
|
|
||||||
|
|
||||||
# If true, show page references after internal links.
|
|
||||||
#latex_show_pagerefs = False
|
|
||||||
|
|
||||||
# If true, show URL addresses after external links.
|
|
||||||
#latex_show_urls = False
|
|
||||||
|
|
||||||
# Documents to append as an appendix to all manuals.
|
|
||||||
#latex_appendices = []
|
|
||||||
|
|
||||||
# If false, no module index is generated.
|
|
||||||
#latex_domain_indices = True
|
|
||||||
|
|
||||||
|
|
||||||
# -- Options for manual page output ---------------------------------------
|
|
||||||
|
|
||||||
# One entry per manual page. List of tuples
|
|
||||||
# (source start file, name, description, authors, manual section).
|
|
||||||
man_pages = [
|
|
||||||
(master_doc, 'trovedashboardreleasenotes', u'Trove Dashboard Release Notes Documentation',
|
|
||||||
[author], 1)
|
|
||||||
]
|
|
||||||
|
|
||||||
# If true, show URL addresses after external links.
|
|
||||||
#man_show_urls = False
|
|
||||||
|
|
||||||
|
|
||||||
# -- Options for Texinfo output -------------------------------------------
|
|
||||||
|
|
||||||
# Grouping the document tree into Texinfo files. List of tuples
|
|
||||||
# (source start file, target name, title, author,
|
|
||||||
# dir menu entry, description, category)
|
|
||||||
texinfo_documents = [
|
|
||||||
(master_doc, 'TroveDashboardReleaseNotes', u'Trove Dashboard Release Notes Documentation',
|
|
||||||
author, 'TroveDashboardReleaseNotes', 'One line description of project.',
|
|
||||||
'Miscellaneous'),
|
|
||||||
]
|
|
||||||
|
|
||||||
# Documents to append as an appendix to all manuals.
|
|
||||||
#texinfo_appendices = []
|
|
||||||
|
|
||||||
# If false, no module index is generated.
|
|
||||||
#texinfo_domain_indices = True
|
|
||||||
|
|
||||||
# How to display URL addresses: 'footnote', 'no', or 'inline'.
|
|
||||||
#texinfo_show_urls = 'footnote'
|
|
||||||
|
|
||||||
# If true, do not generate a @detailmenu in the "Top" node's menu.
|
|
||||||
#texinfo_no_detailmenu = False
|
|
||||||
|
|
||||||
# -- Options for Internationalization output ------------------------------
|
|
||||||
locale_dirs = ['locale/']
|
|
@ -1,18 +0,0 @@
|
|||||||
=============================
|
|
||||||
Trove Dashboard Release Notes
|
|
||||||
=============================
|
|
||||||
|
|
||||||
Contents:
|
|
||||||
|
|
||||||
.. toctree::
|
|
||||||
:maxdepth: 2
|
|
||||||
|
|
||||||
unreleased
|
|
||||||
ocata
|
|
||||||
newton
|
|
||||||
|
|
||||||
|
|
||||||
Search
|
|
||||||
======
|
|
||||||
|
|
||||||
* :ref:`search`
|
|
@ -1,90 +0,0 @@
|
|||||||
# Robert Simai <robert.simai@suse.com>, 2016. #zanata
|
|
||||||
# Robert Simai <robert.simai@suse.com>, 2017. #zanata
|
|
||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Project-Id-Version: Trove Dashboard Release Notes 8.0.0\n"
|
|
||||||
"Report-Msgid-Bugs-To: \n"
|
|
||||||
"POT-Creation-Date: 2017-04-09 20:48+0000\n"
|
|
||||||
"MIME-Version: 1.0\n"
|
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
|
||||||
"PO-Revision-Date: 2017-04-10 09:37+0000\n"
|
|
||||||
"Last-Translator: Robert Simai <robert.simai@suse.com>\n"
|
|
||||||
"Language-Team: German\n"
|
|
||||||
"Language: de\n"
|
|
||||||
"X-Generator: Zanata 3.9.6\n"
|
|
||||||
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
|
|
||||||
|
|
||||||
msgid "7.0.0"
|
|
||||||
msgstr "7.0.0"
|
|
||||||
|
|
||||||
msgid "8.0.0"
|
|
||||||
msgstr "8.0.0"
|
|
||||||
|
|
||||||
msgid ":ref:`search`"
|
|
||||||
msgstr ":ref:`search`"
|
|
||||||
|
|
||||||
msgid ""
|
|
||||||
"Added support for logging features in the dashboard. This includes listing "
|
|
||||||
"logs that can be retrieved and viewing, publishing and stop collection of "
|
|
||||||
"log contents."
|
|
||||||
msgstr ""
|
|
||||||
"Unterstützung für Loggingfunktionen im Dashboard hinzugefügt. Dies "
|
|
||||||
"beinhaltet die Auflistung abrufbarer Logdateien, sowie betrachten, "
|
|
||||||
"publizieren und stoppen der Aufzeichnung von Loginhalten."
|
|
||||||
|
|
||||||
msgid "Bug Fixes"
|
|
||||||
msgstr "Fehlerkorrekturen"
|
|
||||||
|
|
||||||
msgid "Contents:"
|
|
||||||
msgstr "Inhalt:"
|
|
||||||
|
|
||||||
msgid "Current Series Release Notes"
|
|
||||||
msgstr "Aktuelle Serie Releasenotes"
|
|
||||||
|
|
||||||
msgid "Display all endpoints in the dashboard's cluster details endpoint list."
|
|
||||||
msgstr ""
|
|
||||||
"Zeige alle Endpunkte in der Endpunktliste in den Clusterdetails im "
|
|
||||||
"Dashboard ."
|
|
||||||
|
|
||||||
msgid "Enable cluster grow and shrink panels for pxc."
|
|
||||||
msgstr "Aktivieren von Panels für pxc zum wachsen und schrumpfen von Clustern"
|
|
||||||
|
|
||||||
msgid "Enable cluster support for MariaDB and Cassandra."
|
|
||||||
msgstr "Clusterunterstützung für MariaDB und Cassandra aktiviert."
|
|
||||||
|
|
||||||
msgid "New Features"
|
|
||||||
msgstr "Neue Funktionen"
|
|
||||||
|
|
||||||
msgid "Newton Series Release Notes"
|
|
||||||
msgstr "Newton Serie Releasenotes"
|
|
||||||
|
|
||||||
msgid "Ocata Series Release Notes"
|
|
||||||
msgstr "Ocata Serie Releasenotes"
|
|
||||||
|
|
||||||
msgid "Search"
|
|
||||||
msgstr "Suche"
|
|
||||||
|
|
||||||
msgid ""
|
|
||||||
"Support configuration groups in the dashboard. This includes creating and "
|
|
||||||
"deleting groups; adding, editing and removing parameters; attaching and "
|
|
||||||
"detaching groups to running instances; and specifying a group during "
|
|
||||||
"instance creation."
|
|
||||||
msgstr ""
|
|
||||||
"Unterstützung für Konfigurationsgruppen im Dashboard. Dies schließt anlegen "
|
|
||||||
"und löschen von Gruppen ein, sowie hinzufügen, bearbeiten und entfernen von "
|
|
||||||
"Parametern, zuweisen und entfernen von Gruppen zu laufenden Instanzen und "
|
|
||||||
"die Angabe einer Gruppe während der Instanzerzeugung."
|
|
||||||
|
|
||||||
msgid ""
|
|
||||||
"The current caching initializes the instance list for the grow cluster "
|
|
||||||
"manager each time a new process would instantiate the manager. Changed to "
|
|
||||||
"manager to only initialize on creation."
|
|
||||||
msgstr ""
|
|
||||||
"Das aktuelle Caching initialisiert die Instanzliste für den für das Wachsen "
|
|
||||||
"zuständigen Cluster Manager jedes Mal, wenn ein neuer Prozess den Cluster "
|
|
||||||
"Manager instanzieren würde. Geändert auf Initialisierung nur bei der "
|
|
||||||
"Erstellung."
|
|
||||||
|
|
||||||
msgid "Trove Dashboard Release Notes"
|
|
||||||
msgstr "Trove Dashboard Releasenotes"
|
|
@ -1,49 +0,0 @@
|
|||||||
# Andi Chandler <andi@gowling.com>, 2016. #zanata
|
|
||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Project-Id-Version: Trove Dashboard Release Notes 7.0.0\n"
|
|
||||||
"Report-Msgid-Bugs-To: \n"
|
|
||||||
"POT-Creation-Date: 2016-10-07 13:57+0000\n"
|
|
||||||
"MIME-Version: 1.0\n"
|
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
|
||||||
"PO-Revision-Date: 2016-06-09 11:08+0000\n"
|
|
||||||
"Last-Translator: Andi Chandler <andi@gowling.com>\n"
|
|
||||||
"Language-Team: English (United Kingdom)\n"
|
|
||||||
"Language: en-GB\n"
|
|
||||||
"X-Generator: Zanata 3.7.3\n"
|
|
||||||
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
|
|
||||||
|
|
||||||
msgid ":ref:`search`"
|
|
||||||
msgstr ":ref:`search`"
|
|
||||||
|
|
||||||
msgid ""
|
|
||||||
"Added support for logging features in the dashboard. This includes listing "
|
|
||||||
"logs that can be retrieved and viewing, publishing and stop collection of "
|
|
||||||
"log contents."
|
|
||||||
msgstr ""
|
|
||||||
"Added support for logging features in the dashboard. This includes listing "
|
|
||||||
"logs that can be retrieved and viewing, publishing and stop collection of "
|
|
||||||
"log contents."
|
|
||||||
|
|
||||||
msgid "Bug Fixes"
|
|
||||||
msgstr "Bug Fixes"
|
|
||||||
|
|
||||||
msgid "Contents:"
|
|
||||||
msgstr "Contents:"
|
|
||||||
|
|
||||||
msgid "Current Series Release Notes"
|
|
||||||
msgstr "Current Series Release Notes"
|
|
||||||
|
|
||||||
msgid "Display all endpoints in the dashboard's cluster details endpoint list."
|
|
||||||
msgstr ""
|
|
||||||
"Display all endpoints in the dashboard's cluster details endpoint list."
|
|
||||||
|
|
||||||
msgid "Enable cluster support for MariaDB and Cassandra."
|
|
||||||
msgstr "Enable cluster support for MariaDB and Cassandra."
|
|
||||||
|
|
||||||
msgid "New Features"
|
|
||||||
msgstr "New Features"
|
|
||||||
|
|
||||||
msgid "Trove Dashboard Release Notes"
|
|
||||||
msgstr "Trove Dashboard Release Notes"
|
|
@ -1,56 +0,0 @@
|
|||||||
# Gérald LONLAS <g.lonlas@gmail.com>, 2016. #zanata
|
|
||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Project-Id-Version: Trove Dashboard Release Notes 8.0.0\n"
|
|
||||||
"Report-Msgid-Bugs-To: \n"
|
|
||||||
"POT-Creation-Date: 2017-04-09 20:48+0000\n"
|
|
||||||
"MIME-Version: 1.0\n"
|
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
|
||||||
"PO-Revision-Date: 2016-10-22 04:24+0000\n"
|
|
||||||
"Last-Translator: Gérald LONLAS <g.lonlas@gmail.com>\n"
|
|
||||||
"Language-Team: French\n"
|
|
||||||
"Language: fr\n"
|
|
||||||
"X-Generator: Zanata 3.9.6\n"
|
|
||||||
"Plural-Forms: nplurals=2; plural=(n > 1)\n"
|
|
||||||
|
|
||||||
msgid "7.0.0"
|
|
||||||
msgstr "7.0.0"
|
|
||||||
|
|
||||||
msgid ":ref:`search`"
|
|
||||||
msgstr ":ref:`search`"
|
|
||||||
|
|
||||||
#, fuzzy
|
|
||||||
msgid ""
|
|
||||||
"Added support for logging features in the dashboard. This includes listing "
|
|
||||||
"logs that can be retrieved and viewing, publishing and stop collection of "
|
|
||||||
"log contents."
|
|
||||||
msgstr ""
|
|
||||||
"Ajout du support pour logger les fonctionnalités dans le tableau de bord. "
|
|
||||||
"Added support for logging features in the dashboard. Cela inclut la liste de "
|
|
||||||
"logs qui sont récupérés affichés et publiés ainsi que l'arrêt de la "
|
|
||||||
"collecte de logs."
|
|
||||||
|
|
||||||
msgid "Bug Fixes"
|
|
||||||
msgstr "Corrections de bugs"
|
|
||||||
|
|
||||||
msgid "Contents:"
|
|
||||||
msgstr "Contenu :"
|
|
||||||
|
|
||||||
msgid "Current Series Release Notes"
|
|
||||||
msgstr "Note de la release actuelle"
|
|
||||||
|
|
||||||
msgid "Enable cluster support for MariaDB and Cassandra."
|
|
||||||
msgstr "Activer le support de Cluster pour MariaDB et Cassandra."
|
|
||||||
|
|
||||||
msgid "New Features"
|
|
||||||
msgstr "Nouvelles fonctionnalités"
|
|
||||||
|
|
||||||
msgid "Newton Series Release Notes"
|
|
||||||
msgstr "Note de release pour Newton"
|
|
||||||
|
|
||||||
msgid "Search"
|
|
||||||
msgstr "Recherche"
|
|
||||||
|
|
||||||
msgid "Trove Dashboard Release Notes"
|
|
||||||
msgstr "Note de release pour le Tableau de bord Trove"
|
|
@ -1,88 +0,0 @@
|
|||||||
# suhartono <cloudsuhartono@gmail.com>, 2016. #zanata
|
|
||||||
# suhartono <cloudsuhartono@gmail.com>, 2017. #zanata
|
|
||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Project-Id-Version: Trove Dashboard Release Notes 8.0.0\n"
|
|
||||||
"Report-Msgid-Bugs-To: \n"
|
|
||||||
"POT-Creation-Date: 2017-04-09 20:48+0000\n"
|
|
||||||
"MIME-Version: 1.0\n"
|
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
|
||||||
"PO-Revision-Date: 2017-04-17 11:03+0000\n"
|
|
||||||
"Last-Translator: suhartono <cloudsuhartono@gmail.com>\n"
|
|
||||||
"Language-Team: Indonesian\n"
|
|
||||||
"Language: id\n"
|
|
||||||
"X-Generator: Zanata 3.9.6\n"
|
|
||||||
"Plural-Forms: nplurals=1; plural=0\n"
|
|
||||||
|
|
||||||
msgid "7.0.0"
|
|
||||||
msgstr "7.0.0"
|
|
||||||
|
|
||||||
msgid "8.0.0"
|
|
||||||
msgstr "8.0.0"
|
|
||||||
|
|
||||||
msgid ":ref:`search`"
|
|
||||||
msgstr ":ref:`search`"
|
|
||||||
|
|
||||||
msgid ""
|
|
||||||
"Added support for logging features in the dashboard. This includes listing "
|
|
||||||
"logs that can be retrieved and viewing, publishing and stop collection of "
|
|
||||||
"log contents."
|
|
||||||
msgstr ""
|
|
||||||
"Dukungan diitambahkan untuk fitur login di dashboard. Ini termasuk log "
|
|
||||||
"daftar yang dapat diambil dan dilihat, penerbitan dan menghentikan koleksi "
|
|
||||||
"isi log."
|
|
||||||
|
|
||||||
msgid "Bug Fixes"
|
|
||||||
msgstr "Bug Fixes (perbaikan kesalahan)"
|
|
||||||
|
|
||||||
msgid "Contents:"
|
|
||||||
msgstr "Contents:"
|
|
||||||
|
|
||||||
msgid "Current Series Release Notes"
|
|
||||||
msgstr "Current Series Release Notes (catatan rilis Current Series)"
|
|
||||||
|
|
||||||
msgid "Display all endpoints in the dashboard's cluster details endpoint list."
|
|
||||||
msgstr ""
|
|
||||||
"Menampilkan semua endpoint dalam daftar rincian endpoint klaster dashboard."
|
|
||||||
|
|
||||||
msgid "Enable cluster grow and shrink panels for pxc."
|
|
||||||
msgstr "Aktifkan klaster tumbuh dan ciutkan panel untuk PXC."
|
|
||||||
|
|
||||||
msgid "Enable cluster support for MariaDB and Cassandra."
|
|
||||||
msgstr "Aktifkan dukungan klaster MariaDB dan Cassandra."
|
|
||||||
|
|
||||||
msgid "New Features"
|
|
||||||
msgstr "New Features (fitur baru)"
|
|
||||||
|
|
||||||
msgid "Newton Series Release Notes"
|
|
||||||
msgstr "Newton Series Release Notes (catatan rilis Newton Series)"
|
|
||||||
|
|
||||||
msgid "Ocata Series Release Notes"
|
|
||||||
msgstr "Ocata Series Release Notes"
|
|
||||||
|
|
||||||
msgid "Search"
|
|
||||||
msgstr "Search (mencari)"
|
|
||||||
|
|
||||||
msgid ""
|
|
||||||
"Support configuration groups in the dashboard. This includes creating and "
|
|
||||||
"deleting groups; adding, editing and removing parameters; attaching and "
|
|
||||||
"detaching groups to running instances; and specifying a group during "
|
|
||||||
"instance creation."
|
|
||||||
msgstr ""
|
|
||||||
"Mendukung kelompok konfigurasi di dashboard. Ini termasuk membuat dan "
|
|
||||||
"menghapus kelompok; menambahkan, mengedit dan menghapus parameter; "
|
|
||||||
"menghbungkan dan memisahkan kelompok untuk menjalankan instance; dan "
|
|
||||||
"menentukan kelompok selama pembuatan instance."
|
|
||||||
|
|
||||||
msgid ""
|
|
||||||
"The current caching initializes the instance list for the grow cluster "
|
|
||||||
"manager each time a new process would instantiate the manager. Changed to "
|
|
||||||
"manager to only initialize on creation."
|
|
||||||
msgstr ""
|
|
||||||
"Caching saat ini menginisialisasi daftar instance bagi manajer klaster "
|
|
||||||
"tumbuh setiap kali proses baru akan menginstantiate manajer. Diubah menjadi "
|
|
||||||
"manajer hanya menginisialisasi pada pembuatan."
|
|
||||||
|
|
||||||
msgid "Trove Dashboard Release Notes"
|
|
||||||
msgstr "Trove Dashboard Release Notes (catatan rilis Trove Dashboard)"
|
|
@ -1,49 +0,0 @@
|
|||||||
# Shu Muto <shu-mutou@rf.jp.nec.com>, 2016. #zanata
|
|
||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Project-Id-Version: Trove Dashboard Release Notes 8.0.0\n"
|
|
||||||
"Report-Msgid-Bugs-To: \n"
|
|
||||||
"POT-Creation-Date: 2017-04-09 20:48+0000\n"
|
|
||||||
"MIME-Version: 1.0\n"
|
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
|
||||||
"PO-Revision-Date: 2016-05-23 01:11+0000\n"
|
|
||||||
"Last-Translator: Shu Muto <shu-mutou@rf.jp.nec.com>\n"
|
|
||||||
"Language-Team: Japanese\n"
|
|
||||||
"Language: ja\n"
|
|
||||||
"X-Generator: Zanata 3.9.6\n"
|
|
||||||
"Plural-Forms: nplurals=1; plural=0\n"
|
|
||||||
|
|
||||||
msgid ":ref:`search`"
|
|
||||||
msgstr ":ref:`search`"
|
|
||||||
|
|
||||||
msgid ""
|
|
||||||
"Added support for logging features in the dashboard. This includes listing "
|
|
||||||
"logs that can be retrieved and viewing, publishing and stop collection of "
|
|
||||||
"log contents."
|
|
||||||
msgstr ""
|
|
||||||
"ダッシュボードでのロギング機能のサポートを追加しました。これは、取得可能なロ"
|
|
||||||
"グの一覧、表示、公開、ログ内容の収集の停止を含みます。"
|
|
||||||
|
|
||||||
msgid "Bug Fixes"
|
|
||||||
msgstr "バグ修正"
|
|
||||||
|
|
||||||
msgid "Contents:"
|
|
||||||
msgstr "内容:"
|
|
||||||
|
|
||||||
msgid "Current Series Release Notes"
|
|
||||||
msgstr "開発中バージョンのリリースノート"
|
|
||||||
|
|
||||||
msgid "Display all endpoints in the dashboard's cluster details endpoint list."
|
|
||||||
msgstr ""
|
|
||||||
"ダッシュボードのクラスター詳細のエンドポイント一覧にすべてのエンドポイントを"
|
|
||||||
"表示します。"
|
|
||||||
|
|
||||||
msgid "Enable cluster support for MariaDB and Cassandra."
|
|
||||||
msgstr "MariaDB と Cassandora のクラスターをサポートしました。"
|
|
||||||
|
|
||||||
msgid "New Features"
|
|
||||||
msgstr "新機能"
|
|
||||||
|
|
||||||
msgid "Trove Dashboard Release Notes"
|
|
||||||
msgstr "Trove Dashboard リリースノート"
|
|
@ -1,80 +0,0 @@
|
|||||||
# Ian Y. Choi <ianyrchoi@gmail.com>, 2016. #zanata
|
|
||||||
# Ian Y. Choi <ianyrchoi@gmail.com>, 2017. #zanata
|
|
||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Project-Id-Version: Trove Dashboard Release Notes 8.0.0\n"
|
|
||||||
"Report-Msgid-Bugs-To: \n"
|
|
||||||
"POT-Creation-Date: 2017-04-09 20:48+0000\n"
|
|
||||||
"MIME-Version: 1.0\n"
|
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
|
||||||
"PO-Revision-Date: 2017-02-03 06:08+0000\n"
|
|
||||||
"Last-Translator: Ian Y. Choi <ianyrchoi@gmail.com>\n"
|
|
||||||
"Language-Team: Korean (South Korea)\n"
|
|
||||||
"Language: ko-KR\n"
|
|
||||||
"X-Generator: Zanata 3.9.6\n"
|
|
||||||
"Plural-Forms: nplurals=1; plural=0\n"
|
|
||||||
|
|
||||||
msgid "7.0.0"
|
|
||||||
msgstr "7.0.0"
|
|
||||||
|
|
||||||
msgid ":ref:`search`"
|
|
||||||
msgstr ":ref:`search`"
|
|
||||||
|
|
||||||
msgid ""
|
|
||||||
"Added support for logging features in the dashboard. This includes listing "
|
|
||||||
"logs that can be retrieved and viewing, publishing and stop collection of "
|
|
||||||
"log contents."
|
|
||||||
msgstr ""
|
|
||||||
"대시보드 내 로깅 기능에 대한 지원을 추가하였습니다. 가져올 수 있는 로그 목록 "
|
|
||||||
"나열 및 로그 내용 집합을 퍼블리싱 및 중지를 포함합니다."
|
|
||||||
|
|
||||||
msgid "Bug Fixes"
|
|
||||||
msgstr "버그 수정"
|
|
||||||
|
|
||||||
msgid "Contents:"
|
|
||||||
msgstr "내용:"
|
|
||||||
|
|
||||||
msgid "Current Series Release Notes"
|
|
||||||
msgstr "현재 시리즈에 대한 릴리즈 노트"
|
|
||||||
|
|
||||||
msgid "Display all endpoints in the dashboard's cluster details endpoint list."
|
|
||||||
msgstr ""
|
|
||||||
"대시보드의 클러스터 세부 엔드 포인트 목록 내에 모든 엔드 포인트를 보여줍니다."
|
|
||||||
|
|
||||||
msgid "Enable cluster grow and shrink panels for pxc."
|
|
||||||
msgstr "pxc를 위한 패널에 대해 클러스터 확장 및 축소를 활성화합니다."
|
|
||||||
|
|
||||||
msgid "Enable cluster support for MariaDB and Cassandra."
|
|
||||||
msgstr "MariaDB 및 Cassandra에 대한 클러스터 지원을 활성화합니다."
|
|
||||||
|
|
||||||
msgid "New Features"
|
|
||||||
msgstr "새로운 기능"
|
|
||||||
|
|
||||||
msgid "Newton Series Release Notes"
|
|
||||||
msgstr "Newton 시리즈에 대한 릴리즈 노트"
|
|
||||||
|
|
||||||
msgid "Search"
|
|
||||||
msgstr "검색"
|
|
||||||
|
|
||||||
msgid ""
|
|
||||||
"Support configuration groups in the dashboard. This includes creating and "
|
|
||||||
"deleting groups; adding, editing and removing parameters; attaching and "
|
|
||||||
"detaching groups to running instances; and specifying a group during "
|
|
||||||
"instance creation."
|
|
||||||
msgstr ""
|
|
||||||
"구성 그룹을 대시보드에서 지원합니다. 그룹 생성 및 삭제; 매개 변수 추가, 편집 "
|
|
||||||
"및 제거; 그룹을 실행 중인 인스턴스에 연결 및 연결 해제; 그리고 인스턴스 생성 "
|
|
||||||
"중 그룹 지정을 포함합니다."
|
|
||||||
|
|
||||||
msgid ""
|
|
||||||
"The current caching initializes the instance list for the grow cluster "
|
|
||||||
"manager each time a new process would instantiate the manager. Changed to "
|
|
||||||
"manager to only initialize on creation."
|
|
||||||
msgstr ""
|
|
||||||
"현재 캐싱은 새로운 프로세스가 클러스터 확장 관리자를 초기화할 때마다 해당 관"
|
|
||||||
"리자에 대한 인스턴스 목록을 초기화합니다. 생성할 때에만 관리자를 초기화하도"
|
|
||||||
"록 변경하였습니다."
|
|
||||||
|
|
||||||
msgid "Trove Dashboard Release Notes"
|
|
||||||
msgstr "Trove 대시보드 릴리즈 노트"
|
|
@ -1,44 +0,0 @@
|
|||||||
# Alexander <ainikitenkov@gmail.com>, 2016. #zanata
|
|
||||||
# Artem <amikhalev90@gmail.com>, 2017. #zanata
|
|
||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Project-Id-Version: Trove Dashboard Release Notes 8.0.0\n"
|
|
||||||
"Report-Msgid-Bugs-To: \n"
|
|
||||||
"POT-Creation-Date: 2017-04-09 20:48+0000\n"
|
|
||||||
"MIME-Version: 1.0\n"
|
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
|
||||||
"PO-Revision-Date: 2017-01-23 12:49+0000\n"
|
|
||||||
"Last-Translator: Artem <amikhalev90@gmail.com>\n"
|
|
||||||
"Language-Team: Russian\n"
|
|
||||||
"Language: ru\n"
|
|
||||||
"X-Generator: Zanata 3.9.6\n"
|
|
||||||
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
|
|
||||||
"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2)\n"
|
|
||||||
|
|
||||||
msgid "7.0.0"
|
|
||||||
msgstr "7.0.0"
|
|
||||||
|
|
||||||
msgid ":ref:`search`"
|
|
||||||
msgstr ":ref:`search`"
|
|
||||||
|
|
||||||
msgid "Bug Fixes"
|
|
||||||
msgstr "Исправления ошибок"
|
|
||||||
|
|
||||||
msgid "Contents:"
|
|
||||||
msgstr "Содержание:"
|
|
||||||
|
|
||||||
msgid "Current Series Release Notes"
|
|
||||||
msgstr "Примечания к выпуску Current Series "
|
|
||||||
|
|
||||||
msgid "Enable cluster support for MariaDB and Cassandra."
|
|
||||||
msgstr "Включить поддержку кластеризации в MariaDB и Cassandra."
|
|
||||||
|
|
||||||
msgid "New Features"
|
|
||||||
msgstr "Новые особенности"
|
|
||||||
|
|
||||||
msgid "Newton Series Release Notes"
|
|
||||||
msgstr "Примечания к выпуску Newton Series "
|
|
||||||
|
|
||||||
msgid "Search"
|
|
||||||
msgstr "Поиск"
|
|
@ -1,48 +0,0 @@
|
|||||||
# sunanchen <KF.sunanchen@h3c.com>, 2016. #zanata
|
|
||||||
# zzxwill <zzxwill@gmail.com>, 2016. #zanata
|
|
||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Project-Id-Version: Trove Dashboard Release Notes 8.0.0\n"
|
|
||||||
"Report-Msgid-Bugs-To: \n"
|
|
||||||
"POT-Creation-Date: 2017-04-09 20:48+0000\n"
|
|
||||||
"MIME-Version: 1.0\n"
|
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
|
||||||
"PO-Revision-Date: 2016-09-08 06:54+0000\n"
|
|
||||||
"Last-Translator: sunanchen <KF.sunanchen@h3c.com>\n"
|
|
||||||
"Language-Team: Chinese (China)\n"
|
|
||||||
"Language: zh-CN\n"
|
|
||||||
"X-Generator: Zanata 3.9.6\n"
|
|
||||||
"Plural-Forms: nplurals=1; plural=0\n"
|
|
||||||
|
|
||||||
msgid ":ref:`search`"
|
|
||||||
msgstr ":ref:`search`"
|
|
||||||
|
|
||||||
msgid ""
|
|
||||||
"Added support for logging features in the dashboard. This includes listing "
|
|
||||||
"logs that can be retrieved and viewing, publishing and stop collection of "
|
|
||||||
"log contents."
|
|
||||||
msgstr ""
|
|
||||||
"在dashboard中增加对日志特性的支持。这包含了列出可检索和查看的日志,正在发布的"
|
|
||||||
"日志以及停止日志内容的收集。"
|
|
||||||
|
|
||||||
msgid "Bug Fixes"
|
|
||||||
msgstr "Bug修复"
|
|
||||||
|
|
||||||
msgid "Contents:"
|
|
||||||
msgstr "内容"
|
|
||||||
|
|
||||||
msgid "Current Series Release Notes"
|
|
||||||
msgstr "当前版本发布说明"
|
|
||||||
|
|
||||||
msgid "Display all endpoints in the dashboard's cluster details endpoint list."
|
|
||||||
msgstr "在dashboard的集群详情endpoint列表中展示所有的endpoints"
|
|
||||||
|
|
||||||
msgid "Enable cluster support for MariaDB and Cassandra."
|
|
||||||
msgstr "启动MariaDB和Cassandra的集群支持。"
|
|
||||||
|
|
||||||
msgid "New Features"
|
|
||||||
msgstr "新特性"
|
|
||||||
|
|
||||||
msgid "Trove Dashboard Release Notes"
|
|
||||||
msgstr "Trove Dashboard发布说明"
|
|
@ -1,6 +0,0 @@
|
|||||||
===================================
|
|
||||||
Newton Series Release Notes
|
|
||||||
===================================
|
|
||||||
|
|
||||||
.. release-notes::
|
|
||||||
:branch: origin/stable/newton
|
|
@ -1,6 +0,0 @@
|
|||||||
===================================
|
|
||||||
Ocata Series Release Notes
|
|
||||||
===================================
|
|
||||||
|
|
||||||
.. release-notes::
|
|
||||||
:branch: origin/stable/ocata
|
|
@ -1,5 +0,0 @@
|
|||||||
============================
|
|
||||||
Current Series Release Notes
|
|
||||||
============================
|
|
||||||
|
|
||||||
.. release-notes::
|
|
@ -1,14 +0,0 @@
|
|||||||
# The order of packages is significant, because pip processes them in the order
|
|
||||||
# of appearance. Changing the order has an impact on the overall integration
|
|
||||||
# process, which may cause wedges in the gate later.
|
|
||||||
|
|
||||||
pbr>=1.6
|
|
||||||
# Horizon Core Requirements
|
|
||||||
Babel>=1.3
|
|
||||||
Django<1.9,>=1.8
|
|
||||||
django-compressor>=1.4
|
|
||||||
django-openstack-auth>=2.0.0
|
|
||||||
iso8601>=0.1.9
|
|
||||||
python-keystoneclient!=1.8.0,>=1.6.0
|
|
||||||
python-swiftclient>=2.2.0
|
|
||||||
python-troveclient>=1.2.0
|
|
554
run_tests.sh
554
run_tests.sh
@ -1,554 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
set -o errexit
|
|
||||||
|
|
||||||
function usage {
|
|
||||||
echo "Usage: $0 [OPTION]..."
|
|
||||||
echo "Run Horizon's test suite(s)"
|
|
||||||
echo ""
|
|
||||||
echo " -V, --virtual-env Always use virtualenv. Install automatically"
|
|
||||||
echo " if not present"
|
|
||||||
echo " -N, --no-virtual-env Don't use virtualenv. Run tests in local"
|
|
||||||
echo " environment"
|
|
||||||
echo " -c, --coverage Generate reports using Coverage"
|
|
||||||
echo " -f, --force Force a clean re-build of the virtual"
|
|
||||||
echo " environment. Useful when dependencies have"
|
|
||||||
echo " been added."
|
|
||||||
echo " -m, --manage Run a Django management command."
|
|
||||||
echo " --makemessages Create/Update English translation files."
|
|
||||||
echo " --compilemessages Compile all translation files."
|
|
||||||
echo " --check-only Do not update translation files (--makemessages only)."
|
|
||||||
echo " --pseudo Pseudo translate a language."
|
|
||||||
echo " -p, --pep8 Just run pep8"
|
|
||||||
echo " -8, --pep8-changed [<basecommit>]"
|
|
||||||
echo " Just run PEP8 and HACKING compliance check"
|
|
||||||
echo " on files changed since HEAD~1 (or <basecommit>)"
|
|
||||||
echo " -P, --no-pep8 Don't run pep8 by default"
|
|
||||||
echo " -t, --tabs Check for tab characters in files."
|
|
||||||
echo " -y, --pylint Just run pylint"
|
|
||||||
echo " -q, --quiet Run non-interactively. (Relatively) quiet."
|
|
||||||
echo " Implies -V if -N is not set."
|
|
||||||
echo " --only-selenium Run only the Selenium unit tests"
|
|
||||||
echo " --with-selenium Run unit tests including Selenium tests"
|
|
||||||
echo " --selenium-headless Run Selenium tests headless"
|
|
||||||
echo " --integration Run the integration tests (requires a running "
|
|
||||||
echo " OpenStack environment)"
|
|
||||||
echo " --runserver Run the Django development server for"
|
|
||||||
echo " openstack_dashboard in the virtual"
|
|
||||||
echo " environment."
|
|
||||||
echo " --docs Just build the documentation"
|
|
||||||
echo " --backup-environment Make a backup of the environment on exit"
|
|
||||||
echo " --restore-environment Restore the environment before running"
|
|
||||||
echo " --destroy-environment Destroy the environment and exit"
|
|
||||||
echo " -h, --help Print this usage message"
|
|
||||||
echo ""
|
|
||||||
echo "Note: with no options specified, the script will try to run the tests in"
|
|
||||||
echo " a virtual environment, If no virtualenv is found, the script will ask"
|
|
||||||
echo " if you would like to create one. If you prefer to run tests NOT in a"
|
|
||||||
echo " virtual environment, simply pass the -N option."
|
|
||||||
exit
|
|
||||||
}
|
|
||||||
|
|
||||||
# DEFAULTS FOR RUN_TESTS.SH
|
|
||||||
#
|
|
||||||
root=`pwd -P`
|
|
||||||
venv=$root/.venv
|
|
||||||
venv_env_version=$venv/environments
|
|
||||||
with_venv=tools/with_venv.sh
|
|
||||||
included_dirs="trove_dashboard"
|
|
||||||
|
|
||||||
always_venv=0
|
|
||||||
backup_env=0
|
|
||||||
command_wrapper=""
|
|
||||||
destroy=0
|
|
||||||
force=0
|
|
||||||
just_pep8=0
|
|
||||||
just_pep8_changed=0
|
|
||||||
no_pep8=0
|
|
||||||
just_pylint=0
|
|
||||||
just_docs=0
|
|
||||||
just_tabs=0
|
|
||||||
never_venv=0
|
|
||||||
quiet=0
|
|
||||||
restore_env=0
|
|
||||||
runserver=0
|
|
||||||
only_selenium=0
|
|
||||||
with_selenium=0
|
|
||||||
selenium_headless=0
|
|
||||||
integration=0
|
|
||||||
testopts=""
|
|
||||||
testargs=""
|
|
||||||
with_coverage=0
|
|
||||||
makemessages=0
|
|
||||||
compilemessages=0
|
|
||||||
check_only=0
|
|
||||||
pseudo=0
|
|
||||||
manage=0
|
|
||||||
|
|
||||||
# Jenkins sets a "JOB_NAME" variable, if it's not set, we'll make it "default"
|
|
||||||
[ "$JOB_NAME" ] || JOB_NAME="default"
|
|
||||||
|
|
||||||
function process_option {
|
|
||||||
# If running manage command, treat the rest of options as arguments.
|
|
||||||
if [ $manage -eq 1 ]; then
|
|
||||||
testargs="$testargs $1"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
case "$1" in
|
|
||||||
-h|--help) usage;;
|
|
||||||
-V|--virtual-env) always_venv=1; never_venv=0;;
|
|
||||||
-N|--no-virtual-env) always_venv=0; never_venv=1;;
|
|
||||||
-p|--pep8) just_pep8=1;;
|
|
||||||
-8|--pep8-changed) just_pep8_changed=1;;
|
|
||||||
-P|--no-pep8) no_pep8=1;;
|
|
||||||
-y|--pylint) just_pylint=1;;
|
|
||||||
-f|--force) force=1;;
|
|
||||||
-t|--tabs) just_tabs=1;;
|
|
||||||
-q|--quiet) quiet=1;;
|
|
||||||
-c|--coverage) with_coverage=1;;
|
|
||||||
-m|--manage) manage=1;;
|
|
||||||
--makemessages) makemessages=1;;
|
|
||||||
--compilemessages) compilemessages=1;;
|
|
||||||
--check-only) check_only=1;;
|
|
||||||
--pseudo) pseudo=1;;
|
|
||||||
--only-selenium) only_selenium=1;;
|
|
||||||
--with-selenium) with_selenium=1;;
|
|
||||||
--selenium-headless) selenium_headless=1;;
|
|
||||||
--integration) integration=1;;
|
|
||||||
--docs) just_docs=1;;
|
|
||||||
--runserver) runserver=1;;
|
|
||||||
--backup-environment) backup_env=1;;
|
|
||||||
--restore-environment) restore_env=1;;
|
|
||||||
--destroy-environment) destroy=1;;
|
|
||||||
-*) testopts="$testopts $1";;
|
|
||||||
*) testargs="$testargs $1"
|
|
||||||
esac
|
|
||||||
}
|
|
||||||
|
|
||||||
function run_management_command {
|
|
||||||
${command_wrapper} python $root/manage.py $testopts $testargs
|
|
||||||
}
|
|
||||||
|
|
||||||
function run_server {
|
|
||||||
echo "Starting Django development server..."
|
|
||||||
${command_wrapper} python $root/manage.py runserver $testopts $testargs
|
|
||||||
echo "Server stopped."
|
|
||||||
}
|
|
||||||
|
|
||||||
function run_pylint {
|
|
||||||
echo "Running pylint ..."
|
|
||||||
PYTHONPATH=$root ${command_wrapper} pylint --rcfile=.pylintrc -f parseable $included_dirs > pylint.txt || true
|
|
||||||
CODE=$?
|
|
||||||
grep Global -A2 pylint.txt
|
|
||||||
if [ $CODE -lt 32 ]; then
|
|
||||||
echo "Completed successfully."
|
|
||||||
exit 0
|
|
||||||
else
|
|
||||||
echo "Completed with problems."
|
|
||||||
exit $CODE
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
function warn_on_flake8_without_venv {
|
|
||||||
set +o errexit
|
|
||||||
${command_wrapper} python -c "import hacking" 2>/dev/null
|
|
||||||
no_hacking=$?
|
|
||||||
set -o errexit
|
|
||||||
if [ $never_venv -eq 1 -a $no_hacking -eq 1 ]; then
|
|
||||||
echo "**WARNING**:" >&2
|
|
||||||
echo "OpenStack hacking is not installed on your host. Its detection will be missed." >&2
|
|
||||||
echo "Please install or use virtual env if you need OpenStack hacking detection." >&2
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
function run_pep8 {
|
|
||||||
echo "Running flake8 ..."
|
|
||||||
warn_on_flake8_without_venv
|
|
||||||
DJANGO_SETTINGS_MODULE=trove_dashboard.test.settings ${command_wrapper} flake8
|
|
||||||
}
|
|
||||||
|
|
||||||
function run_pep8_changed {
|
|
||||||
# NOTE(gilliard) We want use flake8 to check the entirety of every file that has
|
|
||||||
# a change in it. Unfortunately the --filenames argument to flake8 only accepts
|
|
||||||
# file *names* and there are no files named (eg) "nova/compute/manager.py". The
|
|
||||||
# --diff argument behaves surprisingly as well, because although you feed it a
|
|
||||||
# diff, it actually checks the file on disk anyway.
|
|
||||||
local base_commit=${testargs:-HEAD~1}
|
|
||||||
files=$(git diff --name-only $base_commit | tr '\n' ' ')
|
|
||||||
echo "Running flake8 on ${files}"
|
|
||||||
warn_on_flake8_without_venv
|
|
||||||
diff -u --from-file /dev/null ${files} | DJANGO_SETTINGS_MODULE=trove_dashboard.test.settings ${command_wrapper} flake8 --diff
|
|
||||||
exit
|
|
||||||
}
|
|
||||||
|
|
||||||
function run_sphinx {
|
|
||||||
echo "Building sphinx..."
|
|
||||||
DJANGO_SETTINGS_MODULE=trove_dashboard.test.settings ${command_wrapper} python setup.py build_sphinx
|
|
||||||
echo "Build complete."
|
|
||||||
}
|
|
||||||
|
|
||||||
function tab_check {
|
|
||||||
TAB_VIOLATIONS=`find $included_dirs -type f -regex ".*\.\(css\|js\|py\|html\)" -print0 | xargs -0 awk '/\t/' | wc -l`
|
|
||||||
if [ $TAB_VIOLATIONS -gt 0 ]; then
|
|
||||||
echo "TABS! $TAB_VIOLATIONS of them! Oh no!"
|
|
||||||
HORIZON_FILES=`find $included_dirs -type f -regex ".*\.\(css\|js\|py|\html\)"`
|
|
||||||
for TABBED_FILE in $HORIZON_FILES
|
|
||||||
do
|
|
||||||
TAB_COUNT=`awk '/\t/' $TABBED_FILE | wc -l`
|
|
||||||
if [ $TAB_COUNT -gt 0 ]; then
|
|
||||||
echo "$TABBED_FILE: $TAB_COUNT"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
return $TAB_VIOLATIONS;
|
|
||||||
}
|
|
||||||
|
|
||||||
function destroy_venv {
|
|
||||||
echo "Cleaning environment..."
|
|
||||||
echo "Removing virtualenv..."
|
|
||||||
rm -rf $venv
|
|
||||||
echo "Virtualenv removed."
|
|
||||||
}
|
|
||||||
|
|
||||||
function environment_check {
|
|
||||||
echo "Checking environment."
|
|
||||||
if [ -f $venv_env_version ]; then
|
|
||||||
set +o errexit
|
|
||||||
cat requirements.txt test-requirements.txt | cmp $venv_env_version - > /dev/null
|
|
||||||
local env_check_result=$?
|
|
||||||
set -o errexit
|
|
||||||
if [ $env_check_result -eq 0 ]; then
|
|
||||||
# If the environment exists and is up-to-date then set our variables
|
|
||||||
command_wrapper="${root}/${with_venv}"
|
|
||||||
echo "Environment is up to date."
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ $always_venv -eq 1 ]; then
|
|
||||||
install_venv
|
|
||||||
else
|
|
||||||
if [ ! -e ${venv} ]; then
|
|
||||||
echo -e "Environment not found. Install? (Y/n) \c"
|
|
||||||
else
|
|
||||||
echo -e "Your environment appears to be out of date. Update? (Y/n) \c"
|
|
||||||
fi
|
|
||||||
read update_env
|
|
||||||
if [ "x$update_env" = "xY" -o "x$update_env" = "x" -o "x$update_env" = "xy" ]; then
|
|
||||||
install_venv
|
|
||||||
else
|
|
||||||
# Set our command wrapper anyway.
|
|
||||||
command_wrapper="${root}/${with_venv}"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
function sanity_check {
|
|
||||||
# Anything that should be determined prior to running the tests, server, etc.
|
|
||||||
# Don't sanity-check anything environment-related in -N flag is set
|
|
||||||
if [ $never_venv -eq 0 ]; then
|
|
||||||
if [ ! -e ${venv} ]; then
|
|
||||||
echo "Virtualenv not found at $venv. Did install_venv.py succeed?"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
# Remove .pyc files. This is sanity checking because they can linger
|
|
||||||
# after old files are deleted.
|
|
||||||
find . -name "*.pyc" -exec rm -rf {} \;
|
|
||||||
}
|
|
||||||
|
|
||||||
function backup_environment {
|
|
||||||
if [ $backup_env -eq 1 ]; then
|
|
||||||
echo "Backing up environment \"$JOB_NAME\"..."
|
|
||||||
if [ ! -e ${venv} ]; then
|
|
||||||
echo "Environment not installed. Cannot back up."
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
if [ -d /tmp/.horizon_environment/$JOB_NAME ]; then
|
|
||||||
mv /tmp/.horizon_environment/$JOB_NAME /tmp/.horizon_environment/$JOB_NAME.old
|
|
||||||
rm -rf /tmp/.horizon_environment/$JOB_NAME
|
|
||||||
fi
|
|
||||||
mkdir -p /tmp/.horizon_environment/$JOB_NAME
|
|
||||||
cp -r $venv /tmp/.horizon_environment/$JOB_NAME/
|
|
||||||
# Remove the backup now that we've completed successfully
|
|
||||||
rm -rf /tmp/.horizon_environment/$JOB_NAME.old
|
|
||||||
echo "Backup completed"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
function restore_environment {
|
|
||||||
if [ $restore_env -eq 1 ]; then
|
|
||||||
echo "Restoring environment from backup..."
|
|
||||||
if [ ! -d /tmp/.horizon_environment/$JOB_NAME ]; then
|
|
||||||
echo "No backup to restore from."
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
cp -r /tmp/.horizon_environment/$JOB_NAME/.venv ./ || true
|
|
||||||
|
|
||||||
echo "Environment restored successfully."
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
function install_venv {
|
|
||||||
# Install with install_venv.py
|
|
||||||
export PIP_DOWNLOAD_CACHE=${PIP_DOWNLOAD_CACHE-/tmp/.pip_download_cache}
|
|
||||||
export PIP_USE_MIRRORS=true
|
|
||||||
if [ $quiet -eq 1 ]; then
|
|
||||||
export PIP_NO_INPUT=true
|
|
||||||
fi
|
|
||||||
echo "Fetching new src packages..."
|
|
||||||
rm -rf $venv/src
|
|
||||||
python tools/install_venv.py
|
|
||||||
command_wrapper="$root/${with_venv}"
|
|
||||||
# Make sure it worked and record the environment version
|
|
||||||
sanity_check
|
|
||||||
chmod -R 754 $venv
|
|
||||||
cat requirements.txt test-requirements.txt > $venv_env_version
|
|
||||||
}
|
|
||||||
|
|
||||||
function run_tests {
|
|
||||||
sanity_check
|
|
||||||
|
|
||||||
if [ $with_selenium -eq 1 ]; then
|
|
||||||
export WITH_SELENIUM=1
|
|
||||||
elif [ $only_selenium -eq 1 ]; then
|
|
||||||
export WITH_SELENIUM=1
|
|
||||||
export SKIP_UNITTESTS=1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ $selenium_headless -eq 1 ]; then
|
|
||||||
export SELENIUM_HEADLESS=1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# TODO(david-lyle) remove when configuration files for Trove are not loaded
|
|
||||||
# by default in Horizon
|
|
||||||
${command_wrapper} python tools/clean_enabled_files.py
|
|
||||||
|
|
||||||
if [ -z "$testargs" ]; then
|
|
||||||
run_tests_all
|
|
||||||
else
|
|
||||||
run_tests_subset
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
function run_tests_subset {
|
|
||||||
project=`echo $testargs | awk -F. '{print $1}'`
|
|
||||||
${command_wrapper} python $root/manage.py test --settings=$project.test.settings $testopts $testargs
|
|
||||||
}
|
|
||||||
|
|
||||||
function run_tests_all {
|
|
||||||
echo "Running trove_dashboard application tests"
|
|
||||||
export NOSE_XUNIT_FILE=trove_dashboard/nosetests.xml
|
|
||||||
if [ "$NOSE_WITH_HTML_OUTPUT" = '1' ]; then
|
|
||||||
export NOSE_HTML_OUT_FILE='trove_dashboard_nose_results.html'
|
|
||||||
fi
|
|
||||||
if [ $with_coverage -eq 1 ]; then
|
|
||||||
${command_wrapper} python -m coverage.__main__ erase
|
|
||||||
coverage_run="python -m coverage.__main__ run -p"
|
|
||||||
fi
|
|
||||||
${command_wrapper} ${coverage_run} $root/manage.py test trove_dashboard --settings=trove_dashboard.test.settings $testopts
|
|
||||||
# get results of the Horizon tests
|
|
||||||
trove_dashboard_RESULT=$?
|
|
||||||
|
|
||||||
if [ $with_coverage -eq 1 ]; then
|
|
||||||
echo "Generating coverage reports"
|
|
||||||
${command_wrapper} python -m coverage.__main__ combine
|
|
||||||
${command_wrapper} python -m coverage.__main__ xml -i --omit='/usr*,setup.py,*egg*,.venv/*'
|
|
||||||
${command_wrapper} python -m coverage.__main__ html -i --omit='/usr*,setup.py,*egg*,.venv/*' -d reports
|
|
||||||
fi
|
|
||||||
# Remove the leftover coverage files from the -p flag earlier.
|
|
||||||
rm -f .coverage.*
|
|
||||||
|
|
||||||
PEP8_RESULT=0
|
|
||||||
if [ $no_pep8 -eq 0 ] && [ $only_selenium -eq 0 ]; then
|
|
||||||
run_pep8
|
|
||||||
PEP8_RESULT=$?
|
|
||||||
fi
|
|
||||||
|
|
||||||
TEST_RESULT=$(($trove_dashboard_RESULT || $PEP8_RESULT))
|
|
||||||
if [ $TEST_RESULT -eq 0 ]; then
|
|
||||||
echo "Tests completed successfully."
|
|
||||||
else
|
|
||||||
echo "Tests failed."
|
|
||||||
fi
|
|
||||||
exit $TEST_RESULT
|
|
||||||
}
|
|
||||||
|
|
||||||
function run_integration_tests {
|
|
||||||
export INTEGRATION_TESTS=1
|
|
||||||
|
|
||||||
if [ $selenium_headless -eq 1 ]; then
|
|
||||||
export SELENIUM_HEADLESS=1
|
|
||||||
fi
|
|
||||||
|
|
||||||
export HORIZON_INTEGRATION_TESTS_CONFIG_FILE="trove_dashboard/test/integration_tests/horizon.conf"
|
|
||||||
|
|
||||||
echo "Running Horizon integration tests..."
|
|
||||||
if [ -z "$testargs" ]; then
|
|
||||||
${command_wrapper} nosetests trove_dashboard/test/integration_tests/tests
|
|
||||||
else
|
|
||||||
${command_wrapper} nosetests $testargs
|
|
||||||
fi
|
|
||||||
exit 0
|
|
||||||
}
|
|
||||||
|
|
||||||
function run_makemessages {
|
|
||||||
OPTS="-l en --no-obsolete --settings=openstack_dashboard.test.settings"
|
|
||||||
DASHBOARD_OPTS="--extension=html,txt,csv --ignore=openstack"
|
|
||||||
echo -n "horizon: "
|
|
||||||
cd horizon
|
|
||||||
${command_wrapper} $root/manage.py makemessages $OPTS
|
|
||||||
HORIZON_PY_RESULT=$?
|
|
||||||
echo -n "horizon javascript: "
|
|
||||||
${command_wrapper} $root/manage.py makemessages -d djangojs $OPTS
|
|
||||||
HORIZON_JS_RESULT=$?
|
|
||||||
echo -n "openstack_dashboard: "
|
|
||||||
cd ../openstack_dashboard
|
|
||||||
${command_wrapper} $root/manage.py makemessages $DASHBOARD_OPTS $OPTS
|
|
||||||
DASHBOARD_RESULT=$?
|
|
||||||
cd ..
|
|
||||||
if [ $check_only -eq 1 ]; then
|
|
||||||
git checkout -- horizon/locale/en/LC_MESSAGES/django*.po
|
|
||||||
git checkout -- openstack_dashboard/locale/en/LC_MESSAGES/django.po
|
|
||||||
fi
|
|
||||||
exit $(($HORIZON_PY_RESULT || $HORIZON_JS_RESULT || $DASHBOARD_RESULT))
|
|
||||||
}
|
|
||||||
|
|
||||||
function run_compilemessages {
|
|
||||||
OPTS="--settings=openstack_dashboard.test.settings"
|
|
||||||
cd horizon
|
|
||||||
${command_wrapper} $root/manage.py compilemessages $OPTS
|
|
||||||
HORIZON_PY_RESULT=$?
|
|
||||||
cd ../openstack_dashboard
|
|
||||||
${command_wrapper} $root/manage.py compilemessages $OPTS
|
|
||||||
DASHBOARD_RESULT=$?
|
|
||||||
cd ..
|
|
||||||
# English is the source language, so compiled catalogs are unnecessary.
|
|
||||||
rm -vf horizon/locale/en/LC_MESSAGES/django*.mo
|
|
||||||
rm -vf openstack_dashboard/locale/en/LC_MESSAGES/django.mo
|
|
||||||
exit $(($HORIZON_PY_RESULT || $DASHBOARD_RESULT))
|
|
||||||
}
|
|
||||||
|
|
||||||
function run_pseudo {
|
|
||||||
for lang in $testargs
|
|
||||||
# Use English po file as the source file/pot file just like real Horizon translations
|
|
||||||
do
|
|
||||||
${command_wrapper} $root/tools/pseudo.py openstack_dashboard/locale/en/LC_MESSAGES/django.po openstack_dashboard/locale/$lang/LC_MESSAGES/django.po $lang
|
|
||||||
${command_wrapper} $root/tools/pseudo.py horizon/locale/en/LC_MESSAGES/django.po horizon/locale/$lang/LC_MESSAGES/django.po $lang
|
|
||||||
${command_wrapper} $root/tools/pseudo.py horizon/locale/en/LC_MESSAGES/djangojs.po horizon/locale/$lang/LC_MESSAGES/djangojs.po $lang
|
|
||||||
done
|
|
||||||
exit $?
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
# ---------PREPARE THE ENVIRONMENT------------ #
|
|
||||||
|
|
||||||
# PROCESS ARGUMENTS, OVERRIDE DEFAULTS
|
|
||||||
for arg in "$@"; do
|
|
||||||
process_option $arg
|
|
||||||
done
|
|
||||||
|
|
||||||
if [ $quiet -eq 1 ] && [ $never_venv -eq 0 ] && [ $always_venv -eq 0 ]
|
|
||||||
then
|
|
||||||
always_venv=1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# If destroy is set, just blow it away and exit.
|
|
||||||
if [ $destroy -eq 1 ]; then
|
|
||||||
destroy_venv
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Ignore all of this if the -N flag was set
|
|
||||||
if [ $never_venv -eq 0 ]; then
|
|
||||||
|
|
||||||
# Restore previous environment if desired
|
|
||||||
if [ $restore_env -eq 1 ]; then
|
|
||||||
restore_environment
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Remove the virtual environment if --force used
|
|
||||||
if [ $force -eq 1 ]; then
|
|
||||||
destroy_venv
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Then check if it's up-to-date
|
|
||||||
environment_check
|
|
||||||
|
|
||||||
# Create a backup of the up-to-date environment if desired
|
|
||||||
if [ $backup_env -eq 1 ]; then
|
|
||||||
backup_environment
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
# ---------EXERCISE THE CODE------------ #
|
|
||||||
|
|
||||||
# Run management commands
|
|
||||||
if [ $manage -eq 1 ]; then
|
|
||||||
run_management_command
|
|
||||||
exit $?
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Build the docs
|
|
||||||
if [ $just_docs -eq 1 ]; then
|
|
||||||
run_sphinx
|
|
||||||
exit $?
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Update translation files
|
|
||||||
if [ $makemessages -eq 1 ]; then
|
|
||||||
run_makemessages
|
|
||||||
exit $?
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Compile translation files
|
|
||||||
if [ $compilemessages -eq 1 ]; then
|
|
||||||
run_compilemessages
|
|
||||||
exit $?
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Generate Pseudo translation
|
|
||||||
if [ $pseudo -eq 1 ]; then
|
|
||||||
run_pseudo
|
|
||||||
exit $?
|
|
||||||
fi
|
|
||||||
|
|
||||||
# PEP8
|
|
||||||
if [ $just_pep8 -eq 1 ]; then
|
|
||||||
run_pep8
|
|
||||||
exit $?
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ $just_pep8_changed -eq 1 ]; then
|
|
||||||
run_pep8_changed
|
|
||||||
exit $?
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Pylint
|
|
||||||
if [ $just_pylint -eq 1 ]; then
|
|
||||||
run_pylint
|
|
||||||
exit $?
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Tab checker
|
|
||||||
if [ $just_tabs -eq 1 ]; then
|
|
||||||
tab_check
|
|
||||||
exit $?
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Integration tests
|
|
||||||
if [ $integration -eq 1 ]; then
|
|
||||||
run_integration_tests
|
|
||||||
exit $?
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Django development server
|
|
||||||
if [ $runserver -eq 1 ]; then
|
|
||||||
run_server
|
|
||||||
exit $?
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Full test suite
|
|
||||||
run_tests || exit
|
|
32
setup.cfg
32
setup.cfg
@ -1,32 +0,0 @@
|
|||||||
[metadata]
|
|
||||||
name = trove-dashboard
|
|
||||||
summary = Trove Management Dashboard
|
|
||||||
description-file =
|
|
||||||
README.rst
|
|
||||||
author = OpenStack
|
|
||||||
author-email = openstack-dev@lists.openstack.org
|
|
||||||
home-page = http://docs.openstack.org/developer/trove/
|
|
||||||
classifier =
|
|
||||||
Environment :: OpenStack
|
|
||||||
Intended Audience :: Information Technology
|
|
||||||
Intended Audience :: System Administrators
|
|
||||||
License :: OSI Approved :: Apache Software License
|
|
||||||
Operating System :: POSIX :: Linux
|
|
||||||
Programming Language :: Python
|
|
||||||
Programming Language :: Python :: 2
|
|
||||||
Programming Language :: Python :: 2.7
|
|
||||||
Programming Language :: Python :: 3
|
|
||||||
Programming Language :: Python :: 3.3
|
|
||||||
Programming Language :: Python :: 3.4
|
|
||||||
|
|
||||||
[files]
|
|
||||||
packages =
|
|
||||||
trove_dashboard
|
|
||||||
|
|
||||||
[build_sphinx]
|
|
||||||
source-dir = doc/source
|
|
||||||
build-dir = doc/build
|
|
||||||
all_files = 1
|
|
||||||
|
|
||||||
[upload_sphinx]
|
|
||||||
upload-dir = doc/build/html
|
|
29
setup.py
29
setup.py
@ -1,29 +0,0 @@
|
|||||||
# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
||||||
# implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
|
|
||||||
# THIS FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT
|
|
||||||
import setuptools
|
|
||||||
|
|
||||||
# In python < 2.7.4, a lazy loading of package `pbr` will break
|
|
||||||
# setuptools if some other modules registered functions in `atexit`.
|
|
||||||
# solution from: http://bugs.python.org/issue15881#msg170215
|
|
||||||
try:
|
|
||||||
import multiprocessing # noqa
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
setuptools.setup(
|
|
||||||
setup_requires=['pbr>=1.8'],
|
|
||||||
pbr=True)
|
|
@ -1,20 +0,0 @@
|
|||||||
# The order of packages is significant, because pip processes them in the order
|
|
||||||
# of appearance. Changing the order has an impact on the overall integration
|
|
||||||
# process, which may cause wedges in the gate later.
|
|
||||||
|
|
||||||
hacking<0.11,>=0.10.0
|
|
||||||
coverage>=3.6
|
|
||||||
ddt>=0.7.0
|
|
||||||
django-nose>=1.2
|
|
||||||
mock>=1.2
|
|
||||||
mox3>=0.7.0
|
|
||||||
python-subunit>=0.0.18
|
|
||||||
selenium
|
|
||||||
sphinx!=1.2.0,!=1.3b1,<1.3,>=1.1.2
|
|
||||||
oslosphinx!=3.4.0,>=2.5.0 # Apache-2.0
|
|
||||||
testrepository>=0.0.18
|
|
||||||
testscenarios>=0.4
|
|
||||||
testtools>=1.4.0
|
|
||||||
# This also needs xvfb library installed on your OS
|
|
||||||
xvfbwrapper>=0.1.3 #license: MIT
|
|
||||||
reno>=1.6.2 # Apache2
|
|
@ -1,45 +0,0 @@
|
|||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
# This file is temporarily needed to allow the conversion from integrated
|
|
||||||
# Sahara content in Horizon to plugin based content. Horizon currently defines
|
|
||||||
# the same module name data_processing and imports it by default. This utility
|
|
||||||
# removes the configuration files that are responsible for importing the old
|
|
||||||
# version of the module. Only Sahara content configuration files are effected
|
|
||||||
# in Horizon.
|
|
||||||
|
|
||||||
import os
|
|
||||||
|
|
||||||
from openstack_dashboard import enabled as local_enabled
|
|
||||||
|
|
||||||
from trove_dashboard import enabled
|
|
||||||
|
|
||||||
ROOT = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
|
|
||||||
WITH_VENV = os.path.join(ROOT, 'tools', 'with_venv.sh')
|
|
||||||
|
|
||||||
def main():
|
|
||||||
src_path = os.path.dirname(enabled.__file__)
|
|
||||||
dest_path = os.path.dirname(local_enabled.__file__)
|
|
||||||
|
|
||||||
src_files = os.listdir(src_path)
|
|
||||||
for file in src_files:
|
|
||||||
# skip the __init__.py or bad things happen
|
|
||||||
if file == "__init__.py":
|
|
||||||
continue
|
|
||||||
|
|
||||||
file_path = os.path.join(dest_path, file)
|
|
||||||
if os.path.isfile(file_path):
|
|
||||||
print ("removing ", file_path)
|
|
||||||
os.remove(file_path)
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
@ -1,160 +0,0 @@
|
|||||||
# Copyright 2012 United States Government as represented by the
|
|
||||||
# Administrator of the National Aeronautics and Space Administration.
|
|
||||||
# All Rights Reserved.
|
|
||||||
#
|
|
||||||
# Copyright 2012 OpenStack, LLC
|
|
||||||
#
|
|
||||||
# Copyright 2012 Nebula, Inc.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
"""
|
|
||||||
Installation script for the OpenStack Dashboard development virtualenv.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import os
|
|
||||||
import subprocess
|
|
||||||
import sys
|
|
||||||
|
|
||||||
|
|
||||||
ROOT = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
|
|
||||||
VENV = os.path.join(ROOT, '.venv')
|
|
||||||
WITH_VENV = os.path.join(ROOT, 'tools', 'with_venv.sh')
|
|
||||||
PIP_REQUIRES = os.path.join(ROOT, 'requirements.txt')
|
|
||||||
TEST_REQUIRES = os.path.join(ROOT, 'test-requirements.txt')
|
|
||||||
PIP_INSTALL_WRAPPER = os.path.join(ROOT, 'tools', 'pip_install.sh')
|
|
||||||
|
|
||||||
|
|
||||||
def die(message, *args):
|
|
||||||
print >> sys.stderr, message % args
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
|
|
||||||
def run_command(cmd, redirect_output=True, check_exit_code=True, cwd=ROOT,
|
|
||||||
die_message=None):
|
|
||||||
"""
|
|
||||||
Runs a command in an out-of-process shell, returning the
|
|
||||||
output of that command. Working directory is ROOT.
|
|
||||||
"""
|
|
||||||
if redirect_output:
|
|
||||||
stdout = subprocess.PIPE
|
|
||||||
else:
|
|
||||||
stdout = None
|
|
||||||
|
|
||||||
proc = subprocess.Popen(cmd, cwd=cwd, stdout=stdout)
|
|
||||||
output = proc.communicate()[0]
|
|
||||||
if check_exit_code and proc.returncode != 0:
|
|
||||||
if die_message is None:
|
|
||||||
die('Command "%s" failed.\n%s', ' '.join(cmd), output)
|
|
||||||
else:
|
|
||||||
die(die_message)
|
|
||||||
return output
|
|
||||||
|
|
||||||
|
|
||||||
HAS_EASY_INSTALL = bool(run_command(['which', 'easy_install'],
|
|
||||||
check_exit_code=False).strip())
|
|
||||||
HAS_VIRTUALENV = bool(run_command(['which', 'virtualenv'],
|
|
||||||
check_exit_code=False).strip())
|
|
||||||
|
|
||||||
|
|
||||||
def check_dependencies():
|
|
||||||
"""Make sure virtualenv is in the path."""
|
|
||||||
|
|
||||||
print 'Checking dependencies...'
|
|
||||||
if not HAS_VIRTUALENV:
|
|
||||||
print 'Virtual environment not found.'
|
|
||||||
# Try installing it via easy_install...
|
|
||||||
if HAS_EASY_INSTALL:
|
|
||||||
print 'Installing virtualenv via easy_install...',
|
|
||||||
run_command(['easy_install', 'virtualenv'],
|
|
||||||
die_message='easy_install failed to install virtualenv'
|
|
||||||
'\ndevelopment requires virtualenv, please'
|
|
||||||
' install it using your favorite tool')
|
|
||||||
if not run_command(['which', 'virtualenv']):
|
|
||||||
die('ERROR: virtualenv not found in path.\n\ndevelopment '
|
|
||||||
' requires virtualenv, please install it using your'
|
|
||||||
' favorite package management tool and ensure'
|
|
||||||
' virtualenv is in your path')
|
|
||||||
print 'virtualenv installation done.'
|
|
||||||
else:
|
|
||||||
die('easy_install not found.\n\nInstall easy_install'
|
|
||||||
' (python-setuptools in ubuntu) or virtualenv by hand,'
|
|
||||||
' then rerun.')
|
|
||||||
print 'dependency check done.'
|
|
||||||
|
|
||||||
|
|
||||||
def create_virtualenv(venv=VENV):
|
|
||||||
"""Creates the virtual environment and installs PIP only into the
|
|
||||||
virtual environment
|
|
||||||
"""
|
|
||||||
print 'Creating venv...',
|
|
||||||
run_command(['virtualenv', '-q', '--no-site-packages', VENV])
|
|
||||||
print 'done.'
|
|
||||||
print 'Installing pip in virtualenv...',
|
|
||||||
if not run_command([WITH_VENV, 'easy_install', 'pip']).strip():
|
|
||||||
die("Failed to install pip.")
|
|
||||||
print 'done.'
|
|
||||||
print 'Installing distribute in virtualenv...'
|
|
||||||
pip_install('distribute>=0.6.24')
|
|
||||||
print 'done.'
|
|
||||||
|
|
||||||
|
|
||||||
def pip_install(*args):
|
|
||||||
args = [WITH_VENV, 'pip', 'install', '--upgrade'] + list(args)
|
|
||||||
run_command(args, redirect_output=False)
|
|
||||||
|
|
||||||
|
|
||||||
def pip_install_with_horizon(*args):
|
|
||||||
args = [WITH_VENV, PIP_INSTALL_WRAPPER, 'unconstrained'] + list(args)
|
|
||||||
run_command(args, redirect_output=False)
|
|
||||||
|
|
||||||
|
|
||||||
def install_dependencies(venv=VENV):
|
|
||||||
print "Installing dependencies..."
|
|
||||||
print "(This may take several minutes, don't panic)"
|
|
||||||
pip_install_with_horizon('-r', TEST_REQUIRES)
|
|
||||||
pip_install_with_horizon('-r', PIP_REQUIRES)
|
|
||||||
|
|
||||||
# Tell the virtual env how to "import dashboard"
|
|
||||||
py = 'python%d.%d' % (sys.version_info[0], sys.version_info[1])
|
|
||||||
pthfile = os.path.join(venv, "lib", py, "site-packages", "dashboard.pth")
|
|
||||||
f = open(pthfile, 'w')
|
|
||||||
f.write("%s\n" % ROOT)
|
|
||||||
|
|
||||||
|
|
||||||
def install_horizon():
|
|
||||||
print 'Installing horizon module in development mode...'
|
|
||||||
run_command([WITH_VENV, 'python', 'setup.py', 'develop'], cwd=ROOT)
|
|
||||||
|
|
||||||
|
|
||||||
def print_summary():
|
|
||||||
summary = """
|
|
||||||
Horizon development environment setup is complete.
|
|
||||||
|
|
||||||
To activate the virtualenv for the extent of your current shell session you
|
|
||||||
can run:
|
|
||||||
|
|
||||||
$ source .venv/bin/activate
|
|
||||||
"""
|
|
||||||
print summary
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
check_dependencies()
|
|
||||||
create_virtualenv()
|
|
||||||
install_dependencies()
|
|
||||||
install_horizon()
|
|
||||||
print_summary()
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
@ -1,52 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
# This script is borrowed from Sahara who borrowed from neutron-* repos.
|
|
||||||
|
|
||||||
# Many of horizon's repos suffer from the problem of depending on horizon,
|
|
||||||
# but it not existing on pypi.
|
|
||||||
|
|
||||||
# This wrapper for tox's package installer will use the existing package
|
|
||||||
# if it exists, else use zuul-cloner if that program exists, else grab it
|
|
||||||
# from horizon master via a hard-coded URL. That last case should only
|
|
||||||
# happen with devs running unit tests locally.
|
|
||||||
|
|
||||||
# From the tox.ini config page:
|
|
||||||
# install_command=ARGV
|
|
||||||
# default:
|
|
||||||
# pip install {opts} {packages}
|
|
||||||
|
|
||||||
ZUUL_CLONER=/usr/zuul-env/bin/zuul-cloner
|
|
||||||
BRANCH_NAME=master
|
|
||||||
horizon_installed=$(echo "import horizon" | python 2>/dev/null ; echo $?)
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
install_cmd="pip install $1"
|
|
||||||
shift
|
|
||||||
|
|
||||||
if [ $horizon_installed -eq 0 ]; then
|
|
||||||
echo "ALREADY INSTALLED" > /tmp/tox_install.txt
|
|
||||||
echo "Horizon already installed; using existing package"
|
|
||||||
elif [ -x "$ZUUL_CLONER" ]; then
|
|
||||||
export ZUUL_BRANCH=${ZUUL_BRANCH-$BRANCH}
|
|
||||||
echo "ZUUL CLONER" > /tmp/tox_install.txt
|
|
||||||
cwd=$(/bin/pwd)
|
|
||||||
cd /tmp
|
|
||||||
$ZUUL_CLONER --cache-dir \
|
|
||||||
/opt/git \
|
|
||||||
--branch $BRANCH_NAME \
|
|
||||||
git://git.openstack.org \
|
|
||||||
openstack/horizon
|
|
||||||
cd openstack/horizon
|
|
||||||
$install_cmd -e .
|
|
||||||
cd "$cwd"
|
|
||||||
else
|
|
||||||
echo "PIP HARDCODE" > /tmp/tox_install.txt
|
|
||||||
if [ -z "$HORIZON_PIP_LOCATION" ]; then
|
|
||||||
HORIZON_PIP_LOCATION="git+https://git.openstack.org/openstack/horizon@$BRANCH_NAME#egg=horizon"
|
|
||||||
fi
|
|
||||||
$install_cmd -U -e ${HORIZON_PIP_LOCATION}
|
|
||||||
fi
|
|
||||||
|
|
||||||
$install_cmd -U $*
|
|
||||||
exit $?
|
|
@ -1,7 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
TOOLS_PATH=${TOOLS_PATH:-$(dirname $0)}
|
|
||||||
VENV_PATH=${VENV_PATH:-${TOOLS_PATH}}
|
|
||||||
VENV_DIR=${VENV_NAME:-/../.venv}
|
|
||||||
TOOLS=${TOOLS_PATH}
|
|
||||||
VENV=${VENV:-${VENV_PATH}/${VENV_DIR}}
|
|
||||||
source ${VENV}/bin/activate && "$@"
|
|
67
tox.ini
67
tox.ini
@ -1,67 +0,0 @@
|
|||||||
[tox]
|
|
||||||
minversion = 1.6
|
|
||||||
envlist = py27,pep8,py27dj18
|
|
||||||
skipsdist = True
|
|
||||||
|
|
||||||
[testenv]
|
|
||||||
usedevelop = True
|
|
||||||
install_command = {toxinidir}/tools/pip_install.sh \
|
|
||||||
-c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} \
|
|
||||||
{opts} {packages}
|
|
||||||
setenv =
|
|
||||||
VIRTUAL_ENV={envdir}
|
|
||||||
deps = -r{toxinidir}/requirements.txt
|
|
||||||
-r{toxinidir}/test-requirements.txt
|
|
||||||
commands = /bin/bash run_tests.sh -N --no-pep8 {posargs}
|
|
||||||
|
|
||||||
[testenv:py27]
|
|
||||||
setenv = DJANGO_SETTINGS_MODULE=trove_dashboard.test.settings
|
|
||||||
|
|
||||||
[testenv:pep8]
|
|
||||||
commands = flake8
|
|
||||||
|
|
||||||
[testenv:venv]
|
|
||||||
# This target does not use script since we do not need to install horizon.
|
|
||||||
install_command = pip install \
|
|
||||||
-c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} \
|
|
||||||
-U --force-reinstall {opts} {packages}
|
|
||||||
commands = {posargs}
|
|
||||||
|
|
||||||
# Django-1.8 is LTS
|
|
||||||
[testenv:py27dj18]
|
|
||||||
basepython = python2.7
|
|
||||||
commands = pip install django>=1.8,<1.9
|
|
||||||
/bin/bash run_tests.sh -N --no-pep8 {posargs}
|
|
||||||
|
|
||||||
[testenv:py27dj19]
|
|
||||||
basepython = python2.7
|
|
||||||
commands = pip install django>=1.9,<1.10
|
|
||||||
/bin/bash run_tests.sh -N --no-pep8 {posargs}
|
|
||||||
|
|
||||||
[testenv:py27dj110]
|
|
||||||
basepython = python2.7
|
|
||||||
commands = pip install django>=1.10,<1.11
|
|
||||||
/bin/bash run_tests.sh -N --no-pep8 {posargs}
|
|
||||||
|
|
||||||
[testenv:py27integration]
|
|
||||||
basepython = python2.7
|
|
||||||
commands = /bin/bash run_tests.sh -N --integration --selenium-headless {posargs}
|
|
||||||
|
|
||||||
[testenv:cover]
|
|
||||||
commands = python setup.py testr --coverage --testr-args='{posargs}'
|
|
||||||
|
|
||||||
[testenv:docs]
|
|
||||||
commands = python setup.py build_sphinx
|
|
||||||
|
|
||||||
[testenv:debug]
|
|
||||||
commands = oslo_debug_helper {posargs}
|
|
||||||
|
|
||||||
[testenv:releasenotes]
|
|
||||||
commands = sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html
|
|
||||||
|
|
||||||
[flake8]
|
|
||||||
show-source = True
|
|
||||||
# H405 multi line docstring summary not separated with an empty line
|
|
||||||
ignore = H405
|
|
||||||
builtins = _
|
|
||||||
exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,build,.ropeproject,tools,releasenotes
|
|
@ -1,5 +0,0 @@
|
|||||||
from trove_dashboard.api import trove
|
|
||||||
|
|
||||||
__all__ = [
|
|
||||||
"trove"
|
|
||||||
]
|
|
@ -1,16 +0,0 @@
|
|||||||
# Copyright 2016 IBM Corp.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
|
|
||||||
# import REST API modules here
|
|
||||||
from trove_dashboard.api.rest import trove # noqa
|
|
@ -1,82 +0,0 @@
|
|||||||
# Copyright 2016 IBM Corp.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
|
|
||||||
from django.views import generic
|
|
||||||
|
|
||||||
from trove_dashboard.api import trove
|
|
||||||
|
|
||||||
from openstack_dashboard.api.rest import urls
|
|
||||||
from openstack_dashboard.api.rest import utils as rest_utils
|
|
||||||
|
|
||||||
|
|
||||||
@urls.register
|
|
||||||
class Backup(generic.View):
|
|
||||||
"""API for retrieving information about a single backup.
|
|
||||||
"""
|
|
||||||
url_regex = r'trove_dashboard/backups/(?P<backup_name>[^/]+)$'
|
|
||||||
|
|
||||||
@rest_utils.ajax()
|
|
||||||
def get(self, request, backup_id):
|
|
||||||
"""Get a specific backup.
|
|
||||||
"""
|
|
||||||
return trove.backup_get(request, backup_id).to_dict()
|
|
||||||
|
|
||||||
|
|
||||||
@urls.register
|
|
||||||
class Backups(generic.View):
|
|
||||||
"""API for backups.
|
|
||||||
"""
|
|
||||||
url_regex = r'trove/backups/$'
|
|
||||||
|
|
||||||
@rest_utils.ajax()
|
|
||||||
def get(self, request):
|
|
||||||
"""Get a list of the Backups.
|
|
||||||
|
|
||||||
The returned result is an object with property 'items' and each
|
|
||||||
item under this is a backup.
|
|
||||||
"""
|
|
||||||
result = trove.backup_list(request)
|
|
||||||
backups = []
|
|
||||||
for b in result:
|
|
||||||
instance = trove.instance_get(request, b.instance_id)
|
|
||||||
backups.append({'id': b.id,
|
|
||||||
'name': b.name,
|
|
||||||
'datastore': b.datastore.get('type'),
|
|
||||||
'datastoreversion': b.datastore.get('version'),
|
|
||||||
'created': b.created,
|
|
||||||
'database': instance.name,
|
|
||||||
'incremental': bool(b.parent_id),
|
|
||||||
'status': b.status
|
|
||||||
})
|
|
||||||
return backups
|
|
||||||
|
|
||||||
@rest_utils.ajax(data_required=True)
|
|
||||||
def delete(self, request):
|
|
||||||
"""Delete one or more backup by name.
|
|
||||||
|
|
||||||
Returns HTTP 204 (no content) on successful deletion.
|
|
||||||
"""
|
|
||||||
for backup_id in request.DATA:
|
|
||||||
trove.backup_delete(request, backup_id)
|
|
||||||
|
|
||||||
@rest_utils.ajax(data_required=True)
|
|
||||||
def create(self, request):
|
|
||||||
"""Create a new backup.
|
|
||||||
|
|
||||||
Returns the new backup object on success.
|
|
||||||
"""
|
|
||||||
new_backup = trove.backup_create(request, **request.DATA)
|
|
||||||
return rest_utils.CreatedResponse(
|
|
||||||
'/api/messaging/backups/%s' % new_backup.name,
|
|
||||||
new_backup.to_dict())
|
|
@ -1,403 +0,0 @@
|
|||||||
# Copyright 2013 Rackspace Hosting.
|
|
||||||
#
|
|
||||||
# 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 logging
|
|
||||||
|
|
||||||
from django.conf import settings
|
|
||||||
from troveclient.v1 import client
|
|
||||||
|
|
||||||
from openstack_dashboard.api import base
|
|
||||||
|
|
||||||
from horizon.utils import functions as utils
|
|
||||||
from horizon.utils.memoized import memoized # noqa
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
@memoized
|
|
||||||
def troveclient(request):
|
|
||||||
insecure = getattr(settings, 'OPENSTACK_SSL_NO_VERIFY', False)
|
|
||||||
cacert = getattr(settings, 'OPENSTACK_SSL_CACERT', None)
|
|
||||||
trove_url = base.url_for(request, 'database')
|
|
||||||
c = client.Client(request.user.username,
|
|
||||||
request.user.token.id,
|
|
||||||
project_id=request.user.project_id,
|
|
||||||
auth_url=trove_url,
|
|
||||||
insecure=insecure,
|
|
||||||
cacert=cacert,
|
|
||||||
http_log_debug=settings.DEBUG)
|
|
||||||
c.client.auth_token = request.user.token.id
|
|
||||||
c.client.management_url = trove_url
|
|
||||||
return c
|
|
||||||
|
|
||||||
|
|
||||||
def cluster_list(request, marker=None):
|
|
||||||
page_size = utils.get_page_size(request)
|
|
||||||
return troveclient(request).clusters.list(limit=page_size, marker=marker)
|
|
||||||
|
|
||||||
|
|
||||||
def cluster_get(request, cluster_id):
|
|
||||||
return troveclient(request).clusters.get(cluster_id)
|
|
||||||
|
|
||||||
|
|
||||||
def cluster_delete(request, cluster_id):
|
|
||||||
return troveclient(request).clusters.delete(cluster_id)
|
|
||||||
|
|
||||||
|
|
||||||
def cluster_create(request, name, volume, flavor, num_instances,
|
|
||||||
datastore, datastore_version,
|
|
||||||
nics=None, root_password=None, locality=None):
|
|
||||||
instances = []
|
|
||||||
for i in range(num_instances):
|
|
||||||
instance = {}
|
|
||||||
instance["flavorRef"] = flavor
|
|
||||||
if volume > 0:
|
|
||||||
instance["volume"] = {'size': volume}
|
|
||||||
if nics:
|
|
||||||
instance["nics"] = [{"net-id": nics}]
|
|
||||||
instances.append(instance)
|
|
||||||
|
|
||||||
# TODO(saurabhs): vertica needs root password on cluster create
|
|
||||||
return troveclient(request).clusters.create(
|
|
||||||
name,
|
|
||||||
datastore,
|
|
||||||
datastore_version,
|
|
||||||
instances=instances,
|
|
||||||
locality=locality)
|
|
||||||
|
|
||||||
|
|
||||||
def cluster_grow(request, cluster_id, new_instances):
|
|
||||||
instances = []
|
|
||||||
for new_instance in new_instances:
|
|
||||||
instance = {}
|
|
||||||
instance["flavorRef"] = new_instance.flavor_id
|
|
||||||
if new_instance.volume > 0:
|
|
||||||
instance["volume"] = {'size': new_instance.volume}
|
|
||||||
if new_instance.name:
|
|
||||||
instance["name"] = new_instance.name
|
|
||||||
if new_instance.type:
|
|
||||||
instance["type"] = new_instance.type
|
|
||||||
if new_instance.related_to:
|
|
||||||
instance["related_to"] = new_instance.related_to
|
|
||||||
if new_instance.nics:
|
|
||||||
instance["nics"] = [{'net-id': new_instance.nics}]
|
|
||||||
instances.append(instance)
|
|
||||||
return troveclient(request).clusters.grow(cluster_id, instances)
|
|
||||||
|
|
||||||
|
|
||||||
def cluster_shrink(request, cluster_id, instances):
|
|
||||||
return troveclient(request).clusters.shrink(cluster_id, instances)
|
|
||||||
|
|
||||||
|
|
||||||
def create_cluster_root(request, cluster_id, password):
|
|
||||||
# It appears the code below depends on this trove change
|
|
||||||
# https://review.openstack.org/#/c/166954/. Comment out when that
|
|
||||||
# change merges.
|
|
||||||
# return troveclient(request).cluster.reset_root_password(cluster_id)
|
|
||||||
troveclient(request).root.create_cluster_root(cluster_id, password)
|
|
||||||
|
|
||||||
|
|
||||||
def instance_list(request, marker=None):
|
|
||||||
page_size = utils.get_page_size(request)
|
|
||||||
return troveclient(request).instances.list(limit=page_size, marker=marker)
|
|
||||||
|
|
||||||
|
|
||||||
def instance_list_all(request):
|
|
||||||
instances = instance_list(request)
|
|
||||||
marker = instances.next
|
|
||||||
while marker:
|
|
||||||
temp_instances = instance_list(request, marker=marker)
|
|
||||||
marker = temp_instances.next
|
|
||||||
for instance in temp_instances:
|
|
||||||
instances.append(instance)
|
|
||||||
instances.links = temp_instances.links
|
|
||||||
instances.next = None
|
|
||||||
return instances
|
|
||||||
|
|
||||||
|
|
||||||
def instance_get(request, instance_id):
|
|
||||||
return troveclient(request).instances.get(instance_id)
|
|
||||||
|
|
||||||
|
|
||||||
def instance_delete(request, instance_id):
|
|
||||||
return troveclient(request).instances.delete(instance_id)
|
|
||||||
|
|
||||||
|
|
||||||
def instance_create(request, name, volume, flavor, databases=None,
|
|
||||||
users=None, restore_point=None, nics=None,
|
|
||||||
datastore=None, datastore_version=None,
|
|
||||||
replica_of=None, replica_count=None,
|
|
||||||
volume_type=None, configuration=None, locality=None,
|
|
||||||
availability_zone=None):
|
|
||||||
# TODO(dklyle): adding conditional to support trove without volume
|
|
||||||
# support for now until API supports checking for volume support
|
|
||||||
if volume > 0:
|
|
||||||
volume_params = {'size': volume}
|
|
||||||
if volume_type:
|
|
||||||
volume_params['type'] = volume_type
|
|
||||||
else:
|
|
||||||
volume_params = None
|
|
||||||
return troveclient(request).instances.create(
|
|
||||||
name,
|
|
||||||
flavor,
|
|
||||||
volume=volume_params,
|
|
||||||
databases=databases,
|
|
||||||
users=users,
|
|
||||||
restorePoint=restore_point,
|
|
||||||
nics=nics,
|
|
||||||
datastore=datastore,
|
|
||||||
datastore_version=datastore_version,
|
|
||||||
replica_of=replica_of,
|
|
||||||
replica_count=replica_count,
|
|
||||||
configuration=configuration,
|
|
||||||
locality=locality,
|
|
||||||
availability_zone=availability_zone)
|
|
||||||
|
|
||||||
|
|
||||||
def instance_resize_volume(request, instance_id, size):
|
|
||||||
return troveclient(request).instances.resize_volume(instance_id, size)
|
|
||||||
|
|
||||||
|
|
||||||
def instance_resize(request, instance_id, flavor_id):
|
|
||||||
return troveclient(request).instances.resize_instance(instance_id,
|
|
||||||
flavor_id)
|
|
||||||
|
|
||||||
|
|
||||||
def instance_backups(request, instance_id):
|
|
||||||
return troveclient(request).instances.backups(instance_id)
|
|
||||||
|
|
||||||
|
|
||||||
def instance_restart(request, instance_id):
|
|
||||||
return troveclient(request).instances.restart(instance_id)
|
|
||||||
|
|
||||||
|
|
||||||
def instance_detach_replica(request, instance_id):
|
|
||||||
return troveclient(request).instances.edit(instance_id,
|
|
||||||
detach_replica_source=True)
|
|
||||||
|
|
||||||
|
|
||||||
def promote_to_replica_source(request, instance_id):
|
|
||||||
return troveclient(request).instances.promote_to_replica_source(
|
|
||||||
instance_id)
|
|
||||||
|
|
||||||
|
|
||||||
def eject_replica_source(request, instance_id):
|
|
||||||
return troveclient(request).instances.eject_replica_source(instance_id)
|
|
||||||
|
|
||||||
|
|
||||||
def instance_attach_configuration(request, instance_id, configuration):
|
|
||||||
return troveclient(request).instances.modify(instance_id,
|
|
||||||
configuration=configuration)
|
|
||||||
|
|
||||||
|
|
||||||
def instance_detach_configuration(request, instance_id):
|
|
||||||
return troveclient(request).instances.modify(instance_id)
|
|
||||||
|
|
||||||
|
|
||||||
def database_list(request, instance_id):
|
|
||||||
return troveclient(request).databases.list(instance_id)
|
|
||||||
|
|
||||||
|
|
||||||
def database_create(request, instance_id, db_name, character_set=None,
|
|
||||||
collation=None):
|
|
||||||
database = {'name': db_name}
|
|
||||||
if collation:
|
|
||||||
database['collate'] = collation
|
|
||||||
if character_set:
|
|
||||||
database['character_set'] = character_set
|
|
||||||
return troveclient(request).databases.create(instance_id, [database])
|
|
||||||
|
|
||||||
|
|
||||||
def database_delete(request, instance_id, db_name):
|
|
||||||
return troveclient(request).databases.delete(instance_id, db_name)
|
|
||||||
|
|
||||||
|
|
||||||
def backup_list(request):
|
|
||||||
return troveclient(request).backups.list()
|
|
||||||
|
|
||||||
|
|
||||||
def backup_get(request, backup_id):
|
|
||||||
return troveclient(request).backups.get(backup_id)
|
|
||||||
|
|
||||||
|
|
||||||
def backup_delete(request, backup_id):
|
|
||||||
return troveclient(request).backups.delete(backup_id)
|
|
||||||
|
|
||||||
|
|
||||||
def backup_create(request, name, instance_id, description=None,
|
|
||||||
parent_id=None):
|
|
||||||
return troveclient(request).backups.create(name, instance_id,
|
|
||||||
description, parent_id)
|
|
||||||
|
|
||||||
|
|
||||||
def flavor_list(request):
|
|
||||||
return troveclient(request).flavors.list()
|
|
||||||
|
|
||||||
|
|
||||||
def datastore_flavors(request, datastore_name=None,
|
|
||||||
datastore_version=None):
|
|
||||||
# if datastore info is available then get datastore specific flavors
|
|
||||||
if datastore_name and datastore_version:
|
|
||||||
try:
|
|
||||||
return troveclient(request).flavors.\
|
|
||||||
list_datastore_version_associated_flavors(datastore_name,
|
|
||||||
datastore_version)
|
|
||||||
except Exception:
|
|
||||||
LOG.warning("Failed to retrieve datastore specific flavors")
|
|
||||||
return flavor_list(request)
|
|
||||||
|
|
||||||
|
|
||||||
def flavor_get(request, flavor_id):
|
|
||||||
return troveclient(request).flavors.get(flavor_id)
|
|
||||||
|
|
||||||
|
|
||||||
def root_enable(request, instance_ids):
|
|
||||||
username, password = troveclient(request).root.create(instance_ids[0])
|
|
||||||
return username, password
|
|
||||||
|
|
||||||
|
|
||||||
def root_show(request, instance_id):
|
|
||||||
return troveclient(request).root.is_root_enabled(instance_id)
|
|
||||||
|
|
||||||
|
|
||||||
def root_disable(request, instance_id):
|
|
||||||
return troveclient(request).root.delete(instance_id)
|
|
||||||
|
|
||||||
|
|
||||||
def users_list(request, instance_id):
|
|
||||||
return troveclient(request).users.list(instance_id)
|
|
||||||
|
|
||||||
|
|
||||||
def user_create(request, instance_id, username, password,
|
|
||||||
host=None, databases=[]):
|
|
||||||
user = {'name': username, 'password': password, 'databases': databases}
|
|
||||||
if host:
|
|
||||||
user['host'] = host
|
|
||||||
|
|
||||||
return troveclient(request).users.create(instance_id, [user])
|
|
||||||
|
|
||||||
|
|
||||||
def user_delete(request, instance_id, user, host=None):
|
|
||||||
return troveclient(request).users.delete(instance_id, user, hostname=host)
|
|
||||||
|
|
||||||
|
|
||||||
def user_update_attributes(request, instance_id, name, host=None,
|
|
||||||
new_name=None, new_password=None, new_host=None):
|
|
||||||
new_attributes = {}
|
|
||||||
if new_name:
|
|
||||||
new_attributes['name'] = new_name
|
|
||||||
if new_password:
|
|
||||||
new_attributes['password'] = new_password
|
|
||||||
if new_host:
|
|
||||||
new_attributes['host'] = new_host
|
|
||||||
return troveclient(request).users.update_attributes(
|
|
||||||
instance_id, name, newuserattr=new_attributes, hostname=host)
|
|
||||||
|
|
||||||
|
|
||||||
def user_list_access(request, instance_id, username, host=None):
|
|
||||||
return troveclient(request).users.list_access(
|
|
||||||
instance_id, username, hostname=host)
|
|
||||||
|
|
||||||
|
|
||||||
def user_grant_access(request, instance_id, username, databases, host=None):
|
|
||||||
return troveclient(request).users.grant(
|
|
||||||
instance_id, username, databases, hostname=host)
|
|
||||||
|
|
||||||
|
|
||||||
def user_revoke_access(request, instance_id, username, database, host=None):
|
|
||||||
return troveclient(request).users.revoke(
|
|
||||||
instance_id, username, database, hostname=host)
|
|
||||||
|
|
||||||
|
|
||||||
def user_show_access(request, instance_id, username, host=None):
|
|
||||||
return troveclient(request).users.list_access(
|
|
||||||
instance_id, username, hostname=host)
|
|
||||||
|
|
||||||
|
|
||||||
def datastore_list(request):
|
|
||||||
return troveclient(request).datastores.list()
|
|
||||||
|
|
||||||
|
|
||||||
def datastore_version_list(request, datastore):
|
|
||||||
return troveclient(request).datastore_versions.list(datastore)
|
|
||||||
|
|
||||||
|
|
||||||
def log_list(request, instance_id):
|
|
||||||
return troveclient(request).instances.log_list(instance_id)
|
|
||||||
|
|
||||||
|
|
||||||
def log_enable(request, instance_id, log_name):
|
|
||||||
return troveclient(request).instances.log_enable(instance_id, log_name)
|
|
||||||
|
|
||||||
|
|
||||||
def log_disable(request, instance_id, log_name):
|
|
||||||
return troveclient(request).instances.log_disable(instance_id, log_name)
|
|
||||||
|
|
||||||
|
|
||||||
def log_publish(request, instance_id, log_name):
|
|
||||||
return troveclient(request).instances.log_publish(instance_id, log_name)
|
|
||||||
|
|
||||||
|
|
||||||
def log_discard(request, instance_id, log_name):
|
|
||||||
return troveclient(request).instances.log_discard(instance_id, log_name)
|
|
||||||
|
|
||||||
|
|
||||||
def log_tail(request, instance_id, log_name, publish, lines, swift=None):
|
|
||||||
return troveclient(request).instances.log_generator(instance_id,
|
|
||||||
log_name,
|
|
||||||
publish=publish,
|
|
||||||
lines=lines,
|
|
||||||
swift=swift)
|
|
||||||
|
|
||||||
|
|
||||||
def configuration_list(request):
|
|
||||||
return troveclient(request).configurations.list()
|
|
||||||
|
|
||||||
|
|
||||||
def configuration_get(request, group_id):
|
|
||||||
return troveclient(request).configurations.get(group_id)
|
|
||||||
|
|
||||||
|
|
||||||
def configuration_parameters_list(request, datastore, datastore_version):
|
|
||||||
return troveclient(request).configuration_parameters.parameters(
|
|
||||||
datastore, datastore_version)
|
|
||||||
|
|
||||||
|
|
||||||
def configuration_create(request,
|
|
||||||
name,
|
|
||||||
values,
|
|
||||||
description=None,
|
|
||||||
datastore=None,
|
|
||||||
datastore_version=None):
|
|
||||||
return troveclient(request).configurations.create(name,
|
|
||||||
values,
|
|
||||||
description,
|
|
||||||
datastore,
|
|
||||||
datastore_version)
|
|
||||||
|
|
||||||
|
|
||||||
def configuration_delete(request, group_id):
|
|
||||||
return troveclient(request).configurations.delete(group_id)
|
|
||||||
|
|
||||||
|
|
||||||
def configuration_instances(request, group_id):
|
|
||||||
return troveclient(request).configurations.instances(group_id)
|
|
||||||
|
|
||||||
|
|
||||||
def configuration_update(request, group_id, values):
|
|
||||||
return troveclient(request).configurations.update(group_id, values)
|
|
||||||
|
|
||||||
|
|
||||||
def configuration_default(request, instance_id):
|
|
||||||
return troveclient(request).instances.configuration(instance_id)
|
|
@ -1,28 +0,0 @@
|
|||||||
# Copyright 2013 Rackspace Hosting
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
|
|
||||||
import horizon
|
|
||||||
from openstack_dashboard.dashboards.project import dashboard
|
|
||||||
|
|
||||||
|
|
||||||
class Backups(horizon.Panel):
|
|
||||||
name = _("Backups")
|
|
||||||
slug = 'database_backups'
|
|
||||||
permissions = ('openstack.services.database',
|
|
||||||
'openstack.services.object-store',)
|
|
||||||
|
|
||||||
|
|
||||||
dashboard.Project.register(Backups)
|
|
@ -1,191 +0,0 @@
|
|||||||
# Copyright 2013 Rackspace Hosting
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
from django.conf import settings
|
|
||||||
from django.core.urlresolvers import reverse
|
|
||||||
from django.template import defaultfilters as d_filters
|
|
||||||
from django.utils.translation import pgettext_lazy
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
from django.utils.translation import ungettext_lazy
|
|
||||||
|
|
||||||
from horizon import tables
|
|
||||||
from horizon.utils import filters
|
|
||||||
|
|
||||||
from trove_dashboard import api
|
|
||||||
|
|
||||||
|
|
||||||
STATUS_CHOICES = (
|
|
||||||
("BUILDING", None),
|
|
||||||
("COMPLETED", True),
|
|
||||||
("DELETE_FAILED", False),
|
|
||||||
("FAILED", False),
|
|
||||||
("NEW", None),
|
|
||||||
("SAVING", None),
|
|
||||||
)
|
|
||||||
STATUS_DISPLAY_CHOICES = (
|
|
||||||
("BUILDING", pgettext_lazy("Current status of a Database Backup",
|
|
||||||
u"Building")),
|
|
||||||
("COMPLETED", pgettext_lazy("Current status of a Database Backup",
|
|
||||||
u"Completed")),
|
|
||||||
("DELETE_FAILED", pgettext_lazy("Current status of a Database Backup",
|
|
||||||
u"Delete Failed")),
|
|
||||||
("FAILED", pgettext_lazy("Current status of a Database Backup",
|
|
||||||
u"Failed")),
|
|
||||||
("NEW", pgettext_lazy("Current status of a Database Backup",
|
|
||||||
u"New")),
|
|
||||||
("SAVING", pgettext_lazy("Current status of a Database Backup",
|
|
||||||
u"Saving")),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class LaunchLink(tables.LinkAction):
|
|
||||||
name = "create"
|
|
||||||
verbose_name = _("Create Backup")
|
|
||||||
url = "horizon:project:database_backups:create"
|
|
||||||
classes = ("ajax-modal", "btn-create")
|
|
||||||
icon = "camera"
|
|
||||||
|
|
||||||
|
|
||||||
class RestoreLink(tables.LinkAction):
|
|
||||||
name = "restore"
|
|
||||||
verbose_name = _("Restore Backup")
|
|
||||||
url = "horizon:project:databases:launch"
|
|
||||||
classes = ("ajax-modal",)
|
|
||||||
icon = "cloud-upload"
|
|
||||||
|
|
||||||
def allowed(self, request, backup=None):
|
|
||||||
return backup.status == 'COMPLETED'
|
|
||||||
|
|
||||||
def get_link_url(self, datum):
|
|
||||||
url = reverse(self.url)
|
|
||||||
return url + '?backup=%s' % datum.id
|
|
||||||
|
|
||||||
|
|
||||||
class DownloadBackup(tables.LinkAction):
|
|
||||||
name = "download"
|
|
||||||
verbose_name = _("Download Backup")
|
|
||||||
url = 'horizon:project:containers:object_download'
|
|
||||||
classes = ("btn-download",)
|
|
||||||
|
|
||||||
def get_link_url(self, datum):
|
|
||||||
ref = datum.locationRef.split('/')
|
|
||||||
container_name = ref[5]
|
|
||||||
object_path = '/'.join(ref[6:])
|
|
||||||
return reverse(self.url,
|
|
||||||
kwargs={'container_name': container_name,
|
|
||||||
'object_path': object_path})
|
|
||||||
|
|
||||||
def allowed(self, request, datum):
|
|
||||||
legacy_swift_panel_enabled = False
|
|
||||||
if ('swift_panel' in settings.HORIZON_CONFIG and
|
|
||||||
settings.HORIZON_CONFIG['swift_panel'] == 'legacy' and
|
|
||||||
datum.status == 'COMPLETED'):
|
|
||||||
legacy_swift_panel_enabled = True
|
|
||||||
return legacy_swift_panel_enabled
|
|
||||||
|
|
||||||
|
|
||||||
class DeleteBackup(tables.DeleteAction):
|
|
||||||
@staticmethod
|
|
||||||
def action_present(count):
|
|
||||||
return ungettext_lazy(
|
|
||||||
u"Delete Backup",
|
|
||||||
u"Delete Backups",
|
|
||||||
count
|
|
||||||
)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def action_past(count):
|
|
||||||
return ungettext_lazy(
|
|
||||||
u"Deleted Backup",
|
|
||||||
u"Deleted Backups",
|
|
||||||
count
|
|
||||||
)
|
|
||||||
|
|
||||||
def delete(self, request, obj_id):
|
|
||||||
api.trove.backup_delete(request, obj_id)
|
|
||||||
|
|
||||||
|
|
||||||
class UpdateRow(tables.Row):
|
|
||||||
ajax = True
|
|
||||||
|
|
||||||
def get_data(self, request, backup_id):
|
|
||||||
backup = api.trove.backup_get(request, backup_id)
|
|
||||||
try:
|
|
||||||
backup.instance = api.trove.instance_get(request,
|
|
||||||
backup.instance_id)
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
return backup
|
|
||||||
|
|
||||||
|
|
||||||
def db_link(obj):
|
|
||||||
if not hasattr(obj, 'instance'):
|
|
||||||
return
|
|
||||||
if hasattr(obj.instance, 'name'):
|
|
||||||
return reverse(
|
|
||||||
'horizon:project:databases:detail',
|
|
||||||
kwargs={'instance_id': obj.instance_id})
|
|
||||||
|
|
||||||
|
|
||||||
def db_name(obj):
|
|
||||||
if not hasattr(obj, 'instance') or not hasattr(obj.instance, 'name'):
|
|
||||||
return obj.instance_id
|
|
||||||
return obj.instance.name
|
|
||||||
|
|
||||||
|
|
||||||
def get_datastore(obj):
|
|
||||||
if hasattr(obj, "datastore"):
|
|
||||||
return obj.datastore["type"]
|
|
||||||
return _("Not available")
|
|
||||||
|
|
||||||
|
|
||||||
def get_datastore_version(obj):
|
|
||||||
if hasattr(obj, "datastore"):
|
|
||||||
return obj.datastore["version"]
|
|
||||||
return _("Not available")
|
|
||||||
|
|
||||||
|
|
||||||
def is_incremental(obj):
|
|
||||||
return hasattr(obj, 'parent_id') and obj.parent_id is not None
|
|
||||||
|
|
||||||
|
|
||||||
class BackupsTable(tables.DataTable):
|
|
||||||
name = tables.Column("name",
|
|
||||||
link="horizon:project:database_backups:detail",
|
|
||||||
verbose_name=_("Name"))
|
|
||||||
datastore = tables.Column(get_datastore,
|
|
||||||
verbose_name=_("Datastore"))
|
|
||||||
datastore_version = tables.Column(get_datastore_version,
|
|
||||||
verbose_name=_("Datastore Version"))
|
|
||||||
created = tables.Column("created", verbose_name=_("Created"),
|
|
||||||
filters=[filters.parse_isotime])
|
|
||||||
instance = tables.Column(db_name, link=db_link,
|
|
||||||
verbose_name=_("Database"))
|
|
||||||
incremental = tables.Column(is_incremental,
|
|
||||||
verbose_name=_("Incremental"),
|
|
||||||
filters=(d_filters.yesno,
|
|
||||||
d_filters.capfirst))
|
|
||||||
status = tables.Column("status",
|
|
||||||
verbose_name=_("Status"),
|
|
||||||
status=True,
|
|
||||||
status_choices=STATUS_CHOICES,
|
|
||||||
display_choices=STATUS_DISPLAY_CHOICES)
|
|
||||||
|
|
||||||
class Meta(object):
|
|
||||||
name = "backups"
|
|
||||||
verbose_name = _("Backups")
|
|
||||||
status_columns = ["status"]
|
|
||||||
row_class = UpdateRow
|
|
||||||
table_actions = (LaunchLink, DeleteBackup)
|
|
||||||
row_actions = (RestoreLink, DownloadBackup, DeleteBackup)
|
|
@ -1,4 +0,0 @@
|
|||||||
{% load i18n %}
|
|
||||||
|
|
||||||
<p>{% blocktrans trimmed %}Specify the details for the database backup.{% endblocktrans %}</p>
|
|
||||||
<p>{% blocktrans trimmed %}You can perform an incremental backup by specifying a parent backup. <strong>However,</strong> not all databases support incremental backups in which case this operation will result in an error.{% endblocktrans %}</p>
|
|
@ -1,7 +0,0 @@
|
|||||||
{% extends 'base.html' %}
|
|
||||||
{% load i18n %}
|
|
||||||
{% block title %}{% trans "Backup Database" %}{% endblock %}
|
|
||||||
|
|
||||||
{% block main %}
|
|
||||||
{% include 'horizon/common/_workflow.html' %}
|
|
||||||
{% endblock %}
|
|
@ -1,74 +0,0 @@
|
|||||||
{% extends 'base.html' %}
|
|
||||||
{% load i18n sizeformat %}
|
|
||||||
{% block title %}{% trans "Backup Details" %}{% endblock %}
|
|
||||||
|
|
||||||
{% block main %}
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-sm-12">
|
|
||||||
<h3>{% trans "Backup Overview" %}</h3>
|
|
||||||
|
|
||||||
<div class="status row detail">
|
|
||||||
<h4>{% trans "Information" %}</h4>
|
|
||||||
<hr class="header_rule">
|
|
||||||
<dl class="dl-horizontal">
|
|
||||||
<dt>{% trans "Name" %}</dt>
|
|
||||||
<dd>{{ backup.name }}</dd>
|
|
||||||
<dt>{% trans "Description" %}</dt>
|
|
||||||
<dd>{{ backup.description|linebreaksbr }}</dd>
|
|
||||||
<dt>{% trans "ID" %}</dt>
|
|
||||||
<dd>{{ backup.id }}</dd>
|
|
||||||
{% if backup.datastore %}
|
|
||||||
<dt>{% trans "Datastore" %}</dt>
|
|
||||||
<dd>{{ backup.datastore.type }}</dd>
|
|
||||||
<dt>{% trans "Datastore Version" %}</dt>
|
|
||||||
<dd>{{ backup.datastore.version }}</dd>
|
|
||||||
{% endif %}
|
|
||||||
<dt>{% trans "Status" %}</dt>
|
|
||||||
<dd>{{ backup.status|title }}</dd>
|
|
||||||
<dt>{% trans "Backup File Location" %}</dt>
|
|
||||||
<dd>{{ backup.locationRef }}</dd>
|
|
||||||
<dt>{% trans "Initial Volume Size" %}</dt>
|
|
||||||
<dd>{{ backup.size }} {% trans "GB" %}</dd>
|
|
||||||
<dt>{% trans "Created" %}</dt>
|
|
||||||
<dd>{{ backup.created|parse_isotime }}</dd>
|
|
||||||
<dt>{% trans "Updated" %}</dt>
|
|
||||||
<dd>{{ backup.updated|parse_isotime }}</dd>
|
|
||||||
<dt>{% trans "Backup Duration" %}</dt>
|
|
||||||
<dd>{{ backup.duration }}</dd>
|
|
||||||
</dl>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% if backup.parent %}
|
|
||||||
<div class="status row detail">
|
|
||||||
<h4>{% trans "Incremental Backup" %}</h4>
|
|
||||||
<hr class="header_rule">
|
|
||||||
<dl class="dl-horizontal">
|
|
||||||
<dt>{% trans "Parent Backup" %}</dt>
|
|
||||||
<dd>
|
|
||||||
{% url 'horizon:project:database_backups:detail' backup.parent.id as parent_url %}
|
|
||||||
<a href="{{ parent_url }}">{{ backup.parent.name }}</a>
|
|
||||||
</dd>
|
|
||||||
</dl>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if instance %}
|
|
||||||
<div class="addresses row detail">
|
|
||||||
<h4>{% trans "Database Info" %}</h4>
|
|
||||||
<hr class="header_rule">
|
|
||||||
<dl class="dl-horizontal">
|
|
||||||
<dt>{% trans "Name" %}</dt>
|
|
||||||
<dd>{{ instance.name }}</dd>
|
|
||||||
<dt>{% trans "ID" %}</dt>
|
|
||||||
<dd>
|
|
||||||
{% url 'horizon:project:databases:detail' instance.id as instance_url %}
|
|
||||||
<a href="{{ instance_url }}">{{ instance.id }}</a>
|
|
||||||
</dd>
|
|
||||||
<dt>{% trans "Status" %}</dt>
|
|
||||||
<dd>{{ instance.status|title }}</dd>
|
|
||||||
</dl>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
@ -1,7 +0,0 @@
|
|||||||
{% extends 'base.html' %}
|
|
||||||
{% load i18n %}
|
|
||||||
{% block title %}{% trans "Database Backups" %}{% endblock %}
|
|
||||||
|
|
||||||
{% block main %}
|
|
||||||
{{ table.render }}
|
|
||||||
{% endblock %}
|
|
@ -1,193 +0,0 @@
|
|||||||
# Copyright 2013 Mirantis Inc.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
from django.core.urlresolvers import reverse
|
|
||||||
from django import http
|
|
||||||
from mox3.mox import IsA # noqa
|
|
||||||
import six
|
|
||||||
|
|
||||||
from openstack_auth import policy
|
|
||||||
|
|
||||||
from trove_dashboard import api
|
|
||||||
from trove_dashboard.test import helpers as test
|
|
||||||
|
|
||||||
INDEX_URL = reverse('horizon:project:database_backups:index')
|
|
||||||
BACKUP_URL = reverse('horizon:project:database_backups:create')
|
|
||||||
DETAILS_URL = reverse('horizon:project:database_backups:detail', args=['id'])
|
|
||||||
|
|
||||||
|
|
||||||
class DatabasesBackupsTests(test.TestCase):
|
|
||||||
@test.create_stubs({api.trove: ('backup_list', 'instance_get')})
|
|
||||||
def test_index(self):
|
|
||||||
api.trove.backup_list(IsA(http.HttpRequest))\
|
|
||||||
.AndReturn(self.database_backups.list())
|
|
||||||
|
|
||||||
api.trove.instance_get(IsA(http.HttpRequest),
|
|
||||||
IsA(str))\
|
|
||||||
.MultipleTimes()\
|
|
||||||
.AndReturn(self.databases.first())
|
|
||||||
|
|
||||||
self.mox.ReplayAll()
|
|
||||||
|
|
||||||
res = self.client.get(INDEX_URL)
|
|
||||||
|
|
||||||
self.assertTemplateUsed(res, 'project/database_backups/index.html')
|
|
||||||
|
|
||||||
@test.create_stubs({api.trove: ('backup_list',)})
|
|
||||||
def test_index_exception(self):
|
|
||||||
api.trove.backup_list(IsA(http.HttpRequest))\
|
|
||||||
.AndRaise(self.exceptions.trove)
|
|
||||||
|
|
||||||
self.mox.ReplayAll()
|
|
||||||
|
|
||||||
res = self.client.get(INDEX_URL)
|
|
||||||
|
|
||||||
self.assertTemplateUsed(
|
|
||||||
res, 'project/database_backups/index.html')
|
|
||||||
self.assertEqual(res.status_code, 200)
|
|
||||||
self.assertMessageCount(res, error=1)
|
|
||||||
|
|
||||||
@test.create_stubs({
|
|
||||||
api.trove: ('instance_list', 'backup_list', 'backup_create'),
|
|
||||||
policy: ('check',),
|
|
||||||
})
|
|
||||||
def test_launch_backup(self):
|
|
||||||
policy.check((), IsA(http.HttpRequest)).MultipleTimes().AndReturn(True)
|
|
||||||
api.trove.instance_list(IsA(http.HttpRequest))\
|
|
||||||
.AndReturn(self.databases.list())
|
|
||||||
api.trove.backup_list(IsA(http.HttpRequest)) \
|
|
||||||
.AndReturn(self.database_backups.list())
|
|
||||||
|
|
||||||
database = self.databases.first()
|
|
||||||
backupName = "NewBackup"
|
|
||||||
backupDesc = "Backup Description"
|
|
||||||
|
|
||||||
api.trove.backup_create(
|
|
||||||
IsA(http.HttpRequest),
|
|
||||||
backupName,
|
|
||||||
database.id,
|
|
||||||
backupDesc,
|
|
||||||
"")
|
|
||||||
|
|
||||||
self.mox.ReplayAll()
|
|
||||||
|
|
||||||
post = {
|
|
||||||
'name': backupName,
|
|
||||||
'instance': database.id,
|
|
||||||
'description': backupDesc,
|
|
||||||
'parent': ""
|
|
||||||
}
|
|
||||||
res = self.client.post(BACKUP_URL, post)
|
|
||||||
|
|
||||||
self.assertNoFormErrors(res)
|
|
||||||
self.assertRedirectsNoFollow(res, INDEX_URL)
|
|
||||||
|
|
||||||
@test.create_stubs({
|
|
||||||
api.trove: ('instance_list', 'backup_list'),
|
|
||||||
policy: ('check',),
|
|
||||||
})
|
|
||||||
def test_launch_backup_exception(self):
|
|
||||||
policy.check((), IsA(http.HttpRequest)).MultipleTimes().AndReturn(True)
|
|
||||||
api.trove.instance_list(IsA(http.HttpRequest))\
|
|
||||||
.AndRaise(self.exceptions.trove)
|
|
||||||
api.trove.backup_list(IsA(http.HttpRequest)) \
|
|
||||||
.AndReturn(self.database_backups.list())
|
|
||||||
|
|
||||||
self.mox.ReplayAll()
|
|
||||||
|
|
||||||
res = self.client.get(BACKUP_URL)
|
|
||||||
self.assertMessageCount(res, error=1)
|
|
||||||
self.assertTemplateUsed(res,
|
|
||||||
'project/database_backups/backup.html')
|
|
||||||
|
|
||||||
@test.create_stubs({
|
|
||||||
api.trove: ('instance_list', 'backup_list', 'backup_create'),
|
|
||||||
policy: ('check',),
|
|
||||||
})
|
|
||||||
def test_launch_backup_incr(self):
|
|
||||||
policy.check((), IsA(http.HttpRequest)).MultipleTimes().AndReturn(True)
|
|
||||||
api.trove.instance_list(IsA(http.HttpRequest)) \
|
|
||||||
.AndReturn(self.databases.list())
|
|
||||||
api.trove.backup_list(IsA(http.HttpRequest)) \
|
|
||||||
.AndReturn(self.database_backups.list())
|
|
||||||
|
|
||||||
database = self.databases.first()
|
|
||||||
backupName = "NewBackup"
|
|
||||||
backupDesc = "Backup Description"
|
|
||||||
backupParent = self.database_backups.first()
|
|
||||||
|
|
||||||
api.trove.backup_create(
|
|
||||||
IsA(http.HttpRequest),
|
|
||||||
backupName,
|
|
||||||
database.id,
|
|
||||||
backupDesc,
|
|
||||||
backupParent.id)
|
|
||||||
|
|
||||||
self.mox.ReplayAll()
|
|
||||||
|
|
||||||
post = {
|
|
||||||
'name': backupName,
|
|
||||||
'instance': database.id,
|
|
||||||
'description': backupDesc,
|
|
||||||
'parent': backupParent.id,
|
|
||||||
}
|
|
||||||
res = self.client.post(BACKUP_URL, post)
|
|
||||||
|
|
||||||
self.assertNoFormErrors(res)
|
|
||||||
self.assertRedirectsNoFollow(res, INDEX_URL)
|
|
||||||
|
|
||||||
@test.create_stubs({api.trove: ('backup_get', 'instance_get')})
|
|
||||||
def test_detail_backup(self):
|
|
||||||
api.trove.backup_get(IsA(http.HttpRequest),
|
|
||||||
IsA(six.text_type))\
|
|
||||||
.AndReturn(self.database_backups.first())
|
|
||||||
|
|
||||||
api.trove.instance_get(IsA(http.HttpRequest),
|
|
||||||
IsA(str))\
|
|
||||||
.AndReturn(self.databases.first())
|
|
||||||
|
|
||||||
self.mox.ReplayAll()
|
|
||||||
res = self.client.get(DETAILS_URL)
|
|
||||||
|
|
||||||
self.assertTemplateUsed(res,
|
|
||||||
'project/database_backups/details.html')
|
|
||||||
|
|
||||||
@test.create_stubs({api.trove: ('backup_get',)})
|
|
||||||
def test_detail_backup_notfound(self):
|
|
||||||
api.trove.backup_get(IsA(http.HttpRequest),
|
|
||||||
IsA(six.text_type))\
|
|
||||||
.AndRaise(self.exceptions.trove)
|
|
||||||
|
|
||||||
self.mox.ReplayAll()
|
|
||||||
res = self.client.get(DETAILS_URL)
|
|
||||||
|
|
||||||
self.assertRedirectsNoFollow(res, INDEX_URL)
|
|
||||||
|
|
||||||
@test.create_stubs({api.trove: ('backup_get', 'instance_get')})
|
|
||||||
def test_detail_backup_incr(self):
|
|
||||||
incr_backup = self.database_backups.list()[2]
|
|
||||||
parent_backup = self.database_backups.list()[1]
|
|
||||||
|
|
||||||
api.trove.backup_get(IsA(http.HttpRequest), IsA(six.text_type))\
|
|
||||||
.AndReturn(incr_backup)
|
|
||||||
api.trove.backup_get(IsA(http.HttpRequest), incr_backup.parent_id) \
|
|
||||||
.AndReturn(parent_backup)
|
|
||||||
api.trove.instance_get(IsA(http.HttpRequest), IsA(str))\
|
|
||||||
.AndReturn(self.databases.list()[1])
|
|
||||||
|
|
||||||
self.mox.ReplayAll()
|
|
||||||
url = reverse('horizon:project:database_backups:detail',
|
|
||||||
args=[incr_backup.id])
|
|
||||||
res = self.client.get(url)
|
|
||||||
self.assertTemplateUsed(res, 'project/database_backups/details.html')
|
|
@ -1,24 +0,0 @@
|
|||||||
# Copyright 2013 Rackspace Hosting
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
from django.conf.urls import url
|
|
||||||
|
|
||||||
from trove_dashboard.content.database_backups import views
|
|
||||||
|
|
||||||
urlpatterns = [
|
|
||||||
url(r'^$', views.IndexView.as_view(), name='index'),
|
|
||||||
url(r'^create$', views.BackupView.as_view(), name='create'),
|
|
||||||
url(r'^(?P<backup_id>[^/]+)/$', views.DetailView.as_view(),
|
|
||||||
name='detail'),
|
|
||||||
]
|
|
@ -1,110 +0,0 @@
|
|||||||
# Copyright 2013 Rackspace Hosting
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
|
|
||||||
"""
|
|
||||||
Views for displaying database backups.
|
|
||||||
"""
|
|
||||||
from django.core.urlresolvers import reverse
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
|
|
||||||
from horizon import exceptions
|
|
||||||
from horizon import tables as horizon_tables
|
|
||||||
from horizon.utils import filters
|
|
||||||
from horizon import views as horizon_views
|
|
||||||
from horizon import workflows as horizon_workflows
|
|
||||||
|
|
||||||
from trove_dashboard import api
|
|
||||||
from trove_dashboard.content.database_backups import tables
|
|
||||||
from trove_dashboard.content.database_backups \
|
|
||||||
import workflows
|
|
||||||
|
|
||||||
|
|
||||||
class IndexView(horizon_tables.DataTableView):
|
|
||||||
table_class = tables.BackupsTable
|
|
||||||
template_name = 'project/database_backups/index.html'
|
|
||||||
page_title = _("Backups")
|
|
||||||
|
|
||||||
def _get_extra_data(self, backup):
|
|
||||||
"""Apply extra info to the backup."""
|
|
||||||
instance_id = backup.instance_id
|
|
||||||
# TODO(rdopieralski) It's not clear where this attribute is supposed
|
|
||||||
# to come from. At first glance it looks like it will always be {}.
|
|
||||||
if not hasattr(self, '_instances'):
|
|
||||||
self._instances = {}
|
|
||||||
instance = self._instances.get(instance_id)
|
|
||||||
if instance is None:
|
|
||||||
try:
|
|
||||||
instance = api.trove.instance_get(self.request, instance_id)
|
|
||||||
except Exception:
|
|
||||||
instance = _('Not Found')
|
|
||||||
backup.instance = instance
|
|
||||||
return backup
|
|
||||||
|
|
||||||
def get_data(self):
|
|
||||||
# TODO(rmyers) Add pagination support after it is available
|
|
||||||
# https://blueprints.launchpad.net/trove/+spec/paginate-backup-list
|
|
||||||
try:
|
|
||||||
backups = api.trove.backup_list(self.request)
|
|
||||||
backups = map(self._get_extra_data, backups)
|
|
||||||
except Exception:
|
|
||||||
backups = []
|
|
||||||
msg = _('Error getting database backup list.')
|
|
||||||
exceptions.handle(self.request, msg)
|
|
||||||
return backups
|
|
||||||
|
|
||||||
|
|
||||||
class BackupView(horizon_workflows.WorkflowView):
|
|
||||||
workflow_class = workflows.CreateBackup
|
|
||||||
template_name = "project/database_backups/backup.html"
|
|
||||||
page_title = _("Backup Database")
|
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
|
||||||
context = super(BackupView, self).get_context_data(**kwargs)
|
|
||||||
context["instance_id"] = kwargs.get("instance_id")
|
|
||||||
self._instance = context['instance_id']
|
|
||||||
return context
|
|
||||||
|
|
||||||
|
|
||||||
class DetailView(horizon_views.APIView):
|
|
||||||
template_name = "project/database_backups/details.html"
|
|
||||||
page_title = _("Backup Details: {{ backup.name }}")
|
|
||||||
|
|
||||||
def get_data(self, request, context, *args, **kwargs):
|
|
||||||
backup_id = kwargs.get("backup_id")
|
|
||||||
try:
|
|
||||||
backup = api.trove.backup_get(request, backup_id)
|
|
||||||
created_at = filters.parse_isotime(backup.created)
|
|
||||||
updated_at = filters.parse_isotime(backup.updated)
|
|
||||||
backup.duration = updated_at - created_at
|
|
||||||
except Exception:
|
|
||||||
redirect = reverse('horizon:project:database_backups:index')
|
|
||||||
msg = _('Unable to retrieve details for backup: %s') % backup_id
|
|
||||||
exceptions.handle(self.request, msg, redirect=redirect)
|
|
||||||
|
|
||||||
try:
|
|
||||||
if(hasattr(backup, 'parent_id') and backup.parent_id is not None):
|
|
||||||
backup.parent = api.trove.backup_get(request, backup.parent_id)
|
|
||||||
except Exception:
|
|
||||||
redirect = reverse('horizon:project:database_backups:index')
|
|
||||||
msg = (_('Unable to retrieve details for parent backup: %s')
|
|
||||||
% backup.parent_id)
|
|
||||||
exceptions.handle(self.request, msg, redirect=redirect)
|
|
||||||
|
|
||||||
try:
|
|
||||||
instance = api.trove.instance_get(request, backup.instance_id)
|
|
||||||
except Exception:
|
|
||||||
instance = None
|
|
||||||
context['backup'] = backup
|
|
||||||
context['instance'] = instance
|
|
||||||
return context
|
|
@ -1,3 +0,0 @@
|
|||||||
# Importing non-modules that are not used explicitly
|
|
||||||
|
|
||||||
from .create_backup import CreateBackup # noqa
|
|
@ -1,110 +0,0 @@
|
|||||||
# Copyright 2013 Rackspace Hosting
|
|
||||||
#
|
|
||||||
# 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 logging
|
|
||||||
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
|
|
||||||
from horizon import exceptions
|
|
||||||
from horizon import forms
|
|
||||||
from horizon import workflows
|
|
||||||
|
|
||||||
from trove_dashboard import api
|
|
||||||
from trove_dashboard.content.databases \
|
|
||||||
import tables as project_tables
|
|
||||||
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class BackupDetailsAction(workflows.Action):
|
|
||||||
name = forms.CharField(max_length=80, label=_("Name"))
|
|
||||||
instance = forms.ChoiceField(label=_("Database Instance"))
|
|
||||||
description = forms.CharField(max_length=512, label=_("Description"),
|
|
||||||
widget=forms.TextInput(),
|
|
||||||
required=False,
|
|
||||||
help_text=_("Optional Backup Description"))
|
|
||||||
parent = forms.ChoiceField(label=_("Parent Backup"),
|
|
||||||
required=False,
|
|
||||||
help_text=_("Optional parent backup"))
|
|
||||||
|
|
||||||
class Meta(object):
|
|
||||||
name = _("Details")
|
|
||||||
help_text_template = \
|
|
||||||
"project/database_backups/_backup_details_help.html"
|
|
||||||
|
|
||||||
def populate_instance_choices(self, request, context):
|
|
||||||
LOG.info("Obtaining list of instances.")
|
|
||||||
try:
|
|
||||||
instances = api.trove.instance_list(request)
|
|
||||||
except Exception:
|
|
||||||
instances = []
|
|
||||||
msg = _("Unable to list database instances to backup.")
|
|
||||||
exceptions.handle(request, msg)
|
|
||||||
return [(i.id, i.name) for i in instances
|
|
||||||
if i.status in project_tables.ACTIVE_STATES]
|
|
||||||
|
|
||||||
def populate_parent_choices(self, request, context):
|
|
||||||
try:
|
|
||||||
backups = api.trove.backup_list(request)
|
|
||||||
choices = [(b.id, b.name) for b in backups
|
|
||||||
if b.status == 'COMPLETED']
|
|
||||||
except Exception:
|
|
||||||
choices = []
|
|
||||||
msg = _("Unable to list database backups for parent.")
|
|
||||||
exceptions.handle(request, msg)
|
|
||||||
|
|
||||||
if choices:
|
|
||||||
choices.insert(0, ("", _("Select parent backup")))
|
|
||||||
else:
|
|
||||||
choices.insert(0, ("", _("No backups available")))
|
|
||||||
return choices
|
|
||||||
|
|
||||||
|
|
||||||
class SetBackupDetails(workflows.Step):
|
|
||||||
action_class = BackupDetailsAction
|
|
||||||
contributes = ["name", "description", "instance", "parent"]
|
|
||||||
|
|
||||||
|
|
||||||
class CreateBackup(workflows.Workflow):
|
|
||||||
slug = "create_backup"
|
|
||||||
name = _("Backup Database")
|
|
||||||
finalize_button_name = _("Backup")
|
|
||||||
success_message = _('Scheduled backup "%(name)s".')
|
|
||||||
failure_message = _('Unable to launch %(count)s named "%(name)s".')
|
|
||||||
success_url = "horizon:project:database_backups:index"
|
|
||||||
default_steps = [SetBackupDetails]
|
|
||||||
|
|
||||||
def get_initial(self):
|
|
||||||
initial = super(CreateBackup, self).get_initial()
|
|
||||||
initial['instance_id']
|
|
||||||
|
|
||||||
def format_status_message(self, message):
|
|
||||||
name = self.context.get('name', 'unknown instance')
|
|
||||||
return message % {"count": _("instance"), "name": name}
|
|
||||||
|
|
||||||
def handle(self, request, context):
|
|
||||||
try:
|
|
||||||
LOG.info("Creating backup")
|
|
||||||
api.trove.backup_create(request,
|
|
||||||
context['name'],
|
|
||||||
context['instance'],
|
|
||||||
context['description'],
|
|
||||||
context['parent'])
|
|
||||||
return True
|
|
||||||
except Exception:
|
|
||||||
LOG.exception("Exception while creating backup")
|
|
||||||
msg = _('Error creating database backup.')
|
|
||||||
exceptions.handle(request, msg)
|
|
||||||
return False
|
|
@ -1,89 +0,0 @@
|
|||||||
# Copyright 2016 Tesora Inc.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
|
|
||||||
from django.core import cache
|
|
||||||
|
|
||||||
|
|
||||||
def get(cluster_id):
|
|
||||||
if not has_cluster(cluster_id):
|
|
||||||
manager = ClusterInstanceManager(cluster_id)
|
|
||||||
cache.cache.set(cluster_id, manager)
|
|
||||||
|
|
||||||
return cache.cache.get(cluster_id)
|
|
||||||
|
|
||||||
|
|
||||||
def delete(cluster_id):
|
|
||||||
manager = get(cluster_id)
|
|
||||||
manager.clear_instances()
|
|
||||||
cache.cache.delete(cluster_id)
|
|
||||||
|
|
||||||
|
|
||||||
def update(cluster_id, manager):
|
|
||||||
cache.cache.set(cluster_id, manager)
|
|
||||||
|
|
||||||
|
|
||||||
def has_cluster(cluster_id):
|
|
||||||
if cache.cache.get(cluster_id):
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
class ClusterInstanceManager(object):
|
|
||||||
def __init__(self, cluster_id):
|
|
||||||
self.cluster_id = cluster_id
|
|
||||||
self.instances = []
|
|
||||||
|
|
||||||
def get_instances(self):
|
|
||||||
if not hasattr(self, 'instances'):
|
|
||||||
self.instances = []
|
|
||||||
return self.instances
|
|
||||||
|
|
||||||
def get_instance(self, id):
|
|
||||||
for instance in self.get_instances():
|
|
||||||
if instance.id == id:
|
|
||||||
return instance
|
|
||||||
return None
|
|
||||||
|
|
||||||
def add_instance(self, id, name, flavor_id,
|
|
||||||
flavor, volume, type, related_to, nics):
|
|
||||||
instance = ClusterInstance(id, name, flavor_id, flavor,
|
|
||||||
volume, type, related_to, nics)
|
|
||||||
self.instances.append(instance)
|
|
||||||
update(self.cluster_id, self)
|
|
||||||
return self.get_instances()
|
|
||||||
|
|
||||||
def delete_instance(self, id):
|
|
||||||
instance = self.get_instance(id)
|
|
||||||
if instance:
|
|
||||||
self.instances.remove(instance)
|
|
||||||
update(self.cluster_id, self)
|
|
||||||
|
|
||||||
def clear_instances(self):
|
|
||||||
self.instances = []
|
|
||||||
update(self.cluster_id, self)
|
|
||||||
|
|
||||||
|
|
||||||
class ClusterInstance(object):
|
|
||||||
def __init__(self, id, name, flavor_id, flavor, volume, type,
|
|
||||||
related_to, nics):
|
|
||||||
self.id = id
|
|
||||||
self.name = name
|
|
||||||
self.flavor_id = flavor_id
|
|
||||||
self.flavor = flavor
|
|
||||||
self.volume = volume
|
|
||||||
self.type = type
|
|
||||||
self.related_to = related_to
|
|
||||||
self.nics = nics
|
|
@ -1,493 +0,0 @@
|
|||||||
# Copyright 2015 HP Software, LLC
|
|
||||||
# All Rights Reserved.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
import binascii
|
|
||||||
import collections
|
|
||||||
import logging
|
|
||||||
import uuid
|
|
||||||
|
|
||||||
from django.core.urlresolvers import reverse
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
from django.views.decorators.debug import sensitive_variables # noqa
|
|
||||||
|
|
||||||
from horizon import exceptions
|
|
||||||
from horizon import forms
|
|
||||||
from horizon import messages
|
|
||||||
from horizon.utils import memoized
|
|
||||||
from openstack_dashboard import api
|
|
||||||
|
|
||||||
from openstack_dashboard.dashboards.project.instances \
|
|
||||||
import utils as instance_utils
|
|
||||||
from trove_dashboard import api as trove_api
|
|
||||||
from trove_dashboard.content.database_clusters \
|
|
||||||
import cluster_manager
|
|
||||||
from trove_dashboard.content.databases import db_capability
|
|
||||||
from trove_dashboard.content.databases.workflows \
|
|
||||||
import create_instance
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class LaunchForm(forms.SelfHandlingForm):
|
|
||||||
name = forms.CharField(label=_("Cluster Name"),
|
|
||||||
max_length=80)
|
|
||||||
datastore = forms.ChoiceField(
|
|
||||||
label=_("Datastore"),
|
|
||||||
help_text=_("Type and version of datastore."),
|
|
||||||
widget=forms.Select(attrs={
|
|
||||||
'class': 'switchable',
|
|
||||||
'data-slug': 'datastore'
|
|
||||||
}))
|
|
||||||
network = forms.ChoiceField(
|
|
||||||
label=_("Network"),
|
|
||||||
help_text=_("Network attached to instance."),
|
|
||||||
required=False)
|
|
||||||
volume = forms.IntegerField(
|
|
||||||
label=_("Volume Size"),
|
|
||||||
min_value=0,
|
|
||||||
initial=1,
|
|
||||||
help_text=_("Size of the volume in GB."))
|
|
||||||
locality = forms.ChoiceField(
|
|
||||||
label=_("Locality"),
|
|
||||||
choices=[("", "None"),
|
|
||||||
("affinity", "affinity"),
|
|
||||||
("anti-affinity", "anti-affinity")],
|
|
||||||
required=False,
|
|
||||||
help_text=_("Specify whether instances in the cluster will "
|
|
||||||
"be created on the same hypervisor (affinity) or on "
|
|
||||||
"different hypervisors (anti-affinity)."))
|
|
||||||
root_password = forms.CharField(
|
|
||||||
label=_("Root Password"),
|
|
||||||
required=False,
|
|
||||||
help_text=_("Password for root user."),
|
|
||||||
widget=forms.PasswordInput(attrs={
|
|
||||||
'class': 'switched',
|
|
||||||
'data-switch-on': 'datastore',
|
|
||||||
}))
|
|
||||||
num_instances_vertica = forms.IntegerField(
|
|
||||||
label=_("Number of Instances"),
|
|
||||||
min_value=3,
|
|
||||||
initial=3,
|
|
||||||
required=False,
|
|
||||||
help_text=_("Number of instances in the cluster. (Read only)"),
|
|
||||||
widget=forms.TextInput(attrs={
|
|
||||||
'readonly': 'readonly',
|
|
||||||
'class': 'switched',
|
|
||||||
'data-switch-on': 'datastore',
|
|
||||||
}))
|
|
||||||
num_shards = forms.IntegerField(
|
|
||||||
label=_("Number of Shards"),
|
|
||||||
min_value=1,
|
|
||||||
initial=1,
|
|
||||||
required=False,
|
|
||||||
help_text=_("Number of shards. (Read only)"),
|
|
||||||
widget=forms.TextInput(attrs={
|
|
||||||
'readonly': 'readonly',
|
|
||||||
'class': 'switched',
|
|
||||||
'data-switch-on': 'datastore',
|
|
||||||
}))
|
|
||||||
num_instances = forms.IntegerField(
|
|
||||||
label=_("Number of Instances"),
|
|
||||||
initial=3,
|
|
||||||
required=False,
|
|
||||||
help_text=_("Number of instances in the cluster."),
|
|
||||||
widget=forms.TextInput(attrs={
|
|
||||||
'class': 'switched',
|
|
||||||
'data-switch-on': 'datastore',
|
|
||||||
}))
|
|
||||||
|
|
||||||
# (name of field variable, label)
|
|
||||||
default_fields = [
|
|
||||||
('num_instances', _('Number of Instances'))
|
|
||||||
]
|
|
||||||
mongodb_fields = default_fields + [
|
|
||||||
('num_shards', _('Number of Shards')),
|
|
||||||
]
|
|
||||||
vertica_fields = [
|
|
||||||
('num_instances_vertica', ('Number of Instances')),
|
|
||||||
('root_password', _('Root Password')),
|
|
||||||
]
|
|
||||||
|
|
||||||
def __init__(self, request, *args, **kwargs):
|
|
||||||
super(LaunchForm, self).__init__(request, *args, **kwargs)
|
|
||||||
|
|
||||||
self.fields['datastore'].choices = self.populate_datastore_choices(
|
|
||||||
request)
|
|
||||||
self.fields['network'].choices = self.populate_network_choices(
|
|
||||||
request)
|
|
||||||
|
|
||||||
def clean(self):
|
|
||||||
datastore_field_value = self.data.get("datastore", None)
|
|
||||||
if datastore_field_value:
|
|
||||||
datastore, datastore_version = (
|
|
||||||
create_instance.parse_datastore_and_version_text(
|
|
||||||
binascii.unhexlify(datastore_field_value)))
|
|
||||||
|
|
||||||
flavor_field_name = self._build_widget_field_name(
|
|
||||||
datastore, datastore_version)
|
|
||||||
if not self.data.get(flavor_field_name, None):
|
|
||||||
msg = _("The flavor must be specified.")
|
|
||||||
self._errors[flavor_field_name] = self.error_class([msg])
|
|
||||||
|
|
||||||
if db_capability.is_vertica_datastore(datastore):
|
|
||||||
if not self.data.get("root_password", None):
|
|
||||||
msg = _("Password for root user must be specified.")
|
|
||||||
self._errors["root_password"] = self.error_class([msg])
|
|
||||||
else:
|
|
||||||
if int(self.data.get("num_instances", 0)) < 1:
|
|
||||||
msg = _("The number of instances must be greater than 1.")
|
|
||||||
self._errors["num_instances"] = self.error_class([msg])
|
|
||||||
|
|
||||||
if db_capability.is_mongodb_datastore(datastore):
|
|
||||||
if int(self.data.get("num_shards", 0)) < 1:
|
|
||||||
msg = _("The number of shards must be greater than 1.")
|
|
||||||
self._errors["num_shards"] = self.error_class([msg])
|
|
||||||
|
|
||||||
if not self.data.get("locality", None):
|
|
||||||
self.cleaned_data["locality"] = None
|
|
||||||
|
|
||||||
return self.cleaned_data
|
|
||||||
|
|
||||||
@memoized.memoized_method
|
|
||||||
def datastore_flavors(self, request, datastore_name, datastore_version):
|
|
||||||
try:
|
|
||||||
return trove_api.trove.datastore_flavors(
|
|
||||||
request, datastore_name, datastore_version)
|
|
||||||
except Exception:
|
|
||||||
LOG.exception("Exception while obtaining flavors list")
|
|
||||||
self._flavors = []
|
|
||||||
redirect = reverse('horizon:project:database_clusters:index')
|
|
||||||
exceptions.handle(request,
|
|
||||||
_('Unable to obtain flavors.'),
|
|
||||||
redirect=redirect)
|
|
||||||
|
|
||||||
@memoized.memoized_method
|
|
||||||
def populate_network_choices(self, request):
|
|
||||||
network_list = []
|
|
||||||
try:
|
|
||||||
if api.base.is_service_enabled(request, 'network'):
|
|
||||||
tenant_id = self.request.user.tenant_id
|
|
||||||
networks = api.neutron.network_list_for_tenant(request,
|
|
||||||
tenant_id)
|
|
||||||
network_list = [(network.id, network.name_or_id)
|
|
||||||
for network in networks]
|
|
||||||
else:
|
|
||||||
self.fields['network'].widget = forms.HiddenInput()
|
|
||||||
except exceptions.ServiceCatalogException:
|
|
||||||
network_list = []
|
|
||||||
redirect = reverse('horizon:project:database_clusters:index')
|
|
||||||
exceptions.handle(request,
|
|
||||||
_('Unable to retrieve networks.'),
|
|
||||||
redirect=redirect)
|
|
||||||
return network_list
|
|
||||||
|
|
||||||
@memoized.memoized_method
|
|
||||||
def datastores(self, request):
|
|
||||||
try:
|
|
||||||
return trove_api.trove.datastore_list(request)
|
|
||||||
except Exception:
|
|
||||||
LOG.exception("Exception while obtaining datastores list")
|
|
||||||
self._datastores = []
|
|
||||||
redirect = reverse('horizon:project:database_clusters:index')
|
|
||||||
exceptions.handle(request,
|
|
||||||
_('Unable to obtain datastores.'),
|
|
||||||
redirect=redirect)
|
|
||||||
|
|
||||||
def filter_cluster_datastores(self, request):
|
|
||||||
datastores = []
|
|
||||||
for ds in self.datastores(request):
|
|
||||||
# TODO(michayu): until capabilities lands
|
|
||||||
if db_capability.is_cluster_capable_datastore(ds.name):
|
|
||||||
datastores.append(ds)
|
|
||||||
return datastores
|
|
||||||
|
|
||||||
@memoized.memoized_method
|
|
||||||
def datastore_versions(self, request, datastore):
|
|
||||||
try:
|
|
||||||
return trove_api.trove.datastore_version_list(request, datastore)
|
|
||||||
except Exception:
|
|
||||||
LOG.exception("Exception while obtaining datastore version list")
|
|
||||||
self._datastore_versions = []
|
|
||||||
redirect = reverse('horizon:project:database_clusters:index')
|
|
||||||
exceptions.handle(request,
|
|
||||||
_('Unable to obtain datastore versions.'),
|
|
||||||
redirect=redirect)
|
|
||||||
|
|
||||||
def populate_datastore_choices(self, request):
|
|
||||||
choices = ()
|
|
||||||
datastores = self.filter_cluster_datastores(request)
|
|
||||||
if datastores is not None:
|
|
||||||
datastore_flavor_fields = {}
|
|
||||||
for ds in datastores:
|
|
||||||
versions = self.datastore_versions(request, ds.name)
|
|
||||||
if versions:
|
|
||||||
# only add to choices if datastore has at least one version
|
|
||||||
version_choices = ()
|
|
||||||
for v in versions:
|
|
||||||
if hasattr(v, 'active') and not v.active:
|
|
||||||
continue
|
|
||||||
selection_text = self._build_datastore_display_text(
|
|
||||||
ds.name, v.name)
|
|
||||||
widget_text = self._build_widget_field_name(
|
|
||||||
ds.name, v.name)
|
|
||||||
version_choices = (version_choices +
|
|
||||||
((widget_text, selection_text),))
|
|
||||||
k, v = self._add_datastore_flavor_field(request,
|
|
||||||
ds.name,
|
|
||||||
v.name)
|
|
||||||
datastore_flavor_fields[k] = v
|
|
||||||
self._add_attr_to_optional_fields(ds.name,
|
|
||||||
widget_text)
|
|
||||||
|
|
||||||
choices = choices + version_choices
|
|
||||||
self._insert_datastore_version_fields(datastore_flavor_fields)
|
|
||||||
return choices
|
|
||||||
|
|
||||||
def _add_datastore_flavor_field(self,
|
|
||||||
request,
|
|
||||||
datastore,
|
|
||||||
datastore_version):
|
|
||||||
name = self._build_widget_field_name(datastore, datastore_version)
|
|
||||||
attr_key = 'data-datastore-' + name
|
|
||||||
field = forms.ChoiceField(
|
|
||||||
label=_("Flavor"),
|
|
||||||
help_text=_("Size of image to launch."),
|
|
||||||
required=False,
|
|
||||||
widget=forms.Select(attrs={
|
|
||||||
'class': 'switched',
|
|
||||||
'data-switch-on': 'datastore',
|
|
||||||
attr_key: _("Flavor")
|
|
||||||
}))
|
|
||||||
valid_flavors = self.datastore_flavors(request,
|
|
||||||
datastore,
|
|
||||||
datastore_version)
|
|
||||||
if valid_flavors:
|
|
||||||
field.choices = instance_utils.sort_flavor_list(
|
|
||||||
request, valid_flavors)
|
|
||||||
|
|
||||||
return name, field
|
|
||||||
|
|
||||||
def _build_datastore_display_text(self, datastore, datastore_version):
|
|
||||||
return datastore + ' - ' + datastore_version
|
|
||||||
|
|
||||||
def _build_widget_field_name(self, datastore, datastore_version):
|
|
||||||
# Since the fieldnames cannot contain an uppercase character
|
|
||||||
# we generate a hex encoded string representation of the
|
|
||||||
# datastore and version as the fieldname
|
|
||||||
return binascii.hexlify(
|
|
||||||
self._build_datastore_display_text(datastore, datastore_version))
|
|
||||||
|
|
||||||
def _insert_datastore_version_fields(self, datastore_flavor_fields):
|
|
||||||
fields_to_restore_at_the_end = collections.OrderedDict()
|
|
||||||
while True:
|
|
||||||
k, v = self.fields.popitem()
|
|
||||||
if k == 'datastore':
|
|
||||||
self.fields[k] = v
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
fields_to_restore_at_the_end[k] = v
|
|
||||||
|
|
||||||
for k, v in datastore_flavor_fields.iteritems():
|
|
||||||
self.fields[k] = v
|
|
||||||
|
|
||||||
for k in reversed(fields_to_restore_at_the_end.keys()):
|
|
||||||
self.fields[k] = fields_to_restore_at_the_end[k]
|
|
||||||
|
|
||||||
def _add_attr_to_optional_fields(self, datastore, selection_text):
|
|
||||||
if db_capability.is_mongodb_datastore(datastore):
|
|
||||||
fields = self.mongodb_fields
|
|
||||||
elif db_capability.is_vertica_datastore(datastore):
|
|
||||||
fields = self.vertica_fields
|
|
||||||
else:
|
|
||||||
fields = self.default_fields
|
|
||||||
|
|
||||||
for field in fields:
|
|
||||||
attr_key = 'data-datastore-' + selection_text
|
|
||||||
widget = self.fields[field[0]].widget
|
|
||||||
if attr_key not in widget.attrs:
|
|
||||||
widget.attrs[attr_key] = field[1]
|
|
||||||
|
|
||||||
def _get_locality(self, data):
|
|
||||||
locality = None
|
|
||||||
if data.get('locality'):
|
|
||||||
locality = data['locality']
|
|
||||||
return locality
|
|
||||||
|
|
||||||
@sensitive_variables('data')
|
|
||||||
def handle(self, request, data):
|
|
||||||
try:
|
|
||||||
datastore, datastore_version = (
|
|
||||||
create_instance.parse_datastore_and_version_text(
|
|
||||||
binascii.unhexlify(data['datastore'])))
|
|
||||||
|
|
||||||
flavor_field_name = self._build_widget_field_name(
|
|
||||||
datastore, datastore_version)
|
|
||||||
flavor = data[flavor_field_name]
|
|
||||||
num_instances = data['num_instances']
|
|
||||||
root_password = None
|
|
||||||
if db_capability.is_vertica_datastore(datastore):
|
|
||||||
root_password = data['root_password']
|
|
||||||
num_instances = data['num_instances_vertica']
|
|
||||||
LOG.info("Launching cluster with parameters "
|
|
||||||
"{name=%s, volume=%s, flavor=%s, "
|
|
||||||
"datastore=%s, datastore_version=%s",
|
|
||||||
"locality=%s",
|
|
||||||
data['name'], data['volume'], flavor,
|
|
||||||
datastore, datastore_version, self._get_locality(data))
|
|
||||||
|
|
||||||
trove_api.trove.cluster_create(request,
|
|
||||||
data['name'],
|
|
||||||
data['volume'],
|
|
||||||
flavor,
|
|
||||||
num_instances,
|
|
||||||
datastore=datastore,
|
|
||||||
datastore_version=datastore_version,
|
|
||||||
nics=data['network'],
|
|
||||||
root_password=root_password,
|
|
||||||
locality=self._get_locality(data))
|
|
||||||
messages.success(request,
|
|
||||||
_('Launched cluster "%s"') % data['name'])
|
|
||||||
return True
|
|
||||||
except Exception as e:
|
|
||||||
redirect = reverse("horizon:project:database_clusters:index")
|
|
||||||
exceptions.handle(request,
|
|
||||||
_('Unable to launch cluster. %s') % e.message,
|
|
||||||
redirect=redirect)
|
|
||||||
|
|
||||||
|
|
||||||
class ClusterAddInstanceForm(forms.SelfHandlingForm):
|
|
||||||
cluster_id = forms.CharField(
|
|
||||||
required=False,
|
|
||||||
widget=forms.HiddenInput())
|
|
||||||
flavor = forms.ChoiceField(
|
|
||||||
label=_("Flavor"),
|
|
||||||
help_text=_("Size of image to launch."))
|
|
||||||
volume = forms.IntegerField(
|
|
||||||
label=_("Volume Size"),
|
|
||||||
min_value=0,
|
|
||||||
initial=1,
|
|
||||||
help_text=_("Size of the volume in GB."))
|
|
||||||
name = forms.CharField(
|
|
||||||
label=_("Name"),
|
|
||||||
required=False,
|
|
||||||
help_text=_("Optional name of the instance."))
|
|
||||||
type = forms.CharField(
|
|
||||||
label=_("Instance Type"),
|
|
||||||
required=False,
|
|
||||||
help_text=_("Optional datastore specific type of the instance."))
|
|
||||||
related_to = forms.CharField(
|
|
||||||
label=_("Related To"),
|
|
||||||
required=False,
|
|
||||||
help_text=_("Optional datastore specific value that defines the "
|
|
||||||
"relationship from one instance in the cluster to "
|
|
||||||
"another."))
|
|
||||||
network = forms.ChoiceField(
|
|
||||||
label=_("Network"),
|
|
||||||
help_text=_("Network attached to instance."),
|
|
||||||
required=False)
|
|
||||||
|
|
||||||
def __init__(self, request, *args, **kwargs):
|
|
||||||
super(ClusterAddInstanceForm, self).__init__(request, *args, **kwargs)
|
|
||||||
self.fields['cluster_id'].initial = kwargs['initial']['cluster_id']
|
|
||||||
self.fields['flavor'].choices = self.populate_flavor_choices(request)
|
|
||||||
self.fields['network'].choices = self.populate_network_choices(
|
|
||||||
request)
|
|
||||||
|
|
||||||
@memoized.memoized_method
|
|
||||||
def flavors(self, request):
|
|
||||||
try:
|
|
||||||
datastore = None
|
|
||||||
datastore_version = None
|
|
||||||
datastore_dict = self.initial.get('datastore', None)
|
|
||||||
if datastore_dict:
|
|
||||||
datastore = datastore_dict.get('type', None)
|
|
||||||
datastore_version = datastore_dict.get('version', None)
|
|
||||||
return trove_api.trove.datastore_flavors(
|
|
||||||
request,
|
|
||||||
datastore_name=datastore,
|
|
||||||
datastore_version=datastore_version)
|
|
||||||
except Exception:
|
|
||||||
LOG.exception("Exception while obtaining flavors list")
|
|
||||||
self._flavors = []
|
|
||||||
redirect = reverse('horizon:project:database_clusters:index')
|
|
||||||
exceptions.handle(request,
|
|
||||||
_('Unable to obtain flavors.'),
|
|
||||||
redirect=redirect)
|
|
||||||
|
|
||||||
def populate_flavor_choices(self, request):
|
|
||||||
flavor_list = [(f.id, "%s" % f.name) for f in self.flavors(request)]
|
|
||||||
return sorted(flavor_list)
|
|
||||||
|
|
||||||
@memoized.memoized_method
|
|
||||||
def populate_network_choices(self, request):
|
|
||||||
network_list = []
|
|
||||||
try:
|
|
||||||
if api.base.is_service_enabled(request, 'network'):
|
|
||||||
tenant_id = self.request.user.tenant_id
|
|
||||||
networks = api.neutron.network_list_for_tenant(request,
|
|
||||||
tenant_id)
|
|
||||||
network_list = [(network.id, network.name_or_id)
|
|
||||||
for network in networks]
|
|
||||||
else:
|
|
||||||
self.fields['network'].widget = forms.HiddenInput()
|
|
||||||
except exceptions.ServiceCatalogException:
|
|
||||||
network_list = []
|
|
||||||
redirect = reverse('horizon:project:database_clusters:index')
|
|
||||||
exceptions.handle(request,
|
|
||||||
_('Unable to retrieve networks.'),
|
|
||||||
redirect=redirect)
|
|
||||||
return network_list
|
|
||||||
|
|
||||||
def handle(self, request, data):
|
|
||||||
try:
|
|
||||||
flavor = trove_api.trove.flavor_get(request, data['flavor'])
|
|
||||||
manager = cluster_manager.get(data['cluster_id'])
|
|
||||||
manager.add_instance(str(uuid.uuid4()),
|
|
||||||
data.get('name', None),
|
|
||||||
data['flavor'],
|
|
||||||
flavor.name,
|
|
||||||
data['volume'],
|
|
||||||
data.get('type', None),
|
|
||||||
data.get('related_to', None),
|
|
||||||
data.get('network', None))
|
|
||||||
except Exception as e:
|
|
||||||
redirect = reverse("horizon:project:database_clusters:index")
|
|
||||||
exceptions.handle(request,
|
|
||||||
_('Unable to grow cluster. %s') % e.message,
|
|
||||||
redirect=redirect)
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
class ResetPasswordForm(forms.SelfHandlingForm):
|
|
||||||
cluster_id = forms.CharField(widget=forms.HiddenInput())
|
|
||||||
password = forms.CharField(widget=forms.PasswordInput(),
|
|
||||||
label=_("New Password"),
|
|
||||||
help_text=_("New password for cluster access."))
|
|
||||||
|
|
||||||
@sensitive_variables('data')
|
|
||||||
def handle(self, request, data):
|
|
||||||
password = data.get("password")
|
|
||||||
cluster_id = data.get("cluster_id")
|
|
||||||
try:
|
|
||||||
trove_api.trove.create_cluster_root(request,
|
|
||||||
cluster_id,
|
|
||||||
password)
|
|
||||||
messages.success(request, _('Root password updated for '
|
|
||||||
'cluster "%s"') % cluster_id)
|
|
||||||
except Exception as e:
|
|
||||||
redirect = reverse("horizon:project:database_clusters:index")
|
|
||||||
exceptions.handle(request, _('Unable to reset password. %s') %
|
|
||||||
e.message, redirect=redirect)
|
|
||||||
return True
|
|
@ -1,30 +0,0 @@
|
|||||||
# Copyright (c) 2014 eBay Software Foundation
|
|
||||||
# Copyright 2015 HP Software, LLC
|
|
||||||
# All Rights Reserved.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
|
|
||||||
import horizon
|
|
||||||
from openstack_dashboard.dashboards.project import dashboard
|
|
||||||
|
|
||||||
|
|
||||||
class Clusters(horizon.Panel):
|
|
||||||
name = _("Clusters")
|
|
||||||
slug = 'database_clusters'
|
|
||||||
permissions = ('openstack.services.database',
|
|
||||||
'openstack.services.object-store',)
|
|
||||||
|
|
||||||
|
|
||||||
dashboard.Project.register(Clusters)
|
|
@ -1,434 +0,0 @@
|
|||||||
# Copyright (c) 2014 eBay Software Foundation
|
|
||||||
# Copyright 2015 HP Software, LLC
|
|
||||||
# All Rights Reserved.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
import logging
|
|
||||||
|
|
||||||
from django.core import urlresolvers
|
|
||||||
from django import shortcuts
|
|
||||||
from django.template.defaultfilters import title # noqa
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
from django.utils.translation import ungettext_lazy
|
|
||||||
|
|
||||||
from horizon import messages
|
|
||||||
from horizon import tables
|
|
||||||
from horizon.templatetags import sizeformat
|
|
||||||
from horizon.utils import filters
|
|
||||||
from horizon.utils import functions
|
|
||||||
from horizon.utils import memoized
|
|
||||||
|
|
||||||
from trove_dashboard import api
|
|
||||||
from trove_dashboard.content.database_clusters import cluster_manager
|
|
||||||
from trove_dashboard.content.databases import db_capability
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
ACTIVE_STATES = ("ACTIVE",)
|
|
||||||
|
|
||||||
|
|
||||||
class DeleteCluster(tables.BatchAction):
|
|
||||||
name = "delete"
|
|
||||||
icon = "remove"
|
|
||||||
classes = ('btn-danger',)
|
|
||||||
help_text = _("Deleted cluster is not recoverable.")
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def action_present(count):
|
|
||||||
return ungettext_lazy(
|
|
||||||
u"Delete Cluster",
|
|
||||||
u"Delete Clusters",
|
|
||||||
count
|
|
||||||
)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def action_past(count):
|
|
||||||
return ungettext_lazy(
|
|
||||||
u"Scheduled deletion of Cluster",
|
|
||||||
u"Scheduled deletion of Clusters",
|
|
||||||
count
|
|
||||||
)
|
|
||||||
|
|
||||||
def action(self, request, obj_id):
|
|
||||||
api.trove.cluster_delete(request, obj_id)
|
|
||||||
|
|
||||||
|
|
||||||
class LaunchLink(tables.LinkAction):
|
|
||||||
name = "launch"
|
|
||||||
verbose_name = _("Launch Cluster")
|
|
||||||
url = "horizon:project:database_clusters:launch"
|
|
||||||
classes = ("btn-launch", "ajax-modal")
|
|
||||||
icon = "cloud-upload"
|
|
||||||
|
|
||||||
|
|
||||||
class ClusterGrow(tables.LinkAction):
|
|
||||||
name = "cluster_grow"
|
|
||||||
verbose_name = _("Grow Cluster")
|
|
||||||
url = "horizon:project:database_clusters:cluster_grow_details"
|
|
||||||
|
|
||||||
def allowed(self, request, cluster=None):
|
|
||||||
if (cluster and cluster.task["name"] == 'NONE' and
|
|
||||||
db_capability.can_modify_cluster(cluster.datastore['type'])):
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
class ClusterShrink(tables.LinkAction):
|
|
||||||
name = "cluster_shrink"
|
|
||||||
verbose_name = _("Shrink Cluster")
|
|
||||||
url = "horizon:project:database_clusters:cluster_shrink_details"
|
|
||||||
|
|
||||||
def allowed(self, request, cluster=None):
|
|
||||||
if (cluster and cluster.task["name"] == 'NONE' and
|
|
||||||
db_capability.can_modify_cluster(cluster.datastore['type'])):
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
class ResetPassword(tables.LinkAction):
|
|
||||||
name = "reset_password"
|
|
||||||
verbose_name = _("Reset Root Password")
|
|
||||||
url = "horizon:project:database_clusters:reset_password"
|
|
||||||
classes = ("ajax-modal",)
|
|
||||||
|
|
||||||
def allowed(self, request, cluster=None):
|
|
||||||
if (cluster and cluster.task["name"] == 'NONE' and
|
|
||||||
db_capability.is_vertica_datastore(cluster.datastore['type'])):
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def get_link_url(self, datum):
|
|
||||||
cluster_id = self.table.get_object_id(datum)
|
|
||||||
return urlresolvers.reverse(self.url, args=[cluster_id])
|
|
||||||
|
|
||||||
|
|
||||||
class UpdateRow(tables.Row):
|
|
||||||
ajax = True
|
|
||||||
|
|
||||||
@memoized.memoized_method
|
|
||||||
def get_data(self, request, cluster_id):
|
|
||||||
cluster = api.trove.cluster_get(request, cluster_id)
|
|
||||||
try:
|
|
||||||
# TODO(michayu): assumption that cluster is homogeneous
|
|
||||||
flavor_id = cluster.instances[0]['flavor']['id']
|
|
||||||
cluster.full_flavor = api.trove.flavor_get(request, flavor_id)
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
return cluster
|
|
||||||
|
|
||||||
|
|
||||||
def get_datastore(cluster):
|
|
||||||
return cluster.datastore["type"]
|
|
||||||
|
|
||||||
|
|
||||||
def get_datastore_version(cluster):
|
|
||||||
return cluster.datastore["version"]
|
|
||||||
|
|
||||||
|
|
||||||
def get_size(cluster):
|
|
||||||
if db_capability.is_vertica_datastore(cluster.datastore['type']):
|
|
||||||
return "3"
|
|
||||||
|
|
||||||
if hasattr(cluster, "full_flavor"):
|
|
||||||
size_string = _("%(name)s | %(RAM)s RAM | %(instances)s instances")
|
|
||||||
vals = {'name': cluster.full_flavor.name,
|
|
||||||
'RAM': sizeformat.mbformat(cluster.full_flavor.ram),
|
|
||||||
'instances': len(cluster.instances)}
|
|
||||||
return size_string % vals
|
|
||||||
elif hasattr(cluster, "instances"):
|
|
||||||
return "%s instances" % len(cluster.instances)
|
|
||||||
return _("Not available")
|
|
||||||
|
|
||||||
|
|
||||||
def get_task(cluster):
|
|
||||||
return cluster.task["name"]
|
|
||||||
|
|
||||||
|
|
||||||
class ClustersTable(tables.DataTable):
|
|
||||||
TASK_CHOICES = (
|
|
||||||
("none", True),
|
|
||||||
)
|
|
||||||
name = tables.Column("name",
|
|
||||||
link=("horizon:project:database_clusters:detail"),
|
|
||||||
verbose_name=_("Cluster Name"))
|
|
||||||
datastore = tables.Column(get_datastore,
|
|
||||||
verbose_name=_("Datastore"))
|
|
||||||
datastore_version = tables.Column(get_datastore_version,
|
|
||||||
verbose_name=_("Datastore Version"))
|
|
||||||
size = tables.Column(get_size,
|
|
||||||
verbose_name=_("Cluster Size"),
|
|
||||||
attrs={'data-type': 'size'})
|
|
||||||
task = tables.Column(get_task,
|
|
||||||
filters=(title, filters.replace_underscores),
|
|
||||||
verbose_name=_("Current Task"),
|
|
||||||
status=True,
|
|
||||||
status_choices=TASK_CHOICES)
|
|
||||||
|
|
||||||
class Meta(object):
|
|
||||||
name = "clusters"
|
|
||||||
verbose_name = _("Clusters")
|
|
||||||
status_columns = ["task"]
|
|
||||||
row_class = UpdateRow
|
|
||||||
table_actions = (LaunchLink, DeleteCluster)
|
|
||||||
row_actions = (ClusterGrow, ClusterShrink, ResetPassword,
|
|
||||||
DeleteCluster)
|
|
||||||
|
|
||||||
|
|
||||||
def get_instance_size(instance):
|
|
||||||
if hasattr(instance, "full_flavor"):
|
|
||||||
size_string = _("%(name)s | %(RAM)s RAM")
|
|
||||||
vals = {'name': instance.full_flavor.name,
|
|
||||||
'RAM': sizeformat.mbformat(instance.full_flavor.ram)}
|
|
||||||
return size_string % vals
|
|
||||||
return _("Not available")
|
|
||||||
|
|
||||||
|
|
||||||
def get_instance_type(instance):
|
|
||||||
if hasattr(instance, "type"):
|
|
||||||
return instance.type
|
|
||||||
return _("Not available")
|
|
||||||
|
|
||||||
|
|
||||||
def get_host(instance):
|
|
||||||
if hasattr(instance, "hostname"):
|
|
||||||
return instance.hostname
|
|
||||||
elif hasattr(instance, "ip") and instance.ip:
|
|
||||||
return instance.ip[0]
|
|
||||||
return _("Not Assigned")
|
|
||||||
|
|
||||||
|
|
||||||
class InstancesTable(tables.DataTable):
|
|
||||||
name = tables.Column("name",
|
|
||||||
verbose_name=_("Name"))
|
|
||||||
type = tables.Column(get_instance_type,
|
|
||||||
verbose_name=_("Type"))
|
|
||||||
host = tables.Column(get_host,
|
|
||||||
verbose_name=_("Host"))
|
|
||||||
size = tables.Column(get_instance_size,
|
|
||||||
verbose_name=_("Size"),
|
|
||||||
attrs={'data-type': 'size'})
|
|
||||||
status = tables.Column("status",
|
|
||||||
filters=(title, filters.replace_underscores),
|
|
||||||
verbose_name=_("Status"))
|
|
||||||
|
|
||||||
class Meta(object):
|
|
||||||
name = "instances"
|
|
||||||
verbose_name = _("Instances")
|
|
||||||
|
|
||||||
|
|
||||||
class ClusterShrinkAction(tables.BatchAction):
|
|
||||||
name = "cluster_shrink_action"
|
|
||||||
icon = "remove"
|
|
||||||
classes = ('btn-danger',)
|
|
||||||
success_url = 'horizon:project:database_clusters:index'
|
|
||||||
help_text = _("Shrinking a cluster is not recoverable.")
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def action_present(count):
|
|
||||||
return ungettext_lazy(
|
|
||||||
u"Shrink Cluster",
|
|
||||||
u"Shrink Cluster",
|
|
||||||
count
|
|
||||||
)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def action_past(count):
|
|
||||||
return ungettext_lazy(
|
|
||||||
u"Scheduled Shrinking of Cluster",
|
|
||||||
u"Scheduled Shrinking of Cluster",
|
|
||||||
count
|
|
||||||
)
|
|
||||||
|
|
||||||
def handle(self, table, request, obj_ids):
|
|
||||||
datum_display_objs = []
|
|
||||||
for datum_id in obj_ids:
|
|
||||||
datum = table.get_object_by_id(datum_id)
|
|
||||||
datum_display = table.get_object_display(datum) or datum_id
|
|
||||||
datum_display_objs.append(datum_display)
|
|
||||||
display_str = functions.lazy_join(", ", datum_display_objs)
|
|
||||||
|
|
||||||
try:
|
|
||||||
cluster_id = table.kwargs['cluster_id']
|
|
||||||
data = [{'id': instance_id} for instance_id in obj_ids]
|
|
||||||
api.trove.cluster_shrink(request, cluster_id, data)
|
|
||||||
LOG.info('%s: "%s"' %
|
|
||||||
(self._get_action_name(past=True),
|
|
||||||
display_str))
|
|
||||||
msg = _('Removed instances from cluster.')
|
|
||||||
messages.info(request, msg)
|
|
||||||
except Exception as ex:
|
|
||||||
LOG.error('Action %(action)s failed with %(ex)s for %(data)s' %
|
|
||||||
{'action': self._get_action_name(past=True).lower(),
|
|
||||||
'ex': ex.message,
|
|
||||||
'data': display_str})
|
|
||||||
msg = _('Unable to remove instances from cluster: %s')
|
|
||||||
messages.error(request, msg % ex.message)
|
|
||||||
|
|
||||||
return shortcuts.redirect(self.get_success_url(request))
|
|
||||||
|
|
||||||
|
|
||||||
class ClusterShrinkInstancesTable(tables.DataTable):
|
|
||||||
name = tables.Column("name",
|
|
||||||
verbose_name=_("Name"))
|
|
||||||
status = tables.Column("status",
|
|
||||||
filters=(title, filters.replace_underscores),
|
|
||||||
verbose_name=_("Status"))
|
|
||||||
|
|
||||||
class Meta(object):
|
|
||||||
name = "shrink_cluster_table"
|
|
||||||
verbose_name = _("Instances")
|
|
||||||
table_actions = (ClusterShrinkAction,)
|
|
||||||
row_actions = (ClusterShrinkAction,)
|
|
||||||
|
|
||||||
|
|
||||||
class ClusterGrowAddInstance(tables.LinkAction):
|
|
||||||
name = "cluster_grow_add_instance"
|
|
||||||
verbose_name = _("Add Instance")
|
|
||||||
url = "horizon:project:database_clusters:add_instance"
|
|
||||||
classes = ("ajax-modal",)
|
|
||||||
|
|
||||||
def get_link_url(self):
|
|
||||||
return urlresolvers.reverse(
|
|
||||||
self.url, args=[self.table.kwargs['cluster_id']])
|
|
||||||
|
|
||||||
|
|
||||||
class ClusterGrowRemoveInstance(tables.BatchAction):
|
|
||||||
name = "cluster_grow_remove_instance"
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def action_present(count):
|
|
||||||
return ungettext_lazy(
|
|
||||||
u"Remove Instance",
|
|
||||||
u"Remove Instances",
|
|
||||||
count
|
|
||||||
)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def action_past(count):
|
|
||||||
return ungettext_lazy(
|
|
||||||
u"Removed Instance",
|
|
||||||
u"Removed Instances",
|
|
||||||
count
|
|
||||||
)
|
|
||||||
|
|
||||||
def action(self, request, datum_id):
|
|
||||||
manager = cluster_manager.get(self.table.kwargs['cluster_id'])
|
|
||||||
manager.delete_instance(datum_id)
|
|
||||||
|
|
||||||
def handle(self, table, request, obj_ids):
|
|
||||||
action_success = []
|
|
||||||
action_failure = []
|
|
||||||
action_not_allowed = []
|
|
||||||
for datum_id in obj_ids:
|
|
||||||
datum = table.get_object_by_id(datum_id)
|
|
||||||
datum_display = table.get_object_display(datum) or datum_id
|
|
||||||
if not table._filter_action(self, request, datum):
|
|
||||||
action_not_allowed.append(datum_display)
|
|
||||||
LOG.warning('Permission denied to %s: "%s"' %
|
|
||||||
(self._get_action_name(past=True).lower(),
|
|
||||||
datum_display))
|
|
||||||
continue
|
|
||||||
try:
|
|
||||||
self.action(request, datum_id)
|
|
||||||
# Call update to invoke changes if needed
|
|
||||||
self.update(request, datum)
|
|
||||||
action_success.append(datum_display)
|
|
||||||
self.success_ids.append(datum_id)
|
|
||||||
LOG.info('%s: "%s"' %
|
|
||||||
(self._get_action_name(past=True), datum_display))
|
|
||||||
except Exception as ex:
|
|
||||||
# Handle the exception but silence it since we'll display
|
|
||||||
# an aggregate error message later. Otherwise we'd get
|
|
||||||
# multiple error messages displayed to the user.
|
|
||||||
action_failure.append(datum_display)
|
|
||||||
action_description = (
|
|
||||||
self._get_action_name(past=True).lower(), datum_display)
|
|
||||||
LOG.error(
|
|
||||||
'Action %(action)s Failed for %(reason)s', {
|
|
||||||
'action': action_description, 'reason': ex})
|
|
||||||
|
|
||||||
if action_not_allowed:
|
|
||||||
msg = _('You are not allowed to %(action)s: %(objs)s')
|
|
||||||
params = {"action":
|
|
||||||
self._get_action_name(action_not_allowed).lower(),
|
|
||||||
"objs": functions.lazy_join(", ", action_not_allowed)}
|
|
||||||
messages.error(request, msg % params)
|
|
||||||
if action_failure:
|
|
||||||
msg = _('Unable to %(action)s: %(objs)s')
|
|
||||||
params = {"action": self._get_action_name(action_failure).lower(),
|
|
||||||
"objs": functions.lazy_join(", ", action_failure)}
|
|
||||||
messages.error(request, msg % params)
|
|
||||||
|
|
||||||
return shortcuts.redirect(self.get_success_url(request))
|
|
||||||
|
|
||||||
|
|
||||||
class ClusterGrowAction(tables.Action):
|
|
||||||
name = "grow_cluster_action"
|
|
||||||
verbose_name = _("Grow Cluster")
|
|
||||||
verbose_name_plural = _("Grow Cluster")
|
|
||||||
requires_input = False
|
|
||||||
icon = "plus"
|
|
||||||
|
|
||||||
def handle(self, table, request, obj_ids):
|
|
||||||
if not table.data:
|
|
||||||
msg = _("Cannot grow cluster. No instances specified.")
|
|
||||||
messages.info(request, msg)
|
|
||||||
return shortcuts.redirect(request.build_absolute_uri())
|
|
||||||
|
|
||||||
datum_display_objs = []
|
|
||||||
for instance in table.data:
|
|
||||||
msg = _("[flavor=%(flavor)s, volume=%(volume)s, name=%(name)s, "
|
|
||||||
"type=%(type)s, related_to=%(related_to)s, "
|
|
||||||
"nics=%(nics)s]")
|
|
||||||
params = {"flavor": instance.flavor_id, "volume": instance.volume,
|
|
||||||
"name": instance.name, "type": instance.type,
|
|
||||||
"related_to": instance.related_to, "nics": instance.nics}
|
|
||||||
datum_display_objs.append(msg % params)
|
|
||||||
display_str = functions.lazy_join(", ", datum_display_objs)
|
|
||||||
|
|
||||||
cluster_id = table.kwargs['cluster_id']
|
|
||||||
try:
|
|
||||||
api.trove.cluster_grow(request, cluster_id, table.data)
|
|
||||||
LOG.info('%s: "%s"' % (_("Grow Cluster"), display_str))
|
|
||||||
msg = _('Scheduled growing of cluster.')
|
|
||||||
messages.success(request, msg)
|
|
||||||
except Exception as ex:
|
|
||||||
LOG.error('Action grow cluster failed with %(ex)s for %(data)s' %
|
|
||||||
{'ex': ex.message,
|
|
||||||
'data': display_str})
|
|
||||||
msg = _('Unable to grow cluster: %s')
|
|
||||||
messages.error(request, msg % ex.message)
|
|
||||||
finally:
|
|
||||||
cluster_manager.delete(cluster_id)
|
|
||||||
|
|
||||||
return shortcuts.redirect(urlresolvers.reverse(
|
|
||||||
"horizon:project:database_clusters:index"))
|
|
||||||
|
|
||||||
|
|
||||||
class ClusterGrowInstancesTable(tables.DataTable):
|
|
||||||
id = tables.Column("id", hidden=True)
|
|
||||||
name = tables.Column("name", verbose_name=_("Name"))
|
|
||||||
flavor = tables.Column("flavor", verbose_name=_("Flavor"))
|
|
||||||
flavor_id = tables.Column("flavor_id", hidden=True)
|
|
||||||
volume = tables.Column("volume", verbose_name=_("Volume"))
|
|
||||||
type = tables.Column("type", verbose_name=_("Instance Type"))
|
|
||||||
related_to = tables.Column("related_to", verbose_name=_("Related To"))
|
|
||||||
nics = tables.Column("nics", verbose_name=_("Network"))
|
|
||||||
|
|
||||||
class Meta(object):
|
|
||||||
name = "cluster_grow_instances_table"
|
|
||||||
verbose_name = _("Instances")
|
|
||||||
table_actions = (ClusterGrowAddInstance, ClusterGrowRemoveInstance,
|
|
||||||
ClusterGrowAction)
|
|
||||||
row_actions = (ClusterGrowRemoveInstance,)
|
|
@ -1,92 +0,0 @@
|
|||||||
# Copyright (c) 2014 eBay Software Foundation
|
|
||||||
# Copyright 2015 HP Software, LLC
|
|
||||||
# All Rights Reserved.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
from django import template
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
|
|
||||||
from horizon import exceptions
|
|
||||||
from horizon import tabs
|
|
||||||
|
|
||||||
from trove_dashboard import api
|
|
||||||
from trove_dashboard.content.database_clusters import tables
|
|
||||||
from trove_dashboard.content.databases import db_capability
|
|
||||||
|
|
||||||
|
|
||||||
class OverviewTab(tabs.Tab):
|
|
||||||
name = _("Overview")
|
|
||||||
slug = "overview"
|
|
||||||
|
|
||||||
def get_context_data(self, request):
|
|
||||||
return {"cluster": self.tab_group.kwargs['cluster']}
|
|
||||||
|
|
||||||
def get_template_name(self, request):
|
|
||||||
cluster = self.tab_group.kwargs['cluster']
|
|
||||||
template_file = ('project/database_clusters/_detail_overview_%s.html'
|
|
||||||
% self._get_template_type(cluster.datastore['type']))
|
|
||||||
try:
|
|
||||||
template.loader.get_template(template_file)
|
|
||||||
return template_file
|
|
||||||
except template.TemplateDoesNotExist:
|
|
||||||
# This datastore type does not have a template file
|
|
||||||
# Just use the base template file
|
|
||||||
return ('project/database_clusters/_detail_overview.html')
|
|
||||||
|
|
||||||
def _get_template_type(self, datastore):
|
|
||||||
if db_capability.is_mysql_compatible(datastore):
|
|
||||||
return 'mysql'
|
|
||||||
|
|
||||||
return datastore
|
|
||||||
|
|
||||||
|
|
||||||
class InstancesTab(tabs.TableTab):
|
|
||||||
table_classes = (tables.InstancesTable,)
|
|
||||||
name = _("Instances")
|
|
||||||
slug = "instances_tab"
|
|
||||||
cluster = None
|
|
||||||
template_name = "horizon/common/_detail_table.html"
|
|
||||||
preload = True
|
|
||||||
|
|
||||||
def get_instances_data(self):
|
|
||||||
cluster = self.tab_group.kwargs['cluster']
|
|
||||||
data = []
|
|
||||||
try:
|
|
||||||
instances = api.trove.cluster_get(self.request,
|
|
||||||
cluster.id).instances
|
|
||||||
for instance in instances:
|
|
||||||
instance_info = api.trove.instance_get(self.request,
|
|
||||||
instance['id'])
|
|
||||||
flavor_id = instance_info.flavor['id']
|
|
||||||
instance_info.full_flavor = api.trove.flavor_get(self.request,
|
|
||||||
flavor_id)
|
|
||||||
if "type" in instance:
|
|
||||||
instance_info.type = instance["type"]
|
|
||||||
if "ip" in instance:
|
|
||||||
instance_info.ip = instance["ip"]
|
|
||||||
if "hostname" in instance:
|
|
||||||
instance_info.hostname = instance["hostname"]
|
|
||||||
|
|
||||||
data.append(instance_info)
|
|
||||||
except Exception:
|
|
||||||
msg = _('Unable to get instances data.')
|
|
||||||
exceptions.handle(self.request, msg)
|
|
||||||
data = []
|
|
||||||
return data
|
|
||||||
|
|
||||||
|
|
||||||
class ClusterDetailTabs(tabs.TabGroup):
|
|
||||||
slug = "cluster_details"
|
|
||||||
tabs = (OverviewTab, InstancesTab)
|
|
||||||
sticky = True
|
|
@ -1,8 +0,0 @@
|
|||||||
{% extends "horizon/common/_modal_form.html" %}
|
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
{% block modal-body-right %}
|
|
||||||
<p>{% trans "Specify the details of the instance to be added to the cluster." %}</p>
|
|
||||||
<p>{% trans "The name field is optional. If the field is left blank a name will be generated when the cluster is grown." %}</p>
|
|
||||||
<p>{% trans "The 'Instance Type' and 'Related To' fields are datastore specific and optional. See the Trove documentation for more information on using these fields." %}</p>
|
|
||||||
{% endblock %}
|
|
@ -1,27 +0,0 @@
|
|||||||
{% load i18n sizeformat %}
|
|
||||||
|
|
||||||
<div class="detail">
|
|
||||||
<dl class="dl-horizontal">
|
|
||||||
<dt>{% trans "Name" %}</dt>
|
|
||||||
<dd>{{ cluster.name }}</dd>
|
|
||||||
<dt>{% trans "ID" %}</dt>
|
|
||||||
<dd>{{ cluster.id }}</dd>
|
|
||||||
<dt>{% trans "Datastore" %}</dt>
|
|
||||||
<dd>{{ cluster.datastore.type }}</dd>
|
|
||||||
<dt>{% trans "Datastore Version" %}</dt>
|
|
||||||
<dd>{{ cluster.datastore.version }}</dd>
|
|
||||||
<dt>{% trans "Current Task" %}</dt>
|
|
||||||
<dd>{{ cluster.task.name|title }}</dd>
|
|
||||||
<dt>{% trans "RAM" %}</dt>
|
|
||||||
<dd>{{ cluster.full_flavor.ram|mbformat }}</dd>
|
|
||||||
<dt>{% trans "Number of Instances" %}</dt>
|
|
||||||
<dd>{{ cluster.num_instances }}</dd>
|
|
||||||
{% if cluster.locality %}
|
|
||||||
<dt>{% trans "Locality" %}</dt>
|
|
||||||
<dd>{{ cluster.locality }}</dd>
|
|
||||||
{% endif %}
|
|
||||||
</dl>
|
|
||||||
|
|
||||||
{% block connection_info %}
|
|
||||||
{% endblock %}
|
|
||||||
</div>
|
|
@ -1,19 +0,0 @@
|
|||||||
{% extends "project/database_clusters/_detail_overview.html" %}
|
|
||||||
{% load i18n sizeformat %}
|
|
||||||
|
|
||||||
{% block connection_info %}
|
|
||||||
<h4>{% trans "Connection Information" %}</h4>
|
|
||||||
<hr class="header_rule">
|
|
||||||
<dl class="dl-horizontal">
|
|
||||||
{% with cluster.ip.0 as ip %}
|
|
||||||
<dt>{% trans "Host" %}</dt>
|
|
||||||
{% if not ip %}
|
|
||||||
<dd>{% trans "Not Assigned" %}</dd>
|
|
||||||
{% else %}
|
|
||||||
<dd>{{ ip }}</dd>
|
|
||||||
<dt>{% trans "Connection Examples" %}</dt>
|
|
||||||
<dd>cqlsh {{ ip }} 9042</dd>
|
|
||||||
{% endif %}
|
|
||||||
{% endwith %}
|
|
||||||
</dl>
|
|
||||||
{% endblock %}
|
|
@ -1,22 +0,0 @@
|
|||||||
{% extends "project/database_clusters/_detail_overview.html" %}
|
|
||||||
{% load i18n sizeformat %}
|
|
||||||
|
|
||||||
{% block connection_info %}
|
|
||||||
<h4>{% trans "Connection Information" %}</h4>
|
|
||||||
<hr class="header_rule">
|
|
||||||
<dl>
|
|
||||||
{% with cluster.ip.0 as ip %}
|
|
||||||
<dt>{% trans "Host" %}</dt>
|
|
||||||
{% if not ip %}
|
|
||||||
<dd>{% trans "Not Assigned" %}</dd>
|
|
||||||
{% else %}
|
|
||||||
<dd>{{ cluster.ip|join:', ' }}</dd>
|
|
||||||
<dt>{% trans "Database Port" %}</dt>
|
|
||||||
<dd>27017</dd>
|
|
||||||
<dt>{% trans "Connection Examples" %}</dt>
|
|
||||||
<dd>mongo --host {{ ip }}</dd>
|
|
||||||
<dd>mongodb://[{% trans "USERNAME" %}:{% trans "PASSWORD" %}@]{{ ip }}:27017/{% trans "DATABASE" %}</dd>
|
|
||||||
{% endif %} <!-- ends else block -->
|
|
||||||
{% endwith %}
|
|
||||||
</dl>
|
|
||||||
{% endblock %}
|
|
@ -1,19 +0,0 @@
|
|||||||
{% extends "project/database_clusters/_detail_overview.html" %}
|
|
||||||
{% load i18n sizeformat %}
|
|
||||||
|
|
||||||
{% block connection_info %}
|
|
||||||
<h4>{% trans "Connection Information" %}</h4>
|
|
||||||
<hr class="header_rule">
|
|
||||||
<dl class="dl-horizontal">
|
|
||||||
{% with cluster.ip.0 as ip %}
|
|
||||||
<dt>{% trans "Host" %}</dt>
|
|
||||||
{% if not ip %}
|
|
||||||
<dd>{% trans "Not Assigned" %}</dd>
|
|
||||||
{% else %}
|
|
||||||
<dd>{{ ip }}</dd>
|
|
||||||
<dt>{% trans "Connection Examples" %}</dt>
|
|
||||||
<dd>mysql {{ ip }} 3306</dd>
|
|
||||||
{% endif %}
|
|
||||||
{% endwith %}
|
|
||||||
</dl>
|
|
||||||
{% endblock %}
|
|
@ -1,19 +0,0 @@
|
|||||||
{% extends "project/database_clusters/_detail_overview.html" %}
|
|
||||||
{% load i18n sizeformat %}
|
|
||||||
|
|
||||||
{% block connection_info %}
|
|
||||||
<h4>{% trans "Connection Information" %}</h4>
|
|
||||||
<hr class="header_rule">
|
|
||||||
<dl class="dl-horizontal">
|
|
||||||
{% with cluster.ip.0 as ip %}
|
|
||||||
<dt>{% trans "Host" %}</dt>
|
|
||||||
{% if not ip %}
|
|
||||||
<dd>{% trans "Not Assigned" %}</dd>
|
|
||||||
{% else %}
|
|
||||||
<dd>{{ cluster.ip|join:', ' }}</dd>
|
|
||||||
<dt>{% trans "Connection Examples" %}</dt>
|
|
||||||
<dd>redis-cli -h {{ ip }}</dd>
|
|
||||||
{% endif %} <!-- ends else block -->
|
|
||||||
{% endwith %}
|
|
||||||
</dl>
|
|
||||||
{% endblock %}
|
|
@ -1,25 +0,0 @@
|
|||||||
{% load i18n sizeformat %}
|
|
||||||
|
|
||||||
<div class="detail">
|
|
||||||
<dl class="dl-horizontal">
|
|
||||||
<dt>{% trans "Name" %}</dt>
|
|
||||||
<dd>{{ cluster.name }}</dd>
|
|
||||||
<dt>{% trans "ID" %}</dt>
|
|
||||||
<dd>{{ cluster.id }}</dd>
|
|
||||||
<dt>{% trans "Datastore" %}</dt>
|
|
||||||
<dd>{{ cluster.datastore.type }}</dd>
|
|
||||||
<dt>{% trans "Datastore Version" %}</dt>
|
|
||||||
<dd>{{ cluster.datastore.version }}</dd>
|
|
||||||
<dt>{% trans "Current Task" %}</dt>
|
|
||||||
<dd>{{ cluster.task.name|title }}</dd>
|
|
||||||
<dt>{% trans "RAM" %}</dt>
|
|
||||||
<dd>{{ cluster.full_flavor.ram|mbformat }}</dd>
|
|
||||||
<dt>{% trans "Number of Instances" %}</dt>
|
|
||||||
<dd>{{ cluster.num_instances }}</dd>
|
|
||||||
<dt>{% trans "Management Console" %}</dt>
|
|
||||||
<dd><a href="{{ cluster.mgmt_url }}" target="_blank">{{ cluster.mgmt_url }}</a></dd>
|
|
||||||
</dl>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% block connection_info %}
|
|
||||||
{% endblock %}
|
|
@ -1,9 +0,0 @@
|
|||||||
{% extends "horizon/common/_modal_form.html" %}
|
|
||||||
|
|
||||||
{% block modal-body %}
|
|
||||||
<div class="center">
|
|
||||||
<fieldset>
|
|
||||||
{% include "horizon/common/_form_fields.html" %}
|
|
||||||
</fieldset>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
@ -1,24 +0,0 @@
|
|||||||
{% extends "horizon/common/_modal_form.html" %}
|
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
{% block form_id %}reset_password_form{% endblock %}
|
|
||||||
{% block form_action %}{% url "horizon:project:database_clusters:reset_password" cluster_id %}{% endblock %}
|
|
||||||
|
|
||||||
{% block modal_id %}reset_password_modal{% endblock %}
|
|
||||||
{% block modal-header %}{% trans "Reset Root Password" %}{% endblock %}
|
|
||||||
|
|
||||||
{% block modal-body %}
|
|
||||||
<div class="left">
|
|
||||||
<fieldset>
|
|
||||||
{% include "horizon/common/_form_fields.html" %}
|
|
||||||
</fieldset>
|
|
||||||
</div>
|
|
||||||
<div class="right">
|
|
||||||
<p>{% blocktrans trimmed %}Specify the new root password for vertica cluster.{% endblocktrans %}</p>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block modal-footer %}
|
|
||||||
<input class="btn btn-primary pull-right" type="submit" value="{% trans "Reset Root Password" %}" />
|
|
||||||
<a href="{% url "horizon:project:database_clusters:index" %}" class="btn btn-default secondary cancel close">{% trans "Cancel" %}</a>
|
|
||||||
{% endblock %}
|
|
@ -1,5 +0,0 @@
|
|||||||
{% extends "base.html" %}
|
|
||||||
|
|
||||||
{% block main %}
|
|
||||||
{% include "project/database_clusters/_add_instance.html" %}
|
|
||||||
{% endblock %}
|
|
@ -1,14 +0,0 @@
|
|||||||
{% extends 'base.html' %}
|
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
{% block main %}
|
|
||||||
<hr>
|
|
||||||
<div class="help_text">
|
|
||||||
{% trans "Specify the instances to be added to the cluster. When all the instances are specified click 'Grow Cluster' to perform the grow operation." %}
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-sm-12">
|
|
||||||
{{ table.render }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
@ -1,14 +0,0 @@
|
|||||||
{% extends 'base.html' %}
|
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
{% block main %}
|
|
||||||
<hr>
|
|
||||||
<div class="help_text">
|
|
||||||
{% trans "Select the instance(s) that will be removed from the cluster." %}
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-sm-12">
|
|
||||||
{{ table.render }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
@ -1,11 +0,0 @@
|
|||||||
{% extends 'base.html' %}
|
|
||||||
{% load i18n %}
|
|
||||||
{% block title %}{% trans "Clusters" %}{% endblock %}
|
|
||||||
|
|
||||||
{% block page_header %}
|
|
||||||
{% include "horizon/common/_page_header.html" with title=_("Clusters") %}
|
|
||||||
{% endblock page_header %}
|
|
||||||
|
|
||||||
{% block main %}
|
|
||||||
{{ table.render }}
|
|
||||||
{% endblock %}
|
|
@ -1,5 +0,0 @@
|
|||||||
{% extends 'base.html' %}
|
|
||||||
|
|
||||||
{% block main %}
|
|
||||||
{% include 'project/database_clusters/_launch.html' %}
|
|
||||||
{% endblock %}
|
|
@ -1,7 +0,0 @@
|
|||||||
{% extends "base.html" %}
|
|
||||||
{% load i18n %}
|
|
||||||
{% block title %}{% trans "Reset Root Password" %}{% endblock %}
|
|
||||||
|
|
||||||
{% block main %}
|
|
||||||
{% include "project/database_clusters/_reset_password.html" %}
|
|
||||||
{% endblock %}
|
|
@ -1,666 +0,0 @@
|
|||||||
# Copyright (c) 2014 eBay Software Foundation
|
|
||||||
# Copyright 2015 HP Software, LLC
|
|
||||||
# All Rights Reserved.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
import binascii
|
|
||||||
import logging
|
|
||||||
|
|
||||||
from django.core.urlresolvers import reverse
|
|
||||||
from django import http
|
|
||||||
|
|
||||||
from mox3.mox import IsA # noqa
|
|
||||||
|
|
||||||
from openstack_dashboard import api
|
|
||||||
from troveclient import common
|
|
||||||
|
|
||||||
from trove_dashboard import api as trove_api
|
|
||||||
from trove_dashboard.content.database_clusters \
|
|
||||||
import cluster_manager
|
|
||||||
from trove_dashboard.content.database_clusters import tables
|
|
||||||
from trove_dashboard.test import helpers as test
|
|
||||||
|
|
||||||
INDEX_URL = reverse('horizon:project:database_clusters:index')
|
|
||||||
LAUNCH_URL = reverse('horizon:project:database_clusters:launch')
|
|
||||||
DETAILS_URL = reverse('horizon:project:database_clusters:detail', args=['id'])
|
|
||||||
RESET_PASSWORD_VIEWNAME = 'horizon:project:database_clusters:reset_password'
|
|
||||||
|
|
||||||
|
|
||||||
class ClustersTests(test.TestCase):
|
|
||||||
@test.create_stubs({trove_api.trove: ('cluster_list',
|
|
||||||
'flavor_list')})
|
|
||||||
def test_index(self):
|
|
||||||
clusters = common.Paginated(self.trove_clusters.list())
|
|
||||||
trove_api.trove.cluster_list(IsA(http.HttpRequest), marker=None)\
|
|
||||||
.AndReturn(clusters)
|
|
||||||
trove_api.trove.flavor_list(IsA(http.HttpRequest))\
|
|
||||||
.AndReturn(self.flavors.list())
|
|
||||||
|
|
||||||
self.mox.ReplayAll()
|
|
||||||
res = self.client.get(INDEX_URL)
|
|
||||||
self.assertTemplateUsed(res, 'project/database_clusters/index.html')
|
|
||||||
|
|
||||||
@test.create_stubs({trove_api.trove: ('cluster_list',
|
|
||||||
'flavor_list')})
|
|
||||||
def test_index_flavor_exception(self):
|
|
||||||
clusters = common.Paginated(self.trove_clusters.list())
|
|
||||||
trove_api.trove.cluster_list(IsA(http.HttpRequest), marker=None)\
|
|
||||||
.AndReturn(clusters)
|
|
||||||
trove_api.trove.flavor_list(IsA(http.HttpRequest))\
|
|
||||||
.AndRaise(self.exceptions.trove)
|
|
||||||
|
|
||||||
self.mox.ReplayAll()
|
|
||||||
res = self.client.get(INDEX_URL)
|
|
||||||
self.assertTemplateUsed(res, 'project/database_clusters/index.html')
|
|
||||||
self.assertMessageCount(res, error=1)
|
|
||||||
|
|
||||||
@test.create_stubs({trove_api.trove: ('cluster_list',)})
|
|
||||||
def test_index_list_exception(self):
|
|
||||||
trove_api.trove.cluster_list(IsA(http.HttpRequest), marker=None)\
|
|
||||||
.AndRaise(self.exceptions.trove)
|
|
||||||
|
|
||||||
self.mox.ReplayAll()
|
|
||||||
res = self.client.get(INDEX_URL)
|
|
||||||
self.assertTemplateUsed(res, 'project/database_clusters/index.html')
|
|
||||||
self.assertMessageCount(res, error=1)
|
|
||||||
|
|
||||||
@test.create_stubs({trove_api.trove: ('cluster_list',
|
|
||||||
'flavor_list')})
|
|
||||||
def test_index_pagination(self):
|
|
||||||
clusters = self.trove_clusters.list()
|
|
||||||
last_record = clusters[1]
|
|
||||||
clusters = common.Paginated(clusters, next_marker="foo")
|
|
||||||
trove_api.trove.cluster_list(IsA(http.HttpRequest), marker=None)\
|
|
||||||
.AndReturn(clusters)
|
|
||||||
trove_api.trove.flavor_list(IsA(http.HttpRequest))\
|
|
||||||
.AndReturn(self.flavors.list())
|
|
||||||
|
|
||||||
self.mox.ReplayAll()
|
|
||||||
res = self.client.get(INDEX_URL)
|
|
||||||
self.assertTemplateUsed(res, 'project/database_clusters/index.html')
|
|
||||||
self.assertContains(
|
|
||||||
res, 'marker=' + last_record.id)
|
|
||||||
|
|
||||||
@test.create_stubs({trove_api.trove: ('cluster_list',
|
|
||||||
'flavor_list')})
|
|
||||||
def test_index_flavor_list_exception(self):
|
|
||||||
clusters = common.Paginated(self.trove_clusters.list())
|
|
||||||
trove_api.trove.cluster_list(IsA(http.HttpRequest), marker=None)\
|
|
||||||
.AndReturn(clusters)
|
|
||||||
trove_api.trove.flavor_list(IsA(http.HttpRequest))\
|
|
||||||
.AndRaise(self.exceptions.trove)
|
|
||||||
|
|
||||||
self.mox.ReplayAll()
|
|
||||||
|
|
||||||
res = self.client.get(INDEX_URL)
|
|
||||||
|
|
||||||
self.assertTemplateUsed(res, 'project/database_clusters/index.html')
|
|
||||||
self.assertMessageCount(res, error=1)
|
|
||||||
|
|
||||||
@test.create_stubs({trove_api.trove: ('datastore_flavors',
|
|
||||||
'datastore_list',
|
|
||||||
'datastore_version_list'),
|
|
||||||
api.base: ['is_service_enabled']})
|
|
||||||
def test_launch_cluster(self):
|
|
||||||
api.base.is_service_enabled(IsA(http.HttpRequest), 'network')\
|
|
||||||
.AndReturn(False)
|
|
||||||
filtered_datastores = self._get_filtered_datastores('mongodb')
|
|
||||||
trove_api.trove.datastore_flavors(IsA(http.HttpRequest),
|
|
||||||
'mongodb', '2.6')\
|
|
||||||
.AndReturn(self.flavors.list())
|
|
||||||
trove_api.trove.datastore_list(IsA(http.HttpRequest))\
|
|
||||||
.AndReturn(filtered_datastores)
|
|
||||||
trove_api.trove.datastore_version_list(IsA(http.HttpRequest),
|
|
||||||
IsA(str))\
|
|
||||||
.AndReturn(
|
|
||||||
self._get_filtered_datastore_versions(filtered_datastores))
|
|
||||||
self.mox.ReplayAll()
|
|
||||||
res = self.client.get(LAUNCH_URL)
|
|
||||||
self.assertTemplateUsed(res, 'project/database_clusters/launch.html')
|
|
||||||
|
|
||||||
def test_launch_cluster_mongo_fields(self):
|
|
||||||
datastore = 'mongodb'
|
|
||||||
datastore_version = '2.6'
|
|
||||||
fields = self.launch_cluster_fields_setup(datastore,
|
|
||||||
datastore_version)
|
|
||||||
field_name = self._build_flavor_widget_name(datastore,
|
|
||||||
datastore_version)
|
|
||||||
|
|
||||||
self.assertTrue(self._contains_datastore_in_attribute(
|
|
||||||
fields[field_name], field_name))
|
|
||||||
self.assertTrue(self._contains_datastore_in_attribute(
|
|
||||||
fields['num_instances'], field_name))
|
|
||||||
self.assertTrue(self._contains_datastore_in_attribute(
|
|
||||||
fields['num_shards'], field_name))
|
|
||||||
self.assertFalse(self._contains_datastore_in_attribute(
|
|
||||||
fields['root_password'], field_name))
|
|
||||||
self.assertFalse(self._contains_datastore_in_attribute(
|
|
||||||
fields['num_instances_vertica'], field_name))
|
|
||||||
|
|
||||||
def test_launch_cluster_redis_fields(self):
|
|
||||||
datastore = 'redis'
|
|
||||||
datastore_version = '3.0'
|
|
||||||
fields = self.launch_cluster_fields_setup(datastore,
|
|
||||||
datastore_version)
|
|
||||||
field_name = self._build_flavor_widget_name(datastore,
|
|
||||||
datastore_version)
|
|
||||||
|
|
||||||
self.assertTrue(self._contains_datastore_in_attribute(
|
|
||||||
fields[field_name], field_name))
|
|
||||||
self.assertTrue(self._contains_datastore_in_attribute(
|
|
||||||
fields['num_instances'], field_name))
|
|
||||||
self.assertFalse(self._contains_datastore_in_attribute(
|
|
||||||
fields['num_shards'], field_name))
|
|
||||||
self.assertFalse(self._contains_datastore_in_attribute(
|
|
||||||
fields['root_password'], field_name))
|
|
||||||
self.assertFalse(self._contains_datastore_in_attribute(
|
|
||||||
fields['num_instances_vertica'], field_name))
|
|
||||||
|
|
||||||
def test_launch_cluster_vertica_fields(self):
|
|
||||||
datastore = 'vertica'
|
|
||||||
datastore_version = '7.1'
|
|
||||||
fields = self.launch_cluster_fields_setup(datastore,
|
|
||||||
datastore_version)
|
|
||||||
field_name = self._build_flavor_widget_name(datastore,
|
|
||||||
datastore_version)
|
|
||||||
|
|
||||||
self.assertTrue(self._contains_datastore_in_attribute(
|
|
||||||
fields[field_name], field_name))
|
|
||||||
self.assertFalse(self._contains_datastore_in_attribute(
|
|
||||||
fields['num_instances'], field_name))
|
|
||||||
self.assertFalse(self._contains_datastore_in_attribute(
|
|
||||||
fields['num_shards'], field_name))
|
|
||||||
self.assertTrue(self._contains_datastore_in_attribute(
|
|
||||||
fields['root_password'], field_name))
|
|
||||||
self.assertTrue(self._contains_datastore_in_attribute(
|
|
||||||
fields['num_instances_vertica'], field_name))
|
|
||||||
|
|
||||||
@test.create_stubs({trove_api.trove: ('datastore_flavors',
|
|
||||||
'datastore_list',
|
|
||||||
'datastore_version_list'),
|
|
||||||
api.base: ['is_service_enabled']})
|
|
||||||
def launch_cluster_fields_setup(self, datastore, datastore_version):
|
|
||||||
api.base.is_service_enabled(IsA(http.HttpRequest), 'network')\
|
|
||||||
.AndReturn(False)
|
|
||||||
filtered_datastores = self._get_filtered_datastores(datastore)
|
|
||||||
trove_api.trove.datastore_flavors(IsA(http.HttpRequest),
|
|
||||||
datastore, datastore_version)\
|
|
||||||
.AndReturn(self.flavors.list())
|
|
||||||
trove_api.trove.datastore_list(IsA(http.HttpRequest))\
|
|
||||||
.AndReturn(filtered_datastores)
|
|
||||||
trove_api.trove.datastore_version_list(IsA(http.HttpRequest),
|
|
||||||
IsA(str))\
|
|
||||||
.AndReturn(
|
|
||||||
self._get_filtered_datastore_versions(filtered_datastores))
|
|
||||||
self.mox.ReplayAll()
|
|
||||||
res = self.client.get(LAUNCH_URL)
|
|
||||||
return res.context_data['form'].fields
|
|
||||||
|
|
||||||
@test.create_stubs({trove_api.trove: ['datastore_flavors',
|
|
||||||
'cluster_create',
|
|
||||||
'datastore_list',
|
|
||||||
'datastore_version_list'],
|
|
||||||
api.base: ['is_service_enabled']})
|
|
||||||
def test_create_simple_cluster(self):
|
|
||||||
api.base.is_service_enabled(IsA(http.HttpRequest), 'network')\
|
|
||||||
.AndReturn(False)
|
|
||||||
filtered_datastores = self._get_filtered_datastores('mongodb')
|
|
||||||
trove_api.trove.datastore_flavors(IsA(http.HttpRequest),
|
|
||||||
'mongodb', '2.6')\
|
|
||||||
.AndReturn(self.flavors.list())
|
|
||||||
trove_api.trove.datastore_list(IsA(http.HttpRequest))\
|
|
||||||
.AndReturn(filtered_datastores)
|
|
||||||
trove_api.trove.datastore_version_list(IsA(http.HttpRequest),
|
|
||||||
IsA(str))\
|
|
||||||
.AndReturn(
|
|
||||||
self._get_filtered_datastore_versions(filtered_datastores))
|
|
||||||
|
|
||||||
cluster_name = u'MyCluster'
|
|
||||||
cluster_volume = 1
|
|
||||||
cluster_flavor = u'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa'
|
|
||||||
cluster_instances = 3
|
|
||||||
cluster_datastore = u'mongodb'
|
|
||||||
cluster_datastore_version = u'2.6'
|
|
||||||
cluster_network = u''
|
|
||||||
trove_api.trove.cluster_create(
|
|
||||||
IsA(http.HttpRequest),
|
|
||||||
cluster_name,
|
|
||||||
cluster_volume,
|
|
||||||
cluster_flavor,
|
|
||||||
cluster_instances,
|
|
||||||
datastore=cluster_datastore,
|
|
||||||
datastore_version=cluster_datastore_version,
|
|
||||||
nics=cluster_network,
|
|
||||||
root_password=None,
|
|
||||||
locality=None).AndReturn(self.trove_clusters.first())
|
|
||||||
|
|
||||||
field_name = self._build_flavor_widget_name(cluster_datastore,
|
|
||||||
cluster_datastore_version)
|
|
||||||
self.mox.ReplayAll()
|
|
||||||
post = {
|
|
||||||
'name': cluster_name,
|
|
||||||
'volume': cluster_volume,
|
|
||||||
'num_instances': cluster_instances,
|
|
||||||
'num_shards': 1,
|
|
||||||
'datastore': field_name,
|
|
||||||
field_name: 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa',
|
|
||||||
}
|
|
||||||
|
|
||||||
res = self.client.post(LAUNCH_URL, post)
|
|
||||||
self.assertNoFormErrors(res)
|
|
||||||
self.assertMessageCount(success=1)
|
|
||||||
|
|
||||||
@test.create_stubs({trove_api.trove: ['datastore_flavors',
|
|
||||||
'cluster_create',
|
|
||||||
'datastore_list',
|
|
||||||
'datastore_version_list'],
|
|
||||||
api.neutron: ['network_list_for_tenant'],
|
|
||||||
api.base: ['is_service_enabled']})
|
|
||||||
def test_create_simple_cluster_neutron(self):
|
|
||||||
api.base.is_service_enabled(IsA(http.HttpRequest), 'network')\
|
|
||||||
.AndReturn(True)
|
|
||||||
api.neutron.network_list_for_tenant(IsA(http.HttpRequest), '1')\
|
|
||||||
.AndReturn(self.networks.list())
|
|
||||||
filtered_datastores = self._get_filtered_datastores('mongodb')
|
|
||||||
trove_api.trove.datastore_flavors(IsA(http.HttpRequest),
|
|
||||||
'mongodb', '2.6')\
|
|
||||||
.AndReturn(self.flavors.list())
|
|
||||||
trove_api.trove.datastore_list(IsA(http.HttpRequest))\
|
|
||||||
.AndReturn(filtered_datastores)
|
|
||||||
trove_api.trove.datastore_version_list(IsA(http.HttpRequest),
|
|
||||||
IsA(str))\
|
|
||||||
.AndReturn(
|
|
||||||
self._get_filtered_datastore_versions(filtered_datastores))
|
|
||||||
|
|
||||||
cluster_name = u'MyCluster'
|
|
||||||
cluster_volume = 1
|
|
||||||
cluster_flavor = u'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa'
|
|
||||||
cluster_instances = 3
|
|
||||||
cluster_datastore = u'mongodb'
|
|
||||||
cluster_datastore_version = u'2.6'
|
|
||||||
cluster_network = u'82288d84-e0a5-42ac-95be-e6af08727e42'
|
|
||||||
trove_api.trove.cluster_create(
|
|
||||||
IsA(http.HttpRequest),
|
|
||||||
cluster_name,
|
|
||||||
cluster_volume,
|
|
||||||
cluster_flavor,
|
|
||||||
cluster_instances,
|
|
||||||
datastore=cluster_datastore,
|
|
||||||
datastore_version=cluster_datastore_version,
|
|
||||||
nics=cluster_network,
|
|
||||||
root_password=None,
|
|
||||||
locality=None).AndReturn(self.trove_clusters.first())
|
|
||||||
|
|
||||||
field_name = self._build_flavor_widget_name(cluster_datastore,
|
|
||||||
cluster_datastore_version)
|
|
||||||
self.mox.ReplayAll()
|
|
||||||
post = {
|
|
||||||
'name': cluster_name,
|
|
||||||
'volume': cluster_volume,
|
|
||||||
'num_instances': cluster_instances,
|
|
||||||
'num_shards': 1,
|
|
||||||
'datastore': field_name,
|
|
||||||
field_name: 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa',
|
|
||||||
'network': cluster_network,
|
|
||||||
}
|
|
||||||
|
|
||||||
res = self.client.post(LAUNCH_URL, post)
|
|
||||||
self.assertNoFormErrors(res)
|
|
||||||
self.assertMessageCount(success=1)
|
|
||||||
|
|
||||||
@test.create_stubs({trove_api.trove: ['datastore_flavors',
|
|
||||||
'cluster_create',
|
|
||||||
'datastore_list',
|
|
||||||
'datastore_version_list'],
|
|
||||||
api.neutron: ['network_list_for_tenant']})
|
|
||||||
def test_create_simple_cluster_exception(self):
|
|
||||||
api.neutron.network_list_for_tenant(IsA(http.HttpRequest), '1')\
|
|
||||||
.AndReturn(self.networks.list())
|
|
||||||
filtered_datastores = self._get_filtered_datastores('mongodb')
|
|
||||||
trove_api.trove.datastore_flavors(IsA(http.HttpRequest),
|
|
||||||
'mongodb', '2.6')\
|
|
||||||
.AndReturn(self.flavors.list())
|
|
||||||
trove_api.trove.datastore_list(IsA(http.HttpRequest))\
|
|
||||||
.AndReturn(filtered_datastores)
|
|
||||||
trove_api.trove.datastore_version_list(IsA(http.HttpRequest),
|
|
||||||
IsA(str))\
|
|
||||||
.AndReturn(
|
|
||||||
self._get_filtered_datastore_versions(filtered_datastores))
|
|
||||||
|
|
||||||
cluster_name = u'MyCluster'
|
|
||||||
cluster_volume = 1
|
|
||||||
cluster_flavor = u'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa'
|
|
||||||
cluster_instances = 3
|
|
||||||
cluster_datastore = u'mongodb'
|
|
||||||
cluster_datastore_version = u'2.6'
|
|
||||||
cluster_network = u'82288d84-e0a5-42ac-95be-e6af08727e42'
|
|
||||||
trove_api.trove.cluster_create(
|
|
||||||
IsA(http.HttpRequest),
|
|
||||||
cluster_name,
|
|
||||||
cluster_volume,
|
|
||||||
cluster_flavor,
|
|
||||||
cluster_instances,
|
|
||||||
datastore=cluster_datastore,
|
|
||||||
datastore_version=cluster_datastore_version,
|
|
||||||
nics=cluster_network,
|
|
||||||
root_password=None,
|
|
||||||
locality=None).AndReturn(self.trove_clusters.first())
|
|
||||||
|
|
||||||
field_name = self._build_flavor_widget_name(cluster_datastore,
|
|
||||||
cluster_datastore_version)
|
|
||||||
self.mox.ReplayAll()
|
|
||||||
post = {
|
|
||||||
'name': cluster_name,
|
|
||||||
'volume': cluster_volume,
|
|
||||||
'num_instances': cluster_instances,
|
|
||||||
'num_shards': 1,
|
|
||||||
'datastore': field_name,
|
|
||||||
field_name: 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa',
|
|
||||||
}
|
|
||||||
|
|
||||||
res = self.client.post(LAUNCH_URL, post)
|
|
||||||
self.assertRedirectsNoFollow(res, INDEX_URL)
|
|
||||||
|
|
||||||
@test.create_stubs({trove_api.trove: ('cluster_get',
|
|
||||||
'instance_get',
|
|
||||||
'flavor_get',)})
|
|
||||||
def test_details(self):
|
|
||||||
cluster = self.trove_clusters.first()
|
|
||||||
trove_api.trove.cluster_get(IsA(http.HttpRequest), cluster.id)\
|
|
||||||
.MultipleTimes().AndReturn(cluster)
|
|
||||||
trove_api.trove.instance_get(IsA(http.HttpRequest), IsA(str))\
|
|
||||||
.MultipleTimes().AndReturn(self.databases.first())
|
|
||||||
trove_api.trove.flavor_get(IsA(http.HttpRequest), IsA(str))\
|
|
||||||
.MultipleTimes().AndReturn(self.flavors.first())
|
|
||||||
self.mox.ReplayAll()
|
|
||||||
|
|
||||||
details_url = reverse('horizon:project:database_clusters:detail',
|
|
||||||
args=[cluster.id])
|
|
||||||
res = self.client.get(details_url)
|
|
||||||
self.assertTemplateUsed(res, 'horizon/common/_detail.html')
|
|
||||||
self.assertContains(res, cluster.ip[0])
|
|
||||||
|
|
||||||
@test.create_stubs({trove_api.trove: ('cluster_get',
|
|
||||||
'instance_get',
|
|
||||||
'flavor_get',)})
|
|
||||||
def test_details_without_locality(self):
|
|
||||||
cluster = self.trove_clusters.list()[1]
|
|
||||||
trove_api.trove.cluster_get(IsA(http.HttpRequest), cluster.id) \
|
|
||||||
.MultipleTimes().AndReturn(cluster)
|
|
||||||
trove_api.trove.instance_get(IsA(http.HttpRequest), IsA(str)) \
|
|
||||||
.MultipleTimes().AndReturn(self.databases.first())
|
|
||||||
trove_api.trove.flavor_get(IsA(http.HttpRequest), IsA(str)) \
|
|
||||||
.MultipleTimes().AndReturn(self.flavors.first())
|
|
||||||
self.mox.ReplayAll()
|
|
||||||
|
|
||||||
details_url = reverse('horizon:project:database_clusters:detail',
|
|
||||||
args=[cluster.id])
|
|
||||||
res = self.client.get(details_url)
|
|
||||||
self.assertTemplateUsed(res, 'horizon/common/_detail.html')
|
|
||||||
self.assertNotContains(res, "Locality")
|
|
||||||
|
|
||||||
@test.create_stubs({trove_api.trove: ('cluster_get',
|
|
||||||
'instance_get',
|
|
||||||
'flavor_get',)})
|
|
||||||
def test_details_with_locality(self):
|
|
||||||
cluster = self.trove_clusters.first()
|
|
||||||
trove_api.trove.cluster_get(IsA(http.HttpRequest), cluster.id) \
|
|
||||||
.MultipleTimes().AndReturn(cluster)
|
|
||||||
trove_api.trove.instance_get(IsA(http.HttpRequest), IsA(str)) \
|
|
||||||
.MultipleTimes().AndReturn(self.databases.first())
|
|
||||||
trove_api.trove.flavor_get(IsA(http.HttpRequest), IsA(str)) \
|
|
||||||
.MultipleTimes().AndReturn(self.flavors.first())
|
|
||||||
self.mox.ReplayAll()
|
|
||||||
|
|
||||||
details_url = reverse('horizon:project:database_clusters:detail',
|
|
||||||
args=[cluster.id])
|
|
||||||
res = self.client.get(details_url)
|
|
||||||
self.assertTemplateUsed(res, 'project/database_clusters/'
|
|
||||||
'_detail_overview.html')
|
|
||||||
self.assertContains(res, "Locality")
|
|
||||||
|
|
||||||
@test.create_stubs(
|
|
||||||
{trove_api.trove: ('cluster_get',
|
|
||||||
'cluster_grow'),
|
|
||||||
cluster_manager: ('get',)})
|
|
||||||
def test_grow_cluster(self):
|
|
||||||
cluster = self.trove_clusters.first()
|
|
||||||
trove_api.trove.cluster_get(IsA(http.HttpRequest), cluster.id)\
|
|
||||||
.AndReturn(cluster)
|
|
||||||
cluster_volume = 1
|
|
||||||
flavor = self.flavors.first()
|
|
||||||
cluster_flavor = flavor.id
|
|
||||||
cluster_flavor_name = flavor.name
|
|
||||||
instances = [
|
|
||||||
cluster_manager.ClusterInstance("id1", "name1", cluster_flavor,
|
|
||||||
cluster_flavor_name,
|
|
||||||
cluster_volume, "master", None,
|
|
||||||
None),
|
|
||||||
cluster_manager.ClusterInstance("id2", "name2", cluster_flavor,
|
|
||||||
cluster_flavor_name,
|
|
||||||
cluster_volume, "slave",
|
|
||||||
"master", None),
|
|
||||||
cluster_manager.ClusterInstance("id3", None, cluster_flavor,
|
|
||||||
cluster_flavor_name,
|
|
||||||
cluster_volume, None, None, None),
|
|
||||||
]
|
|
||||||
|
|
||||||
manager = cluster_manager.ClusterInstanceManager(cluster.id)
|
|
||||||
manager.instances = instances
|
|
||||||
cluster_manager.get(cluster.id).MultipleTimes().AndReturn(manager)
|
|
||||||
trove_api.trove.cluster_grow(IsA(http.HttpRequest),
|
|
||||||
cluster.id,
|
|
||||||
instances)
|
|
||||||
self.mox.ReplayAll()
|
|
||||||
|
|
||||||
url = reverse('horizon:project:database_clusters:cluster_grow_details',
|
|
||||||
args=[cluster.id])
|
|
||||||
res = self.client.get(url)
|
|
||||||
self.assertTemplateUsed(
|
|
||||||
res, 'project/database_clusters/cluster_grow_details.html')
|
|
||||||
table = res.context_data[
|
|
||||||
"".join([tables.ClusterGrowInstancesTable.Meta.name, '_table'])]
|
|
||||||
self.assertEqual(len(cluster.instances), len(table.data))
|
|
||||||
|
|
||||||
action = "".join([tables.ClusterGrowInstancesTable.Meta.name, '__',
|
|
||||||
tables.ClusterGrowRemoveInstance.name, '__',
|
|
||||||
'id1'])
|
|
||||||
self.client.post(url, {'action': action})
|
|
||||||
self.assertEqual(len(cluster.instances) - 1, len(table.data))
|
|
||||||
|
|
||||||
action = "".join([tables.ClusterGrowInstancesTable.Meta.name, '__',
|
|
||||||
tables.ClusterGrowAction.name, '__',
|
|
||||||
cluster.id])
|
|
||||||
res = self.client.post(url, {'action': action})
|
|
||||||
self.assertMessageCount(success=1)
|
|
||||||
self.assertRedirectsNoFollow(res, INDEX_URL)
|
|
||||||
|
|
||||||
@test.create_stubs({trove_api.trove: ('cluster_get',)})
|
|
||||||
def test_grow_cluster_no_instances(self):
|
|
||||||
cluster = self.trove_clusters.first()
|
|
||||||
trove_api.trove.cluster_get(IsA(http.HttpRequest), cluster.id)\
|
|
||||||
.AndReturn(cluster)
|
|
||||||
self.mox.ReplayAll()
|
|
||||||
|
|
||||||
url = reverse('horizon:project:database_clusters:cluster_grow_details',
|
|
||||||
args=[cluster.id])
|
|
||||||
res = self.client.get(url)
|
|
||||||
self.assertTemplateUsed(
|
|
||||||
res, 'project/database_clusters/cluster_grow_details.html')
|
|
||||||
|
|
||||||
action = "".join([tables.ClusterGrowInstancesTable.Meta.name, '__',
|
|
||||||
tables.ClusterGrowAction.name, '__',
|
|
||||||
cluster.id])
|
|
||||||
self.client.post(url, {'action': action})
|
|
||||||
self.assertMessageCount(info=1)
|
|
||||||
|
|
||||||
@test.create_stubs(
|
|
||||||
{trove_api.trove: ('cluster_get',
|
|
||||||
'cluster_grow',),
|
|
||||||
cluster_manager: ('get',)})
|
|
||||||
def test_grow_cluster_exception(self):
|
|
||||||
cluster = self.trove_clusters.first()
|
|
||||||
trove_api.trove.cluster_get(IsA(http.HttpRequest), cluster.id)\
|
|
||||||
.AndReturn(cluster)
|
|
||||||
cluster_volume = 1
|
|
||||||
flavor = self.flavors.first()
|
|
||||||
cluster_flavor = flavor.id
|
|
||||||
cluster_flavor_name = flavor.name
|
|
||||||
instances = [
|
|
||||||
cluster_manager.ClusterInstance("id1", "name1", cluster_flavor,
|
|
||||||
cluster_flavor_name,
|
|
||||||
cluster_volume, "master", None,
|
|
||||||
None),
|
|
||||||
cluster_manager.ClusterInstance("id2", "name2", cluster_flavor,
|
|
||||||
cluster_flavor_name,
|
|
||||||
cluster_volume, "slave",
|
|
||||||
"master", None),
|
|
||||||
cluster_manager.ClusterInstance("id3", None, cluster_flavor,
|
|
||||||
cluster_flavor_name,
|
|
||||||
cluster_volume, None, None, None),
|
|
||||||
]
|
|
||||||
|
|
||||||
manager = cluster_manager.ClusterInstanceManager(cluster.id)
|
|
||||||
manager.instances = instances
|
|
||||||
cluster_manager.get(cluster.id).MultipleTimes().AndReturn(manager)
|
|
||||||
trove_api.trove.cluster_grow(IsA(http.HttpRequest),
|
|
||||||
cluster.id,
|
|
||||||
instances).AndRaise(self.exceptions.trove)
|
|
||||||
self.mox.ReplayAll()
|
|
||||||
|
|
||||||
url = reverse('horizon:project:database_clusters:cluster_grow_details',
|
|
||||||
args=[cluster.id])
|
|
||||||
res = self.client.get(url)
|
|
||||||
self.assertTemplateUsed(
|
|
||||||
res, 'project/database_clusters/cluster_grow_details.html')
|
|
||||||
|
|
||||||
toSuppress = ["trove_dashboard.content.database_clusters.tables"]
|
|
||||||
|
|
||||||
# Suppress expected log messages in the test output
|
|
||||||
loggers = []
|
|
||||||
for cls in toSuppress:
|
|
||||||
logger = logging.getLogger(cls)
|
|
||||||
loggers.append((logger, logger.getEffectiveLevel()))
|
|
||||||
logger.setLevel(logging.CRITICAL)
|
|
||||||
|
|
||||||
try:
|
|
||||||
action = "".join([tables.ClusterGrowInstancesTable.Meta.name, '__',
|
|
||||||
tables.ClusterGrowAction.name, '__',
|
|
||||||
cluster.id])
|
|
||||||
res = self.client.post(url, {'action': action})
|
|
||||||
|
|
||||||
self.assertMessageCount(error=1)
|
|
||||||
self.assertRedirectsNoFollow(res, INDEX_URL)
|
|
||||||
finally:
|
|
||||||
# Restore the previous log levels
|
|
||||||
for (log, level) in loggers:
|
|
||||||
log.setLevel(level)
|
|
||||||
|
|
||||||
@test.create_stubs({trove_api.trove: ('cluster_get',
|
|
||||||
'cluster_shrink')})
|
|
||||||
def test_shrink_cluster(self):
|
|
||||||
cluster = self.trove_clusters.first()
|
|
||||||
trove_api.trove.cluster_get(IsA(http.HttpRequest), cluster.id)\
|
|
||||||
.MultipleTimes().AndReturn(cluster)
|
|
||||||
instance_id = cluster.instances[0]['id']
|
|
||||||
cluster_instances = [{'id': instance_id}]
|
|
||||||
trove_api.trove.cluster_shrink(IsA(http.HttpRequest),
|
|
||||||
cluster.id,
|
|
||||||
cluster_instances)
|
|
||||||
self.mox.ReplayAll()
|
|
||||||
|
|
||||||
url = reverse(
|
|
||||||
'horizon:project:database_clusters:cluster_shrink_details',
|
|
||||||
args=[cluster.id])
|
|
||||||
res = self.client.get(url)
|
|
||||||
self.assertTemplateUsed(
|
|
||||||
res, 'project/database_clusters/cluster_shrink_details.html')
|
|
||||||
table = res.context_data[
|
|
||||||
"".join([tables.ClusterShrinkInstancesTable.Meta.name, '_table'])]
|
|
||||||
self.assertEqual(len(cluster.instances), len(table.data))
|
|
||||||
|
|
||||||
action = "".join([tables.ClusterShrinkInstancesTable.Meta.name, '__',
|
|
||||||
tables.ClusterShrinkAction.name, '__',
|
|
||||||
instance_id])
|
|
||||||
res = self.client.post(url, {'action': action})
|
|
||||||
self.assertNoFormErrors(res)
|
|
||||||
self.assertMessageCount(info=1)
|
|
||||||
self.assertRedirectsNoFollow(res, INDEX_URL)
|
|
||||||
|
|
||||||
@test.create_stubs({trove_api.trove: ('cluster_get',
|
|
||||||
'cluster_shrink')})
|
|
||||||
def test_shrink_cluster_exception(self):
|
|
||||||
cluster = self.trove_clusters.first()
|
|
||||||
trove_api.trove.cluster_get(IsA(http.HttpRequest), cluster.id)\
|
|
||||||
.MultipleTimes().AndReturn(cluster)
|
|
||||||
cluster_id = cluster.instances[0]['id']
|
|
||||||
cluster_instances = [cluster_id]
|
|
||||||
trove_api.trove.cluster_shrink(IsA(http.HttpRequest),
|
|
||||||
cluster.id,
|
|
||||||
cluster_instances)\
|
|
||||||
.AndRaise(self.exceptions.trove)
|
|
||||||
self.mox.ReplayAll()
|
|
||||||
|
|
||||||
url = reverse(
|
|
||||||
'horizon:project:database_clusters:cluster_shrink_details',
|
|
||||||
args=[cluster.id])
|
|
||||||
action = "".join([tables.ClusterShrinkInstancesTable.Meta.name, '__',
|
|
||||||
tables.ClusterShrinkAction.name, '__',
|
|
||||||
cluster_id])
|
|
||||||
|
|
||||||
toSuppress = ["trove_dashboard.content.database_clusters.tables"]
|
|
||||||
|
|
||||||
# Suppress expected log messages in the test output
|
|
||||||
loggers = []
|
|
||||||
for cls in toSuppress:
|
|
||||||
logger = logging.getLogger(cls)
|
|
||||||
loggers.append((logger, logger.getEffectiveLevel()))
|
|
||||||
logger.setLevel(logging.CRITICAL)
|
|
||||||
|
|
||||||
try:
|
|
||||||
res = self.client.post(url, {'action': action})
|
|
||||||
self.assertMessageCount(error=1)
|
|
||||||
self.assertRedirectsNoFollow(res, INDEX_URL)
|
|
||||||
finally:
|
|
||||||
# Restore the previous log levels
|
|
||||||
for (log, level) in loggers:
|
|
||||||
log.setLevel(level)
|
|
||||||
|
|
||||||
def _get_filtered_datastores(self, datastore):
|
|
||||||
filtered_datastore = []
|
|
||||||
for ds in self.datastores.list():
|
|
||||||
if datastore in ds.name:
|
|
||||||
filtered_datastore.append(ds)
|
|
||||||
return filtered_datastore
|
|
||||||
|
|
||||||
def _get_filtered_datastore_versions(self, datastores):
|
|
||||||
filtered_datastore_versions = []
|
|
||||||
for ds in datastores:
|
|
||||||
for dsv in self.datastore_versions.list():
|
|
||||||
if ds.id == dsv.datastore:
|
|
||||||
filtered_datastore_versions.append(dsv)
|
|
||||||
return filtered_datastore_versions
|
|
||||||
|
|
||||||
def _contains_datastore_in_attribute(self, field, datastore):
|
|
||||||
for key, value in field.widget.attrs.iteritems():
|
|
||||||
if datastore in key:
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def _build_datastore_display_text(self, datastore, datastore_version):
|
|
||||||
return datastore + ' - ' + datastore_version
|
|
||||||
|
|
||||||
def _build_flavor_widget_name(self, datastore, datastore_version):
|
|
||||||
return binascii.hexlify(self._build_datastore_display_text(
|
|
||||||
datastore, datastore_version))
|
|
@ -1,40 +0,0 @@
|
|||||||
# Copyright (c) 2014 eBay Software Foundation
|
|
||||||
# Copyright 2015 HP Software, LLC
|
|
||||||
# All Rights Reserved.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
from django.conf.urls import url # noqa
|
|
||||||
|
|
||||||
from trove_dashboard.content.database_clusters import views
|
|
||||||
|
|
||||||
CLUSTERS = r'^(?P<cluster_id>[^/]+)/%s$'
|
|
||||||
|
|
||||||
urlpatterns = [
|
|
||||||
url(r'^$', views.IndexView.as_view(), name='index'),
|
|
||||||
url(r'^launch$', views.LaunchClusterView.as_view(), name='launch'),
|
|
||||||
url(r'^(?P<cluster_id>[^/]+)/$', views.DetailView.as_view(),
|
|
||||||
name='detail'),
|
|
||||||
url(CLUSTERS % 'cluster_grow_details',
|
|
||||||
views.ClusterGrowView.as_view(),
|
|
||||||
name='cluster_grow_details'),
|
|
||||||
url(CLUSTERS % 'add_instance',
|
|
||||||
views.ClusterAddInstancesView.as_view(),
|
|
||||||
name='add_instance'),
|
|
||||||
url(CLUSTERS % 'cluster_shrink_details',
|
|
||||||
views.ClusterShrinkView.as_view(),
|
|
||||||
name='cluster_shrink_details'),
|
|
||||||
url(CLUSTERS % 'reset_password',
|
|
||||||
views.ResetPasswordView.as_view(),
|
|
||||||
name='reset_password'),
|
|
||||||
]
|
|
@ -1,262 +0,0 @@
|
|||||||
# Copyright (c) 2014 eBay Software Foundation
|
|
||||||
# Copyright 2015 HP Software, LLC
|
|
||||||
# All Rights Reserved.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
"""
|
|
||||||
Views for managing database clusters.
|
|
||||||
"""
|
|
||||||
from collections import OrderedDict
|
|
||||||
import logging
|
|
||||||
|
|
||||||
from django.core.urlresolvers import reverse
|
|
||||||
from django.core.urlresolvers import reverse_lazy
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
|
|
||||||
import six
|
|
||||||
|
|
||||||
from horizon import exceptions
|
|
||||||
from horizon import forms as horizon_forms
|
|
||||||
from horizon import tables as horizon_tables
|
|
||||||
from horizon import tabs as horizon_tabs
|
|
||||||
from horizon.utils import memoized
|
|
||||||
|
|
||||||
from trove_dashboard import api
|
|
||||||
from trove_dashboard.content.database_clusters \
|
|
||||||
import cluster_manager
|
|
||||||
from trove_dashboard.content.database_clusters import forms
|
|
||||||
from trove_dashboard.content.database_clusters import tables
|
|
||||||
from trove_dashboard.content.database_clusters import tabs
|
|
||||||
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class IndexView(horizon_tables.DataTableView):
|
|
||||||
table_class = tables.ClustersTable
|
|
||||||
template_name = 'project/database_clusters/index.html'
|
|
||||||
|
|
||||||
def has_more_data(self, table):
|
|
||||||
return self._more
|
|
||||||
|
|
||||||
@memoized.memoized_method
|
|
||||||
def get_flavors(self):
|
|
||||||
try:
|
|
||||||
flavors = api.trove.flavor_list(self.request)
|
|
||||||
except Exception:
|
|
||||||
flavors = []
|
|
||||||
msg = _('Unable to retrieve database size information.')
|
|
||||||
exceptions.handle(self.request, msg)
|
|
||||||
return OrderedDict((six.text_type(flavor.id), flavor)
|
|
||||||
for flavor in flavors)
|
|
||||||
|
|
||||||
def _extra_data(self, cluster):
|
|
||||||
try:
|
|
||||||
cluster_flavor = cluster.instances[0]["flavor"]["id"]
|
|
||||||
flavors = self.get_flavors()
|
|
||||||
flavor = flavors.get(cluster_flavor)
|
|
||||||
if flavor is not None:
|
|
||||||
cluster.full_flavor = flavor
|
|
||||||
except Exception:
|
|
||||||
# ignore any errors and just return cluster unaltered
|
|
||||||
pass
|
|
||||||
return cluster
|
|
||||||
|
|
||||||
def get_data(self):
|
|
||||||
marker = self.request.GET.get(
|
|
||||||
tables.ClustersTable._meta.pagination_param)
|
|
||||||
# Gather our clusters
|
|
||||||
try:
|
|
||||||
clusters = api.trove.cluster_list(self.request, marker=marker)
|
|
||||||
self._more = clusters.next or False
|
|
||||||
except Exception:
|
|
||||||
self._more = False
|
|
||||||
clusters = []
|
|
||||||
msg = _('Unable to retrieve database clusters.')
|
|
||||||
exceptions.handle(self.request, msg)
|
|
||||||
|
|
||||||
map(self._extra_data, clusters)
|
|
||||||
|
|
||||||
return clusters
|
|
||||||
|
|
||||||
|
|
||||||
class LaunchClusterView(horizon_forms.ModalFormView):
|
|
||||||
form_class = forms.LaunchForm
|
|
||||||
form_id = "launch_form"
|
|
||||||
modal_header = _("Launch Cluster")
|
|
||||||
modal_id = "launch_modal"
|
|
||||||
template_name = 'project/database_clusters/launch.html'
|
|
||||||
submit_label = _("Launch")
|
|
||||||
submit_url = reverse_lazy('horizon:project:database_clusters:launch')
|
|
||||||
success_url = reverse_lazy('horizon:project:database_clusters:index')
|
|
||||||
|
|
||||||
|
|
||||||
class DetailView(horizon_tabs.TabbedTableView):
|
|
||||||
tab_group_class = tabs.ClusterDetailTabs
|
|
||||||
template_name = 'horizon/common/_detail.html'
|
|
||||||
page_title = "{{ cluster.name|default:cluster.id }}"
|
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
|
||||||
context = super(DetailView, self).get_context_data(**kwargs)
|
|
||||||
context["url"] = reverse('horizon:project:database_clusters:index')
|
|
||||||
context["cluster"] = self.get_data()
|
|
||||||
return context
|
|
||||||
|
|
||||||
@memoized.memoized_method
|
|
||||||
def get_data(self):
|
|
||||||
try:
|
|
||||||
cluster_id = self.kwargs['cluster_id']
|
|
||||||
cluster = api.trove.cluster_get(self.request, cluster_id)
|
|
||||||
except Exception:
|
|
||||||
redirect = reverse('horizon:project:database_clusters:index')
|
|
||||||
msg = _('Unable to retrieve details '
|
|
||||||
'for database cluster: %s') % cluster_id
|
|
||||||
exceptions.handle(self.request, msg, redirect=redirect)
|
|
||||||
try:
|
|
||||||
cluster.full_flavor = api.trove.flavor_get(
|
|
||||||
self.request, cluster.instances[0]["flavor"]["id"])
|
|
||||||
except Exception:
|
|
||||||
LOG.error('Unable to retrieve flavor details'
|
|
||||||
' for database cluster: %s' % cluster_id)
|
|
||||||
cluster.num_instances = len(cluster.instances)
|
|
||||||
|
|
||||||
# Todo(saurabhs) Set mgmt_url to dispaly Mgmt Console URL on
|
|
||||||
# cluster details page
|
|
||||||
# for instance in cluster.instances:
|
|
||||||
# if instance['type'] == "master":
|
|
||||||
# cluster.mgmt_url = "https://%s:5450/webui" % instance['ip'][0]
|
|
||||||
|
|
||||||
return cluster
|
|
||||||
|
|
||||||
def get_tabs(self, request, *args, **kwargs):
|
|
||||||
cluster = self.get_data()
|
|
||||||
return self.tab_group_class(request, cluster=cluster, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class ClusterGrowView(horizon_tables.DataTableView):
|
|
||||||
table_class = tables.ClusterGrowInstancesTable
|
|
||||||
template_name = 'project/database_clusters/cluster_grow_details.html'
|
|
||||||
page_title = _("Grow Cluster: {{cluster_name}}")
|
|
||||||
|
|
||||||
def get_data(self):
|
|
||||||
manager = cluster_manager.get(self.kwargs['cluster_id'])
|
|
||||||
return manager.get_instances()
|
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
|
||||||
context = super(ClusterGrowView, self).get_context_data(**kwargs)
|
|
||||||
context['cluster_id'] = self.kwargs['cluster_id']
|
|
||||||
cluster = self.get_cluster(self.kwargs['cluster_id'])
|
|
||||||
context['cluster_name'] = cluster.name
|
|
||||||
return context
|
|
||||||
|
|
||||||
@memoized.memoized_method
|
|
||||||
def get_cluster(self, cluster_id):
|
|
||||||
try:
|
|
||||||
return api.trove.cluster_get(self.request, cluster_id)
|
|
||||||
except Exception:
|
|
||||||
redirect = reverse("horizon:project:database_clusters:index")
|
|
||||||
msg = _('Unable to retrieve cluster details.')
|
|
||||||
exceptions.handle(self.request, msg, redirect=redirect)
|
|
||||||
|
|
||||||
|
|
||||||
class ClusterAddInstancesView(horizon_forms.ModalFormView):
|
|
||||||
form_class = forms.ClusterAddInstanceForm
|
|
||||||
form_id = "cluster_add_instances_form"
|
|
||||||
modal_header = _("Add Instance")
|
|
||||||
modal_id = "cluster_add_instances_modal"
|
|
||||||
template_name = "project/database_clusters/add_instance.html"
|
|
||||||
submit_label = _("Add")
|
|
||||||
submit_url = "horizon:project:database_clusters:add_instance"
|
|
||||||
success_url = "horizon:project:database_clusters:cluster_grow_details"
|
|
||||||
cancel_url = "horizon:project:database_clusters:cluster_grow_details"
|
|
||||||
page_title = _("Add Instance")
|
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
|
||||||
context = (super(ClusterAddInstancesView, self)
|
|
||||||
.get_context_data(**kwargs))
|
|
||||||
context['cluster_id'] = self.kwargs['cluster_id']
|
|
||||||
args = (self.kwargs['cluster_id'],)
|
|
||||||
context['submit_url'] = reverse(self.submit_url, args=args)
|
|
||||||
return context
|
|
||||||
|
|
||||||
def get_initial(self):
|
|
||||||
initial = super(ClusterAddInstancesView, self).get_initial()
|
|
||||||
initial['cluster_id'] = self.kwargs['cluster_id']
|
|
||||||
return initial
|
|
||||||
|
|
||||||
def get_success_url(self):
|
|
||||||
return reverse(self.success_url, args=[self.kwargs['cluster_id']])
|
|
||||||
|
|
||||||
def get_cancel_url(self):
|
|
||||||
return reverse(self.cancel_url, args=[self.kwargs['cluster_id']])
|
|
||||||
|
|
||||||
|
|
||||||
class ClusterInstance(object):
|
|
||||||
def __init__(self, id, name, status):
|
|
||||||
self.id = id
|
|
||||||
self.name = name
|
|
||||||
self.status = status
|
|
||||||
|
|
||||||
|
|
||||||
class ClusterShrinkView(horizon_tables.DataTableView):
|
|
||||||
table_class = tables.ClusterShrinkInstancesTable
|
|
||||||
template_name = "project/database_clusters/cluster_shrink_details.html"
|
|
||||||
page_title = _("Shrink Cluster: {{cluster_name}}")
|
|
||||||
|
|
||||||
@memoized.memoized_method
|
|
||||||
def get_cluster(self, cluster_id):
|
|
||||||
try:
|
|
||||||
return api.trove.cluster_get(self.request, cluster_id)
|
|
||||||
except Exception:
|
|
||||||
redirect = reverse("horizon:project:database_clusters:index")
|
|
||||||
msg = _('Unable to retrieve cluster details.')
|
|
||||||
exceptions.handle(self.request, msg, redirect=redirect)
|
|
||||||
|
|
||||||
def get_data(self):
|
|
||||||
cluster = self.get_cluster(self.kwargs['cluster_id'])
|
|
||||||
instances = [ClusterInstance(i['id'], i['name'], i['status'])
|
|
||||||
for i in cluster.instances]
|
|
||||||
return instances
|
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
|
||||||
context = super(ClusterShrinkView, self).get_context_data(**kwargs)
|
|
||||||
context['cluster_id'] = self.kwargs['cluster_id']
|
|
||||||
cluster = self.get_cluster(self.kwargs['cluster_id'])
|
|
||||||
context['cluster_name'] = cluster.name
|
|
||||||
return context
|
|
||||||
|
|
||||||
|
|
||||||
class ResetPasswordView(horizon_forms.ModalFormView):
|
|
||||||
form_class = forms.ResetPasswordForm
|
|
||||||
template_name = 'project/database_clusters/reset_password.html'
|
|
||||||
success_url = reverse_lazy('horizon:project:database_clusters:index')
|
|
||||||
page_title = _("Reset Root Password")
|
|
||||||
|
|
||||||
@memoized.memoized_method
|
|
||||||
def get_object(self, *args, **kwargs):
|
|
||||||
cluster_id = self.kwargs['cluster_id']
|
|
||||||
try:
|
|
||||||
return api.trove.cluster_get(self.request, cluster_id)
|
|
||||||
except Exception:
|
|
||||||
msg = _('Unable to retrieve cluster details.')
|
|
||||||
redirect = reverse('horizon:project:database_clusters:index')
|
|
||||||
exceptions.handle(self.request, msg, redirect=redirect)
|
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
|
||||||
context = super(ResetPasswordView, self).get_context_data(**kwargs)
|
|
||||||
context['cluster_id'] = self.kwargs['cluster_id']
|
|
||||||
return context
|
|
||||||
|
|
||||||
def get_initial(self):
|
|
||||||
return {'cluster_id': self.kwargs['cluster_id']}
|
|
@ -1,193 +0,0 @@
|
|||||||
# Copyright 2015 Tesora Inc.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
|
|
||||||
from django.core import cache
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
|
|
||||||
from trove_dashboard import api
|
|
||||||
|
|
||||||
from oslo_serialization import jsonutils
|
|
||||||
|
|
||||||
|
|
||||||
def get(request, configuration_group_id):
|
|
||||||
if not has_config(configuration_group_id):
|
|
||||||
manager = ConfigParamManager(configuration_group_id)
|
|
||||||
manager.configuration_get(request)
|
|
||||||
cache.cache.set(configuration_group_id, manager)
|
|
||||||
|
|
||||||
return cache.cache.get(configuration_group_id)
|
|
||||||
|
|
||||||
|
|
||||||
def delete(configuration_group_id):
|
|
||||||
cache.cache.delete(configuration_group_id)
|
|
||||||
|
|
||||||
|
|
||||||
def update(configuration_group_id, manager):
|
|
||||||
cache.cache.set(configuration_group_id, manager)
|
|
||||||
|
|
||||||
|
|
||||||
def has_config(configuration_group_id):
|
|
||||||
if cache.cache.get(configuration_group_id):
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def dict_has_changes(original, other):
|
|
||||||
if len(other) != len(original):
|
|
||||||
return True
|
|
||||||
|
|
||||||
diffs = (set(original.keys()) - set(other.keys()))
|
|
||||||
if len(diffs).__nonzero__():
|
|
||||||
return True
|
|
||||||
|
|
||||||
for key in original:
|
|
||||||
if original[key] != other[key]:
|
|
||||||
return True
|
|
||||||
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
class ConfigParamManager(object):
|
|
||||||
|
|
||||||
original_configuration_values = None
|
|
||||||
configuration = None
|
|
||||||
|
|
||||||
def __init__(self, configuration_id):
|
|
||||||
self.configuration_id = configuration_id
|
|
||||||
|
|
||||||
def configuration_get(self, request):
|
|
||||||
if self.configuration is None:
|
|
||||||
configuration = api.trove.configuration_get(
|
|
||||||
request, self.configuration_id)
|
|
||||||
# need to make one that can be cached
|
|
||||||
self.configuration = Configuration(
|
|
||||||
self.configuration_id,
|
|
||||||
configuration.name,
|
|
||||||
configuration.description,
|
|
||||||
configuration.datastore_name,
|
|
||||||
configuration.datastore_version_name,
|
|
||||||
configuration.created,
|
|
||||||
configuration.updated)
|
|
||||||
self.configuration.values = dict.copy(configuration.values)
|
|
||||||
self.original_configuration_values = dict.copy(
|
|
||||||
self.configuration.values)
|
|
||||||
|
|
||||||
return self.get_configuration()
|
|
||||||
|
|
||||||
def get_configuration(self):
|
|
||||||
return self.configuration
|
|
||||||
|
|
||||||
def create_config_value(self, name, value):
|
|
||||||
return ConfigParam(self.configuration_id, name, value)
|
|
||||||
|
|
||||||
def get_param(self, name):
|
|
||||||
for key_name in self.configuration.values:
|
|
||||||
if key_name == name:
|
|
||||||
return self.create_config_value(
|
|
||||||
key_name, self.configuration.values[key_name])
|
|
||||||
return None
|
|
||||||
|
|
||||||
def update_param(self, name, value):
|
|
||||||
self.configuration.values[name] = value
|
|
||||||
update(self.configuration_id, self)
|
|
||||||
|
|
||||||
def delete_param(self, name):
|
|
||||||
del self.configuration.values[name]
|
|
||||||
update(self.configuration_id, self)
|
|
||||||
|
|
||||||
def add_param(self, name, value):
|
|
||||||
self.update_param(name, value)
|
|
||||||
|
|
||||||
def to_json(self):
|
|
||||||
return jsonutils.dumps(self.configuration.values)
|
|
||||||
|
|
||||||
def has_changes(self):
|
|
||||||
return dict_has_changes(self.original_configuration_values,
|
|
||||||
self.configuration.values)
|
|
||||||
|
|
||||||
|
|
||||||
class ConfigParam(object):
|
|
||||||
def __init__(self, configuration_id, name, value):
|
|
||||||
self.configuration_id = configuration_id
|
|
||||||
self.name = name
|
|
||||||
self.value = value
|
|
||||||
|
|
||||||
|
|
||||||
class Configuration(object):
|
|
||||||
def __init__(self, id, name, description, datastore_name,
|
|
||||||
datastore_version_name, created, updated):
|
|
||||||
self.id = id
|
|
||||||
self.name = name
|
|
||||||
self.description = description
|
|
||||||
self.datastore_name = datastore_name
|
|
||||||
self.datastore_version_name = datastore_version_name
|
|
||||||
self.created = created
|
|
||||||
self.updated = updated
|
|
||||||
|
|
||||||
|
|
||||||
def validate_config_param_value(config_param, value):
|
|
||||||
if (config_param.type in (u"boolean", u"float", u"integer", u"long")):
|
|
||||||
if config_param.type == u"boolean":
|
|
||||||
if (value.lower() not in ("true", "false")):
|
|
||||||
return _('Value must be "true" or "false".')
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
float(value)
|
|
||||||
except ValueError:
|
|
||||||
return _('Value must be a number.')
|
|
||||||
|
|
||||||
min = getattr(config_param, "min", None)
|
|
||||||
max = getattr(config_param, "max", None)
|
|
||||||
try:
|
|
||||||
val = adjust_type(config_param.type, value)
|
|
||||||
except ValueError:
|
|
||||||
return (_('Value must be of type %s.') % config_param.type)
|
|
||||||
|
|
||||||
if min is not None and max is not None:
|
|
||||||
if val < min or val > max:
|
|
||||||
return (_('Value must be a number '
|
|
||||||
'between %(min)s and %(max)s.') %
|
|
||||||
{"min": min, "max": max})
|
|
||||||
elif min is not None:
|
|
||||||
if val < min:
|
|
||||||
return _('Value must be a number greater '
|
|
||||||
'than or equal to %s.') % min
|
|
||||||
elif max is not None:
|
|
||||||
if val > max:
|
|
||||||
return _('Value must be a number '
|
|
||||||
'less than or equal to %s.') % max
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def find_parameter(name, config_params):
|
|
||||||
for param in config_params:
|
|
||||||
if param.name == name:
|
|
||||||
return param
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def adjust_type(data_type, value):
|
|
||||||
if not value:
|
|
||||||
return value
|
|
||||||
if data_type == "float":
|
|
||||||
new_value = float(value)
|
|
||||||
elif data_type == "long":
|
|
||||||
new_value = long(value)
|
|
||||||
elif data_type == "integer":
|
|
||||||
new_value = int(value)
|
|
||||||
else:
|
|
||||||
new_value = value
|
|
||||||
return new_value
|
|
@ -1,190 +0,0 @@
|
|||||||
# Copyright 2015 Tesora Inc.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
import logging
|
|
||||||
|
|
||||||
from django.core.urlresolvers import reverse
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
|
|
||||||
from horizon import exceptions
|
|
||||||
from horizon import forms
|
|
||||||
from horizon import messages
|
|
||||||
from horizon.utils import memoized
|
|
||||||
|
|
||||||
from trove_dashboard import api
|
|
||||||
from trove_dashboard.content.database_configurations \
|
|
||||||
import config_param_manager
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class CreateConfigurationForm(forms.SelfHandlingForm):
|
|
||||||
name = forms.CharField(label=_("Name"))
|
|
||||||
description = forms.CharField(label=_("Description"), required=False)
|
|
||||||
datastore = forms.ChoiceField(
|
|
||||||
label=_("Datastore"),
|
|
||||||
help_text=_("Type and version of datastore."))
|
|
||||||
|
|
||||||
def __init__(self, request, *args, **kwargs):
|
|
||||||
super(CreateConfigurationForm, self).__init__(request, *args, **kwargs)
|
|
||||||
|
|
||||||
choices = self.get_datastore_choices(request)
|
|
||||||
self.fields['datastore'].choices = choices
|
|
||||||
|
|
||||||
@memoized.memoized_method
|
|
||||||
def datastores(self, request):
|
|
||||||
try:
|
|
||||||
return api.trove.datastore_list(request)
|
|
||||||
except Exception:
|
|
||||||
LOG.exception("Exception while obtaining datastores list")
|
|
||||||
redirect = reverse('horizon:project:database_configurations:index')
|
|
||||||
exceptions.handle(request,
|
|
||||||
_('Unable to obtain datastores.'),
|
|
||||||
redirect=redirect)
|
|
||||||
|
|
||||||
@memoized.memoized_method
|
|
||||||
def datastore_versions(self, request, datastore):
|
|
||||||
try:
|
|
||||||
return api.trove.datastore_version_list(request, datastore)
|
|
||||||
except Exception:
|
|
||||||
LOG.exception("Exception while obtaining datastore version list")
|
|
||||||
redirect = reverse('horizon:project:database_configurations:index')
|
|
||||||
exceptions.handle(request,
|
|
||||||
_('Unable to obtain datastore versions.'),
|
|
||||||
redirect=redirect)
|
|
||||||
|
|
||||||
def get_datastore_choices(self, request):
|
|
||||||
choices = ()
|
|
||||||
set_initial = False
|
|
||||||
datastores = self.datastores(request)
|
|
||||||
if datastores is not None:
|
|
||||||
num_datastores_with_one_version = 0
|
|
||||||
for ds in datastores:
|
|
||||||
versions = self.datastore_versions(request, ds.name)
|
|
||||||
if not set_initial:
|
|
||||||
if len(versions) >= 2:
|
|
||||||
set_initial = True
|
|
||||||
elif len(versions) == 1:
|
|
||||||
num_datastores_with_one_version += 1
|
|
||||||
if num_datastores_with_one_version > 1:
|
|
||||||
set_initial = True
|
|
||||||
if len(versions) > 0:
|
|
||||||
# only add to choices if datastore has at least one version
|
|
||||||
version_choices = ()
|
|
||||||
for v in versions:
|
|
||||||
version_choices = (version_choices +
|
|
||||||
((ds.name + ',' + v.name, v.name),))
|
|
||||||
datastore_choices = (ds.name, version_choices)
|
|
||||||
choices = choices + (datastore_choices,)
|
|
||||||
if set_initial:
|
|
||||||
# prepend choice to force user to choose
|
|
||||||
initial = ('', _('Select datastore type and version'))
|
|
||||||
choices = (initial,) + choices
|
|
||||||
return choices
|
|
||||||
|
|
||||||
def handle(self, request, data):
|
|
||||||
try:
|
|
||||||
datastore = data['datastore'].split(',')[0]
|
|
||||||
datastore_version = data['datastore'].split(',')[1]
|
|
||||||
|
|
||||||
api.trove.configuration_create(request, data['name'], "{}",
|
|
||||||
description=data['description'],
|
|
||||||
datastore=datastore,
|
|
||||||
datastore_version=datastore_version)
|
|
||||||
|
|
||||||
messages.success(request, _('Created configuration group'))
|
|
||||||
except Exception as e:
|
|
||||||
redirect = reverse("horizon:project:database_configurations:index")
|
|
||||||
exceptions.handle(request, _('Unable to create configuration '
|
|
||||||
'group. %s')
|
|
||||||
% e.message, redirect=redirect)
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
class AddParameterForm(forms.SelfHandlingForm):
|
|
||||||
name = forms.ChoiceField(label=_("Name"))
|
|
||||||
value = forms.CharField(label=_("Value"))
|
|
||||||
|
|
||||||
def __init__(self, request, *args, **kwargs):
|
|
||||||
super(AddParameterForm, self).__init__(request, *args, **kwargs)
|
|
||||||
|
|
||||||
configuration = (config_param_manager
|
|
||||||
.get(request, kwargs["initial"]["configuration_id"])
|
|
||||||
.get_configuration())
|
|
||||||
|
|
||||||
self.fields['name'].choices = self.get_parameters(
|
|
||||||
request, configuration.datastore_name,
|
|
||||||
configuration.datastore_version_name)
|
|
||||||
|
|
||||||
self.fields['value'].parameters = self.parameters
|
|
||||||
|
|
||||||
@memoized.memoized_method
|
|
||||||
def parameters(self, request, datastore, datastore_version):
|
|
||||||
try:
|
|
||||||
return api.trove.configuration_parameters_list(
|
|
||||||
request, datastore, datastore_version)
|
|
||||||
except Exception:
|
|
||||||
LOG.exception(
|
|
||||||
"Exception while obtaining configuration parameter list")
|
|
||||||
redirect = reverse('horizon:project:database_configurations:index')
|
|
||||||
exceptions.handle(request,
|
|
||||||
_('Unable to obtain list of parameters.'),
|
|
||||||
redirect=redirect)
|
|
||||||
|
|
||||||
def get_parameters(self, request, datastore, datastore_version):
|
|
||||||
try:
|
|
||||||
choices = []
|
|
||||||
|
|
||||||
self.parameters = self.parameters(
|
|
||||||
request, datastore, datastore_version)
|
|
||||||
for parameter in self.parameters:
|
|
||||||
choices.append((parameter.name, parameter.name))
|
|
||||||
|
|
||||||
return sorted(choices)
|
|
||||||
except Exception:
|
|
||||||
LOG.exception(
|
|
||||||
"Exception while obtaining configuration parameters list")
|
|
||||||
redirect = reverse('horizon:project:database_configurations:index')
|
|
||||||
exceptions.handle(request,
|
|
||||||
_('Unable to create list of parameters.'),
|
|
||||||
redirect=redirect)
|
|
||||||
|
|
||||||
def clean(self):
|
|
||||||
cleaned_data = super(AddParameterForm, self).clean()
|
|
||||||
|
|
||||||
if "value" in cleaned_data:
|
|
||||||
config_param = config_param_manager.find_parameter(
|
|
||||||
cleaned_data["name"], self.parameters)
|
|
||||||
if config_param:
|
|
||||||
error_msg = config_param_manager.validate_config_param_value(
|
|
||||||
config_param, cleaned_data["value"])
|
|
||||||
if error_msg:
|
|
||||||
self._errors['value'] = self.error_class([error_msg])
|
|
||||||
return cleaned_data
|
|
||||||
|
|
||||||
def handle(self, request, data):
|
|
||||||
try:
|
|
||||||
(config_param_manager
|
|
||||||
.get(request, self.initial["configuration_id"])
|
|
||||||
.add_param(data["name"],
|
|
||||||
config_param_manager.adjust_type(
|
|
||||||
config_param_manager.find_parameter(
|
|
||||||
data["name"], self.parameters).type,
|
|
||||||
data["value"])))
|
|
||||||
messages.success(request, _('Successfully added parameter'))
|
|
||||||
except Exception as e:
|
|
||||||
redirect = reverse("horizon:project:database_configurations:index")
|
|
||||||
exceptions.handle(request, _('Unable to add new parameter: %s')
|
|
||||||
% e.message, redirect=redirect)
|
|
||||||
return True
|
|
@ -1,27 +0,0 @@
|
|||||||
# Copyright 2015 Tesora Inc.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
|
|
||||||
import horizon
|
|
||||||
from openstack_dashboard.dashboards.project import dashboard
|
|
||||||
|
|
||||||
|
|
||||||
class Configurations(horizon.Panel):
|
|
||||||
name = _("Configuration Groups")
|
|
||||||
slug = 'database_configurations'
|
|
||||||
permissions = ('openstack.services.database',)
|
|
||||||
|
|
||||||
|
|
||||||
dashboard.Project.register(Configurations)
|
|
@ -1,285 +0,0 @@
|
|||||||
# Copyright 2015 Tesora Inc.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
import types
|
|
||||||
|
|
||||||
from django.core import exceptions as core_exceptions
|
|
||||||
from django.core import urlresolvers
|
|
||||||
from django import shortcuts
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
from django.utils.translation import ungettext_lazy
|
|
||||||
|
|
||||||
from horizon import forms
|
|
||||||
from horizon import messages
|
|
||||||
from horizon import tables
|
|
||||||
from horizon.utils import memoized
|
|
||||||
|
|
||||||
from trove_dashboard import api
|
|
||||||
from trove_dashboard.content.database_configurations \
|
|
||||||
import config_param_manager
|
|
||||||
|
|
||||||
|
|
||||||
class CreateConfiguration(tables.LinkAction):
|
|
||||||
name = "create_configuration"
|
|
||||||
verbose_name = _("Create Configuration Group")
|
|
||||||
url = "horizon:project:database_configurations:create"
|
|
||||||
classes = ('ajax-modal', )
|
|
||||||
icon = "plus"
|
|
||||||
|
|
||||||
|
|
||||||
class DeleteConfiguration(tables.DeleteAction):
|
|
||||||
data_type_singular = _("Configuration Group")
|
|
||||||
data_type_plural = _("Configuration Groups")
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def action_present(count):
|
|
||||||
return ungettext_lazy(
|
|
||||||
u"Delete Configuration Group",
|
|
||||||
u"Delete Configuration Groups",
|
|
||||||
count
|
|
||||||
)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def action_past(count):
|
|
||||||
return ungettext_lazy(
|
|
||||||
u"Deleted Configuration Group",
|
|
||||||
u"Deleted Configuration Groups",
|
|
||||||
count
|
|
||||||
)
|
|
||||||
|
|
||||||
def delete(self, request, obj_id):
|
|
||||||
api.trove.configuration_delete(request, obj_id)
|
|
||||||
|
|
||||||
|
|
||||||
class ConfigurationsTable(tables.DataTable):
|
|
||||||
name = tables.Column(
|
|
||||||
'name',
|
|
||||||
verbose_name=_('Configuration Group Name'),
|
|
||||||
link="horizon:project:database_configurations:detail")
|
|
||||||
description = tables.Column(
|
|
||||||
lambda obj: getattr(obj, 'description', None),
|
|
||||||
verbose_name=_('Description'))
|
|
||||||
datastore = tables.Column(
|
|
||||||
'datastore_name',
|
|
||||||
verbose_name=_('Datastore'))
|
|
||||||
datastore_version = tables.Column(
|
|
||||||
'datastore_version_name',
|
|
||||||
verbose_name=_('Datastore Version'))
|
|
||||||
|
|
||||||
class Meta(object):
|
|
||||||
name = "configurations"
|
|
||||||
verbose_name = _("Configuration Groups")
|
|
||||||
table_actions = [CreateConfiguration, DeleteConfiguration]
|
|
||||||
row_actions = [DeleteConfiguration]
|
|
||||||
|
|
||||||
|
|
||||||
class AddParameter(tables.LinkAction):
|
|
||||||
name = "add_parameter"
|
|
||||||
verbose_name = _("Add Parameter")
|
|
||||||
url = "horizon:project:database_configurations:add"
|
|
||||||
classes = ('ajax-modal', )
|
|
||||||
icon = "plus"
|
|
||||||
|
|
||||||
def get_link_url(self, datum=None):
|
|
||||||
configuration_id = self.table.kwargs['configuration_id']
|
|
||||||
return urlresolvers.reverse(self.url, args=[configuration_id])
|
|
||||||
|
|
||||||
|
|
||||||
class ApplyChanges(tables.Action):
|
|
||||||
name = "apply_changes"
|
|
||||||
verbose_name = _("Apply Changes")
|
|
||||||
verbose_name_plural = _("Apply Changes")
|
|
||||||
icon = "pencil"
|
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
|
||||||
super(ApplyChanges, self).__init__(**kwargs)
|
|
||||||
self.requires_input = False
|
|
||||||
|
|
||||||
def handle(self, table, request, obj_ids):
|
|
||||||
configuration_id = table.kwargs['configuration_id']
|
|
||||||
if config_param_manager.get(request, configuration_id).has_changes():
|
|
||||||
try:
|
|
||||||
api.trove.configuration_update(
|
|
||||||
request, configuration_id,
|
|
||||||
config_param_manager.get(
|
|
||||||
request, configuration_id).to_json())
|
|
||||||
messages.success(request, _('Applied changes to server'))
|
|
||||||
except Exception:
|
|
||||||
messages.error(request, _('Error applying changes'))
|
|
||||||
finally:
|
|
||||||
config_param_manager.delete(configuration_id)
|
|
||||||
|
|
||||||
return shortcuts.redirect(request.build_absolute_uri())
|
|
||||||
|
|
||||||
|
|
||||||
class DiscardChanges(tables.Action):
|
|
||||||
name = "discard_changes"
|
|
||||||
verbose_name = _("Discard Changes")
|
|
||||||
verbose_name_plural = _("Discard Changes")
|
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
|
||||||
super(DiscardChanges, self).__init__(**kwargs)
|
|
||||||
self.requires_input = False
|
|
||||||
|
|
||||||
def handle(self, table, request, obj_ids):
|
|
||||||
configuration_id = table.kwargs['configuration_id']
|
|
||||||
if config_param_manager.get(request, configuration_id).has_changes():
|
|
||||||
try:
|
|
||||||
config_param_manager.delete(configuration_id)
|
|
||||||
messages.success(request, _('Reset Parameters'))
|
|
||||||
except Exception as ex:
|
|
||||||
messages.error(
|
|
||||||
request,
|
|
||||||
_('Error resetting parameters: %s') % ex.message)
|
|
||||||
|
|
||||||
return shortcuts.redirect(request.build_absolute_uri())
|
|
||||||
|
|
||||||
|
|
||||||
class DeleteParameter(tables.DeleteAction):
|
|
||||||
data_type_singular = _("Parameter")
|
|
||||||
data_type_plural = _("Parameters")
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def action_present(count):
|
|
||||||
return ungettext_lazy(
|
|
||||||
u"Delete Parameter",
|
|
||||||
u"Delete Parameters",
|
|
||||||
count
|
|
||||||
)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def action_past(count):
|
|
||||||
return ungettext_lazy(
|
|
||||||
u"Deleted Parameter",
|
|
||||||
u"Deleted Parameters",
|
|
||||||
count
|
|
||||||
)
|
|
||||||
|
|
||||||
def delete(self, request, obj_ids):
|
|
||||||
configuration_id = self.table.kwargs['configuration_id']
|
|
||||||
(config_param_manager
|
|
||||||
.get(request, configuration_id)
|
|
||||||
.delete_param(obj_ids))
|
|
||||||
|
|
||||||
|
|
||||||
class UpdateRow(tables.Row):
|
|
||||||
def get_data(self, request, name):
|
|
||||||
return config_param_manager.get(
|
|
||||||
request, self.table.kwargs["configuration_id"]).get_param(name)
|
|
||||||
|
|
||||||
|
|
||||||
class UpdateCell(tables.UpdateAction):
|
|
||||||
def update_cell(self, request, datum, name,
|
|
||||||
cell_name, new_cell_value):
|
|
||||||
config_param = datum
|
|
||||||
|
|
||||||
config = config_param_manager.get(request,
|
|
||||||
config_param.configuration_id)
|
|
||||||
validation_param = config_param_manager.find_parameter(
|
|
||||||
name,
|
|
||||||
self.parameters(request,
|
|
||||||
config.configuration.datastore_name,
|
|
||||||
config.configuration.datastore_version_name))
|
|
||||||
if validation_param:
|
|
||||||
error_msg = config_param_manager.validate_config_param_value(
|
|
||||||
validation_param, new_cell_value)
|
|
||||||
if error_msg:
|
|
||||||
raise core_exceptions.ValidationError(error_msg)
|
|
||||||
|
|
||||||
if isinstance(config_param.value, types.IntType):
|
|
||||||
value = int(new_cell_value)
|
|
||||||
elif isinstance(config_param.value, types.LongType):
|
|
||||||
value = long(new_cell_value)
|
|
||||||
else:
|
|
||||||
value = new_cell_value
|
|
||||||
|
|
||||||
setattr(datum, cell_name, value)
|
|
||||||
|
|
||||||
(config_param_manager
|
|
||||||
.get(request, config_param.configuration_id)
|
|
||||||
.update_param(name, value))
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
@memoized.memoized_method
|
|
||||||
def parameters(self, request, datastore, datastore_version):
|
|
||||||
return api.trove.configuration_parameters_list(
|
|
||||||
request, datastore, datastore_version)
|
|
||||||
|
|
||||||
def _adjust_type(self, data_type, value):
|
|
||||||
if not value:
|
|
||||||
return value
|
|
||||||
if data_type == "float":
|
|
||||||
new_value = float(value)
|
|
||||||
elif data_type == "long":
|
|
||||||
new_value = long(value)
|
|
||||||
elif data_type == "integer":
|
|
||||||
new_value = int(value)
|
|
||||||
else:
|
|
||||||
new_value = value
|
|
||||||
return new_value
|
|
||||||
|
|
||||||
|
|
||||||
class ValuesTable(tables.DataTable):
|
|
||||||
name = tables.Column("name", verbose_name=_("Name"))
|
|
||||||
value = tables.Column("value", verbose_name=_("Value"),
|
|
||||||
form_field=forms.CharField(required=False),
|
|
||||||
update_action=UpdateCell)
|
|
||||||
|
|
||||||
class Meta(object):
|
|
||||||
name = "values"
|
|
||||||
verbose_name = _("Configuration Group Values")
|
|
||||||
table_actions = [ApplyChanges, DiscardChanges,
|
|
||||||
AddParameter, DeleteParameter]
|
|
||||||
row_class = UpdateRow
|
|
||||||
row_actions = [DeleteParameter]
|
|
||||||
|
|
||||||
def get_object_id(self, datum):
|
|
||||||
return datum.name
|
|
||||||
|
|
||||||
|
|
||||||
class DetachConfiguration(tables.BatchAction):
|
|
||||||
@staticmethod
|
|
||||||
def action_present(count):
|
|
||||||
return ungettext_lazy(
|
|
||||||
u"Detach Configuration Group",
|
|
||||||
u"Detach Configuration Groups",
|
|
||||||
count
|
|
||||||
)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def action_past(count):
|
|
||||||
return ungettext_lazy(
|
|
||||||
u"Detached Configuration Group",
|
|
||||||
u"Detached Configuration Groups",
|
|
||||||
count
|
|
||||||
)
|
|
||||||
|
|
||||||
name = "detach_configuration"
|
|
||||||
classes = ('btn-danger', 'btn-detach-config')
|
|
||||||
|
|
||||||
def action(self, request, obj_id):
|
|
||||||
api.trove.instance_detach_configuration(request, obj_id)
|
|
||||||
|
|
||||||
|
|
||||||
class InstancesTable(tables.DataTable):
|
|
||||||
name = tables.Column("name",
|
|
||||||
link="horizon:project:databases:detail",
|
|
||||||
verbose_name=_("Name"))
|
|
||||||
|
|
||||||
class Meta(object):
|
|
||||||
name = "instances"
|
|
||||||
verbose_name = _("Configuration Group Instances")
|
|
||||||
multi_select = False
|
|
||||||
row_actions = [DetachConfiguration]
|
|
@ -1,73 +0,0 @@
|
|||||||
# Copyright 2015 Tesora Inc.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
|
|
||||||
from horizon import exceptions
|
|
||||||
from horizon import tabs
|
|
||||||
|
|
||||||
from trove_dashboard import api
|
|
||||||
from trove_dashboard.content.database_configurations \
|
|
||||||
import config_param_manager
|
|
||||||
from trove_dashboard.content.database_configurations \
|
|
||||||
import tables
|
|
||||||
|
|
||||||
|
|
||||||
class DetailsTab(tabs.Tab):
|
|
||||||
name = _("Details")
|
|
||||||
slug = "details_tab"
|
|
||||||
template_name = "project/database_configurations/_detail_overview.html"
|
|
||||||
|
|
||||||
def get_context_data(self, request):
|
|
||||||
return {"configuration": self.tab_group.kwargs['configuration']}
|
|
||||||
|
|
||||||
|
|
||||||
class ValuesTab(tabs.TableTab):
|
|
||||||
table_classes = [tables.ValuesTable]
|
|
||||||
name = _("Values")
|
|
||||||
slug = "values_tab"
|
|
||||||
template_name = "project/database_configurations/detail_param.html"
|
|
||||||
|
|
||||||
def get_values_data(self):
|
|
||||||
values_data = []
|
|
||||||
manager = config_param_manager.get(
|
|
||||||
self.request, self.tab_group.kwargs['configuration_id'])
|
|
||||||
for k, v in manager.get_configuration().values.items():
|
|
||||||
manager.add_param(k, v)
|
|
||||||
values_data.append(manager.create_config_value(k, v))
|
|
||||||
return values_data
|
|
||||||
|
|
||||||
|
|
||||||
class InstancesTab(tabs.TableTab):
|
|
||||||
table_classes = [tables.InstancesTable]
|
|
||||||
name = _("Instances")
|
|
||||||
slug = "instances_tab"
|
|
||||||
template_name = "horizon/common/_detail_table.html"
|
|
||||||
|
|
||||||
def get_instances_data(self):
|
|
||||||
configuration = self.tab_group.kwargs['configuration']
|
|
||||||
try:
|
|
||||||
data = api.trove.configuration_instances(self.request,
|
|
||||||
configuration.id)
|
|
||||||
except Exception:
|
|
||||||
msg = _('Unable to get configuration data.')
|
|
||||||
exceptions.handle(self.request, msg)
|
|
||||||
data = []
|
|
||||||
return data
|
|
||||||
|
|
||||||
|
|
||||||
class ConfigurationDetailTabs(tabs.TabGroup):
|
|
||||||
slug = "configuration_details"
|
|
||||||
tabs = (ValuesTab, InstancesTab, DetailsTab)
|
|
||||||
sticky = True
|
|
@ -1,6 +0,0 @@
|
|||||||
{% extends "horizon/common/_modal_form.html" %}
|
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
{% block modal-body-right %}
|
|
||||||
<p>{% trans "Select a parameter and provide a value for the configuration parameter." %}</p>
|
|
||||||
{% endblock %}
|
|
@ -1,9 +0,0 @@
|
|||||||
{% extends "horizon/common/_modal_form.html" %}
|
|
||||||
|
|
||||||
{% block modal-body %}
|
|
||||||
<div class="center">
|
|
||||||
<fieldset>
|
|
||||||
{% include "horizon/common/_form_fields.html" %}
|
|
||||||
</fieldset>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
@ -1,24 +0,0 @@
|
|||||||
{% load i18n sizeformat %}
|
|
||||||
|
|
||||||
<h3>{% trans "Configuration Group Overview" %}</h3>
|
|
||||||
|
|
||||||
<div class="status row-fluid detail">
|
|
||||||
<h4>{% trans "Info" %}</h4>
|
|
||||||
<hr class="header_rule">
|
|
||||||
<dl>
|
|
||||||
<dt>{% trans "Name" %}</dt>
|
|
||||||
<dd>{{ configuration.name }}</dd>
|
|
||||||
<dt>{% trans "Description" %}</dt>
|
|
||||||
<dd>{{ configuration.description|linebreaksbr }}</dd>
|
|
||||||
<dt>{% trans "ID" %}</dt>
|
|
||||||
<dd>{{ configuration.id }}</dd>
|
|
||||||
<dt>{% trans "Datastore" %}</dt>
|
|
||||||
<dd>{{ configuration.datastore_name }}</dd>
|
|
||||||
<dt>{% trans "Datastore Version" %}</dt>
|
|
||||||
<dd>{{ configuration.datastore_version_name }}</dd>
|
|
||||||
<dt>{% trans "Created" %}</dt>
|
|
||||||
<dd>{{ configuration.created|parse_isotime }}</dd>
|
|
||||||
<dt>{% trans "Updated" %}</dt>
|
|
||||||
<dd>{{ configuration.updated|parse_isotime }}</dd>
|
|
||||||
</dl>
|
|
||||||
</div>
|
|
@ -1,5 +0,0 @@
|
|||||||
{% extends "base.html" %}
|
|
||||||
|
|
||||||
{% block main %}
|
|
||||||
{% include "project/database_configurations/_add_parameter.html" %}
|
|
||||||
{% endblock %}
|
|
@ -1,5 +0,0 @@
|
|||||||
{% extends "base.html" %}
|
|
||||||
|
|
||||||
{% block main %}
|
|
||||||
{% include "project/database_configurations/_create.html" %}
|
|
||||||
{% endblock %}
|
|
@ -1,8 +0,0 @@
|
|||||||
{% load i18n %}
|
|
||||||
|
|
||||||
<br>
|
|
||||||
<div class="help_text">
|
|
||||||
{% trans "Add parameters to the configuration group. When all the parameters are added click 'Apply Changes' to persist changes." %}
|
|
||||||
</div>
|
|
||||||
<br>
|
|
||||||
{{ table.render }}
|
|
@ -1,10 +0,0 @@
|
|||||||
{% extends 'base.html' %}
|
|
||||||
|
|
||||||
{% block main %}
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-sm-12">
|
|
||||||
{{ tab_group.render }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user