diff --git a/.mailmap b/.mailmap deleted file mode 100644 index f59873d1b..000000000 --- a/.mailmap +++ /dev/null @@ -1,12 +0,0 @@ -# Format is: -# -# - - - - - - -Zhongyue Luo -Joe Gordon -Kun Huang diff --git a/.pylintrc b/.pylintrc deleted file mode 100644 index 64f5cafe8..000000000 --- a/.pylintrc +++ /dev/null @@ -1,42 +0,0 @@ -# The format of this file isn't really documented; just use --generate-rcfile -[MASTER] -# Add to the black list. It should be a base name, not a -# path. You may set this option multiple times. -ignore=test - -[Messages Control] -# NOTE(justinsb): We might want to have a 2nd strict pylintrc in future -# C0111: Don't require docstrings on every method -# W0511: TODOs in code comments are fine. -# W0142: *args and **kwargs are fine. -# W0622: Redefining id is fine. -disable=C0111,W0511,W0142,W0622 - -[Basic] -# Variable names can be 1 to 31 characters long, with lowercase and underscores -variable-rgx=[a-z_][a-z0-9_]{0,30}$ - -# Argument names can be 2 to 31 characters long, with lowercase and underscores -argument-rgx=[a-z_][a-z0-9_]{1,30}$ - -# Method names should be at least 3 characters long -# and be lowecased with underscores -method-rgx=([a-z_][a-z0-9_]{2,50}|setUp|tearDown)$ - -# Module names matching keystone-* are ok (files in bin/) -module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+)|(keystone-[a-z0-9_-]+))$ - -# Don't require docstrings on tests. -no-docstring-rgx=((__.*__)|([tT]est.*)|setUp|tearDown)$ - -[Design] -max-public-methods=100 -min-public-methods=0 -max-args=6 - -[Variables] - -# List of additional names supposed to be defined in builtins. Remember that -# you should avoid to define new builtins when possible. -# _ is used by our localization -additional-builtins=_ diff --git a/HACKING.rst b/HACKING.rst deleted file mode 100644 index e8df4788e..000000000 --- a/HACKING.rst +++ /dev/null @@ -1,60 +0,0 @@ -Contributing -============ - -The code repository is located at `OpenStack `__. -Please go there if you want to check it out: - - git clone https://github.com/openstack/tuskar-ui.git - -The list of bugs and blueprints is on Launchpad: - -``__ - -We use OpenStack's Gerrit for the code contributions: - -``__ - -and we follow the `OpenStack Gerrit Workflow `__. - -If you're interested in the code, here are some key places to start: - -* `tuskar_ui/api.py `_ - - This file contains all the API calls made to the Tuskar API - (through python-tuskarclient). -* `tuskar_ui/infrastructure `_ - - The Tuskar UI code is contained within this directory. - -Running tests -============= - -There are several ways to run tests for tuskar-ui. - -Using ``tox``: - - This is the easiest way to run tests. When run, tox installs dependencies, - prepares the virtual python environment, then runs test commands. The gate - tests in gerrit usually also use tox to run tests. For avaliable tox - environments, see ``tox.ini``. - -By running ``run_tests.sh``: - - Tests can also be run using the ``run_tests.sh`` script, to see available - options, run it with the ``--help`` option. It handles preparing the - virtual environment and executing tests, but in contrast with tox, it does - not install all dependencies, e.g. ``jshint`` must be installed before - running the jshint testcase. - -Manual tests: - - To manually check tuskar-ui, it is possible to run a development server - for tuskar-ui by running ``run_tests.sh --runserver``. - - To run the server with the settings used by the test environment: - ``run_tests.sh --runserver 0.0.0.0:8000 --settings=tuskar_ui.test.settings`` - -OpenStack Style Commandments -============================ - -- Step 1: Read http://www.python.org/dev/peps/pep-0008/ -- Step 2: Read http://www.python.org/dev/peps/pep-0008/ again -- Step 3: Read https://github.com/openstack-dev/hacking/blob/master/HACKING.rst diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 68c771a09..000000000 --- a/LICENSE +++ /dev/null @@ -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. - diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index 96b865606..000000000 --- a/MANIFEST.in +++ /dev/null @@ -1,19 +0,0 @@ -recursive-include bin *.js -recursive-include doc *.py *.rst *.css *.js *.html *.conf *.jpg *.gif *.png *.css_t -recursive-include tools *.py *.sh -recursive-include tuskar_ui *.py *.html *.js *.scss *.mo *.po *.example *.eot *.svg *.ttf *.woff *.png *.ico *.wsgi *.gif *.csv *.template - -include AUTHORS -include ChangeLog -include LICENSE -include Makefile -include manage.py -include README.rst -include run_tests.sh -include tox.ini -include doc/Makefile -include doc/source/_templates/.placeholder -include requirements.txt -include test-requirements.txt - -exclude openstack_dashboard/local/local_settings.py diff --git a/Makefile b/Makefile deleted file mode 100644 index 49e4f7288..000000000 --- a/Makefile +++ /dev/null @@ -1,24 +0,0 @@ -PYTHON=`which python` -DESTDIR=/ -PROJECT=horizon - -all: - @echo "make test - Run tests" - @echo "make source - Create source package" - @echo "make install - Install on local system" - @echo "make buildrpm - Generate a rpm package" - @echo "make clean - Get rid of scratch and byte files" - -source: - $(PYTHON) setup.py sdist $(COMPILE) - -install: - $(PYTHON) setup.py install --root $(DESTDIR) $(COMPILE) - -buildrpm: - $(PYTHON) setup.py bdist_rpm --post-install=rpm/postinstall --pre-uninstall=rpm/preuninstall - -clean: - $(PYTHON) setup.py clean - rm -rf build/ MANIFEST - find . -name '*.pyc' -delete diff --git a/README b/README new file mode 100644 index 000000000..3a1e7f83f --- /dev/null +++ b/README @@ -0,0 +1,10 @@ +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 any further questions, please email +openstack-dev@lists.openstack.org or join #openstack-dev or #tripleo +on Freenode. diff --git a/README.rst b/README.rst deleted file mode 100644 index bd1d9d301..000000000 --- a/README.rst +++ /dev/null @@ -1,41 +0,0 @@ -========= -Tuskar UI -========= - -**Tuskar UI** is a user interface for -`Tuskar `__, a management API for -OpenStack deployments. It is a plugin for `OpenStack -Horizon `__. - -High-Level Overview -------------------- - -Tuskar UI endeavours to be a stateless UI, relying on Tuskar API calls -as much as possible. We use existing Horizon libraries and components -where possible. If added libraries and components are needed, we will -work with the OpenStack community to push those changes back into Horizon. - -Interested in seeing Tuskar and Tuskar UI in action? -`Watch a demo! `_ - - -Installation Guide ------------------- - -Use the `Installation Guide `_ to install Tuskar UI. - -License -------- - -This project is licensed under the Apache License, version 2. More -information can be found in the LICENSE file. - -Contact Us ----------- - -Join us on IRC (Internet Relay Chat):: - - Network: Freenode (irc.freenode.net/tuskar) - Channel: #tuskar - -Or send an email to openstack-dev@lists.openstack.org. diff --git a/_10_admin.py.example b/_10_admin.py.example deleted file mode 100644 index 868d719da..000000000 --- a/_10_admin.py.example +++ /dev/null @@ -1,2 +0,0 @@ -DASHBOARD = 'admin' -DISABLED = True diff --git a/_20_project.py.example b/_20_project.py.example deleted file mode 100644 index b90069460..000000000 --- a/_20_project.py.example +++ /dev/null @@ -1,2 +0,0 @@ -DASHBOARD = 'project' -DISABLED = True diff --git a/_30_identity.py.example b/_30_identity.py.example deleted file mode 100644 index 260a8505b..000000000 --- a/_30_identity.py.example +++ /dev/null @@ -1,2 +0,0 @@ -DASHBOARD = 'identity' -DISABLED = True diff --git a/_50_tuskar.py.example b/_50_tuskar.py.example deleted file mode 100644 index 7da296980..000000000 --- a/_50_tuskar.py.example +++ /dev/null @@ -1,12 +0,0 @@ -from tuskar_ui import exceptions - -DASHBOARD = 'infrastructure' -ADD_INSTALLED_APPS = [ - 'tuskar_ui.infrastructure', -] -ADD_EXCEPTIONS = { - 'recoverable': exceptions.RECOVERABLE, - 'not_found': exceptions.NOT_FOUND, - 'unauthorized': exceptions.UNAUTHORIZED, -} -DEFAULT = True diff --git a/dev_env.sh b/dev_env.sh deleted file mode 100644 index 020cc2a5b..000000000 --- a/dev_env.sh +++ /dev/null @@ -1,56 +0,0 @@ -#!/bin/bash -set -ex - -USAGE="Usage: `basename $0` " - -if [ "$#" -ne 2 ]; then - echo $USAGE - exit 1 -fi - -UNDERCLOUD_IP=$1 -UNDERCLOUD_ADMIN_PASSWORD=$2 - -echo "Copying SSH key..." -cp /home/stack/.ssh/id_rsa /root/.ssh/ - -echo "Installing system requirements..." -yum install -y git python-devel swig openssl-devel mysql-devel libxml2-devel libxslt-devel gcc gcc-c++ -easy_install pip nose - -echo "Cloning repos..." -mkdir /opt/stack -cd /opt/stack -git clone git://github.com/openstack/horizon.git -git clone git://github.com/openstack/python-tuskarclient.git -git clone git://github.com/openstack/tuskar-ui.git -git clone git://github.com/rdo-management/tuskar-ui-extras.git - -echo "Setting up repos..." -cd horizon -python tools/install_venv.py -./run_tests.sh -V -cp openstack_dashboard/local/local_settings.py.example openstack_dashboard/local/local_settings.py -tools/with_venv.sh pip install -e ../python-tuskarclient/ -tools/with_venv.sh pip install -e ../tuskar-ui/ -tools/with_venv.sh pip install -e ../tuskar-ui-extras/ -cp ../tuskar-ui/_50_tuskar.py.example openstack_dashboard/local/enabled/_50_tuskar.py -cp ../tuskar-ui-extras/_60_tuskar_boxes.py.example openstack_dashboard/local/enabled/_60_tuskar_boxes.py -cp ../tuskar-ui/_10_admin.py.example openstack_dashboard/local/enabled/_10_admin.py -cp ../tuskar-ui/_20_project.py.example openstack_dashboard/local/enabled/_20_project.py -cp ../tuskar-ui/_30_identity.py.example openstack_dashboard/local/enabled/_30_identity.py -sed -i s/'OPENSTACK_HOST = "127.0.0.1"'/'OPENSTACK_HOST = "192.0.2.1"'/ openstack_dashboard/local/local_settings.py -echo 'IRONIC_DISCOVERD_URL = "http://%s:5050" % OPENSTACK_HOST' >> openstack_dashboard/local/local_settings.py -echo 'UNDERCLOUD_ADMIN_PASSWORD = "'$UNDERCLOUD_ADMIN_PASSWORD'"' >> openstack_dashboard/local/local_settings.py -echo 'DEPLOYMENT_MODE = "scale"' >> openstack_dashboard/local/local_settings.py - -echo "Setting up networking..." -sudo ip route replace 192.0.2.0/24 dev virbr0 via $UNDERCLOUD_IP - -echo "Setting up iptables on the undercloud..." -RULE_1="-A INPUT -p tcp -m tcp --dport 8585 -j ACCEPT" -RULE_2="-A INPUT -p tcp -m tcp --dport 9696 -j ACCEPT" -RULE_3="-A INPUT -p tcp -m tcp --dport 8777 -j ACCEPT" -ssh $UNDERCLOUD_IP "sed -i '/$RULE_1/a $RULE_2' /etc/sysconfig/iptables" -ssh $UNDERCLOUD_IP "sed -i '/$RULE_2/a $RULE_3' /etc/sysconfig/iptables" -ssh $UNDERCLOUD_IP "service iptables restart" diff --git a/doc/Makefile b/doc/Makefile deleted file mode 100644 index 745433413..000000000 --- a/doc/Makefile +++ /dev/null @@ -1,153 +0,0 @@ -# Makefile for Sphinx documentation -# - -# You can set these variables from the command line. -SPHINXOPTS = -SPHINXBUILD = sphinx-build -PAPER = -BUILDDIR = build - -# Internal variables. -PAPEROPT_a4 = -D latex_paper_size=a4 -PAPEROPT_letter = -D latex_paper_size=letter -ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source -# the i18n builder cannot share the environment and doctrees with the others -I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source - -.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext - -help: - @echo "Please use \`make ' where is one of" - @echo " html to make standalone HTML files" - @echo " dirhtml to make HTML files named index.html in directories" - @echo " singlehtml to make a single large HTML file" - @echo " pickle to make pickle files" - @echo " json to make JSON files" - @echo " htmlhelp to make HTML files and a HTML help project" - @echo " qthelp to make HTML files and a qthelp project" - @echo " devhelp to make HTML files and a Devhelp project" - @echo " epub to make an epub" - @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" - @echo " latexpdf to make LaTeX files and run them through pdflatex" - @echo " text to make text files" - @echo " man to make manual pages" - @echo " texinfo to make Texinfo files" - @echo " info to make Texinfo files and run them through makeinfo" - @echo " gettext to make PO message catalogs" - @echo " changes to make an overview of all changed/added/deprecated items" - @echo " linkcheck to check all external links for integrity" - @echo " doctest to run all doctests embedded in the documentation (if enabled)" - -clean: - -rm -rf $(BUILDDIR)/* - -html: - $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." - -dirhtml: - $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." - -singlehtml: - $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml - @echo - @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." - -pickle: - $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle - @echo - @echo "Build finished; now you can process the pickle files." - -json: - $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json - @echo - @echo "Build finished; now you can process the JSON files." - -htmlhelp: - $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp - @echo - @echo "Build finished; now you can run HTML Help Workshop with the" \ - ".hhp project file in $(BUILDDIR)/htmlhelp." - -qthelp: - $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp - @echo - @echo "Build finished; now you can run "qcollectiongenerator" with the" \ - ".qhcp project file in $(BUILDDIR)/qthelp, like this:" - @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/TuskarUI.qhcp" - @echo "To view the help file:" - @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/TuskarUI.qhc" - -devhelp: - $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp - @echo - @echo "Build finished." - @echo "To view the help file:" - @echo "# mkdir -p $$HOME/.local/share/devhelp/TuskarUI" - @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/TuskarUI" - @echo "# devhelp" - -epub: - $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub - @echo - @echo "Build finished. The epub file is in $(BUILDDIR)/epub." - -latex: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo - @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." - @echo "Run \`make' in that directory to run these through (pdf)latex" \ - "(use \`make latexpdf' here to do that automatically)." - -latexpdf: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo "Running LaTeX files through pdflatex..." - $(MAKE) -C $(BUILDDIR)/latex all-pdf - @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." - -text: - $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text - @echo - @echo "Build finished. The text files are in $(BUILDDIR)/text." - -man: - $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man - @echo - @echo "Build finished. The manual pages are in $(BUILDDIR)/man." - -texinfo: - $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo - @echo - @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." - @echo "Run \`make' in that directory to run these through makeinfo" \ - "(use \`make info' here to do that automatically)." - -info: - $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo - @echo "Running Texinfo files through makeinfo..." - make -C $(BUILDDIR)/texinfo info - @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." - -gettext: - $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale - @echo - @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." - -changes: - $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes - @echo - @echo "The overview file is in $(BUILDDIR)/changes." - -linkcheck: - $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck - @echo - @echo "Link check complete; look for any errors in the above output " \ - "or in $(BUILDDIR)/linkcheck/output.txt." - -doctest: - $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest - @echo "Testing of doctests in the sources finished, look at the " \ - "results in $(BUILDDIR)/doctest/output.txt." diff --git a/doc/make.bat b/doc/make.bat deleted file mode 100644 index 5004e35e7..000000000 --- a/doc/make.bat +++ /dev/null @@ -1,190 +0,0 @@ -@ECHO OFF - -REM Command file for Sphinx documentation - -if "%SPHINXBUILD%" == "" ( - set SPHINXBUILD=sphinx-build -) -set BUILDDIR=build -set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% source -set I18NSPHINXOPTS=%SPHINXOPTS% source -if NOT "%PAPER%" == "" ( - set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% - set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% -) - -if "%1" == "" goto help - -if "%1" == "help" ( - :help - echo.Please use `make ^` where ^ is one of - echo. html to make standalone HTML files - echo. dirhtml to make HTML files named index.html in directories - echo. singlehtml to make a single large HTML file - echo. pickle to make pickle files - echo. json to make JSON files - echo. htmlhelp to make HTML files and a HTML help project - echo. qthelp to make HTML files and a qthelp project - echo. devhelp to make HTML files and a Devhelp project - echo. epub to make an epub - echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter - echo. text to make text files - echo. man to make manual pages - echo. texinfo to make Texinfo files - echo. gettext to make PO message catalogs - echo. changes to make an overview over all changed/added/deprecated items - echo. linkcheck to check all external links for integrity - echo. doctest to run all doctests embedded in the documentation if enabled - goto end -) - -if "%1" == "clean" ( - for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i - del /q /s %BUILDDIR%\* - goto end -) - -if "%1" == "html" ( - %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/html. - goto end -) - -if "%1" == "dirhtml" ( - %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. - goto end -) - -if "%1" == "singlehtml" ( - %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. - goto end -) - -if "%1" == "pickle" ( - %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can process the pickle files. - goto end -) - -if "%1" == "json" ( - %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can process the JSON files. - goto end -) - -if "%1" == "htmlhelp" ( - %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can run HTML Help Workshop with the ^ -.hhp project file in %BUILDDIR%/htmlhelp. - goto end -) - -if "%1" == "qthelp" ( - %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can run "qcollectiongenerator" with the ^ -.qhcp project file in %BUILDDIR%/qthelp, like this: - echo.^> qcollectiongenerator %BUILDDIR%\qthelp\TuskarUI.qhcp - echo.To view the help file: - echo.^> assistant -collectionFile %BUILDDIR%\qthelp\TuskarUI.ghc - goto end -) - -if "%1" == "devhelp" ( - %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. - goto end -) - -if "%1" == "epub" ( - %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The epub file is in %BUILDDIR%/epub. - goto end -) - -if "%1" == "latex" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. - goto end -) - -if "%1" == "text" ( - %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The text files are in %BUILDDIR%/text. - goto end -) - -if "%1" == "man" ( - %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The manual pages are in %BUILDDIR%/man. - goto end -) - -if "%1" == "texinfo" ( - %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. - goto end -) - -if "%1" == "gettext" ( - %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The message catalogs are in %BUILDDIR%/locale. - goto end -) - -if "%1" == "changes" ( - %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes - if errorlevel 1 exit /b 1 - echo. - echo.The overview file is in %BUILDDIR%/changes. - goto end -) - -if "%1" == "linkcheck" ( - %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck - if errorlevel 1 exit /b 1 - echo. - echo.Link check complete; look for any errors in the above output ^ -or in %BUILDDIR%/linkcheck/output.txt. - goto end -) - -if "%1" == "doctest" ( - %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest - if errorlevel 1 exit /b 1 - echo. - echo.Testing of doctests in the sources finished, look at the ^ -results in %BUILDDIR%/doctest/output.txt. - goto end -) - -:end diff --git a/doc/source/HACKING.rst b/doc/source/HACKING.rst deleted file mode 120000 index a2f06b723..000000000 --- a/doc/source/HACKING.rst +++ /dev/null @@ -1 +0,0 @@ -../../HACKING.rst \ No newline at end of file diff --git a/doc/source/README.rst b/doc/source/README.rst deleted file mode 120000 index c768ff7d9..000000000 --- a/doc/source/README.rst +++ /dev/null @@ -1 +0,0 @@ -../../README.rst \ No newline at end of file diff --git a/doc/source/conf.py b/doc/source/conf.py deleted file mode 100644 index 756f56f9e..000000000 --- a/doc/source/conf.py +++ /dev/null @@ -1,242 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Tuskar UI documentation build configuration file, created by -# sphinx-quickstart on Thu Apr 24 09:19:32 2014. -# -# This file is execfile()d with the current directory set to its containing dir. -# -# Note that not all possible configuration values are present in this -# autogenerated file. -# -# All configuration values have a default; values that are commented out -# serve to show the default. - -import sys, os - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -#sys.path.insert(0, os.path.abspath('.')) - -# -- General configuration ----------------------------------------------------- - -# If your documentation needs a minimal Sphinx version, state it here. -#needs_sphinx = '1.0' - -# Add any Sphinx extension module names here, as strings. They can be extensions -# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode', 'oslosphinx'] - -# Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] - -# The suffix of source filenames. -source_suffix = '.rst' - -# The encoding of source files. -#source_encoding = 'utf-8-sig' - -# The master toctree document. -master_doc = 'index' - -# General information about the project. -project = u'Tuskar UI' -copyright = u'2014, Tuskar Team' - -# The version info for the project you're documenting, acts as replacement for -# |version| and |release|, also used in various other places throughout the -# built documents. -# -# The short X.Y version. -version = 'Juno' -# The full version, including alpha/beta/rc tags. -release = 'Juno' - -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -#language = None - -# There are two options for replacing |today|: either, you set today to some -# non-false value, then it is used: -#today = '' -# Else, today_fmt is used as the format for a strftime call. -#today_fmt = '%B %d, %Y' - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -exclude_patterns = [] - -# The reST default role (used for this markup: `text`) to use for all documents. -#default_role = None - -# If true, '()' will be appended to :func: etc. cross-reference text. -#add_function_parentheses = True - -# If true, the current module name will be prepended to all description -# unit titles (such as .. function::). -#add_module_names = True - -# If true, sectionauthor and moduleauthor directives will be shown in the -# output. They are ignored by default. -#show_authors = False - -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' - -# A list of ignored prefixes for module index sorting. -#modindex_common_prefix = [] - - -# -- Options for HTML output --------------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -html_theme = 'default' - -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. -#html_theme_options = {} - -# Add any paths that contain custom themes here, relative to this directory. -#html_theme_path = [] - -# The name for this set of Sphinx documents. If None, it defaults to -# " v documentation". -#html_title = None - -# A shorter title for the navigation bar. Default is the same as html_title. -#html_short_title = None - -# The name of an image file (relative to this directory) to place at the top -# of the sidebar. -#html_logo = None - -# The name of an image file (within the static path) to use as favicon of the -# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 -# pixels large. -#html_favicon = None - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] - -# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, -# using the given strftime format. -#html_last_updated_fmt = '%b %d, %Y' - -# If true, SmartyPants will be used to convert quotes and dashes to -# typographically correct entities. -#html_use_smartypants = True - -# Custom sidebar templates, maps document names to template names. -#html_sidebars = {} - -# Additional templates that should be rendered to pages, maps page names to -# template names. -#html_additional_pages = {} - -# If false, no module index is generated. -#html_domain_indices = True - -# If false, no index is generated. -#html_use_index = True - -# If true, the index is split into individual pages for each letter. -#html_split_index = False - -# If true, links to the reST sources are added to the pages. -#html_show_sourcelink = True - -# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -#html_show_sphinx = True - -# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -#html_show_copyright = True - -# If true, an OpenSearch description file will be output, and all pages will -# contain a tag referring to it. The value of this option must be the -# base URL from which the finished HTML is served. -#html_use_opensearch = '' - -# This is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = None - -# Output file base name for HTML help builder. -htmlhelp_basename = 'TuskarUIdoc' - - -# -- Options for LaTeX output -------------------------------------------------- - -latex_elements = { -# The paper size ('letterpaper' or 'a4paper'). -#'papersize': 'letterpaper', - -# The font size ('10pt', '11pt' or '12pt'). -#'pointsize': '10pt', - -# Additional stuff for the LaTeX preamble. -#'preamble': '', -} - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, author, documentclass [howto/manual]). -latex_documents = [ - ('index', 'TuskarUI.tex', u'Tuskar UI Documentation', - u'Tuskar Team', 'manual'), -] - -# The name of an image file (relative to this directory) to place at the top of -# the title page. -#latex_logo = None - -# For "manual" documents, if this is true, then toplevel headings are parts, -# not chapters. -#latex_use_parts = False - -# If true, show page references after internal links. -#latex_show_pagerefs = False - -# If true, show URL addresses after external links. -#latex_show_urls = False - -# Documents to append as an appendix to all manuals. -#latex_appendices = [] - -# If false, no module index is generated. -#latex_domain_indices = True - - -# -- Options for manual page output -------------------------------------------- - -# One entry per manual page. List of tuples -# (source start file, name, description, authors, manual section). -man_pages = [ - ('index', 'tuskarui', u'Tuskar UI Documentation', - [u'Tuskar Team'], 1) -] - -# If true, show URL addresses after external links. -#man_show_urls = False - - -# -- Options for Texinfo output ------------------------------------------------ - -# Grouping the document tree into Texinfo files. List of tuples -# (source start file, target name, title, author, -# dir menu entry, description, category) -texinfo_documents = [ - ('index', 'TuskarUI', u'Tuskar UI Documentation', - u'Tuskar Team', 'TuskarUI', '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' diff --git a/doc/source/index.rst b/doc/source/index.rst deleted file mode 100644 index d6176cb05..000000000 --- a/doc/source/index.rst +++ /dev/null @@ -1,20 +0,0 @@ -Tuskar UI -========= - -Contents: - -.. toctree:: - :maxdepth: 2 - - README - install - user_guide - HACKING - -Indices and tables -================== - -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` - diff --git a/doc/source/install.rst b/doc/source/install.rst deleted file mode 100644 index 7f2590ab6..000000000 --- a/doc/source/install.rst +++ /dev/null @@ -1,133 +0,0 @@ -Installation instructions -========================= - -Note ----- - -If you want to install and configure the entire TripleO + Tuskar + Tuskar UI -stack, you can use -`the devtest installation guide `_. - -Otherwise, you can use the installation instructions for Tuskar UI below. - -Prerequisites -------------- - -Installation prerequisites are: - -1. A functional OpenStack installation. Horizon and Tuskar UI will - connect to the Keystone service here. Keystone does *not* need to be - on the same machine as your Tuskar UI interface, but its HTTP API - must be accessible. -2. A functional Tuskar installation. Tuskar UI talks to Tuskar via an - HTTP interface. It may, but does not have to, reside on the same - machine as Tuskar UI, but it must be network accessible. - -You may find -`the Tuskar install guide `_ -helpful. - - -Installing the packages ------------------------ - -Tuskar UI is a Django app written in Python and has a few installation -dependencies: - -On a RHEL 6 system, you should install the following: - -:: - - yum install git python-devel swig openssl-devel mysql-devel libxml2-devel libxslt-devel gcc gcc-c++ - -The above should work well for similar RPM-based distributions. For -other distros or platforms, you will obviously need to convert as -appropriate. - -Then, you'll want to use the ``easy_install`` utility to set up a few -other tools: - -:: - - easy_install pip - easy_install nose - -Install the management UI -------------------------- - -Begin by cloning the Horizon and Tuskar UI repositories: - -:: - - git clone git://github.com/openstack/horizon.git - git clone git://github.com/openstack/python-tuskarclient.git - git clone git://github.com/openstack/tuskar-ui.git - -Go into ``horizon`` and install a virtual environment for your setup:: - - cd horizon - python tools/install_venv.py - - -Next, run ``run_tests.sh`` to have pip install Horizon dependencies: - -:: - - ./run_tests.sh - -Set up your ``local_settings.py`` file: - -:: - - cp openstack_dashboard/local/local_settings.py.example openstack_dashboard/local/local_settings.py - -Open up the copied ``local_settings.py`` file in your preferred text -editor. You will want to customize several settings: - -- ``OPENSTACK_HOST`` should be configured with the hostname of your - OpenStack server. Verify that the ``OPENSTACK_KEYSTONE_URL`` and - ``OPENSTACK_KEYSTONE_DEFAULT_ROLE`` settings are correct for your - environment. (They should be correct unless you modified your - OpenStack server to change them.) - -Install Tuskar UI with all dependencies in your virtual environment:: - - tools/with_venv.sh pip install -e ../python-tuskarclient/ - tools/with_venv.sh pip install -e ../tuskar-ui/ - -And enable it in Horizon:: - - cp ../tuskar-ui/_50_tuskar.py.example openstack_dashboard/local/enabled/_50_tuskar.py - -Then disable the other dashboards:: - - cp ../tuskar-ui/_10_admin.py.example openstack_dashboard/local/enabled/_10_admin.py - cp ../tuskar-ui/_20_project.py.example openstack_dashboard/local/enabled/_20_project.py - cp ../tuskar-ui/_30_identity.py.example openstack_dashboard/local/enabled/_30_identity.py - - -Starting the app ----------------- - -If everything has gone according to plan, you should be able to run: - -:: - - tools/with_venv.sh ./manage.py runserver - -and have the application start on port 8080. The Tuskar UI dashboard will -be located at http://localhost:8080/infrastructure - -If you wish to access it remotely (i.e., not just from localhost), you -need to open port 8080 in iptables: - -:: - - iptables -I INPUT -p tcp --dport 8080 -j ACCEPT - -and launch the server with ``0.0.0.0:8080`` on the end: - -:: - - tools/with_venv.sh ./manage.py runserver 0.0.0.0:8080 - diff --git a/doc/source/user_guide.rst b/doc/source/user_guide.rst deleted file mode 100644 index 782c9e050..000000000 --- a/doc/source/user_guide.rst +++ /dev/null @@ -1,16 +0,0 @@ -========== -User Guide -========== - -Nodes List File ---------------- - -To allow users to load a bunch of nodes at once, there is possibility to -upload CSV file with given list of nodes. This file should be formatted as - -:: - - driver,address,username,password/ssh key,mac addresses,cpu architecture,number of CPUs,available memory,available storage - -Even if there is no all data available, we assume empty values for missing -keys and try to parse everything, what is possible. diff --git a/manage.py b/manage.py deleted file mode 100755 index 256981c54..000000000 --- a/manage.py +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/env python - -import os -import sys - -from django.core.management import execute_from_command_line - -if __name__ == "__main__": - os.environ.setdefault("DJANGO_SETTINGS_MODULE", - "openstack_dashboard.settings") - execute_from_command_line(sys.argv) diff --git a/nodes.sh b/nodes.sh deleted file mode 100755 index bef9f51e6..000000000 --- a/nodes.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/bash -set -eux - -OUTPUT_FILE=${OUTPUT_FILE:-"nodes.csv"} -NODES_JSON_FILE=${NODES_JSON_FILE:-"/home/stack/instackenv.json"} - -NUM_NODES=$(jq '.nodes | length' $NODES_JSON_FILE) - -if [ -e $OUTPUT_FILE ]; then - rm $OUTPUT_FILE -fi - -for i in $(seq 0 $(expr $NUM_NODES - 1)); do - DRIVER=$(jq -r ".nodes[${i}] | .[\"pm_type\"]" $NODES_JSON_FILE) - SSH_ADDRESS=$(jq -r ".nodes[${i}] | .[\"pm_addr\"]" $NODES_JSON_FILE) - SSH_USERNAME=$(jq -r ".nodes[${i}] | .[\"pm_user\"]" $NODES_JSON_FILE) - SSH_KEY_CONTENTS=$(jq -r ".nodes[${i}] | .[\"pm_password\"]" $NODES_JSON_FILE) - MAC=$(jq -r ".nodes[${i}] | .mac[0]" $NODES_JSON_FILE) - echo "${DRIVER},${SSH_ADDRESS},${SSH_USERNAME},\"${SSH_KEY_CONTENTS}\",${MAC}" >> $OUTPUT_FILE -done diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 4fe5429e7..000000000 --- a/requirements.txt +++ /dev/null @@ -1,6 +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. -os-cloud-config -python-ironic-inspector-client>=1.0.1 -python-ironicclient>=0.8.0 diff --git a/run_tests.sh b/run_tests.sh deleted file mode 100755 index 37b44a2c2..000000000 --- a/run_tests.sh +++ /dev/null @@ -1,552 +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 []" - echo " Just run PEP8 and HACKING compliance check" - echo " on files changed since HEAD~1 (or )" - 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 " -j, --jshint Just run jshint" - echo " -s, --jscs Just run jscs" - 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="tuskar_ui" - -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 -just_jscs=0 -just_jshint=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;; - -j|--jshint) just_jshint=1;; - -s|--jscs) just_jscs=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 run_jshint { - echo "Running jshint ..." - jshint tuskar_ui/infrastructure/static/infrastructure -} - -function run_jscs { - echo "Running jscs ..." - if [ "`which jscs`" == '' ] ; then - echo "jscs is not present; please install, e.g. sudo npm install jscs -g" - else - jscs tuskar_ui/infrastructure/static/infrastructure/js \ - tuskar_ui/infrastructure/static/infrastructure/tests - 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=tuskar_ui.test.settings ${command_wrapper} flake8 $included_dirs -} - -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=openstack_dashboard.test.settings ${command_wrapper} flake8 --diff - exit -} - -function run_sphinx { - echo "Building sphinx..." - export DJANGO_SETTINGS_MODULE=openstack_dashboard.settings - ${command_wrapper} sphinx-build -b html doc/source doc/build/html - 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/ - cp .environment_version /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 [ $with_selenium -eq 0 -a $integration -eq 0 ]; then - testopts="$testopts --exclude-dir=tuskar_ui/test/integration_tests" - fi - - if [ $selenium_headless -eq 1 ]; then - export SELENIUM_HEADLESS=1 - fi - - 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 Tuskar-UI application tests" - export NOSE_XUNIT_FILE=tuskar_ui/nosetests.xml - if [ "$NOSE_WITH_HTML_OUTPUT" = '1' ]; then - export NOSE_HTML_OUT_FILE='tuskar_ui_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 tuskar_ui --settings=tuskar_ui.test.settings $testopts - # get results of the Horizon tests - TUSKAR_UI_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 --include="tuskar_ui/*" --omit='/usr*,setup.py,*egg*,.venv/*' - ${command_wrapper} python -m coverage.__main__ html -i --include="tuskar_ui/*" --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 [ $only_selenium -eq 0 ]; then - run_pep8 - PEP8_RESULT=$? - fi - - TEST_RESULT=$(($TUSKAR_UI_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 - - echo "Running Tuskar-UI integration tests..." - if [ -z "$testargs" ]; then - ${command_wrapper} nosetests tuskar_ui/test/integration_tests/tests - else - ${command_wrapper} nosetests $testargs - fi - exit 0 -} - -function run_makemessages { - cd horizon - ${command_wrapper} $root/manage.py makemessages --all --no-obsolete - HORIZON_PY_RESULT=$? - ${command_wrapper} $root/manage.py makemessages -d djangojs --all --no-obsolete - HORIZON_JS_RESULT=$? - cd ../openstack_dashboard - ${command_wrapper} $root/manage.py makemessages --all --no-obsolete - DASHBOARD_RESULT=$? - cd .. - exit $(($HORIZON_PY_RESULT || $HORIZON_JS_RESULT || $DASHBOARD_RESULT)) -} - -function run_compilemessages { - cd horizon - ${command_wrapper} $root/manage.py compilemessages - HORIZON_PY_RESULT=$? - cd ../openstack_dashboard - ${command_wrapper} $root/manage.py compilemessages - DASHBOARD_RESULT=$? - cd .. - exit $(($HORIZON_PY_RESULT || $DASHBOARD_RESULT)) -} - - -# ---------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 - -# 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 - -# Jshint -if [ $just_jshint -eq 1 ]; then - run_jshint - exit $? -fi - -# Jscs -if [ $just_jscs -eq 1 ]; then - run_jscs - exit $? -fi - -# Tab checker -if [ $just_tabs -eq 1 ]; then - tab_check - exit $? -fi - - -# Django development server -if [ $runserver -eq 1 ]; then - run_server - exit $? -fi - -# Full test suite -run_tests || exit diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 8e4c5c6f8..000000000 --- a/setup.cfg +++ /dev/null @@ -1,41 +0,0 @@ -[metadata] -name = tuskar-ui -version = 2013.2 -summary = Tuskar Management Dashboard -description-file = - README.rst -author = OpenStack -author-email = openstack-dev@lists.openstack.org -home-page = http://www.openstack.org/ -classifier = - Development Status :: 5 - Production/Stable - Environment :: OpenStack - Framework :: Django - Intended Audience :: Developers - Intended Audience :: Information Technology - Intended Audience :: System Administrators - License :: OSI Approved :: Apache Software License - Operating System :: OS Independent - Operating System :: POSIX :: Linux - Programming Language :: Python - Programming Language :: Python :: 2 - Programming Language :: Python :: 2.7 - Programming Language :: Python :: 2.6 - Topic :: Internet :: WWW/HTTP - -[global] -setup-hooks = - pbr.hooks.setup_hook - -[files] -packages = - tuskar_ui - -[build_sphinx] -all_files = 1 -build-dir = doc/build -source-dir = doc/source - -[nosetests] -verbosity=2 -detailed-errors=1 diff --git a/setup.py b/setup.py deleted file mode 100644 index 782bb21f0..000000000 --- a/setup.py +++ /dev/null @@ -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) diff --git a/test-requirements.txt b/test-requirements.txt deleted file mode 100644 index bc873bfc5..000000000 --- a/test-requirements.txt +++ /dev/null @@ -1,37 +0,0 @@ -# The order of packages is significant, because pip processes them in the order -# of appearance. Changing the order has an impact on the overall integration -# process, which may cause wedges in the gate later. -# Hacking already pins down pep8, pyflakes and flake8 -hacking<0.11,>=0.10.0 -# Testing Requirements -http://tarballs.openstack.org/horizon/horizon-master.tar.gz#egg=horizon - - - - - - -http://tarballs.openstack.org/python-tuskarclient/python-tuskarclient-master.tar.gz#egg=python-tuskarclient - - - - - - -coverage>=3.6 -django-nose>=1.2 -mock>=1.2 -mox>=0.5.3 -mox3>=0.7.0 -nodeenv>=0.9.4 # BSD License -nose -nose-exclude -nosexcover -openstack.nose-plugin>=0.7 -nosehtmloutput>=0.0.3 -selenium -xvfbwrapper>=0.1.3 #license: MIT -# Docs Requirements -sphinx!=1.2.0,!=1.3b1,<1.3,>=1.1.2 -oslosphinx>=2.5.0 # Apache-2.0 - diff --git a/tools/install_venv.py b/tools/install_venv.py deleted file mode 100644 index 8550e2c6b..000000000 --- a/tools/install_venv.py +++ /dev/null @@ -1,154 +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') - - -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 install_dependencies(venv=VENV): - print "Installing dependencies..." - print "(This may take several minutes, don't panic)" - pip_install('-r', TEST_REQUIRES) - pip_install('-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() diff --git a/tools/with_venv.sh b/tools/with_venv.sh deleted file mode 100755 index c8d2940fc..000000000 --- a/tools/with_venv.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/bash -TOOLS=`dirname $0` -VENV=$TOOLS/../.venv -source $VENV/bin/activate && $@ diff --git a/tox.ini b/tox.ini deleted file mode 100644 index 4af6d7a7d..000000000 --- a/tox.ini +++ /dev/null @@ -1,68 +0,0 @@ -[tox] -envlist = py27,py27dj14,py27dj15,py27dj16,pep8,selenium,jshint - -[testenv] -setenv = VIRTUAL_ENV={envdir} - NOSE_WITH_OPENSTACK=1 - NOSE_OPENSTACK_COLOR=1 - NOSE_OPENSTACK_RED=0.05 - NOSE_OPENSTACK_YELLOW=0.025 - NOSE_OPENSTACK_SHOW_ELAPSED=1 -deps = -r{toxinidir}/requirements.txt - -r{toxinidir}/test-requirements.txt -commands = /bin/bash run_tests.sh -N - -[testenv:pep8] -commands = /bin/bash run_tests.sh -N --pep8 - -[testenv:venv] -commands = {posargs} - -[testenv:cover] -commands = /bin/bash run_tests.sh -N --coverage - -[testenv:py27dj14] -basepython = python2.7 -commands = pip install django>=1.4,<1.5 - /bin/bash run_tests.sh -N - -[testenv:py27dj15] -basepython = python2.7 -commands = pip install django>=1.5,<1.6 - /bin/bash run_tests.sh -N - -[testenv:py27dj16] -basepython = python2.7 -commands = pip install django>=1.6,<1.7 - /bin/bash run_tests.sh -N - -[testenv:selenium] -commands = /bin/bash run_tests.sh -N --only-selenium - -[testenv:jshint] -commands = nodeenv -p - npm install jshint -g - /bin/bash run_tests.sh -N --jshint - -[flake8] -builtins = _ -exclude = .venv,.git,.tox,dist,doc,*openstack/common*,*lib/python*,*egg, - build,panel_template,dash_template,local_settings.py - -[hacking] -import_exceptions = collections.defaultdict, - django.conf.settings, - django.core.urlresolvers.reverse, - django.core.urlresolvers.reverse_lazy, - django.template.loader.render_to_string, - django.utils.datastructures.SortedDict, - django.utils.encoding.force_unicode, - django.utils.html.conditional_escape, - django.utils.html.escape, - django.utils.http.urlencode, - django.utils.safestring.mark_safe, - django.utils.translation.pgettext_lazy, - django.utils.translation.ugettext_lazy, - django.utils.translation.ungettext_lazy, - operator.attrgetter, - StringIO.StringIO diff --git a/tuskar_ui/__init__.py b/tuskar_ui/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/tuskar_ui/api/__init__.py b/tuskar_ui/api/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/tuskar_ui/api/flavor.py b/tuskar_ui/api/flavor.py deleted file mode 100644 index 518400cc5..000000000 --- a/tuskar_ui/api/flavor.py +++ /dev/null @@ -1,121 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -import logging - -from django.utils.translation import ugettext_lazy as _ -from horizon.utils import memoized -from openstack_dashboard.api import nova - -import tuskar_ui -from tuskar_ui.cached_property import cached_property # noqa -from tuskar_ui.handle_errors import handle_errors # noqa - - -LOG = logging.getLogger(__name__) - - -class Flavor(object): - - def __init__(self, flavor): - """Construct by wrapping Nova flavor - - :param flavor: Nova flavor - :type flavor: novaclient.v2.flavors.Flavor - """ - self._flavor = flavor - - def __getattr__(self, name): - return getattr(self._flavor, name) - - @property - def ram_bytes(self): - """Get RAM size in bytes - - Default RAM size is in MB. - """ - return self.ram * 1024 * 1024 - - @property - def disk_bytes(self): - """Get disk size in bytes - - Default disk size is in GB. - """ - return self.disk * 1024 * 1024 * 1024 - - @cached_property - def extras_dict(self): - """Return extra flavor parameters - - :return: Nova flavor keys - :rtype: dict - """ - return self._flavor.get_keys() - - @property - def cpu_arch(self): - return self.extras_dict.get('cpu_arch', '') - - @property - def kernel_image_id(self): - return self.extras_dict.get('baremetal:deploy_kernel_id', '') - - @property - def ramdisk_image_id(self): - return self.extras_dict.get('baremetal:deploy_ramdisk_id', '') - - @classmethod - def create(cls, request, name, memory, vcpus, disk, cpu_arch, - kernel_image_id=None, ramdisk_image_id=None): - extras_dict = { - 'cpu_arch': cpu_arch, - 'capabilities:boot_option': 'local', - } - if kernel_image_id is not None: - extras_dict['baremetal:deploy_kernel_id'] = kernel_image_id - if ramdisk_image_id is not None: - extras_dict['baremetal:deploy_ramdisk_id'] = ramdisk_image_id - return cls(nova.flavor_create(request, name, memory, vcpus, disk, - metadata=extras_dict)) - - @classmethod - @handle_errors(_("Unable to load flavor.")) - def get(cls, request, flavor_id): - return cls(nova.flavor_get(request, flavor_id)) - - @classmethod - @handle_errors(_("Unable to load flavor.")) - def get_by_name(cls, request, name): - for flavor in cls.list(request): - if flavor.name == name: - return flavor - - @classmethod - @handle_errors(_("Unable to retrieve flavor list."), []) - def list(cls, request): - return [cls(item) for item in nova.flavor_list(request)] - - @classmethod - @memoized.memoized - @handle_errors(_("Unable to retrieve existing servers list."), []) - def list_deployed_ids(cls, request): - """Get and memoize ID's of deployed flavors.""" - servers = nova.server_list(request)[0] - deployed_ids = set(server.flavor['id'] for server in servers) - deployed_names = [] - for plan in tuskar_ui.api.tuskar.Plan.list(request): - deployed_names.extend( - [plan.parameter_value(role.flavor_parameter_name) - for role in plan.role_list]) - return [flavor.id for flavor in cls.list(request) - if flavor.id in deployed_ids or flavor.name in deployed_names] diff --git a/tuskar_ui/api/heat.py b/tuskar_ui/api/heat.py deleted file mode 100644 index 18056de5c..000000000 --- a/tuskar_ui/api/heat.py +++ /dev/null @@ -1,553 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -import logging -import os -import tempfile -import urlparse - -from django.conf import settings -from django.utils.translation import ugettext_lazy as _ -from heatclient.common import template_utils -from heatclient.exc import HTTPNotFound -from horizon.utils import memoized -from openstack_dashboard.api import base -from openstack_dashboard.api import heat -from openstack_dashboard.api import keystone - -from tuskar_ui.api import node -from tuskar_ui.api import tuskar -from tuskar_ui.cached_property import cached_property # noqa -from tuskar_ui.handle_errors import handle_errors # noqa -from tuskar_ui.utils import utils - - -LOG = logging.getLogger(__name__) - - -@memoized.memoized -def overcloud_keystoneclient(request, endpoint, password): - """Returns a client connected to the Keystone backend. - - Several forms of authentication are supported: - - * Username + password -> Unscoped authentication - * Username + password + tenant id -> Scoped authentication - * Unscoped token -> Unscoped authentication - * Unscoped token + tenant id -> Scoped authentication - * Scoped token -> Scoped authentication - - Available services and data from the backend will vary depending on - whether the authentication was scoped or unscoped. - - Lazy authentication if an ``endpoint`` parameter is provided. - - Calls requiring the admin endpoint should have ``admin=True`` passed in - as a keyword argument. - - The client is cached so that subsequent API calls during the same - request/response cycle don't have to be re-authenticated. - """ - api_version = keystone.VERSIONS.get_active_version() - - # TODO(lsmola) add support of certificates and secured http and rest of - # parameters according to horizon and add configuration to local settings - # (somehow plugin based, we should not maintain a copy of settings) - LOG.debug("Creating a new keystoneclient connection to %s." % endpoint) - - # TODO(lsmola) we should create tripleo-admin user for this purpose - # this needs to be done first on tripleo side - conn = api_version['client'].Client(username="admin", - password=password, - tenant_name="admin", - auth_url=endpoint) - - return conn - - -def _save_templates(templates): - """Saves templates into tmpdir on server - - This should go away and get replaced by libutils.save_templates from - tripleo-common https://github.com/openstack/tripleo-common/ - """ - output_dir = tempfile.mkdtemp() - - for template_name, template_content in templates.items(): - - # It's possible to organize the role templates and their dependent - # files into directories, in which case the template_name will carry - # the directory information. If that's the case, first create the - # directory structure (if it hasn't already been created by another - # file in the templates list). - template_dir = os.path.dirname(template_name) - output_template_dir = os.path.join(output_dir, template_dir) - if template_dir and not os.path.exists(output_template_dir): - os.makedirs(output_template_dir) - - filename = os.path.join(output_dir, template_name) - with open(filename, 'w+') as template_file: - template_file.write(template_content) - return output_dir - - -def _process_templates(templates): - """Process templates - - Due to bug in heat api - https://bugzilla.redhat.com/show_bug.cgi?id=1212740, we need to - save the templates in tmpdir, reprocess them with template_utils - from heatclient and then we can use them in creating/updating stack. - - This should be replaced by the same code that is in tripleo-common and - eventually it will not be needed at all. - """ - - tpl_dir = _save_templates(templates) - - tpl_files, template = template_utils.get_template_contents( - template_file=os.path.join(tpl_dir, tuskar.MASTER_TEMPLATE_NAME)) - env_files, env = ( - template_utils.process_multiple_environments_and_files( - env_paths=[os.path.join(tpl_dir, tuskar.ENVIRONMENT_NAME)])) - - files = dict(list(tpl_files.items()) + list(env_files.items())) - - return template, env, files - - -class Stack(base.APIResourceWrapper): - _attrs = ('id', 'stack_name', 'outputs', 'stack_status', 'parameters') - - def __init__(self, apiresource, request=None): - super(Stack, self).__init__(apiresource) - self._request = request - - @classmethod - def create(cls, request, stack_name, templates): - template, environment, files = _process_templates(templates) - - fields = { - 'stack_name': stack_name, - 'template': template, - 'environment': environment, - 'files': files, - 'timeout_mins': 240, - } - password = getattr(settings, 'UNDERCLOUD_ADMIN_PASSWORD', None) - stack = heat.stack_create(request, password, **fields) - return cls(stack, request=request) - - def update(self, request, stack_name, templates): - template, environment, files = _process_templates(templates) - - fields = { - 'stack_name': stack_name, - 'template': template, - 'environment': environment, - 'files': files, - } - password = getattr(settings, 'UNDERCLOUD_ADMIN_PASSWORD', None) - heat.stack_update(request, self.id, password, **fields) - - @classmethod - @handle_errors(_("Unable to retrieve heat stacks"), []) - def list(cls, request): - """Return a list of stacks in Heat - - :param request: request object - :type request: django.http.HttpRequest - - :return: list of Heat stacks, or an empty list if there - are none - :rtype: list of tuskar_ui.api.heat.Stack - """ - stacks, has_more_data, has_prev_data = heat.stacks_list(request) - return [cls(stack, request=request) for stack in stacks] - - @classmethod - @handle_errors(_("Unable to retrieve stack")) - def get(cls, request, stack_id): - """Return the Heat Stack associated with this Overcloud - - :return: Heat Stack associated with the stack_id; or None - if no Stack is associated, or no Stack can be - found - :rtype: tuskar_ui.api.heat.Stack or None - """ - return cls(heat.stack_get(request, stack_id), request=request) - - @classmethod - @handle_errors(_("Unable to retrieve stack")) - def get_by_plan(cls, request, plan): - """Return the Heat Stack associated with a Plan - - :return: Heat Stack associated with the plan; or None - if no Stack is associated, or no Stack can be - found - :rtype: tuskar_ui.api.heat.Stack or None - """ - # TODO(lsmola) until we have working deployment through Tuskar-API, - # this will not work - # for stack in Stack.list(request): - # if stack.plan and (stack.plan.id == plan.id): - # return stack - try: - stack = Stack.list(request)[0] - except IndexError: - return None - # TODO(lsmola) stack list actually does not contain all the detail - # info, there should be call for that, investigate - return Stack.get(request, stack.id) - - @classmethod - @handle_errors(_("Unable to delete Heat stack"), []) - def delete(cls, request, stack_id): - heat.stack_delete(request, stack_id) - - @memoized.memoized - def resources(self, with_joins=True, role=None): - """Return list of OS::Nova::Server Resources - - Return list of OS::Nova::Server Resources associated with the Stack - and which are associated with a Role - - :param with_joins: should we also retrieve objects associated with each - retrieved Resource? - :type with_joins: bool - - :return: list of all Resources or an empty list if there are none - :rtype: list of tuskar_ui.api.heat.Resource - """ - - if role: - roles = [role] - else: - roles = self.plan.role_list - resource_dicts = [] - - # A provider resource is deployed as a nested stack, so we have to - # drill down and retrieve those that match a tuskar role - for role in roles: - resource_group_name = role.name - try: - resource_group = heat.resource_get(self._request, - self.id, - resource_group_name) - - group_resources = heat.resources_list( - self._request, resource_group.physical_resource_id) - for group_resource in group_resources: - if not group_resource.physical_resource_id: - # Skip groups who has no physical resource. - continue - nova_resources = heat.resources_list( - self._request, - group_resource.physical_resource_id) - resource_dicts.extend([{"resource": resource, - "role": role} - for resource in nova_resources]) - - except HTTPNotFound: - pass - - if not with_joins: - return [Resource(rd['resource'], request=self._request, - stack=self, role=rd['role']) - for rd in resource_dicts] - - nodes_dict = utils.list_to_dict(node.Node.list(self._request, - associated=True), - key_attribute='instance_uuid') - joined_resources = [] - for rd in resource_dicts: - resource = rd['resource'] - joined_resources.append( - Resource(resource, - node=nodes_dict.get(resource.physical_resource_id, - None), - request=self._request, stack=self, role=rd['role'])) - # TODO(lsmola) I want just resources with nova instance - # this could be probably filtered a better way, investigate - return [r for r in joined_resources if r.node is not None] - - @memoized.memoized - def resources_count(self, overcloud_role=None): - """Return count of associated Resources - - :param overcloud_role: role of resources to be counted; None means all - :type overcloud_role: tuskar_ui.api.tuskar.Role - - :return: Number of matching resources - :rtype: int - """ - # TODO(dtantsur): there should be better way to do it, rather than - # fetching and calling len() - # FIXME(dtantsur): should also be able to use with_joins=False - # but unable due to bug #1289505 - if overcloud_role is None: - resources = self.resources() - else: - resources = self.resources(role=overcloud_role) - return len(resources) - - @cached_property - def plan(self): - """return associated Plan if a plan_id exists within stack parameters. - - :return: associated Plan if plan_id exists and a matching plan - exists as well; None otherwise - :rtype: tuskar_ui.api.tuskar.Plan - """ - # TODO(lsmola) replace this by actual reference, I am pretty sure - # the relation won't be stored in parameters, that would mean putting - # that into template, which doesn't make sense - # if 'plan_id' in self.parameters: - # return tuskar.Plan.get(self._request, - # self.parameters['plan_id']) - try: - plan = tuskar.Plan.list(self._request)[0] - except IndexError: - return None - return plan - - @cached_property - def is_initialized(self): - """Check if this Stack is successfully initialized. - - :return: True if this Stack is successfully initialized, False - otherwise - :rtype: bool - """ - return len(self.dashboard_urls) > 0 - - @cached_property - def is_deployed(self): - """Check if this Stack is successfully deployed. - - :return: True if this Stack is successfully deployed, False otherwise - :rtype: bool - """ - return self.stack_status in ('CREATE_COMPLETE', - 'UPDATE_COMPLETE') - - @cached_property - def is_deploying(self): - """Check if this Stack is currently deploying. - - :return: True if deployment is in progress, False otherwise. - :rtype: bool - """ - return self.stack_status in ('CREATE_IN_PROGRESS',) - - @cached_property - def is_updating(self): - """Check if this Stack is currently updating. - - :return: True if updating is in progress, False otherwise. - :rtype: bool - """ - return self.stack_status in ('UPDATE_IN_PROGRESS',) - - @cached_property - def is_failed(self): - """Check if this Stack failed to update or deploy. - - :return: True if deployment there was an error, False otherwise. - :rtype: bool - """ - return self.stack_status in ('CREATE_FAILED', - 'UPDATE_FAILED',) - - @cached_property - def is_deleting(self): - """Check if this Stack is deleting. - - :return: True if Stack is deleting, False otherwise. - :rtype: bool - """ - return self.stack_status in ('DELETE_IN_PROGRESS', ) - - @cached_property - def is_delete_failed(self): - """Check if Stack deleting has failed. - - :return: True if Stack deleting has failed, False otherwise. - :rtype: bool - """ - return self.stack_status in ('DELETE_FAILED', ) - - @cached_property - def events(self): - """Return the Heat Events associated with this Stack - - :return: list of Heat Events associated with this Stack; - or an empty list if there is no Stack associated with - this Stack, or there are no Events - :rtype: list of heatclient.v1.events.Event - """ - return heat.events_list(self._request, - self.stack_name) - - @property - def stack_outputs(self): - return getattr(self, 'outputs', []) - - @cached_property - def keystone_auth_url(self): - for output in self.stack_outputs: - if output['output_key'] == 'KeystoneURL': - return output['output_value'] - - @cached_property - def keystone_ip(self): - if self.keystone_auth_url: - return urlparse.urlparse(self.keystone_auth_url).hostname - - @cached_property - def overcloud_keystone(self): - try: - return overcloud_keystoneclient( - self._request, - self.keystone_auth_url, - self.plan.parameter_value('Controller-1::AdminPassword')) - except Exception: - LOG.debug('Unable to connect to overcloud keystone.') - return None - - @cached_property - def dashboard_urls(self): - client = self.overcloud_keystone - if not client: - return [] - - try: - services = client.services.list() - for service in services: - if service.name == 'horizon': - break - else: - return [] - except Exception: - return [] - - admin_urls = [endpoint.adminurl for endpoint - in client.endpoints.list() - if endpoint.service_id == service.id] - - return admin_urls - - -class Resource(base.APIResourceWrapper): - _attrs = ('resource_name', 'resource_type', 'resource_status', - 'physical_resource_id') - - def __init__(self, apiresource, request=None, **kwargs): - """Initialize a resource - - :param apiresource: apiresource we want to wrap - :type apiresource: heatclient.v1.resources.Resource - - :param request: request - :type request: django.core.handlers.wsgi.WSGIRequest - - :param node: node relation we want to cache - :type node: tuskar_ui.api.node.Node - - :return: Resource object - :rtype: Resource - """ - super(Resource, self).__init__(apiresource) - self._request = request - if 'node' in kwargs: - self._node = kwargs['node'] - if 'stack' in kwargs: - self._stack = kwargs['stack'] - if 'role' in kwargs: - self._role = kwargs['role'] - - @classmethod - @memoized.memoized - def _resources_by_nodes(cls, request): - return {resource.physical_resource_id: resource - for resource in cls.list_all_resources(request)} - - @classmethod - def get_by_node(cls, request, node): - """Return the specified Heat Resource given a Node - - :param request: request object - :type request: django.http.HttpRequest - - :param node: node to match - :type node: tuskar_ui.api.node.Node - - :return: matching Resource, or raises LookupError if no - resource matches the node - :rtype: tuskar_ui.api.heat.Resource - """ - return cls._resources_by_nodes(request)[node.instance_uuid] - - @classmethod - def list_all_resources(cls, request): - """Iterate through all the stacks and return all relevant resources - - :param request: request object - :type request: django.http.HttpRequest - - :return: list of resources - :rtype: list of tuskar_ui.api.heat.Resource - """ - all_resources = [] - for stack in Stack.list(request): - all_resources.extend(stack.resources(with_joins=False)) - return all_resources - - @cached_property - def role(self): - """Return the Role associated with this Resource - - :return: Role associated with this Resource, or None if no - Role is associated - :rtype: tuskar_ui.api.tuskar.Role - """ - if hasattr(self, '_role'): - return self._role - - @cached_property - def node(self): - """Return the Ironic Node associated with this Resource - - :return: Ironic Node associated with this Resource, or None if no - Node is associated - :rtype: tuskar_ui.api.node.Node - - :raises: ironicclient.exc.HTTPNotFound if there is no Node with the - matching instance UUID - """ - if hasattr(self, '_node'): - return self._node - if self.physical_resource_id: - return node.Node.get_by_instance_uuid(self._request, - self.physical_resource_id) - return None - - @cached_property - def stack(self): - """Return the Stack associated with this Resource - - :return: Stack associated with this Resource, or None if no - Stack is associated - :rtype: tuskar_ui.api.heat.Stack - """ - if hasattr(self, '_stack'): - return self._stack diff --git a/tuskar_ui/api/node.py b/tuskar_ui/api/node.py deleted file mode 100644 index e9fef71b3..000000000 --- a/tuskar_ui/api/node.py +++ /dev/null @@ -1,445 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -import logging -import time - -from django.conf import settings -from django.utils.translation import ugettext_lazy as _ -from horizon.utils import memoized -from ironic_inspector_client import client as inspector_client -from ironicclient import client as ironic_client -from openstack_dashboard.api import base -from openstack_dashboard.api import glance -from openstack_dashboard.api import nova - -from tuskar_ui.cached_property import cached_property # noqa -from tuskar_ui.handle_errors import handle_errors # noqa -from tuskar_ui.utils import utils - - -# power states -ERROR_STATES = set(['deploy failed', 'error']) -POWER_ON_STATES = set(['on', 'power on']) - -# provision_states of ironic aggregated to reasonable groups -PROVISION_STATE_FREE = ['available', 'deleted', None] -PROVISION_STATE_PROVISIONED = ['active'] -PROVISION_STATE_PROVISIONING = [ - 'deploying', 'wait call-back', 'rebuild', 'deploy complete'] -PROVISION_STATE_DELETING = ['deleting'] -PROVISION_STATE_ERROR = ['error', 'deploy failed'] - -# names for states of ironic used in UI, -# provison_states + discovery states -DISCOVERING_STATE = 'discovering' -DISCOVERED_STATE = 'discovered' -DISCOVERY_FAILED_STATE = 'discovery failed' -MAINTENANCE_STATE = 'manageable' -PROVISIONED_STATE = 'provisioned' -PROVISIONING_FAILED_STATE = 'provisioning failed' -PROVISIONING_STATE = 'provisioning' -DELETING_STATE = 'deleting' -FREE_STATE = 'free' - - -IRONIC_DISCOVERD_URL = getattr(settings, 'IRONIC_DISCOVERD_URL', None) -LOG = logging.getLogger(__name__) - - -@memoized.memoized -def ironicclient(request): - api_version = 1 - kwargs = {'os_auth_token': request.user.token.id, - 'ironic_url': base.url_for(request, 'baremetal')} - return ironic_client.get_client(api_version, **kwargs) - - -# FIXME(lsmola) This should be done in Horizon, they don't have caching -@memoized.memoized -@handle_errors(_("Unable to retrieve image.")) -def image_get(request, image_id): - """Returns an Image object with metadata - - Returns an Image object populated with metadata for image - with supplied identifier. - - :param image_id: list of objects to be put into a dict - :type image_id: list - - :return: object - :rtype: glanceclient.v1.images.Image - """ - image = glance.image_get(request, image_id) - return image - - -class Node(base.APIResourceWrapper): - _attrs = ('id', 'uuid', 'instance_uuid', 'driver', 'driver_info', - 'properties', 'power_state', 'target_power_state', - 'provision_state', 'maintenance', 'extra') - - def __init__(self, apiresource, request=None, instance=None): - """Initialize a Node - - :param apiresource: apiresource we want to wrap - :type apiresource: IronicNode - - :param request: request - :type request: django.core.handlers.wsgi.WSGIRequest - - :param instance: instance relation we want to cache - :type instance: openstack_dashboard.api.nova.Server - - :return: Node object - :rtype: tusar_ui.api.node.Node - """ - super(Node, self).__init__(apiresource) - self._request = request - self._instance = instance - - @classmethod - def create(cls, request, ipmi_address=None, cpu_arch=None, cpus=None, - memory_mb=None, local_gb=None, mac_addresses=[], - ipmi_username=None, ipmi_password=None, ssh_address=None, - ssh_username=None, ssh_key_contents=None, - deployment_kernel=None, deployment_ramdisk=None, - driver=None): - """Create a Node in Ironic.""" - if driver == 'pxe_ssh': - driver_info = { - 'ssh_address': ssh_address, - 'ssh_username': ssh_username, - 'ssh_key_contents': ssh_key_contents, - 'ssh_virt_type': 'virsh', - } - else: - driver_info = { - 'ipmi_address': ipmi_address, - 'ipmi_username': ipmi_username, - 'ipmi_password': ipmi_password - } - driver_info.update( - deploy_kernel=deployment_kernel, - deploy_ramdisk=deployment_ramdisk - ) - - properties = {'capabilities': 'boot_option:local', } - if cpus: - properties.update(cpus=cpus) - if memory_mb: - properties.update(memory_mb=memory_mb) - if local_gb: - properties.update(local_gb=local_gb) - if cpu_arch: - properties.update(cpu_arch=cpu_arch) - - node = ironicclient(request).node.create( - driver=driver, - driver_info=driver_info, - properties=properties, - ) - for mac_address in mac_addresses: - ironicclient(request).port.create( - node_uuid=node.uuid, - address=mac_address - ) - - return cls(node, request) - - @classmethod - @memoized.memoized - @handle_errors(_("Unable to retrieve node")) - def get(cls, request, uuid): - """Return the Node that matches the ID - - :param request: request object - :type request: django.http.HttpRequest - - :param uuid: ID of Node to be retrieved - :type uuid: str - - :return: matching Node, or None if no IronicNode matches the ID - :rtype: tuskar_ui.api.node.Node - """ - node = ironicclient(request).node.get(uuid) - if node.instance_uuid is not None: - server = nova.server_get(request, node.instance_uuid) - else: - server = None - return cls(node, request, server) - - @classmethod - @handle_errors(_("Unable to retrieve node")) - def get_by_instance_uuid(cls, request, instance_uuid): - """Return the Node associated with the instance ID - - :param request: request object - :type request: django.http.HttpRequest - - :param instance_uuid: ID of Instance that is deployed on the Node - to be retrieved - :type instance_uuid: str - - :return: matching Node - :rtype: tuskar_ui.api.node.Node - - :raises: ironicclient.exc.HTTPNotFound if there is no Node with - the matching instance UUID - """ - node = ironicclient(request).node.get_by_instance_uuid(instance_uuid) - server = nova.server_get(request, instance_uuid) - return cls(node, request, server) - - @classmethod - @memoized.memoized - @handle_errors(_("Unable to retrieve nodes"), []) - def list(cls, request, associated=None, maintenance=None): - """Return a list of Nodes - - :param request: request object - :type request: django.http.HttpRequest - - :param associated: should we also retrieve all Nodes, only those - associated with an Instance, or only those not - associated with an Instance? - :type associated: bool - - :param maintenance: should we also retrieve all Nodes, only those - in maintenance mode, or those which are not in - maintenance mode? - :type maintenance: bool - - :return: list of Nodes, or an empty list if there are none - :rtype: list of tuskar_ui.api.node.Node - """ - nodes = ironicclient(request).node.list(associated=associated, - maintenance=maintenance) - if associated is None or associated: - servers = nova.server_list(request)[0] - servers_dict = utils.list_to_dict(servers) - nodes_with_instance = [] - for n in nodes: - server = servers_dict.get(n.instance_uuid, None) - nodes_with_instance.append(cls(n, instance=server, - request=request)) - return [cls.get(request, node.uuid) - for node in nodes_with_instance] - return [cls.get(request, node.uuid) for node in nodes] - - @classmethod - def delete(cls, request, uuid): - """Delete an Node - - Remove the IronicNode matching the ID if it - exists; otherwise, does nothing. - - :param request: request object - :type request: django.http.HttpRequest - - :param uuid: ID of IronicNode to be removed - :type uuid: str - """ - return ironicclient(request).node.delete(uuid) - - @classmethod - def discover(cls, request, uuids): - """Set the maintenance status of node - - :param request: request object - :type request: django.http.HttpRequest - - :param uuids: IDs of IronicNodes - :type uuids: list of str - """ - if not IRONIC_DISCOVERD_URL: - return - for uuid in uuids: - - inspector_client.introspect( - uuid, - base_url=IRONIC_DISCOVERD_URL, - auth_token=request.user.token.id) - - # NOTE(dtantsur): PXE firmware on virtual machines misbehaves when - # a lot of nodes start DHCPing simultaneously: it ignores NACK from - # DHCP server, tries to get the same address, then times out. Work - # around it by using sleep, anyway introspection takes much longer. - time.sleep(5) - - @classmethod - def set_maintenance(cls, request, uuid, maintenance): - """Set the maintenance status of node - - :param request: request object - :type request: django.http.HttpRequest - - :param uuid: ID of Node to be removed - :type uuid: str - - :param maintenance: desired maintenance state - :type maintenance: bool - """ - patch = { - 'op': 'replace', - 'value': 'True' if maintenance else 'False', - 'path': '/maintenance' - } - node = ironicclient(request).node.update(uuid, [patch]) - return cls(node, request) - - @classmethod - def set_power_state(cls, request, uuid, power_state): - """Set the power_state of node - - :param request: request object - :type request: django.http.HttpRequest - - :param uuid: ID of Node - :type uuid: str - - :param power_state: desired power_state - :type power_state: str - """ - node = ironicclient(request).node.set_power_state(uuid, power_state) - return cls(node, request) - - @classmethod - @memoized.memoized - def list_ports(cls, request, uuid): - """Return a list of ports associated with this Node - - :param request: request object - :type request: django.http.HttpRequest - - :param uuid: ID of IronicNode - :type uuid: str - """ - return ironicclient(request).node.list_ports(uuid) - - @cached_property - def addresses(self): - """Return a list of port addresses associated with this IronicNode - - :return: list of port addresses associated with this IronicNode, or - an empty list if no addresses are associated with - this IronicNode - :rtype: list of str - """ - ports = self.list_ports(self._request, self.uuid) - return [port.address for port in ports] - - @cached_property - def cpus(self): - return self.properties.get('cpus', None) - - @cached_property - def memory_mb(self): - return self.properties.get('memory_mb', None) - - @cached_property - def local_gb(self): - return self.properties.get('local_gb', None) - - @cached_property - def cpu_arch(self): - return self.properties.get('cpu_arch', None) - - @cached_property - def state(self): - if self.maintenance: - if not IRONIC_DISCOVERD_URL: - return MAINTENANCE_STATE - try: - status = inspector_client.get_status( - uuid=self.uuid, - base_url=IRONIC_DISCOVERD_URL, - auth_token=self._request.user.token.id, - ) - except inspector_client.ClientError as e: - if getattr(e.response, 'status_code', None) == 404: - return MAINTENANCE_STATE - raise - if status['error']: - return DISCOVERY_FAILED_STATE - elif status['finished']: - return DISCOVERED_STATE - else: - return DISCOVERING_STATE - else: - if self.provision_state in PROVISION_STATE_FREE: - return FREE_STATE - if self.provision_state in PROVISION_STATE_PROVISIONING: - return PROVISIONING_STATE - if self.provision_state in PROVISION_STATE_PROVISIONED: - return PROVISIONED_STATE - if self.provision_state in PROVISION_STATE_DELETING: - return DELETING_STATE - if self.provision_state in PROVISION_STATE_ERROR: - return PROVISIONING_FAILED_STATE - # Unknown state - return None - - @cached_property - def instance(self): - """Return the Nova Instance associated with this Node - - :return: Nova Instance associated with this Node; or - None if there is no Instance associated with this - Node, or no matching Instance is found - :rtype: Instance - """ - if self._instance is not None: - return self._instance - if self.instance_uuid: - servers, _has_more_data = nova.server_list(self._request) - for server in servers: - if server.id == self.instance_uuid: - return server - - @cached_property - def ip_address(self): - try: - apiresource = self.instace._apiresource - except AttributeError: - LOG.error("Couldn't obtain IP address") - return None - return apiresource.addresses['ctlplane'][0]['addr'] - - @cached_property - def image_name(self): - """Return image name of associated instance - - Returns image name of instance associated with node - - :return: Image name of instance - :rtype: string - """ - if self.instance is None: - return - image = image_get(self._request, self.instance.image['id']) - return image.name - - @cached_property - def instance_status(self): - return getattr(getattr(self, 'instance', None), 'status', None) - - @cached_property - def provisioning_status(self): - if self.instance_uuid: - return _("Provisioned") - return _("Free") - - @classmethod - def get_all_mac_addresses(cls, request): - macs = [node.addresses for node in cls.list(request)] - return set([mac.upper() for sublist in macs for mac in sublist]) diff --git a/tuskar_ui/api/tuskar.py b/tuskar_ui/api/tuskar.py deleted file mode 100644 index 892445919..000000000 --- a/tuskar_ui/api/tuskar.py +++ /dev/null @@ -1,558 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -import logging -import random -import string - -from django.conf import settings -from django.utils.translation import ugettext_lazy as _ -from glanceclient import exc as glance_exceptions -from horizon.utils import memoized -from openstack_dashboard.api import base -from openstack_dashboard.api import glance -from openstack_dashboard.api import neutron -from os_cloud_config import keystone_pki -from tuskarclient import client as tuskar_client - -from tuskar_ui.api import flavor -from tuskar_ui.cached_property import cached_property # noqa -from tuskar_ui.handle_errors import handle_errors # noqa - -LOG = logging.getLogger(__name__) -MASTER_TEMPLATE_NAME = 'plan.yaml' -ENVIRONMENT_NAME = 'environment.yaml' -TUSKAR_SERVICE = 'management' - -SSL_HIDDEN_PARAMS = ('SSLCertificate', 'SSLKey') -KEYSTONE_CERTIFICATE_PARAMS = ( - 'KeystoneSigningCertificate', 'KeystoneCACertificate', - 'KeystoneSigningKey') - - -@memoized.memoized -def tuskarclient(request, password=None): - api_version = "2" - insecure = getattr(settings, 'OPENSTACK_SSL_NO_VERIFY', False) - ca_file = getattr(settings, 'OPENSTACK_SSL_CACERT', None) - endpoint = base.url_for(request, TUSKAR_SERVICE) - - LOG.debug('tuskarclient connection created using token "%s" and url "%s"' % - (request.user.token.id, endpoint)) - - client = tuskar_client.get_client(api_version, - tuskar_url=endpoint, - insecure=insecure, - ca_file=ca_file, - username=request.user.username, - password=password, - os_auth_token=request.user.token.id) - return client - - -def password_generator(size=40, chars=(string.ascii_uppercase + - string.ascii_lowercase + - string.digits)): - return ''.join(random.choice(chars) for _ in range(size)) - - -def strip_prefix(parameter_name): - return parameter_name.split('::', 1)[-1] - - -def _is_blank(parameter): - return not parameter['value'] or parameter['value'] == 'unset' - - -def _should_generate_password(parameter): - # TODO(lsmola) Filter out SSL params for now. Once it will be generated - # in TripleO add it here too. Note: this will also affect how endpoints are - # created - key = parameter['name'] - return all([ - parameter['hidden'], - _is_blank(parameter), - strip_prefix(key) not in SSL_HIDDEN_PARAMS, - strip_prefix(key) not in KEYSTONE_CERTIFICATE_PARAMS, - key != 'SnmpdReadonlyUserPassword', - ]) - - -def _should_generate_keystone_cert(parameter): - return all([ - strip_prefix(parameter['name']) in KEYSTONE_CERTIFICATE_PARAMS, - _is_blank(parameter), - ]) - - -def _should_generate_neutron_control_plane(parameter): - return all([ - strip_prefix(parameter['name']) == 'NeutronControlPlaneID', - _is_blank(parameter), - ]) - - -class Plan(base.APIResourceWrapper): - _attrs = ('uuid', 'name', 'description', 'created_at', 'modified_at', - 'roles', 'parameters') - - def __init__(self, apiresource, request=None): - super(Plan, self).__init__(apiresource) - self._request = request - - @classmethod - def create(cls, request, name, description): - """Create a Plan in Tuskar - - :param request: request object - :type request: django.http.HttpRequest - - :param name: plan name - :type name: string - - :param description: plan description - :type description: string - - :return: the created Plan object - :rtype: tuskar_ui.api.tuskar.Plan - """ - plan = tuskarclient(request).plans.create(name=name, - description=description) - return cls(plan, request=request) - - @classmethod - def patch(cls, request, plan_id, parameters): - """Update a Plan in Tuskar - - :param request: request object - :type request: django.http.HttpRequest - - :param plan_id: id of the plan we want to update - :type plan_id: string - - :param parameters: new values for the plan's parameters - :type parameters: dict - - :return: the updated Plan object - :rtype: tuskar_ui.api.tuskar.Plan - """ - parameter_list = [{ - 'name': unicode(name), - 'value': unicode(value), - } for (name, value) in parameters.items()] - plan = tuskarclient(request).plans.patch(plan_id, parameter_list) - return cls(plan, request=request) - - @classmethod - @memoized.memoized - def list(cls, request): - """Return a list of Plans in Tuskar - - :param request: request object - :type request: django.http.HttpRequest - - :return: list of Plans, or an empty list if there are none - :rtype: list of tuskar_ui.api.tuskar.Plan - """ - plans = tuskarclient(request).plans.list() - return [cls(plan, request=request) for plan in plans] - - @classmethod - @handle_errors(_("Unable to retrieve plan")) - def get(cls, request, plan_id): - """Return the Plan that matches the ID - - :param request: request object - :type request: django.http.HttpRequest - - :param plan_id: id of Plan to be retrieved - :type plan_id: int - - :return: matching Plan, or None if no Plan matches - the ID - :rtype: tuskar_ui.api.tuskar.Plan - """ - plan = tuskarclient(request).plans.get(plan_uuid=plan_id) - return cls(plan, request=request) - - # TODO(lsmola) before will will support multiple overclouds, we - # can work only with overcloud that is named overcloud. Delete - # this once we have more overclouds. Till then, this is the overcloud - # that rules them all. - # This is how API supports it now, so we have to have it this way. - # Also till Overcloud workflow is done properly, we have to work - # with situations that overcloud is deleted, but stack is still - # there. So overcloud will pretend to exist when stack exist. - @classmethod - def get_the_plan(cls, request): - plan_list = cls.list(request) - for plan in plan_list: - return plan - # if plan doesn't exist, create it - plan = cls.create(request, 'overcloud', 'overcloud') - return plan - - @classmethod - def delete(cls, request, plan_id): - """Delete a Plan - - :param request: request object - :type request: django.http.HttpRequest - - :param plan_id: plan id - :type plan_id: int - """ - tuskarclient(request).plans.delete(plan_uuid=plan_id) - - @cached_property - def role_list(self): - return [Role.get(self._request, role.uuid) - for role in self.roles] - - @cached_property - def _roles_by_name(self): - return dict((role.name, role) for role in self.role_list) - - def get_role_by_name(self, role_name): - """Get the role with the given name.""" - return self._roles_by_name[role_name] - - def get_role_node_count(self, role): - """Get the node count for the given role.""" - return int(self.parameter_value(role.node_count_parameter_name, - 0) or 0) - - @cached_property - def templates(self): - return tuskarclient(self._request).plans.templates(self.uuid) - - @cached_property - def master_template(self): - return self.templates.get(MASTER_TEMPLATE_NAME, '') - - @cached_property - def environment(self): - return self.templates.get(ENVIRONMENT_NAME, '') - - @cached_property - def provider_resource_templates(self): - template_dict = dict(self.templates) - del template_dict[MASTER_TEMPLATE_NAME] - del template_dict[ENVIRONMENT_NAME] - return template_dict - - def parameter_list(self, include_key_parameters=True): - params = self.parameters - if not include_key_parameters: - key_params = [] - for role in self.role_list: - key_params.extend([role.node_count_parameter_name, - role.image_parameter_name, - role.flavor_parameter_name]) - params = [p for p in params if p['name'] not in key_params] - return [Parameter(p, plan=self) for p in params] - - def parameter(self, param_name): - for parameter in self.parameters: - if parameter['name'] == param_name: - return Parameter(parameter, plan=self) - - def parameter_value(self, param_name, default=None): - parameter = self.parameter(param_name) - if parameter is not None: - return parameter.value - return default - - def list_generated_parameters(self, with_prefix=True): - if with_prefix: - key_format = lambda key: key - else: - key_format = strip_prefix - - # Get all password like parameters - return dict( - (key_format(parameter['name']), parameter) - for parameter in self.parameter_list() - if any([ - _should_generate_password(parameter), - _should_generate_keystone_cert(parameter), - _should_generate_neutron_control_plane(parameter), - ]) - ) - - def _make_keystone_certificates(self, wanted_generated_params): - generated_params = {} - for cert_param in KEYSTONE_CERTIFICATE_PARAMS: - if cert_param in wanted_generated_params.keys(): - # If one of the keystone certificates is not set, we have - # to generate all of them. - generate_certificates = True - break - else: - generate_certificates = False - - # Generate keystone certificates - if generate_certificates: - ca_key_pem, ca_cert_pem = keystone_pki.create_ca_pair() - signing_key_pem, signing_cert_pem = ( - keystone_pki.create_signing_pair(ca_key_pem, ca_cert_pem)) - generated_params['KeystoneSigningCertificate'] = ( - signing_cert_pem) - generated_params['KeystoneCACertificate'] = ca_cert_pem - generated_params['KeystoneSigningKey'] = signing_key_pem - return generated_params - - def make_generated_parameters(self): - wanted_generated_params = self.list_generated_parameters( - with_prefix=False) - - # Generate keystone certificates - generated_params = self._make_keystone_certificates( - wanted_generated_params) - - # Generate passwords and control plane id - for (key, param) in wanted_generated_params.items(): - if _should_generate_password(param): - generated_params[key] = password_generator() - elif _should_generate_neutron_control_plane(param): - generated_params[key] = neutron.network_list( - self._request, name='ctlplane')[0].id - - # Fill all the Tuskar parameters with generated content. There are - # parameters that has just different prefix, such parameters should - # have the same values. - wanted_prefixed_params = self.list_generated_parameters( - with_prefix=True) - tuskar_params = {} - - for (key, param) in wanted_prefixed_params.items(): - tuskar_params[key] = generated_params[strip_prefix(key)] - - return tuskar_params - - @property - def id(self): - return self.uuid - - -class Role(base.APIResourceWrapper): - _attrs = ('uuid', 'name', 'version', 'description', 'created') - - def __init__(self, apiresource, request=None): - super(Role, self).__init__(apiresource) - self._request = request - - @classmethod - @memoized.memoized - @handle_errors(_("Unable to retrieve overcloud roles"), []) - def list(cls, request): - """Return a list of Overcloud Roles in Tuskar - - :param request: request object - :type request: django.http.HttpRequest - - :return: list of Overcloud Roles, or an empty list if there - are none - :rtype: list of tuskar_ui.api.tuskar.Role - """ - roles = tuskarclient(request).roles.list() - return [cls(role, request=request) for role in roles] - - @classmethod - @memoized.memoized - @handle_errors(_("Unable to retrieve overcloud role")) - def get(cls, request, role_id): - """Return the Tuskar Role that matches the ID - - :param request: request object - :type request: django.http.HttpRequest - - :param role_id: ID of Role to be retrieved - :type role_id: int - - :return: matching Role, or None if no matching - Role can be found - :rtype: tuskar_ui.api.tuskar.Role - """ - for role in Role.list(request): - if role.uuid == role_id: - return role - - @classmethod - @memoized.memoized - def _roles_by_image(cls, request, plan): - roles_by_image = {} - - for role in Role.list(request): - image = plan.parameter_value(role.image_parameter_name) - if image in roles_by_image: - roles_by_image[image].append(role) - else: - roles_by_image[image] = [role] - - return roles_by_image - - @classmethod - @handle_errors(_("Unable to retrieve overcloud role")) - def get_by_image(cls, request, plan, image): - """Return the Role whose ImageID parameter matches the image. - - :param request: request object - :type request: django.http.HttpRequest - - :param plan: associated plan to check against - :type plan: Plan - - :param image: image to be matched - :type image: Image - - :return: matching Role, or None if no matching - Role can be found - :rtype: tuskar_ui.api.tuskar.Role - """ - roles = cls._roles_by_image(request, plan) - try: - return roles[image.name] - except KeyError: - return [] - - @classmethod - @memoized.memoized - def _roles_by_resource_type(cls, request): - return {role.provider_resource_type: role - for role in Role.list(request)} - - @classmethod - @handle_errors(_("Unable to retrieve overcloud role")) - def get_by_resource_type(cls, request, resource_type): - roles = cls._roles_by_resource_type(request) - try: - return roles[resource_type] - except KeyError: - return None - - @property - def provider_resource_type(self): - return "Tuskar::{0}-{1}".format(self.name, self.version) - - @property - def parameter_prefix(self): - return "{0}-{1}::".format(self.name, self.version) - - @property - def node_count_parameter_name(self): - return self.parameter_prefix + 'count' - - @property - def image_parameter_name(self): - return self.parameter_prefix + 'Image' - - @property - def flavor_parameter_name(self): - return self.parameter_prefix + 'Flavor' - - def image(self, plan): - image_name = plan.parameter_value(self.image_parameter_name) - if image_name: - try: - return glance.image_list_detailed( - self._request, filters={'name': image_name})[0][0] - except (glance_exceptions.HTTPNotFound, IndexError): - LOG.error("Couldn't obtain image with name %s" % image_name) - return None - - def flavor(self, plan): - flavor_name = plan.parameter_value( - self.flavor_parameter_name) - if flavor_name: - return flavor.Flavor.get_by_name(self._request, flavor_name) - - def parameter_list(self, plan): - return [p for p in plan.parameter_list() if self == p.role] - - def is_valid_for_deployment(self, plan): - node_count = plan.get_role_node_count(self) - pending_required_params = list(Parameter.pending_parameters( - Parameter.required_parameters(self.parameter_list(plan)))) - return not ( - self.image(plan) is None or - (node_count and self.flavor(plan) is None) or - pending_required_params - ) - - @property - def id(self): - return self.uuid - - -class Parameter(base.APIDictWrapper): - - _attrs = ['name', 'value', 'default', 'description', 'hidden', 'label', - 'parameter_type', 'constraints'] - - def __init__(self, apidict, plan=None): - super(Parameter, self).__init__(apidict) - self._plan = plan - - @property - def stripped_name(self): - return strip_prefix(self.name) - - @property - def plan(self): - return self._plan - - @property - def role(self): - if self.plan: - for role in self.plan.role_list: - if self.name.startswith(role.parameter_prefix): - return role - - def is_required(self): - """Boolean: True if parameter is required, False otherwise.""" - return self.default is None - - def get_constraint_by_type(self, constraint_type): - """Returns parameter constraint by it's type. - - For available constraint types see HOT Spec: - http://docs.openstack.org/developer/heat/template_guide/hot_spec.html - """ - - constraints_of_type = [c for c in self.constraints - if c['constraint_type'] == constraint_type] - if constraints_of_type: - return constraints_of_type[0] - else: - return None - - @staticmethod - def required_parameters(parameters): - """Yields parameters which are required.""" - for parameter in parameters: - if parameter.is_required(): - yield parameter - - @staticmethod - def pending_parameters(parameters): - """Yields parameters which don't have value set.""" - for parameter in parameters: - if not parameter.value: - yield parameter - - @staticmethod - def global_parameters(parameters): - """Yields parameters with name without role prefix.""" - for parameter in parameters: - if '::' not in parameter.name: - yield parameter diff --git a/tuskar_ui/cached_property.py b/tuskar_ui/cached_property.py deleted file mode 100644 index 7e3704412..000000000 --- a/tuskar_ui/cached_property.py +++ /dev/null @@ -1,63 +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. - -# Copyright (c) Django Software Foundation and individual contributors. -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# 1. Redistributions of source code must retain the above copyright notice, -# this list of conditions and the following disclaimer. -# -# 2. Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# -# 3. Neither the name of Django nor the names of its contributors may be -# used to endorse or promote products derived from this software without -# specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE -# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -# POSSIBILITY OF SUCH DAMAGE. - - -# We would be using django.utils.functional.cached_property, except it -# breaks when used with mox in our tests, because of -# https://code.djangoproject.com/ticket/19872 -# -# So we have a copy of it here, with the bug fixed. -# FIXME: Use django's version when the bug is fixed there. -class cached_property(object): - """Cached property decorator. - - Decorator that creates converts a method with a single self argument - into a property cached on the instance. - """ - - def __init__(self, func): - self.func = func - - def __get__(self, instance, type): - if instance is None: - return self - res = instance.__dict__[self.func.__name__] = self.func(instance) - return res diff --git a/tuskar_ui/exceptions.py b/tuskar_ui/exceptions.py deleted file mode 100644 index 0fd36788f..000000000 --- a/tuskar_ui/exceptions.py +++ /dev/null @@ -1,22 +0,0 @@ -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -from ironicclient import exceptions as ironic_exceptions -from openstack_dashboard import exceptions -from tuskarclient.openstack.common.apiclient import exceptions as tuskarclient - -NOT_FOUND = exceptions.NOT_FOUND -RECOVERABLE = exceptions.RECOVERABLE + ( - ironic_exceptions.Conflict, tuskarclient.ClientException, -) -UNAUTHORIZED = exceptions.UNAUTHORIZED diff --git a/tuskar_ui/forms.py b/tuskar_ui/forms.py deleted file mode 100644 index e5aab2043..000000000 --- a/tuskar_ui/forms.py +++ /dev/null @@ -1,174 +0,0 @@ -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -import re - -from django import forms -from django.utils import html -from django.utils.translation import ugettext_lazy as _ -import netaddr - - -SEPARATOR_RE = re.compile('[\s,;|]+', re.UNICODE) - - -def label_with_tooltip(label, tooltip=None, title=None): - if not tooltip: - return label - return html.format_html( - u'{0} ', - html.escape(label), - html.escape(tooltip), - html.escape(title or label) - ) - - -def fieldset(form, *args, **kwargs): - """A helper function for grouping fields based on their names.""" - - prefix = kwargs.pop('prefix', '.*') - names = args or form.fields.keys() - - for name in names: - if prefix is not None and re.match(prefix, name): - yield forms.forms.BoundField(form, form.fields[name], name) - - -class MACDialect(netaddr.mac_eui48): - """For validating MAC addresses. Same validation as Nova uses.""" - word_fmt = '%.02x' - word_sep = ':' - - -def normalize_MAC(value): - try: - return str(netaddr.EUI( - value.strip(), version=48, dialect=MACDialect)).upper() - except (netaddr.AddrFormatError, TypeError): - raise ValueError('Invalid MAC address') - - -class NumberInput(forms.widgets.TextInput): - """A form input for numbers.""" - input_type = 'number' - - -class NumberPickerInput(forms.widgets.TextInput): - """A form input that is rendered as a big number picker.""" - - def __init__(self, attrs=None): - default_attrs = {'class': 'number-picker'} - if attrs: - default_attrs.update(attrs) - super(NumberPickerInput, self).__init__(default_attrs) - - -class MACField(forms.fields.Field): - """A form field for entering a single MAC address.""" - - def clean(self, value): - value = super(MACField, self).clean(value) - try: - return normalize_MAC(value) - except ValueError: - raise forms.ValidationError(_(u'Enter a valid MAC address.')) - - -class MultiMACField(forms.fields.Field): - """A form field for entering multiple MAC addresses. - - The individual MAC addresses can be separated by any whitespace, - commas, semicolons or pipe characters. - - Gives a string of normalized MAC addresses separated by spaces. - """ - - def clean(self, value): - value = super(MultiMACField, self).clean(value) - - macs = [] - for mac in SEPARATOR_RE.split(value): - if mac: - try: - normalized_mac = normalize_MAC(mac) - except ValueError: - raise forms.ValidationError( - _(u'%r is not a valid MAC address.') % mac) - else: - macs.append(normalized_mac) - - return ' '.join(sorted(set(macs))) - - -class NetworkField(forms.fields.Field): - """A form field for entering a network specification with a mask.""" - - def clean(self, value): - value = super(NetworkField, self).clean(value) - try: - return str(netaddr.IPNetwork(value, version=4)) - except netaddr.AddrFormatError: - raise forms.ValidationError(_("Enter valid IPv4 network address.")) - - -class SelfHandlingFormset(forms.formsets.BaseFormSet): - def __init__(self, *args, **kwargs): - self.request = kwargs.pop('request', None) - super(SelfHandlingFormset, self).__init__(*args, **kwargs) - - def handle(self, request, data): - success = True - for form in self: - form_success = form.handle(request, form.cleaned_data) - if not form_success: - success = False - else: - pass - return success - - -class LabelWidget(forms.Widget): - """A widget for displaying information. - - This is a custom widget to show context information just as text, - as readonly inputs are confusing. - Note that the field also must be required=False, as no input - is rendered, and it must be ignored in the handle() method. - """ - def render(self, name, value, attrs=None): - if value: - return html.escape(value) - return '' - - -class StaticTextWidget(forms.Widget): - def render(self, name, value, attrs=None): - if value is None: - value = '' - return html.format_html('

{0}

', - value) - - -class StaticTextPasswordWidget(forms.Widget): - def render(self, name, value, attrs=None): - if value is None or value == '': - return html.format_html(u'

') - else: - return html.format_html( - u'

' - u' {1}' - u'

', value, _(u"Reveal") - ) diff --git a/tuskar_ui/handle_errors.py b/tuskar_ui/handle_errors.py deleted file mode 100644 index a505f4eb0..000000000 --- a/tuskar_ui/handle_errors.py +++ /dev/null @@ -1,71 +0,0 @@ -# -*- coding: utf8 -*- -# -# 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 functools -import inspect - -import horizon.exceptions - - -def handle_errors(error_message, error_default=None, request_arg=None): - """A decorator for adding default error handling to API calls. - - It wraps the original method in a try-except block, with horizon's - error handling added. - - Note: it should only be used on functions or methods that take request as - their argument (it has to be named "request", or ``request_arg`` has to be - provided, indicating which argument is the request). - - The decorated method accepts a number of additional parameters: - - :param _error_handle: whether to handle the errors in this call - :param _error_message: override the error message - :param _error_default: override the default value returned on error - :param _error_redirect: specify a redirect url for errors - :param _error_ignore: ignore known errors - """ - def decorator(func): - # XXX This is an ugly hack for finding the 'request' argument. - if request_arg is None: - for _request_arg, name in enumerate(inspect.getargspec(func).args): - if name == 'request': - break - else: - raise RuntimeError( - "The handle_errors decorator requires 'request' as " - "an argument of the function or method being decorated") - else: - _request_arg = request_arg - - @functools.wraps(func) - def wrapper(*args, **kwargs): - _error_handle = kwargs.pop('_error_handle', True) - _error_message = kwargs.pop('_error_message', error_message) - _error_default = kwargs.pop('_error_default', error_default) - _error_redirect = kwargs.pop('_error_redirect', None) - _error_ignore = kwargs.pop('_error_ignore', False) - if not _error_handle: - return func(*args, **kwargs) - try: - return func(*args, **kwargs) - except Exception: - request = args[_request_arg] - horizon.exceptions.handle(request, _error_message, - ignore=_error_ignore, - redirect=_error_redirect) - return _error_default - wrapper.wrapped = func - return wrapper - return decorator diff --git a/tuskar_ui/infrastructure/__init__.py b/tuskar_ui/infrastructure/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/tuskar_ui/infrastructure/dashboard.py b/tuskar_ui/infrastructure/dashboard.py deleted file mode 100644 index 39c281456..000000000 --- a/tuskar_ui/infrastructure/dashboard.py +++ /dev/null @@ -1,34 +0,0 @@ -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -from django.utils.translation import ugettext_lazy as _ -import horizon - - -class Infrastructure(horizon.Dashboard): - name = _("Infrastructure") - slug = "infrastructure" - panels = ( - 'overview', - 'parameters', - 'roles', - 'nodes', - 'flavors', - 'images', - 'history', - ) - default_panel = 'overview' - permissions = ('openstack.roles.admin',) - - -horizon.register(Infrastructure) diff --git a/tuskar_ui/infrastructure/flavors/__init__.py b/tuskar_ui/infrastructure/flavors/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/tuskar_ui/infrastructure/flavors/panel.py b/tuskar_ui/infrastructure/flavors/panel.py deleted file mode 100644 index 2129b292e..000000000 --- a/tuskar_ui/infrastructure/flavors/panel.py +++ /dev/null @@ -1,33 +0,0 @@ -# -*- coding: utf8 -*- -# -# 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 tuskar_ui.infrastructure import dashboard -from tuskar_ui.infrastructure.flavors import utils - - -class Flavors(horizon.Panel): - name = _("Flavors") - slug = "flavors" - - def can_access(self, context): - if not utils.matching_deployment_mode(): - return False - - return super(Flavors, self).can_access(context) - - -dashboard.Infrastructure.register(Flavors) diff --git a/tuskar_ui/infrastructure/flavors/tables.py b/tuskar_ui/infrastructure/flavors/tables.py deleted file mode 100644 index 68431a5bb..000000000 --- a/tuskar_ui/infrastructure/flavors/tables.py +++ /dev/null @@ -1,157 +0,0 @@ -# -*- coding: utf8 -*- -# -# 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 django.shortcuts -from django.utils.translation import ugettext_lazy as _ -import horizon.exceptions -import horizon.messages -import horizon.tables -from openstack_dashboard.dashboards.admin.flavors import ( - tables as flavor_tables) - -from tuskar_ui import api -from tuskar_ui.infrastructure.flavors import utils - - -class CreateFlavor(flavor_tables.CreateFlavor): - verbose_name = _(u"New Flavor") - url = "horizon:infrastructure:flavors:create" - - -class CreateSuggestedFlavor(horizon.tables.Action): - name = 'create' - verbose_name = _(u"Create") - verbose_name_plural = _(u"Create Suggested Flavors") - method = 'POST' - icon = 'plus' - - def create_flavor(self, request, node_id): - node = api.node.Node.get(request, node_id) - suggestion = utils.FlavorSuggestion.from_node(node) - return suggestion.create_flavor(request) - - def handle(self, data_table, request, node_ids): - for node_id in node_ids: - try: - self.create_flavor(request, node_id) - except Exception: - horizon.exceptions.handle( - request, - _(u"Unable to create flavor for node %r") % node_id, - ) - return django.shortcuts.redirect(request.get_full_path()) - - -class EditAndCreateSuggestedFlavor(CreateFlavor): - name = 'edit_and_create' - verbose_name = _(u"Edit before creating") - icon = 'pencil' - - -class DeleteFlavor(flavor_tables.DeleteFlavor): - - def __init__(self, **kwargs): - super(DeleteFlavor, self).__init__(**kwargs) - # NOTE(dtantsur): setting class attributes doesn't work - # probably due to metaclass magic in actions - self.data_type_singular = _("Flavor") - self.data_type_plural = _("Flavors") - - def allowed(self, request, datum=None): - """Check that action is allowed on flavor - - This is overridden method from horizon.tables.BaseAction. - - :param datum: flavor we're operating on - :type datum: tuskar_ui.api.Flavor - """ - if datum is not None: - deployed_flavors = api.flavor.Flavor.list_deployed_ids( - request, _error_default=None) - if deployed_flavors is None or datum.id in deployed_flavors: - return False - return super(DeleteFlavor, self).allowed(request, datum) - - -class FlavorsTable(horizon.tables.DataTable): - name = horizon.tables.Column('name', - link="horizon:infrastructure:flavors:details") - arch = horizon.tables.Column('cpu_arch', verbose_name=_('Architecture')) - vcpus = horizon.tables.Column('vcpus', verbose_name=_('CPUs')) - ram = horizon.tables.Column(flavor_tables.get_size, - verbose_name=_('Memory'), - attrs={'data-type': 'size'}) - disk = horizon.tables.Column(flavor_tables.get_disk_size, - verbose_name=_('Disk'), - attrs={'data-type': 'size'}) - - class Meta(object): - name = "flavors" - verbose_name = _("Available") - table_actions = ( - DeleteFlavor, - flavor_tables.FlavorFilterAction, - ) - row_actions = ( - DeleteFlavor, - ) - template = "horizon/common/_enhanced_data_table.html" - - -class FlavorRolesTable(horizon.tables.DataTable): - name = horizon.tables.Column('name', verbose_name=_('Role Name')) - - def __init__(self, request, *args, **kwargs): - # TODO(dtantsur): support multiple overclouds - plan = api.tuskar.Plan.get_the_plan(request) - stack = api.heat.Stack.get_by_plan(request, plan) - - if stack is None: - count = lambda role: _('Not Deployed') - else: - count = stack.resources_count - - self._columns['count'] = horizon.tables.Column( - count, - verbose_name=_("Instances Count") - ) - super(FlavorRolesTable, self).__init__(request, *args, **kwargs) - - class Meta(object): - name = "flavor_roles" - verbose_name = _("Overcloud Roles") - table_actions = () - row_actions = () - hidden_title = False - template = "horizon/common/_enhanced_data_table.html" - - -class FlavorSuggestionsTable(horizon.tables.DataTable): - name = horizon.tables.Column('name',) - arch = horizon.tables.Column('cpu_arch', verbose_name=_('Architecture')) - vcpus = horizon.tables.Column('vcpus', verbose_name=_('CPUs')) - ram = horizon.tables.Column(flavor_tables.get_size, - verbose_name=_('Memory'), - attrs={'data-type': 'size'}) - disk = horizon.tables.Column(flavor_tables.get_disk_size, - verbose_name=_('Disk'), - attrs={'data-type': 'size'}) - - class Meta(object): - name = "suggested_flavors" - verbose_name = _("Suggested") - row_actions = ( - CreateSuggestedFlavor, - EditAndCreateSuggestedFlavor, - ) - template = "horizon/common/_enhanced_data_table.html" diff --git a/tuskar_ui/infrastructure/flavors/templates/flavors/create.html b/tuskar_ui/infrastructure/flavors/templates/flavors/create.html deleted file mode 100644 index 4fedb05a8..000000000 --- a/tuskar_ui/infrastructure/flavors/templates/flavors/create.html +++ /dev/null @@ -1,11 +0,0 @@ -{% extends 'infrastructure/base.html' %} -{% load i18n %} -{% block title %}{% trans "Create Flavor" %}{% endblock %} - -{% block page_header %} - {% include "horizon/common/_page_header.html" with title=_("Create Flavor") %} -{% endblock page_header %} - -{% block main %} - {% include 'horizon/common/_workflow.html' %} -{% endblock %} diff --git a/tuskar_ui/infrastructure/flavors/templates/flavors/details.html b/tuskar_ui/infrastructure/flavors/templates/flavors/details.html deleted file mode 100644 index 41d59603c..000000000 --- a/tuskar_ui/infrastructure/flavors/templates/flavors/details.html +++ /dev/null @@ -1,31 +0,0 @@ -{% extends 'infrastructure/base.html' %} -{% load i18n %} -{% block title %}{% trans 'Flavor: ' %}{{ flavor.name }}{% endblock %} - -{% block page_header %} - {% include 'horizon/common/_page_header.html' with title=_('Flavor: ')|add:flavor.name %} -{% endblock page_header %} - -{% block main %} -
-
-

{% trans "Hardware Info" %}

-
-
{% trans "Architecture" %}
-
{{ flavor.cpu_arch|default:"—" }}
-
{% trans "CPUs" %}
-
{{ flavor.vcpus|default:"—" }}
-
{% trans "Memory" %}
-
{{ flavor.ram_bytes|filesizeformat|default:"—" }}
-
{% trans "Disk" %}
-
{{ flavor.disk_bytes|filesizeformat|default:"—" }}
-
-
-
-
-
- {{ table.render }} -
-
- -{% endblock %} diff --git a/tuskar_ui/infrastructure/flavors/templates/flavors/index.html b/tuskar_ui/infrastructure/flavors/templates/flavors/index.html deleted file mode 100644 index 830efc7b3..000000000 --- a/tuskar_ui/infrastructure/flavors/templates/flavors/index.html +++ /dev/null @@ -1,27 +0,0 @@ -{% extends 'infrastructure/base.html' %} -{% load i18n %} -{% load url from future %} -{% block title %}{% trans 'Flavors' %}{% endblock %} - -{% block page_header %} - {% include 'horizon/common/_items_count_domain_page_header.html' with title=_('Flavors') items_count=flavors_count %} -{% endblock page_header %} - -{% block main %} -{% if suggested_flavors_count %} -
- -
-
- {{ suggested_flavors_table.render }} -
-
-
-{% endif %} - -
- {{ flavors_table.render }} -
-{% endblock %} diff --git a/tuskar_ui/infrastructure/flavors/tests.py b/tuskar_ui/infrastructure/flavors/tests.py deleted file mode 100644 index 470cb4f9f..000000000 --- a/tuskar_ui/infrastructure/flavors/tests.py +++ /dev/null @@ -1,265 +0,0 @@ -# -*- coding: utf8 -*- -# -# 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 contextlib - -from django.core import urlresolvers -from mock import patch, call # noqa -from novaclient import exceptions as nova_exceptions -from novaclient.v2 import servers -from openstack_dashboard.test.test_data import utils - -from tuskar_ui import api -from tuskar_ui.infrastructure.flavors import utils as flavors_utils -from tuskar_ui.test import helpers as test -from tuskar_ui.test.test_data import flavor_data -from tuskar_ui.test.test_data import heat_data -from tuskar_ui.test.test_data import tuskar_data - - -TEST_DATA = utils.TestDataContainer() -flavor_data.data(TEST_DATA) -heat_data.data(TEST_DATA) -tuskar_data.data(TEST_DATA) -INDEX_URL = urlresolvers.reverse( - 'horizon:infrastructure:flavors:index') -CREATE_URL = urlresolvers.reverse( - 'horizon:infrastructure:flavors:create') -DETAILS_VIEW = 'horizon:infrastructure:flavors:details' - - -@contextlib.contextmanager -def _prepare_create(): - flavor = TEST_DATA.novaclient_flavors.first() - all_flavors = TEST_DATA.novaclient_flavors.list() - data = {'name': 'foobar', - 'vcpus': 3, - 'memory_mb': 1024, - 'disk_gb': 40, - 'arch': 'amd64'} - with contextlib.nested( - patch('tuskar_ui.api.flavor.Flavor.create', - return_value=flavor), - # Inherited code calls this directly - patch('openstack_dashboard.api.nova.flavor_list', - return_value=all_flavors), - ) as mocks: - yield mocks[0], data - - -def _raise_nova_client_exception(*args, **kwargs): - raise nova_exceptions.ClientException("Boom!") - - -class FlavorsTest(test.BaseAdminViewTests): - - def test_index(self): - plans = [api.tuskar.Plan(plan, self.request) - for plan in TEST_DATA.tuskarclient_plans.list()] - roles = [api.tuskar.Role(role) - for role in self.tuskarclient_roles.list()] - - with contextlib.nested( - patch('tuskar_ui.api.node.ironicclient'), - patch('tuskar_ui.api.tuskar.Plan.list', - return_value=plans), - patch('tuskar_ui.api.tuskar.Role.list', - return_value=roles), - patch('openstack_dashboard.api.nova.flavor_list', - return_value=TEST_DATA.novaclient_flavors.list()), - patch('openstack_dashboard.api.nova.server_list', - return_value=([], False)), - ) as (ironic_mock, plans_mock, roles_mock, flavors_mock, servers_mock): - res = self.client.get(INDEX_URL) - self.assertEqual(plans_mock.call_count, 1) - self.assertEqual(roles_mock.call_count, 4) - self.assertEqual(flavors_mock.call_count, 3) - self.assertEqual(servers_mock.call_count, 2) - - self.assertTemplateUsed(res, 'infrastructure/flavors/index.html') - - def test_index_recoverable_failure(self): - with patch( - 'openstack_dashboard.api.nova.flavor_list', - side_effect=_raise_nova_client_exception - ) as flavor_list, patch('tuskar_ui.api.node.ironicclient'): - res = self.client.get(INDEX_URL) - self.assertEqual(flavor_list.call_count, 2) - self.assertEqual( - [(m.message, m.tags) for m in res.context['messages']], - [ - (u'Unable to retrieve flavor list.', u'error'), - (u'Unable to retrieve nodes', u'error'), - ], - ) - self.assertMessageCount(response=res, error=2, warning=0) - - def test_create_get(self): - res = self.client.get(CREATE_URL) - self.assertTemplateUsed(res, 'infrastructure/flavors/create.html') - - def test_create_post_ok(self): - with _prepare_create() as (create_mock, data): - res = self.client.post(CREATE_URL, data) - self.assertNoFormErrors(res) - self.assertRedirectsNoFollow(res, INDEX_URL) - request = create_mock.call_args_list[0][0][0] - self.assertListEqual(create_mock.call_args_list, [ - call(request, name=u'foobar', memory=1024, vcpus=3, disk=40, - cpu_arch='amd64') - ]) - - def test_create_post_name_exists(self): - flavor = TEST_DATA.novaclient_flavors.first() - with _prepare_create() as (create_mock, data): - data['name'] = flavor.name - res = self.client.post(CREATE_URL, data) - self.assertFormErrors(res) - - def test_delete_ok(self): - flavors = TEST_DATA.novaclient_flavors.list() - data = {'action': 'flavors__delete', - 'object_ids': [flavors[0].id, flavors[1].id]} - with contextlib.nested( - patch('openstack_dashboard.api.nova.flavor_delete'), - patch('openstack_dashboard.api.nova.server_list', - return_value=([], False)), - patch('tuskar_ui.api.tuskar.Role.list', - return_value=[]), - patch('tuskar_ui.api.tuskar.Plan.list', - return_value=[]), - patch('openstack_dashboard.api.nova.flavor_list', - return_value=TEST_DATA.novaclient_flavors.list()) - ): - res = self.client.post(INDEX_URL, data) - self.assertNoFormErrors(res) - self.assertRedirectsNoFollow(res, INDEX_URL) - - def test_delete_deployed_on_servers(self): - flavors = TEST_DATA.novaclient_flavors.list() - server = servers.Server( - servers.ServerManager(None), - {'id': 'aa', - 'name': 'Compute', - 'image': {'id': 1}, - 'status': 'ACTIVE', - 'flavor': {'id': flavors[0].id}} - ) - data = {'action': 'flavors__delete', - 'object_ids': [flavors[0].id, flavors[1].id]} - with contextlib.nested( - patch('openstack_dashboard.api.nova.flavor_delete'), - patch('openstack_dashboard.api.nova.server_list', - return_value=([server], False)), - patch('tuskar_ui.api.tuskar.Role.list', - return_value=[]), - patch('tuskar_ui.api.tuskar.Plan.list', - return_value=[]), - patch('openstack_dashboard.api.nova.flavor_list', - return_value=TEST_DATA.novaclient_flavors.list()), - patch('tuskar_ui.api.node.Node.list', - return_value=[]) - ): - res = self.client.post(INDEX_URL, data) - self.assertMessageCount(error=1, warning=0) - self.assertNoFormErrors(res) - self.assertRedirectsNoFollow(res, INDEX_URL) - - def test_details_no_overcloud(self): - flavor = api.flavor.Flavor(TEST_DATA.novaclient_flavors.first()) - plan = api.tuskar.Plan(TEST_DATA.tuskarclient_plans.first()) - roles = [api.tuskar.Role(role) - for role in self.tuskarclient_roles.list()] - - with contextlib.nested( - patch('tuskar_ui.api.flavor.Flavor.get', - return_value=flavor), - patch('tuskar_ui.api.tuskar.Plan.get_the_plan', - return_value=plan), - patch('tuskar_ui.api.tuskar.Role.list', return_value=roles), - patch('tuskar_ui.api.tuskar.Role.flavor', return_value=flavor), - ) as (get_mock, plan_mock, roles_mock, role_flavor_mock): - res = self.client.get(urlresolvers.reverse(DETAILS_VIEW, - args=(flavor.id,))) - self.assertEqual(get_mock.call_count, 1) - self.assertEqual(plan_mock.call_count, 2) - self.assertEqual(roles_mock.call_count, 1) - self.assertEqual(role_flavor_mock.call_count, 8) - self.assertTemplateUsed(res, 'infrastructure/flavors/details.html') - - def test_details(self): - flavor = api.flavor.Flavor(TEST_DATA.novaclient_flavors.first()) - plan = api.tuskar.Plan(TEST_DATA.tuskarclient_plans.first()) - roles = [api.tuskar.Role(role) - for role in self.tuskarclient_roles.list()] - stack = api.heat.Stack(TEST_DATA.heatclient_stacks.first()) - - with contextlib.nested( - patch('tuskar_ui.api.flavor.Flavor.get', - return_value=flavor), - patch('tuskar_ui.api.tuskar.Plan.get_the_plan', - return_value=plan), - patch('tuskar_ui.api.tuskar.Role.list', return_value=roles), - patch('tuskar_ui.api.tuskar.Role.flavor', return_value=flavor), - patch('tuskar_ui.api.heat.Stack.get_by_plan', - return_value=stack), - # __name__ is required for horizon.tables - patch('tuskar_ui.api.heat.Stack.resources_count', - return_value=42, __name__='') - ) as (flavor_mock, plan_mock, roles_mock, role_flavor_mock, - stack_mock, count_mock): - res = self.client.get(urlresolvers.reverse(DETAILS_VIEW, - args=(flavor.id,))) - self.assertEqual(flavor_mock.call_count, 1) - self.assertEqual(plan_mock.call_count, 2) - self.assertEqual(roles_mock.call_count, 1) - self.assertEqual(role_flavor_mock.call_count, 8) - self.assertEqual(stack_mock.call_count, 1) - self.assertEqual(count_mock.call_count, 4) - self.assertTemplateUsed(res, 'infrastructure/flavors/details.html') - - -class FlavorsUtilsTest(test.TestCase): - def test_get_unmached_suggestions(self): - flavors = [api.flavor.Flavor(flavor) - for flavor in TEST_DATA.novaclient_flavors.list()] - nodes = [api.node.Node(api.node.Node(node)) - for node in self.ironicclient_nodes.list()] - with ( - patch('tuskar_ui.api.flavor.Flavor.list', return_value=flavors) - ), ( - patch('tuskar_ui.api.node.Node.list', return_value=nodes) - ): - ret = flavors_utils.get_flavor_suggestions(None) - FS = flavors_utils.FlavorSuggestion - self.assertEqual(ret, set([ - FS(vcpus=8, ram_bytes=4294967296, disk_bytes=10737418240, - cpu_arch='x86_64', node_id='aa-11'), - FS(vcpus=16, ram_bytes=4294967296, disk_bytes=107374182400, - cpu_arch='x86_64', node_id='bb-22'), - FS(vcpus=32, ram_bytes=8589934592, disk_bytes=1073741824, - cpu_arch='x86_64', node_id='cc-33'), - FS(vcpus=8, ram_bytes=4294967296, disk_bytes=10737418240, - cpu_arch='x86_64', node_id='cc-44'), - FS(vcpus=8, ram_bytes=4294967296, disk_bytes=10737418240, - cpu_arch='x86_64', node_id='dd-55'), - FS(vcpus=8, ram_bytes=4294967296, disk_bytes=10737418240, - cpu_arch='x86_64', node_id='ff-66'), - FS(vcpus=8, ram_bytes=4294967296, disk_bytes=10737418240, - cpu_arch='x86_64', node_id='gg-77'), - FS(vcpus=8, ram_bytes=4294967296, disk_bytes=10737418240, - cpu_arch='x86_64', node_id='hh-88'), - FS(vcpus=16, ram_bytes=8589934592, disk_bytes=1073741824000, - cpu_arch='x86_64', node_id='ii-99'), - ])) diff --git a/tuskar_ui/infrastructure/flavors/urls.py b/tuskar_ui/infrastructure/flavors/urls.py deleted file mode 100644 index 7f78dc45b..000000000 --- a/tuskar_ui/infrastructure/flavors/urls.py +++ /dev/null @@ -1,28 +0,0 @@ -# -*- coding: utf8 -*- -# -# 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 urls - -from tuskar_ui.infrastructure.flavors import views - - -urlpatterns = urls.patterns( - 'tuskar_ui.infrastructure.flavors.views', - urls.url(r'^$', views.IndexView.as_view(), name='index'), - urls.url(r'^create/(?P[^/]+)$', views.CreateView.as_view(), - name='create'), - urls.url(r'^create/$', views.CreateView.as_view(), name='create'), - urls.url(r'^(?P[^/]+)/$', views.DetailView.as_view(), - name='details'), -) diff --git a/tuskar_ui/infrastructure/flavors/utils.py b/tuskar_ui/infrastructure/flavors/utils.py deleted file mode 100644 index cf2d63ddd..000000000 --- a/tuskar_ui/infrastructure/flavors/utils.py +++ /dev/null @@ -1,121 +0,0 @@ -# -*- coding: utf8 -*- -# -# 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 tuskar_ui import api -from tuskar_ui.utils import utils - - -def matching_deployment_mode(): - deployment_mode = getattr(settings, 'DEPLOYMENT_MODE', 'scale') - return deployment_mode.lower() == 'scale' - - -def _get_unmatched_suggestions(request): - unmatched_suggestions = [] - flavor_suggestions = [FlavorSuggestion.from_flavor(flavor) - for flavor in api.flavor.Flavor.list(request)] - for node in api.node.Node.list(request): - node_suggestion = FlavorSuggestion.from_node(node) - for flavor_suggestion in flavor_suggestions: - if flavor_suggestion == node_suggestion: - break - else: - unmatched_suggestions.append(node_suggestion) - return unmatched_suggestions - - -def get_flavor_suggestions(request): - return set(_get_unmatched_suggestions(request)) - - -class FlavorSuggestion(object): - """Describe node parameters in a way that is easy to compare.""" - - def __init__(self, vcpus=None, ram=None, disk=None, cpu_arch=None, - ram_bytes=None, disk_bytes=None, node_id=None): - self.vcpus = vcpus - self.ram_bytes = ram_bytes or ram * 1024 * 1024 or 0 - self.disk_bytes = disk_bytes or (disk or 0) * 1024 * 1024 * 1024 - self.cpu_arch = cpu_arch - self.id = node_id - - @classmethod - def from_node(cls, node): - return cls( - node_id=node.uuid, - vcpus=utils.safe_int_cast(node.cpus), - ram=utils.safe_int_cast(node.memory_mb), - disk=utils.safe_int_cast(node.local_gb), - cpu_arch=node.cpu_arch - ) - - @classmethod - def from_flavor(cls, flavor): - return cls( - vcpus=flavor.vcpus, - ram_bytes=flavor.ram_bytes, - disk_bytes=flavor.disk_bytes, - cpu_arch=flavor.cpu_arch - ) - - @property - def name(self): - return 'Flavor-%scpu-%s-%sMB-%sGB' % ( - self.vcpus or '0', - self.cpu_arch or '', - self.ram or '0', - self.disk or '0', - ) - - @property - def ram(self): - return self.ram_bytes / 1024 / 1024 - - @property - def disk(self): - return self.disk_bytes / 1024 / 1024 / 1024 - - def __hash__(self): - return self.name.__hash__() - - def __eq__(self, other): - return self.name == other.name - - def __ne__(self, other): - return not self == other - - def __repr__(self): - return ( - '%s(vcpus=%r, ram_bytes=%r, disk_bytes=%r, ' - 'cpu_arch=%r, node_id=%r)' % ( - self.__class__.__name__, - self.vcpus, - self.ram_bytes, - self.disk_bytes, - self.cpu_arch, - self.id, - ) - ) - - def create_flavor(self, request): - return api.flavor.Flavor.create( - request, - name=self.name, - memory=self.ram, - vcpus=self.vcpus, - disk=self.disk, - cpu_arch=self.cpu_arch, - ) diff --git a/tuskar_ui/infrastructure/flavors/views.py b/tuskar_ui/infrastructure/flavors/views.py deleted file mode 100644 index 09c36ac7c..000000000 --- a/tuskar_ui/infrastructure/flavors/views.py +++ /dev/null @@ -1,103 +0,0 @@ -# -*- coding: utf8 -*- -# -# 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.core.urlresolvers import reverse_lazy -from django.utils.translation import ugettext_lazy as _ -import horizon.exceptions -import horizon.tables -import horizon.tabs -from horizon.utils import memoized -import horizon.workflows - -from tuskar_ui import api -from tuskar_ui.infrastructure.flavors import tables -from tuskar_ui.infrastructure.flavors import utils -from tuskar_ui.infrastructure.flavors import workflows - - -class IndexView(horizon.tables.MultiTableView): - table_classes = (tables.FlavorsTable, tables.FlavorSuggestionsTable) - template_name = 'infrastructure/flavors/index.html' - - def get_context_data(self, **kwargs): - context = super(IndexView, self).get_context_data(**kwargs) - create_action = { - 'name': _("New Flavor"), - 'url': reverse('horizon:infrastructure:flavors:create'), - 'icon': 'fa-plus', - 'ajax_modal': True, - } - context['header_actions'] = [create_action] - context['flavors_count'] = self.get_flavors_count() - context['suggested_flavors_count'] = self.get_suggested_flavors_count() - return context - - @memoized.memoized_method - def get_flavors_data(self): - flavors = api.flavor.Flavor.list(self.request) - flavors.sort(key=lambda np: (np.vcpus, np.ram, np.disk)) - return flavors - - @memoized.memoized_method - def get_suggested_flavors_data(self): - return list(utils.get_flavor_suggestions(self.request)) - - def get_flavors_count(self): - return len(self.get_flavors_data()) - - def get_suggested_flavors_count(self): - return len(self.get_suggested_flavors_data()) - - -class CreateView(horizon.workflows.WorkflowView): - workflow_class = workflows.CreateFlavor - template_name = 'infrastructure/flavors/create.html' - - def get_initial(self): - suggestion_id = self.kwargs.get('suggestion_id') - if not suggestion_id: - return super(CreateView, self).get_initial() - node = api.node.Node.get(self.request, suggestion_id) - suggestion = utils.FlavorSuggestion.from_node(node) - return { - 'name': suggestion.name, - 'vcpus': suggestion.vcpus, - 'memory_mb': suggestion.ram, - 'disk_gb': suggestion.disk, - 'arch': suggestion.cpu_arch, - } - - -class DetailView(horizon.tables.DataTableView): - table_class = tables.FlavorRolesTable - template_name = 'infrastructure/flavors/details.html' - error_redirect = reverse_lazy('horizon:infrastructure:flavors:index') - - def get_context_data(self, **kwargs): - context = super(DetailView, self).get_context_data(**kwargs) - context['flavor'] = api.flavor.Flavor.get( - self.request, - kwargs.get('flavor_id'), - _error_redirect=self.error_redirect - ) - return context - - def get_data(self): - flavor_id = self.kwargs.get('flavor_id') - plan = api.tuskar.Plan.get_the_plan(self.request) - - return [role for role in api.tuskar.Role.list(self.request) - if role.flavor(plan) - and role.flavor(plan).id == flavor_id] diff --git a/tuskar_ui/infrastructure/flavors/workflows.py b/tuskar_ui/infrastructure/flavors/workflows.py deleted file mode 100644 index 6f1e1dee8..000000000 --- a/tuskar_ui/infrastructure/flavors/workflows.py +++ /dev/null @@ -1,79 +0,0 @@ -# -*- coding: utf8 -*- -# -# 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.forms import fields -from django.utils.translation import ugettext_lazy as _ -from horizon import exceptions -from horizon import workflows -from openstack_dashboard.dashboards.admin.flavors import ( - workflows as flavor_workflows) - -from tuskar_ui import api - - -class CreateFlavorAction(flavor_workflows.CreateFlavorInfoAction): - arch = fields.ChoiceField(choices=(('i386', 'i386'), ('amd64', 'amd64'), - ('x86_64', 'x86_64')), - label=_("Architecture")) - - def __init__(self, *args, **kwrds): - super(CreateFlavorAction, self).__init__(*args, **kwrds) - # Delete what is not applicable to hardware - del self.fields['eph_gb'] - del self.fields['swap_mb'] - # Alter user-visible strings - self.fields['vcpus'].label = _("CPUs") - self.fields['disk_gb'].label = _("Disk GB") - # No idea why Horizon exposes this database detail - del self.fields['flavor_id'] - - class Meta(object): - name = _("Flavor") - help_text = _("Flavors define the sizes for RAM, disk, number of " - "cores, and other resources. Flavors should be " - "associated with roles when planning a deployment.") - - -class CreateFlavorStep(workflows.Step): - action_class = CreateFlavorAction - contributes = ("name", - "vcpus", - "memory_mb", - "disk_gb", - "arch") - - -class CreateFlavor(flavor_workflows.CreateFlavor): - slug = "create_flavor" - name = _("Create Flavor") - finalize_button_name = _("Create Flavor") - success_message = _('Created new flavor "%s".') - failure_message = _('Unable to create flavor "%s".') - success_url = "horizon:infrastructure:flavors:index" - default_steps = (CreateFlavorStep,) - - def handle(self, request, data): - try: - self.object = api.flavor.Flavor.create( - request, - name=data['name'], - memory=data['memory_mb'], - vcpus=data['vcpus'], - disk=data['disk_gb'], - cpu_arch=data['arch'] - ) - except Exception: - exceptions.handle(request, _("Unable to create flavor")) - return False - return True diff --git a/tuskar_ui/infrastructure/history/__init__.py b/tuskar_ui/infrastructure/history/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/tuskar_ui/infrastructure/history/panel.py b/tuskar_ui/infrastructure/history/panel.py deleted file mode 100644 index 0f29c3722..000000000 --- a/tuskar_ui/infrastructure/history/panel.py +++ /dev/null @@ -1,26 +0,0 @@ -# -*- coding: utf8 -*- -# -# 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 tuskar_ui.infrastructure import dashboard - - -class History(horizon.Panel): - name = _("Deployment Log") - slug = "history" - - -dashboard.Infrastructure.register(History) diff --git a/tuskar_ui/infrastructure/history/tables.py b/tuskar_ui/infrastructure/history/tables.py deleted file mode 100644 index 4e0b789e1..000000000 --- a/tuskar_ui/infrastructure/history/tables.py +++ /dev/null @@ -1,37 +0,0 @@ -# -*- coding: utf8 -*- -# -# 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 tables - - -class HistoryTable(tables.DataTable): - - timestamp = tables.Column('event_time', - verbose_name=_("Timestamp"), - attrs={'data-type': 'timestamp'}) - resource_name = tables.Column('resource_name', - verbose_name=_("Resource Name")) - resource_status = tables.Column('resource_status', - verbose_name=_("Status")) - resource_status_reason = tables.Column('resource_status_reason', - verbose_name=_("Reason")) - - class Meta(object): - name = "log" - verbose_name = _("Deployment Log") - multi_select = False - table_actions = () - row_actions = () - template = "horizon/common/_enhanced_data_table.html" diff --git a/tuskar_ui/infrastructure/history/templates/history/index.html b/tuskar_ui/infrastructure/history/templates/history/index.html deleted file mode 100644 index a2ae7db6f..000000000 --- a/tuskar_ui/infrastructure/history/templates/history/index.html +++ /dev/null @@ -1,16 +0,0 @@ -{% extends 'infrastructure/base.html' %} -{% load i18n %} -{% block title %}{% trans 'Deployment Log' %}{% endblock %} - -{% block page_header %} - {% include 'horizon/common/_page_header.html' with title=_('Deployment Log') %} -{% endblock page_header %} - -{% block main %} -
-
- {{ table.render }} -
-
- -{% endblock %} diff --git a/tuskar_ui/infrastructure/history/tests.py b/tuskar_ui/infrastructure/history/tests.py deleted file mode 100644 index 7a687ef2f..000000000 --- a/tuskar_ui/infrastructure/history/tests.py +++ /dev/null @@ -1,53 +0,0 @@ -# -*- coding: utf8 -*- -# -# 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 contextlib - -from django.core import urlresolvers -from mock import patch, call # noqa -from openstack_dashboard.test.test_data import utils - -from tuskar_ui import api -from tuskar_ui.test import helpers as test -from tuskar_ui.test.test_data import heat_data -from tuskar_ui.test.test_data import tuskar_data - - -TEST_DATA = utils.TestDataContainer() -heat_data.data(TEST_DATA) -tuskar_data.data(TEST_DATA) -INDEX_URL = urlresolvers.reverse( - 'horizon:infrastructure:history:index') - - -class HistoryTest(test.BaseAdminViewTests): - - def test_index(self): - plan = api.tuskar.Plan( - TEST_DATA.tuskarclient_plans.first()) - stack = api.heat.Stack( - TEST_DATA.heatclient_stacks.first()) - events = TEST_DATA.heatclient_events.list() - - with contextlib.nested( - patch('tuskar_ui.api.tuskar.Plan.get_the_plan', - return_value=plan), - patch('tuskar_ui.api.heat.Stack.get_by_plan', - return_value=stack), - patch('tuskar_ui.api.heat.Stack.events', - return_value=events) - ): - res = self.client.get(INDEX_URL) - - self.assertTemplateUsed(res, 'infrastructure/history/index.html') diff --git a/tuskar_ui/infrastructure/history/urls.py b/tuskar_ui/infrastructure/history/urls.py deleted file mode 100644 index 15e57e1cd..000000000 --- a/tuskar_ui/infrastructure/history/urls.py +++ /dev/null @@ -1,23 +0,0 @@ -# -*- coding: utf8 -*- -# -# 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 urls - -from tuskar_ui.infrastructure.history import views - - -urlpatterns = urls.patterns( - '', - urls.url(r'^$', views.IndexView.as_view(), name='index'), -) diff --git a/tuskar_ui/infrastructure/history/views.py b/tuskar_ui/infrastructure/history/views.py deleted file mode 100644 index 31449bf22..000000000 --- a/tuskar_ui/infrastructure/history/views.py +++ /dev/null @@ -1,31 +0,0 @@ -# -*- coding: utf8 -*- -# -# 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 horizon import tables as horizon_tables - -from tuskar_ui import api -from tuskar_ui.infrastructure.history import tables - - -class IndexView(horizon_tables.DataTableView): - table_class = tables.HistoryTable - template_name = "infrastructure/history/index.html" - - def get_data(self): - plan = api.tuskar.Plan.get_the_plan(self.request) - if plan: - stack = api.heat.Stack.get_by_plan(self.request, plan) - if stack: - return stack.events - return [] diff --git a/tuskar_ui/infrastructure/images/__init__.py b/tuskar_ui/infrastructure/images/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/tuskar_ui/infrastructure/images/forms.py b/tuskar_ui/infrastructure/images/forms.py deleted file mode 100644 index 0d14befbf..000000000 --- a/tuskar_ui/infrastructure/images/forms.py +++ /dev/null @@ -1,17 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -from openstack_dashboard.dashboards.project.images.images import forms - - -class UpdateImageForm(forms.UpdateImageForm): - pass diff --git a/tuskar_ui/infrastructure/images/panel.py b/tuskar_ui/infrastructure/images/panel.py deleted file mode 100644 index 3bb728c4f..000000000 --- a/tuskar_ui/infrastructure/images/panel.py +++ /dev/null @@ -1,26 +0,0 @@ -# -*- coding: utf8 -*- -# -# 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 tuskar_ui.infrastructure import dashboard - - -class Images(horizon.Panel): - name = _("Provisioning Images") - slug = "images" - - -dashboard.Infrastructure.register(Images) diff --git a/tuskar_ui/infrastructure/images/tables.py b/tuskar_ui/infrastructure/images/tables.py deleted file mode 100644 index bbc8c0a94..000000000 --- a/tuskar_ui/infrastructure/images/tables.py +++ /dev/null @@ -1,74 +0,0 @@ -# -*- coding: utf8 -*- -# -# 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 tables -from openstack_dashboard import api -from openstack_dashboard.dashboards.project.images.images import ( - tables as project_tables) - - -class DeleteImage(project_tables.DeleteImage): - def allowed(self, request, image=None): - if image and image.protected: - return False - else: - return True - - -class CreateImage(project_tables.CreateImage): - url = "horizon:infrastructure:images:create" - - -class UpdateRow(tables.Row): - ajax = True - - def get_data(self, request, image_id): - image = api.glance.image_get(request, image_id) - return image - - -class ImageFilterAction(tables.FilterAction): - filter_type = "server" - filter_choices = (('name', _("Image Name ="), True), - ('status', _('Status ='), True), - ('disk_format', _('Format ='), True), - ('size_min', _('Min. Size (MB)'), True), - ('size_max', _('Max. Size (MB)'), True)) - - -class EditImage(project_tables.EditImage): - url = "horizon:infrastructure:images:update" - - def allowed(self, request, image=None): - return True - - -class ImagesTable(tables.DataTable): - - name = tables.Column('name', - verbose_name=_("Image Name")) - disk_format = tables.Column('disk_format', - verbose_name=_("Format")) - roles = tables.Column(lambda image: - ', '.join([r.name for r in image.roles]), - verbose_name=_("Deployment Roles")) - - class Meta(object): - name = "images" - row_class = UpdateRow - verbose_name = _("Provisioning Images") - table_actions = (CreateImage, DeleteImage, ImageFilterAction) - row_actions = (EditImage, DeleteImage) - template = "horizon/common/_enhanced_data_table.html" diff --git a/tuskar_ui/infrastructure/images/templates/images/_create.html b/tuskar_ui/infrastructure/images/templates/images/_create.html deleted file mode 100644 index 65c9f2b6c..000000000 --- a/tuskar_ui/infrastructure/images/templates/images/_create.html +++ /dev/null @@ -1,15 +0,0 @@ -{% extends "horizon/common/_modal_form.html" %} -{% load i18n %} -{% load url from future %} - -{% block form_id %}create_image_form{% endblock %} -{% block form_action %}{% url 'horizon:infrastructure:images:create' %}{% endblock %} -{% block form_attrs %}enctype="multipart/form-data"{% endblock %} - -{% block modal_id %}create_image_modal{% endblock %} -{% block modal-header %}{% trans "Create Image" %}{% endblock %} - -{% block modal-body-right %} -

{% trans "Description" %}:

-

{% trans "Modify different properties of an image." %}

-{% endblock %} diff --git a/tuskar_ui/infrastructure/images/templates/images/_update.html b/tuskar_ui/infrastructure/images/templates/images/_update.html deleted file mode 100644 index 4a3e98f95..000000000 --- a/tuskar_ui/infrastructure/images/templates/images/_update.html +++ /dev/null @@ -1,14 +0,0 @@ -{% extends "horizon/common/_modal_form.html" %} -{% load i18n %} -{% load url from future %} - -{% block form_id %}update_image_form{% endblock %} -{% block form_action %}{% url 'horizon:infrastructure:images:update' image.id %}{% endblock %} - -{% block modal_id %}update_image_modal{% endblock %} -{% block modal-header %}{% trans "Update Image" %}{% endblock %} - -{% block modal-body-right %} -

{% trans "Description" %}:

-

{% trans "Modify different properties of an image." %}

-{% endblock %} diff --git a/tuskar_ui/infrastructure/images/templates/images/create.html b/tuskar_ui/infrastructure/images/templates/images/create.html deleted file mode 100644 index d9d23a024..000000000 --- a/tuskar_ui/infrastructure/images/templates/images/create.html +++ /dev/null @@ -1,12 +0,0 @@ -{% extends 'base.html' %} -{% load i18n %} - -{% block title %}{% trans "Create Image" %}{% endblock %} - -{% block page_header %} - {% include "horizon/common/_page_header.html" with title=_("Create Image") %} -{% endblock page_header %} - -{% block main %} - {% include 'infrastructure/images/_create.html' %} -{% endblock %} diff --git a/tuskar_ui/infrastructure/images/templates/images/index.html b/tuskar_ui/infrastructure/images/templates/images/index.html deleted file mode 100644 index 88e69c954..000000000 --- a/tuskar_ui/infrastructure/images/templates/images/index.html +++ /dev/null @@ -1,16 +0,0 @@ -{% extends 'infrastructure/base.html' %} -{% load i18n %} -{% block title %}{% trans 'Provisioning Images' %}{% endblock %} - -{% block page_header %} - {% include 'horizon/common/_items_count_domain_page_header.html' with title=_('Provisioning Images') %} -{% endblock page_header %} - -{% block main %} -
-
- {{ table.render }} -
-
- -{% endblock %} diff --git a/tuskar_ui/infrastructure/images/templates/images/update.html b/tuskar_ui/infrastructure/images/templates/images/update.html deleted file mode 100644 index d6d6d0b95..000000000 --- a/tuskar_ui/infrastructure/images/templates/images/update.html +++ /dev/null @@ -1,12 +0,0 @@ -{% extends 'base.html' %} -{% load i18n %} - -{% block title %}{% trans "Update Image" %}{% endblock %} - -{% block page_header %} - {% include "horizon/common/_page_header.html" with title=_("Update Image") %} -{% endblock page_header %} - -{% block main %} - {% include 'infrastructure/images/_update.html' %} -{% endblock %} diff --git a/tuskar_ui/infrastructure/images/tests.py b/tuskar_ui/infrastructure/images/tests.py deleted file mode 100644 index b74244301..000000000 --- a/tuskar_ui/infrastructure/images/tests.py +++ /dev/null @@ -1,168 +0,0 @@ -# -*- coding: utf8 -*- -# -# 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 contextlib - -import mock -from mock import patch, call # noqa -from django.core import urlresolvers -from openstack_dashboard.dashboards.project.images.images import forms - -from tuskar_ui import api -from tuskar_ui.test import helpers as test - -INDEX_URL = urlresolvers.reverse('horizon:infrastructure:images:index') -CREATE_URL = 'horizon:infrastructure:images:create' -UPDATE_URL = 'horizon:infrastructure:images:update' - - -class ImagesTest(test.BaseAdminViewTests): - - def test_index(self): - roles = [api.tuskar.Role(role) for role in - self.tuskarclient_roles.list()] - plans = [api.tuskar.Plan(plan) for plan in - self.tuskarclient_plans.list()] - - with contextlib.nested( - patch('tuskar_ui.api.tuskar.Role.list', - return_value=roles), - patch('tuskar_ui.api.tuskar.Plan.list', - return_value=plans), - patch('openstack_dashboard.api.glance.image_list_detailed', - return_value=[self.glanceclient_images.list(), - False, False]),): - - res = self.client.get(INDEX_URL) - - self.assertTemplateUsed(res, 'infrastructure/images/index.html') - - def test_create_get(self): - res = self.client.get(urlresolvers.reverse(CREATE_URL)) - self.assertTemplateUsed(res, 'infrastructure/images/create.html') - - def test_create_post(self): - image = self.images.list()[0] - data = { - 'name': 'Fedora', - 'description': 'Login with admin/admin', - 'source_type': 'url', - 'image_url': 'http://www.test.com/test.iso', - 'disk_format': 'qcow2', - 'architecture': 'x86-64', - 'minimum_disk': 15, - 'minimum_ram': 512, - 'is_public': True, - 'protected': False} - - forms.IMAGE_FORMAT_CHOICES = [('qcow2', 'qcow2')] - - with contextlib.nested( - patch('openstack_dashboard.api.glance.image_create', - return_value=image),) as (mocked_create,): - - res = self.client.post( - urlresolvers.reverse(CREATE_URL), data) - - self.assertNoFormErrors(res) - self.assertEqual(res.status_code, 302) - self.assertRedirectsNoFollow(res, INDEX_URL) - - mocked_create.assert_called_once_with( - mock.ANY, name='Fedora', container_format='bare', - min_ram=512, disk_format='qcow2', protected=False, - is_public=True, min_disk=15, - location='http://www.test.com/test.iso', - properties={'description': 'Login with admin/admin', - 'architecture': 'x86-64'}) - - def test_update_get(self): - image = self.images.list()[0] - - with contextlib.nested( - patch('openstack_dashboard.api.glance.image_get', - return_value=image),) as (mocked_get,): - - res = self.client.get( - urlresolvers.reverse(UPDATE_URL, args=(image.id,))) - - mocked_get.assert_called_once_with(mock.ANY, image.id) - self.assertTemplateUsed(res, 'infrastructure/images/update.html') - - def test_update_post(self): - image = self.images.list()[0] - data = { - 'image_id': image.id, - 'name': 'Fedora', - 'description': 'Login with admin/admin', - 'source_type': 'url', - 'copy_from': 'http://test_url.com', - 'disk_format': 'qcow2', - 'architecture': 'x86-64', - 'minimum_disk': 15, - 'minimum_ram': 512, - 'is_public': True, - 'protected': False} - - forms.IMAGE_FORMAT_CHOICES = [('qcow2', 'qcow2')] - - with contextlib.nested( - patch('openstack_dashboard.api.glance.image_get', - return_value=image), - patch('openstack_dashboard.api.glance.image_update', - return_value=image),) as (mocked_get, mocked_update,): - - res = self.client.post( - urlresolvers.reverse(UPDATE_URL, args=(image.id,)), data) - - self.assertNoFormErrors(res) - self.assertEqual(res.status_code, 302) - self.assertRedirectsNoFollow(res, INDEX_URL) - - mocked_get.assert_called_once_with(mock.ANY, image.id) - mocked_update.assert_called_once_with( - mock.ANY, image.id, name='Fedora', container_format='bare', - min_ram=512, disk_format='qcow2', protected=False, - is_public=False, min_disk=15, purge_props=False, - properties={'description': 'Login with admin/admin', - 'architecture': 'x86-64'}) - - def test_delete_ok(self): - roles = [api.tuskar.Role(role) for role in - self.tuskarclient_roles.list()] - plans = [api.tuskar.Plan(plan) for plan in - self.tuskarclient_plans.list()] - images = self.glanceclient_images.list() - - data = {'action': 'images__delete', - 'object_ids': [images[0].id, images[1].id]} - - with contextlib.nested( - patch('tuskar_ui.api.tuskar.Role.list', - return_value=roles), - patch('tuskar_ui.api.tuskar.Plan.list', - return_value=plans), - patch('openstack_dashboard.api.glance.image_list_detailed', - return_value=[images, False, False]), - patch('openstack_dashboard.api.glance.image_delete', - return_value=None),) as ( - mock_role_list, plan_list, mock_image_lict, mock_image_delete): - - res = self.client.post(INDEX_URL, data) - - mock_image_delete.has_calls( - call(mock.ANY, images[0].id), - call(mock.ANY, images[1].id)) - self.assertNoFormErrors(res) - self.assertRedirectsNoFollow(res, INDEX_URL) diff --git a/tuskar_ui/infrastructure/images/urls.py b/tuskar_ui/infrastructure/images/urls.py deleted file mode 100644 index f67d55769..000000000 --- a/tuskar_ui/infrastructure/images/urls.py +++ /dev/null @@ -1,25 +0,0 @@ -# -*- coding: utf8 -*- -# -# 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 urls - -from tuskar_ui.infrastructure.images import views - -urlpatterns = urls.patterns( - '', - urls.url(r'^$', views.IndexView.as_view(), name='index'), - urls.url(r'^create/$', views.CreateView.as_view(), name='create'), - urls.url(r'^(?P[^/]+)/update/$', - views.UpdateView.as_view(), name='update'), -) diff --git a/tuskar_ui/infrastructure/images/views.py b/tuskar_ui/infrastructure/images/views.py deleted file mode 100644 index 198fdf433..000000000 --- a/tuskar_ui/infrastructure/images/views.py +++ /dev/null @@ -1,109 +0,0 @@ -# -*- coding: utf8 -*- -# -# 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_lazy -from django.utils.translation import ugettext_lazy as _ -from horizon import exceptions -from horizon import tables as horizon_tables -from horizon.utils import memoized -from openstack_dashboard import api -from openstack_dashboard.dashboards.project.images.images import views - -from tuskar_ui import api as tuskar_api -from tuskar_ui.infrastructure.images import forms -from tuskar_ui.infrastructure.images import tables -import tuskar_ui.infrastructure.views as infrastructure_views -from tuskar_ui.utils import utils - -LOG = logging.getLogger(__name__) - - -class IndexView(infrastructure_views.ItemCountMixin, - horizon_tables.DataTableView): - table_class = tables.ImagesTable - template_name = "infrastructure/images/index.html" - - @memoized.memoized_method - def get_data(self): - images = [] - filters = self.get_filters() - - sort_dir = 'desc' - try: - images, self._more, self._prev = api.glance.image_list_detailed( - self.request, - paginate=False, - filters=filters, - sort_dir=sort_dir) - images = [image for image in images - if utils.check_image_type(image, - 'overcloud provisioning')] - except Exception: - msg = _('Unable to retrieve image list.') - exceptions.handle(self.request, msg) - - plan = tuskar_api.tuskar.Plan.get_the_plan(self.request) - for image in images: - image.roles = tuskar_api.tuskar.Role.get_by_image( - self.request, plan, image) - - return images - - def get_filters(self): - filters = {'is_public': None} - filter_field = self.table.get_filter_field() - filter_string = self.table.get_filter_string() - filter_action = self.table._meta._filter_action - if filter_field and filter_string and ( - filter_action.is_api_filter(filter_field)): - if filter_field in ['size_min', 'size_max']: - invalid_msg = ('API query is not valid and is ignored: %s=%s' - % (filter_field, filter_string)) - try: - filter_string = long(float(filter_string) * (1024 ** 2)) - if filter_string >= 0: - filters[filter_field] = filter_string - else: - LOG.warning(invalid_msg) - except ValueError: - LOG.warning(invalid_msg) - else: - filters[filter_field] = filter_string - return filters - - -class CreateView(views.CreateView): - submit_url = "horizon:infrastructure:images:create" - template_name = 'infrastructure/images/create.html' - success_url = reverse_lazy("horizon:infrastructure:images:index") - page_title = _("Create Image") - - -class UpdateView(views.UpdateView): - template_name = 'infrastructure/images/update.html' - form_class = forms.UpdateImageForm - success_url = reverse_lazy('horizon:infrastructure:images:index') - submit_url = "horizon:infrastructure:images:update" - submit_label = _("Update Image") - - @memoized.memoized_method - def get_object(self): - try: - return api.glance.image_get(self.request, self.kwargs['image_id']) - except Exception: - msg = _('Unable to retrieve image.') - url = reverse_lazy('horizon:infrastructure:images:index') - exceptions.handle(self.request, msg, redirect=url) diff --git a/tuskar_ui/infrastructure/nodes/__init__.py b/tuskar_ui/infrastructure/nodes/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/tuskar_ui/infrastructure/nodes/forms.py b/tuskar_ui/infrastructure/nodes/forms.py deleted file mode 100644 index 6ec6b6e80..000000000 --- a/tuskar_ui/infrastructure/nodes/forms.py +++ /dev/null @@ -1,319 +0,0 @@ -# -*- coding: utf8 -*- -# -# 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 django.forms -from django.utils.translation import ugettext_lazy as _ -from horizon import exceptions -from horizon import forms -from horizon import messages - -from tuskar_ui import api -import tuskar_ui.forms -from tuskar_ui.utils import utils - - -DEFAULT_KERNEL_IMAGE_NAME = 'bm-deploy-kernel' -DEFAULT_RAMDISK_IMAGE_NAME = 'bm-deploy-ramdisk' - -CPU_ARCH_CHOICES = [ - ('', _("unspecified")), - ('amd64', _("amd64")), - ('x86', _("x86")), - ('x86_64', _("x86_64")), -] -DRIVER_CHOICES = [ - ('pxe_ipmitool', _("IPMI Driver")), - ('pxe_ssh', _("PXE + SSH")), -] - - -def get_driver_info_dict(data): - driver = data['driver'] - driver_dict = {'driver': driver, - 'deployment_kernel': data['deployment_kernel'], - 'deployment_ramdisk': data['deployment_ramdisk'], - } - if driver == 'pxe_ipmitool': - driver_dict.update( - ipmi_address=data['ipmi_address'], - ipmi_username=data.get('ipmi_username'), - ipmi_password=data.get('ipmi_password'), - ) - elif driver == 'pxe_ssh': - driver_dict.update( - ssh_address=data['ssh_address'], - ssh_username=data['ssh_username'], - ssh_key_contents=data['ssh_key_contents'], - ) - return driver_dict - - -def create_node(request, data): - cpu_arch = data.get('cpu_arch') - cpus = data.get('cpus') - memory_mb = data.get('memory_mb') - local_gb = data.get('local_gb') - - kwargs = get_driver_info_dict(data) - kwargs.update( - cpu_arch=cpu_arch, - cpus=cpus, - memory_mb=memory_mb, - local_gb=local_gb, - mac_addresses=data['mac_addresses'].split(), - ) - success = True - try: - node = api.node.Node.create(request, **kwargs) - except Exception: - success = False - exceptions.handle(request, _(u"Unable to register node.")) - else: - # If not all the parameters have been filled in, - # run the auto-discovery. Note, that the node has been created, - # so even if we fail here, we report success. - if not all([cpu_arch, cpus, memory_mb, local_gb]): - node_uuid = node.uuid - try: - api.node.Node.set_maintenance(request, node_uuid, True) - except Exception: - exceptions.handle(request, _( - u"Can't set maintenance mode on node {0}." - ).format(node_uuid)) - else: - try: - api.node.Node.discover(request, [node_uuid]) - except Exception: - exceptions.handle(request, _( - u"Can't start discovery on node {0}." - ).format(node_uuid)) - return success - - -class NodeForm(django.forms.Form): - id = django.forms.IntegerField( - label="", - required=False, - widget=django.forms.HiddenInput(), - ) - - driver = django.forms.ChoiceField( - label=_("Driver"), - choices=DRIVER_CHOICES, - required=True, - widget=django.forms.Select(attrs={ - 'class': 'form-control switchable', - 'data-slug': 'driver', - }), - ) - - ipmi_address = django.forms.IPAddressField( - label=_("IPMI Address"), - required=False, - widget=django.forms.TextInput(attrs={ - 'class': 'form-control switched', - 'data-switch-on': 'driver', - 'data-driver-pxe_ipmitool': _("IPMI Driver"), - }), - ) - ipmi_username = django.forms.CharField( - label=_("IPMI User"), - required=False, - widget=django.forms.TextInput(attrs={ - 'class': 'form-control switched', - 'data-switch-on': 'driver', - 'data-driver-pxe_ipmitool': _("IPMI Driver"), - }), - ) - ipmi_password = django.forms.CharField( - label=_("IPMI Password"), - required=False, - widget=django.forms.PasswordInput(render_value=True, attrs={ - 'class': 'form-control switched', - 'data-switch-on': 'driver', - 'data-driver-pxe_ipmitool': _("IPMI Driver"), - }), - ) - ssh_address = django.forms.IPAddressField( - label=_("SSH Address"), - required=False, - widget=django.forms.TextInput(attrs={ - 'class': 'form-control switched', - 'data-switch-on': 'driver', - 'data-driver-pxe_ssh': _("PXE + SSH"), - }), - ) - ssh_username = django.forms.CharField( - label=_("SSH User"), - required=False, - widget=django.forms.TextInput(attrs={ - 'class': 'form-control switched', - 'data-switch-on': 'driver', - 'data-driver-pxe_ssh': _("PXE + SSH"), - }), - ) - ssh_key_contents = django.forms.CharField( - label=_("SSH Key Contents"), - required=False, - widget=django.forms.Textarea(attrs={ - 'class': 'form-control switched', - 'data-switch-on': 'driver', - 'data-driver-pxe_ssh': _("PXE + SSH"), - 'rows': 2, - }), - ) - mac_addresses = tuskar_ui.forms.MultiMACField( - label=_("NIC MAC Addresses"), - required=True, - widget=django.forms.Textarea(attrs={ - 'placeholder': _('unspecified'), - 'rows': '2', - }), - ) - cpu_arch = django.forms.ChoiceField( - label=_("Architecture"), - required=False, - choices=CPU_ARCH_CHOICES, - widget=django.forms.Select( - attrs={'placeholder': _('unspecified')}), - ) - cpus = django.forms.IntegerField( - label=_("CPUs"), - required=False, - min_value=0, - widget=tuskar_ui.forms.NumberInput( - attrs={'placeholder': _('unspecified')}), - ) - memory_mb = django.forms.IntegerField( - label=_("Memory"), - required=False, - min_value=0, - widget=tuskar_ui.forms.NumberInput( - attrs={'placeholder': _('unspecified')}), - ) - local_gb = django.forms.IntegerField( - label=_("Local Disk"), - required=False, - min_value=0, - widget=tuskar_ui.forms.NumberInput( - attrs={'placeholder': _('unspecified')}), - ) - deployment_kernel = django.forms.ChoiceField( - label=_("Kernel"), - required=False, - choices=[], - widget=django.forms.Select(), - ) - deployment_ramdisk = django.forms.ChoiceField( - label=_("Ramdisk"), - required=False, - choices=[], - widget=django.forms.Select(), - ) - - def get_name(self): - try: - name = (self.fields['ipmi_address'].value() or - self.fields['ssh_address'].value()) - except AttributeError: - # when the field is not bound - name = _("Undefined node") - return name - - def handle(self, request, data): - return create_node(request, data) - - def clean_ipmi_username(self): - return self.cleaned_data.get('ipmi_username') or None - - def clean_ipmi_password(self): - return self.cleaned_data.get('ipmi_password') or None - - def _require_field(self, field_name, cleaned_data): - if cleaned_data.get(field_name): - return - self._errors[field_name] = self.error_class([_( - u"This field is required" - )]) - - def clean(self): - cleaned_data = super(NodeForm, self).clean() - driver = cleaned_data['driver'] - - if driver == 'pxe_ipmitool': - self._require_field('ipmi_address', cleaned_data) - elif driver == 'pxe_ssh': - self._require_field('ssh_address', cleaned_data) - self._require_field('ssh_username', cleaned_data) - self._require_field('ssh_key_contents', cleaned_data) - - return cleaned_data - - -class BaseNodeFormset(tuskar_ui.forms.SelfHandlingFormset): - def __init__(self, *args, **kwargs): - self.kernel_images = kwargs.pop('kernel_images') - self.ramdisk_images = kwargs.pop('ramdisk_images') - super(BaseNodeFormset, self).__init__(*args, **kwargs) - - def add_fields(self, form, index): - deployment_kernel_choices = [(kernel.id, kernel.name) - for kernel in self.kernel_images] - deployment_ramdisk_choices = [(ramdisk.id, ramdisk.name) - for ramdisk in self.ramdisk_images] - form.fields['deployment_kernel'].choices = deployment_kernel_choices - form.fields['deployment_ramdisk'].choices = deployment_ramdisk_choices - - def clean(self): - all_macs = api.node.Node.get_all_mac_addresses(self.request) - bad_macs = set() - bad_macs_error = _("Duplicate MAC addresses submitted: %s.") - - for form in self: - if not form.cleaned_data: - raise django.forms.ValidationError( - _("Please provide node data for all nodes.")) - - new_macs = form.cleaned_data.get('mac_addresses') - if not new_macs: - continue - new_macs = set(new_macs.split()) - - # Prevent submitting duplicated MAC addresses - # or MAC addresses of existing nodes - bad_macs |= all_macs & new_macs - all_macs |= new_macs - - if bad_macs: - raise django.forms.ValidationError( - bad_macs_error % ", ".join(bad_macs)) - - -class UploadNodeForm(forms.SelfHandlingForm): - csv_file = forms.FileField(label='', required=False) - - def handle(self, request, data): - return True - - def get_data(self): - try: - output = utils.parse_csv_file(self.cleaned_data['csv_file']) - except ValueError as e: - messages.error(self.request, e.message) - output = [] - - return output - - -RegisterNodeFormset = django.forms.formsets.formset_factory( - NodeForm, extra=1, formset=BaseNodeFormset) diff --git a/tuskar_ui/infrastructure/nodes/panel.py b/tuskar_ui/infrastructure/nodes/panel.py deleted file mode 100644 index 124ec3d31..000000000 --- a/tuskar_ui/infrastructure/nodes/panel.py +++ /dev/null @@ -1,26 +0,0 @@ -# -*- coding: utf8 -*- -# -# 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 tuskar_ui.infrastructure import dashboard - - -class Nodes(horizon.Panel): - name = _("Nodes") - slug = "nodes" - - -dashboard.Infrastructure.register(Nodes) diff --git a/tuskar_ui/infrastructure/nodes/tables.py b/tuskar_ui/infrastructure/nodes/tables.py deleted file mode 100644 index 898bd76ed..000000000 --- a/tuskar_ui/infrastructure/nodes/tables.py +++ /dev/null @@ -1,269 +0,0 @@ -# -*- coding: utf8 -*- -# -# 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.utils.translation import ugettext_lazy as _ -from horizon import messages -from horizon import tables -from horizon.utils import memoized - -from tuskar_ui import api - - -class DeleteNode(tables.BatchAction): - name = "delete" - action_present = _("Delete") - action_past = _("Deleting") - data_type_singular = _("Node") - data_type_plural = _("Nodes") - classes = ('btn-danger',) - - def allowed(self, request, obj=None): - if not obj: - # this is necessary because table actions use this function - # with obj=None - return True - return (getattr(obj, 'instance_uuid', None) is None and - obj.power_state not in api.node.POWER_ON_STATES) - - def action(self, request, obj_id): - if obj_id is None: - messages.error(request, _("Select some nodes to delete.")) - return - api.node.Node.delete(request, obj_id) - - -class ActivateNode(tables.BatchAction): - name = "activate" - action_present = _("Activate") - action_past = _("Activated") - data_type_singular = _("Node") - data_type_plural = _("Nodes") - - def allowed(self, request, obj=None): - if not obj: - # this is necessary because table actions use this function - # with obj=None - return True - return (obj.cpus and obj.memory_mb and obj.local_gb and - obj.cpu_arch) - - def action(self, request, obj_id): - if obj_id is None: - messages.error(request, _("Select some nodes to activate.")) - return - api.node.Node.set_maintenance(request, obj_id, False) - api.node.Node.set_power_state(request, obj_id, 'off') - - -class SetPowerStateOn(tables.BatchAction): - name = "set_power_state_on" - action_present = _("Power On") - action_past = _("Powering On") - data_type_singular = _("Node") - data_type_plural = _("Nodes") - - def allowed(self, request, obj=None): - if not obj: - # this is necessary because table actions use this function - # with obj=None - return True - return obj.power_state not in api.node.POWER_ON_STATES - - def action(self, request, obj_id): - if obj_id is None: - messages.error(request, _("Select some nodes to power on.")) - return - api.node.Node.set_power_state(request, obj_id, 'on') - - -class SetPowerStateOff(tables.BatchAction): - name = "set_power_state_off" - action_present = _("Power Off") - action_past = _("Powering Off") - data_type_singular = _("Node") - data_type_plural = _("Nodes") - - def allowed(self, request, obj=None): - if not obj: - # this is necessary because table actions use this function - # with obj=None - return True - return ( - obj.power_state in api.node.POWER_ON_STATES and - getattr(obj, 'instance_uuid', None) is None - ) - - def action(self, request, obj_id): - if obj_id is None: - messages.error(request, _("Select some nodes to power off.")) - return - api.node.Node.set_power_state(request, obj_id, 'off') - - -class NodeFilterAction(tables.FilterAction): - def filter(self, table, nodes, filter_string): - """Really naive case-insensitive search.""" - q = filter_string.lower() - - def comp(node): - return any(q in unicode(value).lower() for value in ( - node.ip_address, - node.cpus, - node.memory_mb, - node.local_gb, - )) - - return filter(comp, nodes) - - -class DiscoverNode(tables.BatchAction): - name = "discover_nodes" - action_present = _("Discover") - action_past = _("Discovered") - data_type_singular = _("Node") - data_type_plural = _("Nodes") - - def allowed(self, request, obj=None): - if not obj: - # this is necessary because table actions use this function - # with obj=None - return True - return obj.state == api.node.MAINTENANCE_STATE - - def action(self, request, obj_id): - if obj_id is None: - messages.error(request, _("Select some nodes to discover.")) - return - api.node.Node.discover(request, [obj_id]) - - -@memoized.memoized -def _get_role_link(role_id): - if role_id: - return reverse('horizon:infrastructure:roles:detail', - kwargs={'role_id': role_id}) - - -def get_role_link(datum): - return _get_role_link(getattr(datum, 'role_id', None)) - - -def get_power_state_with_transition(node): - if node.target_power_state and ( - node.power_state != node.target_power_state): - return "{0} -> {1}".format( - node.power_state, node.target_power_state) - return node.power_state - - -def get_state_string(node): - state_dict = { - api.node.DISCOVERING_STATE: _('Discovering'), - api.node.DISCOVERED_STATE: _('Discovered'), - api.node.PROVISIONED_STATE: _('Provisioned'), - api.node.PROVISIONING_FAILED_STATE: _('Provisioning Failed'), - api.node.PROVISIONING_STATE: _('Provisioning'), - api.node.FREE_STATE: _('Free'), - } - - node_state = node.state - return state_dict.get(node_state, node_state) - - -class BaseNodesTable(tables.DataTable): - node = tables.Column('uuid', - link="horizon:infrastructure:nodes:node_detail", - verbose_name=_("Node Name")) - role_name = tables.Column('role_name', - link=get_role_link, - verbose_name=_("Deployment Role")) - cpus = tables.Column('cpus', - verbose_name=_("CPU (cores)")) - memory_mb = tables.Column('memory_mb', - verbose_name=_("Memory (MB)")) - local_gb = tables.Column('local_gb', - verbose_name=_("Disk (GB)")) - power_status = tables.Column(get_power_state_with_transition, - verbose_name=_("Power Status")) - state = tables.Column(get_state_string, - verbose_name=_("Status")) - - class Meta(object): - name = "nodes_table" - verbose_name = _("Nodes") - table_actions = (NodeFilterAction, SetPowerStateOn, SetPowerStateOff, - DeleteNode) - row_actions = (SetPowerStateOn, SetPowerStateOff, DeleteNode) - template = "horizon/common/_enhanced_data_table.html" - - def get_object_id(self, datum): - return datum.uuid - - def get_object_display(self, datum): - return datum.uuid - - -class AllNodesTable(BaseNodesTable): - - class Meta(object): - name = "all_nodes_table" - verbose_name = _("All") - hidden_title = False - columns = ('node', 'cpus', 'memory_mb', 'local_gb', 'power_status', - 'state') - table_actions = (NodeFilterAction, SetPowerStateOn, SetPowerStateOff, - DeleteNode) - row_actions = (SetPowerStateOn, SetPowerStateOff, DeleteNode) - template = "horizon/common/_enhanced_data_table.html" - - -class ProvisionedNodesTable(BaseNodesTable): - - class Meta(object): - name = "provisioned_nodes_table" - verbose_name = _("Provisioned") - hidden_title = False - table_actions = (NodeFilterAction, SetPowerStateOn, SetPowerStateOff, - DeleteNode) - row_actions = (SetPowerStateOn, SetPowerStateOff, DeleteNode) - template = "horizon/common/_enhanced_data_table.html" - - -class FreeNodesTable(BaseNodesTable): - - class Meta(object): - name = "free_nodes_table" - verbose_name = _("Free") - hidden_title = False - columns = ('node', 'cpus', 'memory_mb', 'local_gb', 'power_status') - table_actions = (NodeFilterAction, SetPowerStateOn, SetPowerStateOff, - DeleteNode) - row_actions = (SetPowerStateOn, SetPowerStateOff, DeleteNode,) - template = "horizon/common/_enhanced_data_table.html" - - -class MaintenanceNodesTable(BaseNodesTable): - - class Meta(object): - name = "maintenance_nodes_table" - verbose_name = _("Maintenance") - hidden_title = False - columns = ('node', 'cpus', 'memory_mb', 'local_gb', 'power_status', - 'state') - table_actions = (NodeFilterAction, ActivateNode, SetPowerStateOn, - SetPowerStateOff, DiscoverNode, DeleteNode) - row_actions = (ActivateNode, SetPowerStateOn, SetPowerStateOff, - DeleteNode) - template = "horizon/common/_enhanced_data_table.html" diff --git a/tuskar_ui/infrastructure/nodes/tabs.py b/tuskar_ui/infrastructure/nodes/tabs.py deleted file mode 100644 index 1b8f81b6c..000000000 --- a/tuskar_ui/infrastructure/nodes/tabs.py +++ /dev/null @@ -1,378 +0,0 @@ -# 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. - -import itertools - -from django.core import urlresolvers -from django.utils.translation import ugettext_lazy as _ -from horizon import tabs -from horizon.utils import functions -from openstack_dashboard.api import base as api_base - -from tuskar_ui import api -from tuskar_ui.cached_property import cached_property # noqa -from tuskar_ui.infrastructure.nodes import tables -from tuskar_ui.utils import metering as metering_utils -from tuskar_ui.utils import utils - - -def filter_extra(nodes, index, value): - return (node for node in nodes - if node.extra.get(index, None) == value) - - -class OverviewTab(tabs.Tab): - name = _("Overview") - slug = "overview" - template_name = "infrastructure/nodes/_overview.html" - - def get_context_data(self, request): - nodes = self.tab_group.kwargs['nodes'] - cpus = sum(int(node.cpus) for node in nodes if node.cpus) - memory_mb = sum(int(node.memory_mb) for node in nodes if - node.memory_mb) - local_gb = sum(int(node.local_gb) for node in nodes if node.local_gb) - - nodes_provisioned = set(utils.filter_items( - nodes, provision_state__in=api.node.PROVISION_STATE_PROVISIONED)) - nodes_free = set(utils.filter_items( - nodes, provision_state__in=api.node.PROVISION_STATE_FREE)) - nodes_deleting = set(utils.filter_items( - nodes, provision_state__in=api.node.PROVISION_STATE_DELETING)) - nodes_error = set(utils.filter_items( - nodes, provision_state__in=api.node.PROVISION_STATE_ERROR)) - - nodes_provisioned_maintenance = set(utils.filter_items( - nodes_provisioned, maintenance=True)) - nodes_provisioned_not_maintenance = ( - nodes_provisioned - nodes_provisioned_maintenance) - - nodes_provisioning = set(utils.filter_items( - nodes, - provision_state__in=api.node.PROVISION_STATE_PROVISIONING)) - - nodes_free_maintenance = set(utils.filter_items( - nodes_free, maintenance=True)) - nodes_free_not_maintenance = ( - nodes_free - nodes_free_maintenance) - - nodes_maintenance = ( - nodes_provisioned_maintenance | nodes_free_maintenance) - - nodes_provisioned_down = utils.filter_items( - nodes_provisioned, power_state__not_in=api.node.POWER_ON_STATES) - nodes_free_down = utils.filter_items( - nodes_free, power_state__not_in=api.node.POWER_ON_STATES) - - nodes_on_discovery = filter_extra( - nodes_maintenance, 'on_discovery', 'true') - nodes_discovered = filter_extra( - nodes_maintenance, 'newly_discovered', 'true') - nodes_discovery_failed = filter_extra( - nodes_maintenance, 'discovery_failed', 'true') - - nodes_down = itertools.chain(nodes_provisioned_down, nodes_free_down) - nodes_up = utils.filter_items( - nodes, power_state__in=api.node.POWER_ON_STATES) - - nodes_free_count = len(nodes_free_not_maintenance) - nodes_provisioned_count = len( - nodes_provisioned_not_maintenance) - nodes_provisioning_count = len(nodes_provisioning) - nodes_maintenance_count = len(nodes_maintenance) - nodes_deleting_count = len(nodes_deleting) - nodes_error_count = len(nodes_error) - - context = { - 'cpus': cpus, - 'memory_gb': memory_mb / 1024.0, - 'local_gb': local_gb, - 'nodes_up_count': utils.length(nodes_up), - 'nodes_down_count': utils.length(nodes_down), - 'nodes_provisioned_count': nodes_provisioned_count, - 'nodes_provisioning_count': nodes_provisioning_count, - 'nodes_free_count': nodes_free_count, - 'nodes_deleting_count': nodes_deleting_count, - 'nodes_error_count': nodes_error_count, - 'nodes_maintenance_count': nodes_maintenance_count, - 'nodes_all_count': len(nodes), - 'nodes_on_discovery_count': utils.length(nodes_on_discovery), - 'nodes_discovered_count': utils.length(nodes_discovered), - 'nodes_discovery_failed_count': utils.length( - nodes_discovery_failed), - 'nodes_status_data': - 'Provisioned={0}|Free={1}|Maintenance={2}'.format( - nodes_provisioned_count, nodes_free_count, - nodes_maintenance_count) - } - # additional node status pie chart data, showing only if it appears - if nodes_provisioning_count: - context['nodes_status_data'] += '|Provisioning={0}'.format( - nodes_provisioning_count) - if nodes_deleting_count: - context['nodes_status_data'] += '|Deleting={0}'.format( - nodes_deleting_count) - if nodes_error_count: - context['nodes_status_data'] += '|Error={0}'.format( - nodes_error_count) - - if api_base.is_service_enabled(self.request, 'metering'): - context['meter_conf'] = ( - (_('System Load'), - metering_utils.url_part('hardware.cpu.load.1min', False), - None), - (_('CPU Utilization'), - metering_utils.url_part('hardware.system_stats.cpu.util', - True), - '100'), - (_('Swap Utilization'), - metering_utils.url_part('hardware.memory.swap.util', - True), - '100'), - ) - - # TODO(akrivoka): Ajaxize these calls so that they don't hold up the - # whole page load - context['top_5'] = { - 'fan': metering_utils.get_top_5(request, 'hardware.ipmi.fan'), - 'voltage': metering_utils.get_top_5( - request, 'hardware.ipmi.voltage'), - 'temperature': metering_utils.get_top_5( - request, 'hardware.ipmi.temperature'), - 'current': metering_utils.get_top_5( - request, 'hardware.ipmi.current'), - } - - return context - - -class BaseTab(tabs.TableTab): - table_classes = (tables.BaseNodesTable,) - name = _("Nodes") - slug = "nodes" - template_name = "horizon/common/_detail_table.html" - - def __init__(self, tab_group, request): - super(BaseTab, self).__init__(tab_group, request) - - @cached_property - def _nodes(self): - return [] - - def get_items_count(self): - return len(self._nodes) - - @cached_property - def _nodes_info(self): - page_size = functions.get_page_size(self.request) - - prev_marker = self.request.GET.get( - self.table_classes[0]._meta.prev_pagination_param, None) - - if prev_marker is not None: - sort_dir = 'asc' - marker = prev_marker - else: - sort_dir = 'desc' - marker = self.request.GET.get( - self.table_classes[0]._meta.pagination_param, None) - - nodes = self._nodes - - if marker: - node_ids = [node.uuid for node in self._nodes] - position = node_ids.index(marker) - if sort_dir == 'asc': - start = max(0, position - page_size) - end = position - else: - start = position + 1 - end = start + page_size - else: - start = 0 - end = page_size - - prev = start != 0 - more = len(nodes) > end - return nodes[start:end], prev, more - - def get_base_nodes_table_data(self): - nodes, prev, more = self._nodes_info - return nodes - - def has_prev_data(self, table): - return self._nodes_info[1] - - def has_more_data(self, table): - return self._nodes_info[2] - - -class AllTab(BaseTab): - table_classes = (tables.AllNodesTable,) - name = _("All") - slug = "all" - - def __init__(self, tab_group, request): - super(AllTab, self).__init__(tab_group, request) - - @cached_property - def _nodes(self): - return self.tab_group.kwargs['nodes'] - - def get_all_nodes_table_data(self): - nodes, prev, more = self._nodes_info - return nodes - - -class ProvisionedTab(BaseTab): - table_classes = (tables.ProvisionedNodesTable,) - name = _("Provisioned") - slug = "provisioned" - - def __init__(self, tab_group, request): - super(ProvisionedTab, self).__init__(tab_group, request) - - @cached_property - def _nodes(self): - redirect = urlresolvers.reverse('horizon:infrastructure:nodes:index') - return api.node.Node.list(self.request, associated=True, - maintenance=False, _error_redirect=redirect) - - def get_provisioned_nodes_table_data(self): - nodes, prev, more = self._nodes_info - - if nodes: - for node in nodes: - try: - resource = api.heat.Resource.get_by_node( - self.request, node) - except LookupError: - node.role_name = '-' - else: - node.role_name = resource.role.name - node.role_id = resource.role.id - node.stack_id = resource.stack.id - - return nodes - - -class FreeTab(BaseTab): - table_classes = (tables.FreeNodesTable,) - name = _("Free") - slug = "free" - - def __init__(self, tab_group, request): - super(FreeTab, self).__init__(tab_group, request) - - @cached_property - def _nodes(self): - redirect = urlresolvers.reverse('horizon:infrastructure:nodes:index') - return api.node.Node.list(self.request, associated=False, - maintenance=False, _error_redirect=redirect) - - def get_free_nodes_table_data(self): - nodes, prev, more = self._nodes_info - return nodes - - -class MaintenanceTab(BaseTab): - table_classes = (tables.MaintenanceNodesTable,) - name = _("Maintenance") - slug = "maintenance" - - def __init__(self, tab_group, request): - super(MaintenanceTab, self).__init__(tab_group, request) - - @cached_property - def _nodes(self): - nodes = self.tab_group.kwargs['nodes'] - return list(utils.filter_items(nodes, maintenance=True)) - - def get_maintenance_nodes_table_data(self): - return self._nodes - - -class DetailOverviewTab(tabs.Tab): - name = _("Overview") - slug = "detail_overview" - template_name = 'infrastructure/nodes/_detail_overview.html' - - def get_context_data(self, request): - node = self.tab_group.kwargs['node'] - context = {'node': node} - try: - resource = api.heat.Resource.get_by_node(self.request, node) - except LookupError: - pass - else: - context['role'] = resource.role - context['stack'] = resource.stack - - kernel_id = node.driver_info.get('deploy_kernel') - if kernel_id: - context['kernel_image'] = api.node.image_get(request, kernel_id) - - ramdisk_id = node.driver_info.get('deploy_ramdisk') - if ramdisk_id: - context['ramdisk_image'] = api.node.image_get(request, ramdisk_id) - - if node.instance_uuid: - if api_base.is_service_enabled(self.request, 'metering'): - # Meter configuration in the following format: - # (meter label, url part, y_max) - context['meter_conf'] = ( - (_('System Load'), - metering_utils.url_part('hardware.cpu.load.1min', False), - None), - (_('CPU Utilization'), - metering_utils.url_part('hardware.system_stats.cpu.util', - True), - '100'), - (_('Swap Utilization'), - metering_utils.url_part('hardware.memory.swap.util', - True), - '100'), - (_('Current'), - metering_utils.url_part('hardware.ipmi.current', False), - None), - (_('Network IO'), - metering_utils.url_part('network-io', False), - None), - (_('Disk IO'), - metering_utils.url_part('disk-io', False), - None), - (_('Temperature'), - metering_utils.url_part('hardware.ipmi.temperature', - False), - None), - (_('Fan Speed'), - metering_utils.url_part('hardware.ipmi.fan', False), - None), - (_('Voltage'), - metering_utils.url_part('hardware.ipmi.voltage', False), - None), - ) - return context - - -class NodeTabs(tabs.TabGroup): - slug = "nodes" - tabs = (OverviewTab, AllTab, ProvisionedTab, FreeTab, MaintenanceTab,) - sticky = True - template_name = "horizon/common/_items_count_tab_group.html" - - -class NodeDetailTabs(tabs.TabGroup): - slug = "node_details" - tabs = (DetailOverviewTab,) diff --git a/tuskar_ui/infrastructure/nodes/tests.py b/tuskar_ui/infrastructure/nodes/tests.py deleted file mode 100644 index 485a31b4d..000000000 --- a/tuskar_ui/infrastructure/nodes/tests.py +++ /dev/null @@ -1,596 +0,0 @@ -# -*- coding: utf8 -*- -# -# 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 contextlib -import json - -from ceilometerclient.v2 import client as ceilometer_client -from django.core import urlresolvers -from horizon import exceptions as horizon_exceptions -from ironicclient import exceptions as ironic_exceptions -import mock -from novaclient import exceptions as nova_exceptions -from openstack_dashboard.test.test_data import utils - -from tuskar_ui import api -from tuskar_ui.handle_errors import handle_errors # noqa -from tuskar_ui.infrastructure.nodes import forms -from tuskar_ui.test import helpers as test -from tuskar_ui.test.test_data import heat_data -from tuskar_ui.test.test_data import node_data -from tuskar_ui.test.test_data import tuskar_data - - -INDEX_URL = urlresolvers.reverse('horizon:infrastructure:nodes:index') -REGISTER_URL = urlresolvers.reverse('horizon:infrastructure:nodes:register') -DETAIL_VIEW = 'horizon:infrastructure:nodes:node_detail' -PERFORMANCE_VIEW = 'horizon:infrastructure:nodes:performance' -TEST_DATA = utils.TestDataContainer() -node_data.data(TEST_DATA) -heat_data.data(TEST_DATA) -tuskar_data.data(TEST_DATA) - - -def _raise_nova_client_exception(*args, **kwargs): - raise nova_exceptions.ClientException("Boom!") - - -class NodesTests(test.BaseAdminViewTests): - @handle_errors("Error!", []) - def _raise_tuskar_exception(self, request, *args, **kwargs): - raise self.exceptions.tuskar - - @handle_errors("Error!", []) - def _raise_horizon_exception_not_found(self, request, *args, **kwargs): - raise horizon_exceptions.NotFound - - def _raise_ironic_exception(self, request, *args, **kwargs): - raise ironic_exceptions.Conflict - - def stub_ceilometerclient(self): - if not hasattr(self, "ceilometerclient"): - self.mox.StubOutWithMock(ceilometer_client, 'Client') - self.ceilometerclient = self.mox.CreateMock( - ceilometer_client.Client, - ) - return self.ceilometerclient - - def test_index_get(self): - with mock.patch('tuskar_ui.api.node.Node', **{ - 'spec_set': ['list'], - 'list.return_value': [], - }) as mocked: - res = self.client.get(INDEX_URL) - self.assertEqual(mocked.list.call_count, 3) - - self.assertTemplateUsed( - res, 'infrastructure/nodes/index.html') - self.assertTemplateUsed(res, 'infrastructure/nodes/_overview.html') - - def _all_mocked_nodes(self): - request = mock.MagicMock() - return [api.node.Node(api.node.Node(node, request)) - for node in self.ironicclient_nodes.list()] - - def _test_index_tab(self, tab_name, nodes): - with mock.patch('tuskar_ui.api.node.Node', **{ - 'spec_set': ['list'], - 'list.return_value': nodes, - }) as Node: - res = self.client.get(INDEX_URL + '?tab=nodes__' + tab_name) - self.assertEqual(Node.list.call_count, 3) - - self.assertTemplateUsed( - res, 'infrastructure/nodes/index.html') - self.assertTemplateUsed(res, 'horizon/common/_detail_table.html') - self.assertItemsEqual( - res.context[tab_name + '_nodes_table_table'].data, - nodes) - - def test_all_nodes(self): - nodes = self._all_mocked_nodes() - self._test_index_tab('all', nodes) - - def test_provisioned_nodes(self): - nodes = self._all_mocked_nodes() - self._test_index_tab('provisioned', nodes) - - def test_free_nodes(self): - nodes = self._all_mocked_nodes() - self._test_index_tab('free', nodes) - - def test_maintenance_nodes(self): - nodes = self._all_mocked_nodes()[6:] - self._test_index_tab('maintenance', nodes) - - def _test_index_tab_list_exception(self, tab_name): - with mock.patch('tuskar_ui.api.node.Node', **{ - 'spec_set': ['list'], - 'list.side_effect': self._raise_tuskar_exception, - }) as mocked: - res = self.client.get(INDEX_URL + '?tab=nodes__' + tab_name) - self.assertEqual(mocked.list.call_count, 2) - - self.assertRedirectsNoFollow(res, INDEX_URL) - - def test_all_nodes_list_exception(self): - self._test_index_tab_list_exception('all') - - def test_provisioned_nodes_list_exception(self): - self._test_index_tab_list_exception('provisioned') - - def test_free_nodes_list_exception(self): - self._test_index_tab_list_exception('free') - - def test_maintenance_nodes_list_exception(self): - self._test_index_tab_list_exception('maintenance') - - def test_register_get(self): - with mock.patch('openstack_dashboard.api.glance.image_list_detailed', - return_value=([], False)) as mocked: - res = self.client.get(REGISTER_URL) - self.assertEqual(mocked.call_count, 2) - self.assertTemplateUsed( - res, 'infrastructure/nodes/register.html') - - def test_register_post(self): - node = TEST_DATA.ironicclient_nodes.first - nodes = self._all_mocked_nodes() - images = self.glanceclient_images.list() - data = { - 'register_nodes-TOTAL_FORMS': 2, - 'register_nodes-INITIAL_FORMS': 1, - 'register_nodes-MAX_NUM_FORMS': 1000, - - 'register_nodes-0-driver': 'pxe_ipmitool', - 'register_nodes-0-ipmi_address': '127.0.0.1', - 'register_nodes-0-ipmi_username': 'username', - 'register_nodes-0-ipmi_password': 'password', - 'register_nodes-0-mac_addresses': 'de:ad:be:ef:ca:fe', - 'register_nodes-0-cpu_arch': 'x86', - 'register_nodes-0-cpus': '1', - 'register_nodes-0-memory_mb': '2', - 'register_nodes-0-local_gb': '3', - 'register_nodes-0-deployment_kernel': images[3].id, - 'register_nodes-0-deployment_ramdisk': images[4].id, - - 'register_nodes-1-driver': 'pxe_ipmitool', - 'register_nodes-1-ipmi_address': '127.0.0.2', - 'register_nodes-1-mac_addresses': 'de:ad:be:ef:ca:ff', - 'register_nodes-1-cpu_arch': 'x86', - 'register_nodes-1-cpus': '4', - 'register_nodes-1-memory_mb': '5', - 'register_nodes-1-local_gb': '6', - 'register_nodes-1-deployment_kernel': images[3].id, - 'register_nodes-1-deployment_ramdisk': images[4].id, - } - with mock.patch('tuskar_ui.api.node.Node', **{ - 'spec_set': ['create', 'get_all_mac_addresses'], - 'create.return_value': node, - 'get_all_mac_addresses.return_value': set(nodes), - }) as Node, mock.patch( - 'openstack_dashboard.api.glance.image_list_detailed', - return_value=[images, False, False] - ): - res = self.client.post(REGISTER_URL, data) - self.assertNoFormErrors(res) - self.assertRedirectsNoFollow(res, INDEX_URL) - self.assertListEqual(Node.create.call_args_list, [ - mock.call( - mock.ANY, - ipmi_address=u'127.0.0.1', - cpu_arch='x86', - cpus=1, - memory_mb=2, - local_gb=3, - mac_addresses=['DE:AD:BE:EF:CA:FE'], - ipmi_username=u'username', - ipmi_password=u'password', - driver='pxe_ipmitool', - deployment_kernel=images[3].id, - deployment_ramdisk=images[4].id, - ), - mock.call( - mock.ANY, - ipmi_address=u'127.0.0.2', - cpu_arch='x86', - cpus=4, - memory_mb=5, - local_gb=6, - mac_addresses=['DE:AD:BE:EF:CA:FF'], - ipmi_username=None, - ipmi_password=None, - driver='pxe_ipmitool', - deployment_kernel=images[3].id, - deployment_ramdisk=images[4].id, - ), - ]) - - def test_register_post_exception(self): - nodes = self._all_mocked_nodes() - images = self.glanceclient_images.list() - data = { - 'register_nodes-TOTAL_FORMS': 2, - 'register_nodes-INITIAL_FORMS': 1, - 'register_nodes-MAX_NUM_FORMS': 1000, - - 'register_nodes-0-driver': 'pxe_ipmitool', - 'register_nodes-0-ipmi_address': '127.0.0.1', - 'register_nodes-0-ipmi_username': 'username', - 'register_nodes-0-ipmi_password': 'password', - 'register_nodes-0-mac_addresses': 'de:ad:be:ef:ca:fe', - 'register_nodes-0-cpu_arch': 'x86', - 'register_nodes-0-cpus': '1', - 'register_nodes-0-memory_mb': '2', - 'register_nodes-0-local_gb': '3', - 'register_nodes-0-deployment_kernel': images[3].id, - 'register_nodes-0-deployment_ramdisk': images[4].id, - - 'register_nodes-1-driver': 'pxe_ipmitool', - 'register_nodes-1-ipmi_address': '127.0.0.2', - 'register_nodes-1-mac_addresses': 'de:ad:be:ef:ca:ff', - 'register_nodes-1-cpu_arch': 'x86', - 'register_nodes-1-cpus': '4', - 'register_nodes-1-memory_mb': '5', - 'register_nodes-1-local_gb': '6', - 'register_nodes-1-deployment_kernel': images[3].id, - 'register_nodes-1-deployment_ramdisk': images[4].id, - } - with mock.patch('tuskar_ui.api.node.Node', **{ - 'spec_set': ['create', 'get_all_mac_addresses'], - 'create.side_effect': self.exceptions.tuskar, - 'get_all_mac_addresses.return_value': set(nodes), - }) as Node, mock.patch( - 'openstack_dashboard.api.glance.image_list_detailed', - return_value=[images, False, False] - ): - res = self.client.post(REGISTER_URL, data) - self.assertEqual(res.status_code, 200) - self.assertListEqual(Node.create.call_args_list, [ - mock.call( - mock.ANY, - ipmi_address=u'127.0.0.1', - cpu_arch='x86', - cpus=1, - memory_mb=2, - local_gb=3, - mac_addresses=['DE:AD:BE:EF:CA:FE'], - ipmi_username=u'username', - ipmi_password=u'password', - driver='pxe_ipmitool', - deployment_kernel=images[3].id, - deployment_ramdisk=images[4].id, - ), - mock.call( - mock.ANY, - ipmi_address=u'127.0.0.2', - cpu_arch='x86', - cpus=4, - memory_mb=5, - local_gb=6, - mac_addresses=['DE:AD:BE:EF:CA:FF'], - ipmi_username=None, - ipmi_password=None, - driver='pxe_ipmitool', - deployment_kernel=images[3].id, - deployment_ramdisk=images[4].id, - ), - ]) - self.assertTemplateUsed( - res, 'infrastructure/nodes/register.html') - - def test_node_detail(self): - node = api.node.Node(self.ironicclient_nodes.list()[0]) - - def get_node(request, uuid, **kwargs): - node._request = request - node.addresses = [] - return node - - image = self.glanceclient_images.first() - - with contextlib.nested( - mock.patch('tuskar_ui.api.node.Node', **{ - 'spec_set': ['get'], - 'get.side_effect': get_node, - }), - mock.patch('tuskar_ui.api.heat.Resource', **{ - 'spec_set': ['get_by_node'], - 'get_by_node.side_effect': lambda *args, **kwargs: {}[None], - # Raises LookupError - }), - mock.patch( - 'openstack_dashboard.api.glance.image_get', - return_value=image, - ), - mock.patch( - 'openstack_dashboard.api.nova.server_list', - return_value=([], False), - ), - ) as (mock_node, mock_heat, mock_glance, mock_nova): - res = self.client.get( - urlresolvers.reverse(DETAIL_VIEW, args=(node.uuid,)) - ) - self.assertEqual(mock_node.get.call_count, 1) - - self.assertTemplateUsed(res, 'infrastructure/nodes/detail.html') - self.assertEqual(res.context['node'], node) - - def test_node_detail_exception(self): - with mock.patch('tuskar_ui.api.node.Node', **{ - 'spec_set': ['get'], - 'get.side_effect': self._raise_tuskar_exception, - }) as mocked: - res = self.client.get( - urlresolvers.reverse(DETAIL_VIEW, args=('no-such-node',)) - ) - self.assertEqual(mocked.get.call_count, 1) - - self.assertRedirectsNoFollow(res, INDEX_URL) - - def test_node_set_power_on(self): - all_nodes = [api.node.Node(api.node.Node(node)) - for node in self.ironicclient_nodes.list()] - node = all_nodes[6] - roles = [api.tuskar.Role(r) - for r in TEST_DATA.tuskarclient_roles.list()] - instance = TEST_DATA.novaclient_servers.first() - image = TEST_DATA.glanceclient_images.first() - data = {'action': "all_nodes_table__set_power_state_on__{0}".format( - node.uuid)} - - with contextlib.nested( - mock.patch('tuskar_ui.api.node.Node', **{ - 'spec_set': ['list', 'set_power_state'], - 'list.return_value': all_nodes, - 'set_power_state.return_value': node, - }), - mock.patch('tuskar_ui.api.tuskar.Role', **{ - 'spec_set': ['list', 'name'], - 'list.return_value': roles, - }), - mock.patch('tuskar_ui.api.node.nova', **{ - 'spec_set': ['server_get', 'server_list'], - 'server_get.return_value': instance, - 'server_list.return_value': ([instance], False), - }), - mock.patch('tuskar_ui.api.node.glance', **{ - 'spec_set': ['image_get'], - 'image_get.return_value': image, - }), - mock.patch('tuskar_ui.api.heat.Resource', **{ - 'spec_set': ['get_by_node', 'list_all_resources'], - 'get_by_node.side_effect': ( - self._raise_horizon_exception_not_found), - 'list_all_resources.return_value': [], - }), - ) as (mock_node, mock_role, mock_nova, mock_glance, mock_resource): - res = self.client.post(INDEX_URL + '?tab=nodes__all', data) - self.assertNoFormErrors(res) - self.assertEqual(mock_node.set_power_state.call_count, 1) - self.assertRedirectsNoFollow(res, INDEX_URL + '?tab=nodes__all') - - def test_node_set_power_on_empty(self): - all_nodes = [api.node.Node(api.node.Node(node)) - for node in self.ironicclient_nodes.list()] - node = all_nodes[6] - roles = [api.tuskar.Role(r) - for r in TEST_DATA.tuskarclient_roles.list()] - instance = TEST_DATA.novaclient_servers.first() - image = TEST_DATA.glanceclient_images.first() - data = { - 'action': 'all_nodes_table__set_power_state_on', - 'object_ids': '', - } - - with contextlib.nested( - mock.patch('tuskar_ui.api.node.Node', **{ - 'spec_set': ['list', 'set_power_state'], - 'list.return_value': all_nodes, - 'set_power_state.return_value': node, - }), - mock.patch('tuskar_ui.api.tuskar.Role', **{ - 'spec_set': ['list', 'name'], - 'list.return_value': roles, - }), - mock.patch('tuskar_ui.api.node.nova', **{ - 'spec_set': ['server_get', 'server_list'], - 'server_get.return_value': instance, - 'server_list.return_value': ([instance], False), - }), - mock.patch('tuskar_ui.api.node.glance', **{ - 'spec_set': ['image_get'], - 'image_get.return_value': image, - }), - mock.patch('tuskar_ui.api.heat.Resource', **{ - 'spec_set': ['get_by_node', 'list_all_resources'], - 'get_by_node.side_effect': ( - self._raise_horizon_exception_not_found), - 'list_all_resources.return_value': [], - }), - ) as (mock_node, mock_role, mock_nova, mock_glance, mock_resource): - res = self.client.post(INDEX_URL + '?tab=nodes__all', data) - self.assertEqual(mock_node.set_power_state.call_count, 0) - self.assertRedirectsNoFollow(res, INDEX_URL) - - def test_node_set_power_off(self): - all_nodes = [api.node.Node(api.node.Node(node)) - for node in self.ironicclient_nodes.list()] - node = all_nodes[8] - roles = [api.tuskar.Role(r) - for r in TEST_DATA.tuskarclient_roles.list()] - instance = TEST_DATA.novaclient_servers.first() - image = TEST_DATA.glanceclient_images.first() - data = {'action': "all_nodes_table__set_power_state_off__{0}".format( - node.uuid)} - - with contextlib.nested( - mock.patch('tuskar_ui.api.node.Node', **{ - 'spec_set': ['list', 'set_power_state'], - 'list.return_value': all_nodes, - 'set_power_state.return_value': node, - }), - mock.patch('tuskar_ui.api.tuskar.Role', **{ - 'spec_set': ['list', 'name'], - 'list.return_value': roles, - }), - mock.patch('tuskar_ui.api.node.nova', **{ - 'spec_set': ['server_get', 'server_list'], - 'server_get.return_value': instance, - 'server_list.return_value': ([instance], False), - }), - mock.patch('tuskar_ui.api.node.glance', **{ - 'spec_set': ['image_get'], - 'image_get.return_value': image, - }), - mock.patch('tuskar_ui.api.heat.Resource', **{ - 'spec_set': ['get_by_node', 'list_all_resources'], - 'get_by_node.side_effect': ( - self._raise_horizon_exception_not_found), - 'list_all_resources.return_value': [], - }), - ) as (mock_node, mock_role, mock_nova, mock_glance, mock_resource): - res = self.client.post(INDEX_URL + '?tab=nodes__all', data) - self.assertNoFormErrors(res) - self.assertEqual(mock_node.set_power_state.call_count, 1) - self.assertRedirectsNoFollow(res, - INDEX_URL + '?tab=nodes__all') - - def test_performance(self): - node = api.node.Node(self.ironicclient_nodes.list()[0]) - instance = TEST_DATA.novaclient_servers.first() - - ceilometerclient = self.stub_ceilometerclient() - ceilometerclient.resources = self.mox.CreateMockAnything() - ceilometerclient.meters = self.mox.CreateMockAnything() - - self.mox.ReplayAll() - - with contextlib.nested( - mock.patch('tuskar_ui.api.node.Node', **{ - 'spec_set': ['get'], - 'get.return_value': node, - }), - mock.patch('tuskar_ui.api.node.nova', **{ - 'spec_set': ['servers', 'server_get', 'server_list'], - 'servers.return_value': [instance], - 'server_list.return_value': ([instance], None), - }), - mock.patch('tuskar_ui.utils.metering.query_data', - return_value=[]), - ): - url = urlresolvers.reverse(PERFORMANCE_VIEW, args=(node.uuid,)) - url += '?meter=cpu&date_options=7' - res = self.client.get(url) - - json_content = json.loads(res.content) - self.assertEqual(res.status_code, 200) - self.assertIn('series', json_content) - self.assertIn('settings', json_content) - - def test_get_driver_info_dict(self): - data = { - 'driver': 'pxe_ipmitool', - 'ipmi_address': '127.0.0.1', - 'ipmi_username': 'root', - 'ipmi_password': 'P@55W0rd', - 'deployment_kernel': '7', - 'deployment_ramdisk': '8', - } - ret = forms.get_driver_info_dict(data) - self.assertEqual(ret, { - 'driver': 'pxe_ipmitool', - 'ipmi_address': '127.0.0.1', - 'ipmi_username': 'root', - 'ipmi_password': 'P@55W0rd', - 'deployment_kernel': '7', - 'deployment_ramdisk': '8', - }) - data = { - 'driver': 'pxe_ssh', - 'ssh_address': '127.0.0.1', - 'ssh_username': 'root', - 'ssh_key_contents': 'P@55W0rd', - 'deployment_kernel': '7', - 'deployment_ramdisk': '8', - } - ret = forms.get_driver_info_dict(data) - self.assertEqual(ret, { - 'driver': 'pxe_ssh', - 'ssh_address': '127.0.0.1', - 'ssh_username': 'root', - 'ssh_key_contents': 'P@55W0rd', - 'deployment_kernel': '7', - 'deployment_ramdisk': '8', - }) - - def test_create_node(self): - data = { - 'ipmi_address': '127.0.0.1', - 'cpu_arch': 'x86', - 'cpus': 1, - 'memory_mb': 2, - 'local_gb': 3, - 'mac_addresses': 'DE:AD:BE:EF:CA:FE', - 'ipmi_username': 'username', - 'ipmi_password': 'password', - 'driver': 'pxe_ipmitool', - 'deployment_kernel': '7', - 'deployment_ramdisk': '8', - } - with mock.patch('tuskar_ui.api.node.Node', **{ - 'spec_set': ['create', 'set_maintenance', 'discover'], - 'create.return_value': None, - }) as Node: - forms.create_node(None, data) - self.assertListEqual(Node.create.call_args_list, [ - mock.call( - mock.ANY, - ipmi_address=u'127.0.0.1', - cpu_arch='x86', - cpus=1, - memory_mb=2, - local_gb=3, - mac_addresses=['DE:AD:BE:EF:CA:FE'], - ipmi_username=u'username', - ipmi_password=u'password', - driver='pxe_ipmitool', - deployment_kernel='7', - deployment_ramdisk='8', - ), - ]) - - def test_delete_deployed_on_servers(self): - all_nodes = [api.node.Node(node) - for node in self.ironicclient_nodes.list()] - node = all_nodes[6] - data = {'action': 'all_nodes_table__delete', - 'object_ids': [node.uuid]} - - with contextlib.nested( - mock.patch('tuskar_ui.api.node.Node', **{ - 'spec_set': [ - 'list', - 'delete', - ], - 'list.return_value': [node], - 'delete.side_effect': self._raise_ironic_exception, - }), - mock.patch('openstack_dashboard.api.nova.server_list', - return_value=([], False)), - ): - res = self.client.post(INDEX_URL, data) - self.assertMessageCount(error=1, warning=0) - self.assertNoFormErrors(res) - self.assertRedirectsNoFollow(res, INDEX_URL) diff --git a/tuskar_ui/infrastructure/nodes/urls.py b/tuskar_ui/infrastructure/nodes/urls.py deleted file mode 100644 index c75129fef..000000000 --- a/tuskar_ui/infrastructure/nodes/urls.py +++ /dev/null @@ -1,31 +0,0 @@ -# -*- coding: utf8 -*- -# -# 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 urls - -from tuskar_ui.infrastructure.nodes import views - - -urlpatterns = urls.patterns( - '', - urls.url(r'^$', views.IndexView.as_view(), name='index'), - urls.url(r'^register/$', views.RegisterView.as_view(), - name='register'), - urls.url(r'^nodes_performance/$', - views.PerformanceView.as_view(), name='nodes_performance'), - urls.url(r'^(?P[^/]+)/$', views.DetailView.as_view(), - name='node_detail'), - urls.url(r'^(?P[^/]+)/performance/$', - views.PerformanceView.as_view(), name='performance'), -) diff --git a/tuskar_ui/infrastructure/nodes/views.py b/tuskar_ui/infrastructure/nodes/views.py deleted file mode 100644 index eece7bfbb..000000000 --- a/tuskar_ui/infrastructure/nodes/views.py +++ /dev/null @@ -1,199 +0,0 @@ -# -*- coding: utf8 -*- -# -# 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 json - -from django.core.urlresolvers import reverse -from django.core.urlresolvers import reverse_lazy -import django.forms -import django.http -from django.utils.translation import ugettext_lazy as _ -from django.views.generic import base -from horizon import exceptions -from horizon import forms as horizon_forms -from horizon import tabs as horizon_tabs -from horizon.utils import memoized -from openstack_dashboard.api import glance - -from tuskar_ui import api -from tuskar_ui.infrastructure.nodes import forms -from tuskar_ui.infrastructure.nodes import tables -from tuskar_ui.infrastructure.nodes import tabs -import tuskar_ui.infrastructure.views as infrastructure_views -from tuskar_ui.utils import metering as metering_utils - - -def get_kernel_images(request): - try: - kernel_images = glance.image_list_detailed( - request, filters={'disk_format': 'aki'})[0] - except Exception: - exceptions.handle(request, _('Unable to retrieve kernel image list.')) - kernel_images = [] - return kernel_images - - -def get_ramdisk_images(request): - try: - ramdisk_images = glance.image_list_detailed( - request, filters={'disk_format': 'ari'})[0] - except Exception: - exceptions.handle(request, _('Unable to retrieve ramdisk image list.')) - ramdisk_images = [] - return ramdisk_images - - -class IndexView(infrastructure_views.ItemCountMixin, - horizon_tabs.TabbedTableView): - tab_group_class = tabs.NodeTabs - template_name = 'infrastructure/nodes/index.html' - - def get_context_data(self, **kwargs): - context = super(IndexView, self).get_context_data(**kwargs) - register_action = { - 'name': _('Register Nodes'), - 'url': reverse('horizon:infrastructure:nodes:register'), - 'icon': 'fa-plus', - 'ajax_modal': True, - } - context['header_actions'] = [register_action] - return context - - @memoized.memoized_method - def get_data(self): - return api.node.Node.list(self.request) - - def get_tabs(self, request, **kwargs): - nodes = self.get_data() - return self.tab_group_class(request, nodes=nodes, **kwargs) - - -class RegisterView(horizon_forms.ModalFormView): - form_class = forms.RegisterNodeFormset - form_prefix = 'register_nodes' - template_name = 'infrastructure/nodes/register.html' - success_url = reverse_lazy('horizon:infrastructure:nodes:index') - submit_label = _("Register Nodes") - - def get_data(self): - return [] - - def get_form(self, form_class): - initial = [] - - if self.request.FILES: - csv_form = forms.UploadNodeForm(self.request, - data=self.request.POST, - files=self.request.FILES) - if csv_form.is_valid(): - initial = csv_form.get_data() - formset = forms.RegisterNodeFormset( - self.request.POST, - prefix=self.form_prefix, - request=self.request, - kernel_images=get_kernel_images(self.request), - ramdisk_images=get_ramdisk_images(self.request) - ) - if formset.is_valid(): - initial += formset.cleaned_data - formset = forms.RegisterNodeFormset( - None, - initial=initial, - prefix=self.form_prefix, - request=self.request, - kernel_images=get_kernel_images(self.request), - ramdisk_images=get_ramdisk_images(self.request) - ) - formset.extra = 0 - return formset - return forms.RegisterNodeFormset( - self.request.POST or None, - initial=initial, - prefix=self.form_prefix, - request=self.request, - kernel_images=get_kernel_images(self.request), - ramdisk_images=get_ramdisk_images(self.request) - ) - - def get_context_data(self, **kwargs): - context = super(RegisterView, self).get_context_data(**kwargs) - context['upload_form'] = forms.UploadNodeForm(self.request) - return context - - -class DetailView(horizon_tabs.TabView): - tab_group_class = tabs.NodeDetailTabs - template_name = 'infrastructure/nodes/detail.html' - - def get_context_data(self, **kwargs): - context = super(DetailView, self).get_context_data(**kwargs) - - node = self.get_data() - - if node.maintenance: - table = tables.MaintenanceNodesTable(self.request) - else: - table = tables.ProvisionedNodesTable(self.request) - - context['node'] = node - context['title'] = _("Node: %(uuid)s") % {'uuid': node.uuid} - context['url'] = self.get_redirect_url() - context['actions'] = table.render_row_actions(node) - return context - - @memoized.memoized_method - def get_data(self): - node_uuid = self.kwargs.get('node_uuid') - node = api.node.Node.get(self.request, node_uuid, - _error_redirect=self.get_redirect_url()) - return node - - def get_tabs(self, request, **kwargs): - node = self.get_data() - return self.tab_group_class(self.request, node=node, **kwargs) - - @staticmethod - def get_redirect_url(): - return reverse_lazy('horizon:infrastructure:nodes:index') - - -class PerformanceView(base.TemplateView): - def get(self, request, *args, **kwargs): - meter = request.GET.get('meter') - date_options = request.GET.get('date_options') - date_from = request.GET.get('date_from') - date_to = request.GET.get('date_to') - stats_attr = request.GET.get('stats_attr', 'avg') - barchart = bool(request.GET.get('barchart')) - - node_uuid = kwargs.get('node_uuid', None) - if node_uuid: - node = api.node.Node.get(request, node_uuid) - instance_uuids = [node.instance_uuid] - else: - # Aggregated stats for all nodes - instance_uuids = [] - - json_output = metering_utils.get_nodes_stats( - request=request, - node_uuid=node_uuid, - instance_uuids=instance_uuids, - meter=meter, - date_options=date_options, - date_from=date_from, - date_to=date_to, - stats_attr=stats_attr, - barchart=barchart) - - return django.http.HttpResponse( - json.dumps(json_output), content_type='application/json') diff --git a/tuskar_ui/infrastructure/overview/__init__.py b/tuskar_ui/infrastructure/overview/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/tuskar_ui/infrastructure/overview/forms.py b/tuskar_ui/infrastructure/overview/forms.py deleted file mode 100644 index 77ff8c372..000000000 --- a/tuskar_ui/infrastructure/overview/forms.py +++ /dev/null @@ -1,488 +0,0 @@ -# -*- coding: utf8 -*- -# -# 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 -import six -import uuid - -from django.conf import settings -import django.forms -from django.utils.translation import ugettext_lazy as _ -import horizon.exceptions -import horizon.forms -import horizon.messages -from os_cloud_config import keystone as keystone_config -from os_cloud_config.utils import clients - -from tuskar_ui import api -import tuskar_ui.api.heat -import tuskar_ui.api.tuskar -import tuskar_ui.forms -import tuskar_ui.infrastructure.flavors.utils as flavors_utils -import tuskar_ui.utils.utils as tuskar_utils - -MATCHING_DEPLOYMENT_MODE = flavors_utils.matching_deployment_mode() -LOG = logging.getLogger(__name__) -MESSAGE_ICONS = { - 'ok': 'fa-check-square-o text-success', - 'pending': 'fa-square-o text-info', - 'error': 'fa-exclamation-circle text-danger', - 'warning': 'fa-exclamation-triangle text-warning', - None: 'fa-exclamation-triangle text-warning', -} -WEBROOT = getattr(settings, 'WEBROOT', '/') - - -def validate_roles(request, plan): - """Validates the roles in plan and returns dict describing the issues""" - for role in plan.role_list: - if ( - plan.get_role_node_count(role) and - not role.is_valid_for_deployment(plan) - ): - message = { - 'text': _(u"Configure Roles."), - 'is_critical': True, - 'status': 'pending', - } - break - else: - message = { - 'text': _(u"Configure Roles."), - 'status': 'ok', - } - return message - - -def validate_global_parameters(request, plan): - pending_required_global_params = list( - api.tuskar.Parameter.pending_parameters( - api.tuskar.Parameter.required_parameters( - api.tuskar.Parameter.global_parameters( - plan.parameter_list())))) - if pending_required_global_params: - message = { - 'text': _(u"Global Service Configuration."), - 'is_critical': True, - 'status': 'pending', - } - else: - message = { - 'text': _(u"Global Service Configuration."), - 'status': 'ok', - } - return message - - -def validate_plan(request, plan): - """Validates the plan and returns a list of dicts describing the issues.""" - messages = [] - requested_nodes = 0 - for role in plan.role_list: - node_count = plan.get_role_node_count(role) - requested_nodes += node_count - available_flavors = len(api.flavor.Flavor.list(request)) - if available_flavors == 0: - messages.append({ - 'text': _(u"Define Flavors."), - 'is_critical': True, - 'status': 'pending', - }) - else: - messages.append({ - 'text': _(u"Define Flavors."), - 'status': 'ok', - }) - available_nodes = len(api.node.Node.list(request, associated=False, - maintenance=False)) - if available_nodes == 0: - messages.append({ - 'text': _(u"Register Nodes."), - 'is_critical': True, - 'status': 'pending', - }) - elif requested_nodes > available_nodes: - messages.append({ - 'text': _(u"Not enough registered nodes for this plan. " - u"You need {0} more.").format( - requested_nodes - available_nodes), - 'is_critical': True, - 'status': 'error', - }) - else: - messages.append({ - 'text': _(u"Register Nodes."), - 'status': 'ok', - }) - messages.append(validate_roles(request, plan)) - messages.append(validate_global_parameters(request, plan)) - if not MATCHING_DEPLOYMENT_MODE: - # All roles have to have the same flavor. - default_flavor_name = api.flavor.Flavor.list(request)[0].name - for role in plan.role_list: - if role.flavor(plan).name != default_flavor_name: - messages.append({ - 'text': _(u"Role {0} doesn't use default flavor.").format( - role.name, - ), - 'is_critical': False, - 'statis': 'error', - }) - roles_assigned = True - messages.append({ - 'text': _(u"Assign roles."), - 'status': lambda: 'ok' if roles_assigned else 'pending', - }) - try: - controller_role = plan.get_role_by_name("Controller") - except KeyError: - messages.append({ - 'text': _(u"Controller Role Needed."), - 'is_critical': True, - 'status': 'error', - 'indent': 1, - }) - roles_assigned = False - else: - if plan.get_role_node_count(controller_role) not in (1, 3): - messages.append({ - 'text': _(u"1 or 3 Controllers Needed."), - 'is_critical': True, - 'status': 'pending', - 'indent': 1, - }) - roles_assigned = False - else: - messages.append({ - 'text': _(u"1 or 3 Controllers Needed."), - 'status': 'ok', - 'indent': 1, - }) - - try: - compute_role = plan.get_role_by_name("Compute") - except KeyError: - messages.append({ - 'text': _(u"Compute Role Needed."), - 'is_critical': True, - 'status': 'error', - 'indent': 1, - }) - roles_assigned = False - else: - if plan.get_role_node_count(compute_role) < 1: - messages.append({ - 'text': _(u"1 Compute Needed."), - 'is_critical': True, - 'status': 'pending', - 'indent': 1, - }) - roles_assigned = False - else: - messages.append({ - 'text': _(u"1 Compute Needed."), - 'status': 'ok', - 'indent': 1, - }) - for message in messages: - status = message.get('status') - if callable(status): - message['status'] = status = status() - message['classes'] = MESSAGE_ICONS.get(status, MESSAGE_ICONS[None]) - return messages - - -class EditPlan(horizon.forms.SelfHandlingForm): - def __init__(self, *args, **kwargs): - super(EditPlan, self).__init__(*args, **kwargs) - self.plan = api.tuskar.Plan.get_the_plan(self.request) - self.fields.update(self._role_count_fields(self.plan)) - - def _role_count_fields(self, plan): - fields = {} - for role in plan.role_list: - field = django.forms.IntegerField( - label=role.name, - widget=tuskar_ui.forms.NumberPickerInput(attrs={ - 'min': 1 if role.name in ('Controller', 'Compute') else 0, - 'step': 2 if role.name == 'Controller' else 1, - }), - initial=plan.get_role_node_count(role), - required=False - ) - field.role = role - fields['%s-count' % role.id] = field - return fields - - def handle(self, request, data): - parameters = dict( - (field.role.node_count_parameter_name, data[name]) - for (name, field) in self.fields.items() if name.endswith('-count') - ) - # NOTE(gfidente): this is a bad hack meant to magically add the - # parameter which enables Neutron L3 HA when the number of - # Controllers is > 1 - try: - controller_role = self.plan.get_role_by_name('Controller') - compute_role = self.plan.get_role_by_name('Compute') - except Exception as e: - LOG.warning('Unable to find a required role: %s', e.message) - else: - number_controllers = parameters[ - controller_role.node_count_parameter_name] - if number_controllers > 1: - for role in [controller_role, compute_role]: - l3ha_param = role.parameter_prefix + 'NeutronL3HA' - parameters[l3ha_param] = 'True' - l3agent_param = (role.parameter_prefix + - 'NeutronAllowL3AgentFailover') - parameters[l3agent_param] = 'True' - dhcp_agents_per_net = (number_controllers if number_controllers and - number_controllers > 3 else 3) - dhcp_agents_param = (controller_role.parameter_prefix + - 'NeutronDhcpAgentsPerNetwork') - parameters[dhcp_agents_param] = dhcp_agents_per_net - - try: - ceph_storage_role = self.plan.get_role_by_name('Ceph-Storage') - except Exception as e: - LOG.warning('Unable to find role: %s', 'Ceph-Storage') - else: - if parameters[ceph_storage_role.node_count_parameter_name] > 0: - parameters.update({ - 'CephClusterFSID': six.text_type(uuid.uuid4()), - 'CephMonKey': tuskar_utils.create_cephx_key(), - 'CephAdminKey': tuskar_utils.create_cephx_key() - }) - - cinder_enable_rbd_param = (controller_role.parameter_prefix - + 'CinderEnableRbdBackend') - glance_backend_param = (controller_role.parameter_prefix + - 'GlanceBackend') - nova_enable_rbd_param = (compute_role.parameter_prefix + - 'NovaEnableRbdBackend') - cinder_enable_iscsi_param = ( - controller_role.parameter_prefix + - 'CinderEnableIscsiBackend') - - parameters.update({ - cinder_enable_rbd_param: True, - glance_backend_param: 'rbd', - nova_enable_rbd_param: True, - cinder_enable_iscsi_param: False - }) - - try: - self.plan = self.plan.patch(request, self.plan.uuid, parameters) - except Exception as e: - horizon.exceptions.handle(request, _("Unable to update the plan.")) - LOG.exception(e) - return False - return True - - -class ScaleOut(EditPlan): - def __init__(self, *args, **kwargs): - super(ScaleOut, self).__init__(*args, **kwargs) - for name, field in self.fields.items(): - if name.endswith('-count'): - field.widget.attrs['min'] = field.initial - - def handle(self, request, data): - if not super(ScaleOut, self).handle(request, data): - return False - plan = self.plan - try: - stack = api.heat.Stack.get_by_plan(self.request, plan) - stack.update(request, plan.name, plan.templates) - except Exception as e: - LOG.exception(e) - if hasattr(e, 'error'): - horizon.exceptions.handle( - request, - _( - "Unable to deploy overcloud. Reason: {0}" - ).format(e.error['error']['message']), - ) - return False - else: - raise - else: - msg = _('Deployment in progress.') - horizon.messages.success(request, msg) - return True - - -class DeployOvercloud(horizon.forms.SelfHandlingForm): - network_isolation = horizon.forms.BooleanField( - label=_("Enable Network Isolation"), - required=False) - - def handle(self, request, data): - try: - plan = api.tuskar.Plan.get_the_plan(request) - except Exception as e: - LOG.exception(e) - horizon.exceptions.handle(request, - _("Unable to deploy overcloud.")) - return False - - # If network isolation selected, read environment file data - # and add to plan - env_temp = '/usr/share/openstack-tripleo-heat-templates/environments' - try: - if self.cleaned_data['network_isolation']: - with open(env_temp, 'r') as env_file: - env_contents = ''.join( - [line for line in - env_file.readlines() if '#' not in line] - ) - plan.environment += env_contents - except Exception as e: - LOG.exception(e) - pass - - # Auto-generate missing passwords and certificates - if plan.list_generated_parameters(): - generated_params = plan.make_generated_parameters() - plan = plan.patch(request, plan.uuid, generated_params) - - # Validate plan and create stack - for message in validate_plan(request, plan): - if message.get('is_critical'): - horizon.messages.success(request, message.text) - return False - try: - stack = api.heat.Stack.get_by_plan(self.request, plan) - if not stack: - api.heat.Stack.create(request, plan.name, plan.templates) - except Exception as e: - LOG.exception(e) - horizon.exceptions.handle( - request, _("Unable to deploy overcloud. Reason: {0}").format( - e.error['error']['message'])) - return False - else: - msg = _('Deployment in progress.') - horizon.messages.success(request, msg) - return True - - -class UndeployOvercloud(horizon.forms.SelfHandlingForm): - def handle(self, request, data): - try: - plan = api.tuskar.Plan.get_the_plan(request) - stack = api.heat.Stack.get_by_plan(self.request, plan) - if stack: - api.heat.Stack.delete(request, stack.id) - except Exception as e: - LOG.exception(e) - horizon.exceptions.handle(request, - _("Unable to undeploy overcloud.")) - return False - else: - msg = _('Undeployment in progress.') - horizon.messages.success(request, msg) - return True - - -class PostDeployInit(horizon.forms.SelfHandlingForm): - admin_email = horizon.forms.CharField(label=_("Admin Email")) - public_host = horizon.forms.CharField( - label=_("Public Host"), initial="", required=False) - region = horizon.forms.CharField( - label=_("Region"), initial="regionOne") - - def build_endpoints(self, plan, controller_role): - return { - "ceilometer": { - "password": plan.parameter_value( - controller_role.parameter_prefix + 'CeilometerPassword')}, - "cinder": { - "password": plan.parameter_value( - controller_role.parameter_prefix + 'CinderPassword')}, - "cinderv2": { - "password": plan.parameter_value( - controller_role.parameter_prefix + 'CinderPassword')}, - "ec2": { - "password": plan.parameter_value( - controller_role.parameter_prefix + 'GlancePassword')}, - "glance": { - "password": plan.parameter_value( - controller_role.parameter_prefix + 'GlancePassword')}, - "heat": { - "password": plan.parameter_value( - controller_role.parameter_prefix + 'HeatPassword')}, - "neutron": { - "password": plan.parameter_value( - controller_role.parameter_prefix + 'NeutronPassword')}, - "nova": { - "password": plan.parameter_value( - controller_role.parameter_prefix + 'NovaPassword')}, - "novav3": { - "password": plan.parameter_value( - controller_role.parameter_prefix + 'NovaPassword')}, - "swift": { - "password": plan.parameter_value( - controller_role.parameter_prefix + 'SwiftPassword'), - 'path': '/v1/AUTH_%(tenant_id)s', - 'admin_path': '/v1'}, - "horizon": { - 'port': '80', - 'path': WEBROOT, - 'admin_path': '%sadmin' % WEBROOT}} - - def handle(self, request, data): - try: - plan = api.tuskar.Plan.get_the_plan(request) - controller_role = plan.get_role_by_name("Controller") - stack = api.heat.Stack.get_by_plan(self.request, plan) - - admin_token = plan.parameter_value( - controller_role.parameter_prefix + 'AdminToken') - admin_password = plan.parameter_value( - controller_role.parameter_prefix + 'AdminPassword') - admin_email = data['admin_email'] - auth_ip = stack.keystone_ip - auth_url = stack.keystone_auth_url - auth_tenant = 'admin' - auth_user = 'admin' - - # do the keystone init - keystone_config.initialize( - auth_ip, admin_token, admin_email, admin_password, - region='regionOne', ssl=None, public=None, user='heat-admin', - pki_setup=False) - - # retrieve needed Overcloud clients - keystone_client = clients.get_keystone_client( - auth_user, admin_password, auth_tenant, auth_url) - - # do the setup endpoints - keystone_config.setup_endpoints( - self.build_endpoints(plan, controller_role), - public_host=data['public_host'], - region=data['region'], - os_auth_url=auth_url, - client=keystone_client) - - except Exception as e: - LOG.exception(e) - horizon.exceptions.handle(request, - _("Unable to initialize Overcloud.")) - return False - else: - msg = _('Overcloud has been initialized.') - horizon.messages.success(request, msg) - return True diff --git a/tuskar_ui/infrastructure/overview/panel.py b/tuskar_ui/infrastructure/overview/panel.py deleted file mode 100644 index 535cd13b7..000000000 --- a/tuskar_ui/infrastructure/overview/panel.py +++ /dev/null @@ -1,26 +0,0 @@ -# -*- coding: utf8 -*- -# -# 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 tuskar_ui.infrastructure import dashboard - - -class Overview(horizon.Panel): - name = _("Overview") - slug = "overview" - - -dashboard.Infrastructure.register(Overview) diff --git a/tuskar_ui/infrastructure/overview/tests.py b/tuskar_ui/infrastructure/overview/tests.py deleted file mode 100644 index 20155c867..000000000 --- a/tuskar_ui/infrastructure/overview/tests.py +++ /dev/null @@ -1,364 +0,0 @@ -# -*- coding: utf8 -*- -# -# 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 contextlib - -from django.core import urlresolvers -from mock import patch, call # noqa -from openstack_dashboard.test.test_data import utils - -from tuskar_ui import api -from tuskar_ui.infrastructure.overview import forms -from tuskar_ui.infrastructure.overview import views -from tuskar_ui.test import helpers as test -from tuskar_ui.test.test_data import heat_data -from tuskar_ui.test.test_data import tuskar_data - - -INDEX_URL = urlresolvers.reverse( - 'horizon:infrastructure:overview:index') -DEPLOY_URL = urlresolvers.reverse( - 'horizon:infrastructure:overview:deploy_confirmation') -DELETE_URL = urlresolvers.reverse( - 'horizon:infrastructure:overview:undeploy_confirmation') -POST_DEPLOY_INIT_URL = urlresolvers.reverse( - 'horizon:infrastructure:overview:post_deploy_init') -TEST_DATA = utils.TestDataContainer() -heat_data.data(TEST_DATA) -tuskar_data.data(TEST_DATA) - - -@contextlib.contextmanager -def _mock_plan(**kwargs): - plan = None - - params = { - 'spec_set': [ - 'create', - 'delete', - 'get', - 'get_the_plan', - 'id', - 'uuid', - 'patch', - 'parameters', - 'role_list', - 'parameter_value', - 'get_role_by_name', - 'get_role_node_count', - 'list_generated_parameters', - 'make_generated_parameters', - 'parameter_list', - ], - 'create.side_effect': lambda *args, **kwargs: plan, - 'delete.return_value': None, - 'get.side_effect': lambda *args, **kwargs: plan, - 'get_the_plan.side_effect': lambda *args, **kwargs: plan, - 'id': 'plan-1', - 'uuid': 'plan-1', - 'patch.side_effect': lambda *args, **kwargs: plan, - 'role_list': [], - 'parameter_list.return_value': [], - 'parameter_value.return_value': None, - 'get_role_by_name.side_effect': KeyError, - 'get_role_node_count.return_value': 0, - 'list_generated_parameters.return_value': {}, - 'make_generated_parameters.return_value': {}, - } - params.update(kwargs) - with patch( - 'tuskar_ui.api.tuskar.Plan', **params) as Plan: - plan = Plan - yield Plan - - -class OverviewTests(test.BaseAdminViewTests): - def test_index_stack_not_created(self): - with contextlib.nested( - _mock_plan(), - patch('tuskar_ui.api.heat.Stack.list', return_value=[]), - patch('tuskar_ui.api.node.Node.list', return_value=[]), - patch('tuskar_ui.api.flavor.Flavor.list', return_value=[]), - ): - res = self.client.get(INDEX_URL) - get_the_plan = api.tuskar.Plan.get_the_plan - request = get_the_plan.call_args_list[0][0][0] - self.assertListEqual(get_the_plan.call_args_list, [ - call(request), - call(request), - call(request), - ]) - self.assertListEqual(api.heat.Stack.list.call_args_list, [ - call(request), - ]) - self.assertListEqual(api.node.Node.list.call_args_list, [ - call(request, associated=False, maintenance=False), - ]) - self.assertListEqual(api.flavor.Flavor.list.call_args_list, [ - call(request), - ]) - self.assertTemplateUsed( - res, 'infrastructure/overview/index.html') - self.assertTemplateUsed( - res, 'infrastructure/overview/role_nodes_edit.html') - - def test_index_stack_not_created_post(self): - with contextlib.nested( - _mock_plan(), - patch('tuskar_ui.api.heat.Stack.list', return_value=[]), - patch('tuskar_ui.api.node.Node.list', return_value=[]), - patch('tuskar_ui.api.flavor.Flavor.list', return_value=[]), - ) as (plan, _stack_list, _node_list, _flavor_list): - data = { - 'role-1-count': 1, - 'role-2-count': 0, - 'role-3-count': 0, - 'role-4-count': 0, - } - res = self.client.post(INDEX_URL, data) - self.assertNoFormErrors(res) - self.assertRedirectsNoFollow(res, INDEX_URL) - get_the_plan = api.tuskar.Plan.get_the_plan - request = get_the_plan.call_args_list[0][0][0] - self.assertListEqual(get_the_plan.call_args_list, [ - call(request), - ]) - self.assertListEqual( - api.tuskar.Plan.patch.call_args_list, - [call(request, plan.id, {})], - ) - - def test_index_stack_deployed(self): - stack = api.heat.Stack(TEST_DATA.heatclient_stacks.first()) - roles = [api.tuskar.Role(role) - for role in self.tuskarclient_roles.list()] - - with contextlib.nested( - _mock_plan(**{'get_role_by_name.side_effect': None, - 'get_role_by_name.return_value': roles[0]}), - patch('tuskar_ui.api.heat.Stack.get_by_plan', - return_value=stack), - patch('tuskar_ui.api.heat.Stack.events', - return_value=[]), - ) as (Plan, stack_get_mock, stack_events_mock): - res = self.client.get(INDEX_URL) - request = Plan.get_the_plan.call_args_list[0][0][0] - self.assertListEqual( - Plan.get_the_plan.call_args_list, - [ - call(request), - call(request), - call(request), - ]) - - self.assertTemplateUsed( - res, 'infrastructure/overview/index.html') - self.assertTemplateUsed( - res, 'infrastructure/overview/deployment_live.html') - - def test_index_stack_undeploy_in_progress(self): - stack = api.heat.Stack(TEST_DATA.heatclient_stacks.first()) - - with contextlib.nested( - _mock_plan(), - patch('tuskar_ui.api.heat.Stack.get_by_plan', - return_value=stack), - patch('tuskar_ui.api.heat.Stack.is_deleting', - return_value=True), - patch('tuskar_ui.api.heat.Stack.is_deployed', - return_value=False), - patch('tuskar_ui.api.heat.Stack.resources', - return_value=[]), - patch('tuskar_ui.api.heat.Stack.events', - return_value=[]), - ): - res = self.client.get(INDEX_URL) - - self.assertTemplateUsed( - res, 'infrastructure/overview/index.html') - self.assertTemplateUsed( - res, 'infrastructure/overview/deployment_progress.html') - - def test_deploy_get(self): - with _mock_plan(): - res = self.client.get(DEPLOY_URL) - self.assertTemplateUsed( - res, 'infrastructure/overview/deploy_confirmation.html') - - def test_delete_get(self): - stack = api.heat.Stack(TEST_DATA.heatclient_stacks.first()) - - with contextlib.nested( - _mock_plan(), - patch('tuskar_ui.api.heat.Stack.get_by_plan', - return_value=stack), - ): - res = self.client.get(DELETE_URL) - self.assertTemplateUsed( - res, 'infrastructure/overview/undeploy_confirmation.html') - - def test_delete_post(self): - stack = api.heat.Stack(TEST_DATA.heatclient_stacks.first()) - - with contextlib.nested( - _mock_plan(), - patch('tuskar_ui.api.heat.Stack.get_by_plan', - return_value=stack), - patch('tuskar_ui.api.heat.Stack.delete', - return_value=None), - ): - res = self.client.post(DELETE_URL) - self.assertRedirectsNoFollow(res, INDEX_URL) - - def test_post_deploy_init_get(self): - stack = api.heat.Stack(TEST_DATA.heatclient_stacks.first()) - - with contextlib.nested( - _mock_plan(), - patch('tuskar_ui.api.heat.Stack.get_by_plan', - return_value=stack), - ): - res = self.client.get(POST_DEPLOY_INIT_URL) - self.assertEqual(res.context['form']['admin_email'].value(), '') - self.assertTemplateUsed( - res, 'infrastructure/overview/post_deploy_init.html') - - def test_post_deploy_init_post(self): - stack = api.heat.Stack(TEST_DATA.heatclient_stacks.first()) - roles = [api.tuskar.Role(role) - for role in self.tuskarclient_roles.list()] - - data = { - 'admin_email': "example@example.org", - 'public_host': '', - 'region': 'regionOne', - } - - with contextlib.nested( - _mock_plan(**{'get_role_by_name.side_effect': None, - 'get_role_by_name.return_value': roles[0]}), - patch('tuskar_ui.api.heat.Stack.get_by_plan', - return_value=stack), - patch('os_cloud_config.keystone.initialize', - return_value=None), - patch('os_cloud_config.keystone.setup_endpoints', - return_value=None), - patch('os_cloud_config.utils.clients.get_keystone_client', - return_value='keystone_client'), - ) as (mock_plan, mock_get_by_plan, mock_initialize, - mock_setup_endpoints, mock_get_keystone_client): - res = self.client.post(POST_DEPLOY_INIT_URL, data) - - self.assertNoFormErrors(res) - self.assertEqual(res.status_code, 302) - self.assertRedirectsNoFollow(res, INDEX_URL) - - mock_initialize.assert_called_once_with( - '192.0.2.23', None, 'example@example.org', None, ssl=None, - region='regionOne', user='heat-admin', public=None, - pki_setup=False) - mock_setup_endpoints.assert_called_once_with( - {'nova': {'password': None}, - 'heat': {'password': None}, - 'ceilometer': {'password': None}, - 'ec2': {'password': None}, - "horizon": { - 'port': '80', - 'path': '/', - 'admin_path': '/admin'}, - 'cinder': {'password': None}, - 'cinderv2': {'password': None}, - 'glance': {'password': None}, - 'swift': {'password': None, - 'path': '/v1/AUTH_%(tenant_id)s', - 'admin_path': '/v1'}, - 'novav3': {'password': None}, - 'neutron': {'password': None}}, - os_auth_url=stack.keystone_auth_url, - client='keystone_client', - region='regionOne', - public_host='') - mock_get_keystone_client.assert_called_once_with( - 'admin', None, 'admin', stack.keystone_auth_url) - - def test_get_role_data(self): - plan = api.tuskar.Plan(self.tuskarclient_plans.first()) - stack = api.heat.Stack(self.heatclient_stacks.first()) - role = api.tuskar.Role(self.tuskarclient_roles.first()) - stack.resources = lambda *args, **kwargs: [] - ret = views._get_role_data(plan, stack, None, role) - self.assertEqual(ret, { - 'deployed_node_count': 0, - 'deploying_node_count': 0, - 'error_node_count': 0, - 'field': '', - 'finished': False, - 'icon': 'fa-exclamation', - 'id': 'role-1', - 'name': 'Controller', - 'planned_node_count': 1, - 'role': role, - 'status': 'warning', - 'total_node_count': 0, - 'waiting_node_count': 0, - }) - - def test_validate_plan_empty(self): - with ( - _mock_plan() - ) as plan, ( - patch('tuskar_ui.api.node.Node.list', return_value=[]) - ), ( - patch('tuskar_ui.api.flavor.Flavor.list', return_value=[]) - ): - ret = forms.validate_plan(None, plan) - for m in ret: - m['text'] = unicode(m['text']) - self.assertEqual(ret, [ - { - 'is_critical': True, - 'text': u'Define Flavors.', - 'status': 'pending', - 'classes': 'fa-square-o text-info', - }, { - 'is_critical': True, - 'text': u'Register Nodes.', - 'status': 'pending', - 'classes': 'fa-square-o text-info', - }, { - 'status': 'ok', - 'text': u'Configure Roles.', - 'classes': 'fa-check-square-o text-success', - }, { - 'status': 'ok', - 'text': u'Global Service Configuration.', - 'classes': 'fa-check-square-o text-success', - }, { - 'status': 'pending', - 'text': u'Assign roles.', - 'classes': 'fa-square-o text-info', - }, { - 'is_critical': True, - 'text': u'Controller Role Needed.', - 'status': 'error', - 'indent': 1, - 'classes': 'fa-exclamation-circle text-danger', - }, { - 'is_critical': True, - 'text': u'Compute Role Needed.', - 'status': 'error', - 'indent': 1, - 'classes': 'fa-exclamation-circle text-danger', - }, - ]) diff --git a/tuskar_ui/infrastructure/overview/urls.py b/tuskar_ui/infrastructure/overview/urls.py deleted file mode 100644 index 35410d5df..000000000 --- a/tuskar_ui/infrastructure/overview/urls.py +++ /dev/null @@ -1,38 +0,0 @@ -# -*- coding: utf8 -*- -# -# 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 urls - -from tuskar_ui.infrastructure.overview import views - - -urlpatterns = urls.patterns( - '', - urls.url(r'^$', views.IndexView.as_view(), name='index'), - urls.url(r'^deploy-confirmation$', - views.DeployConfirmationView.as_view(), - name='deploy_confirmation'), - urls.url(r'^undeploy-confirmation$', - views.UndeployConfirmationView.as_view(), - name='undeploy_confirmation'), - urls.url(r'^post-deploy-init$', - views.PostDeployInitView.as_view(), - name='post_deploy_init'), - urls.url(r'^scale-out$', - views.ScaleOutView.as_view(), - name='scale_out'), - urls.url(r'^download-overcloudrc$', - views.download_overcloudrc_file, - name='download_overcloudrc'), -) diff --git a/tuskar_ui/infrastructure/overview/views.py b/tuskar_ui/infrastructure/overview/views.py deleted file mode 100644 index 90f9fa371..000000000 --- a/tuskar_ui/infrastructure/overview/views.py +++ /dev/null @@ -1,390 +0,0 @@ -# -*- coding: utf8 -*- -# -# 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 json -import logging -import urlparse - -from django.core.urlresolvers import reverse -from django.core.urlresolvers import reverse_lazy -from django import http -from django import shortcuts -import django.utils.text -from django.utils.translation import ugettext_lazy as _ -import heatclient -import horizon.forms -from horizon import messages - -from tuskar_ui import api -from tuskar_ui.infrastructure.overview import forms -from tuskar_ui.infrastructure import views - - -INDEX_URL = 'horizon:infrastructure:overview:index' - -LOG = logging.getLogger(__name__) - - -def _steps_message(messages): - total_steps = len(messages) - completed_steps = len([m for m in messages if not m.get('is_critical')]) - return _("{0} of {1} Steps Completed").format(completed_steps, total_steps) - - -def _get_role_data(plan, stack, form, role): - """Gathers data about a single deployment role. - - Gathers data about a single deployment role from the related Overcloud - and Role objects, and presents it in the form convenient for use - from the template. - - """ - data = { - 'id': role.id, - 'role': role, - 'name': role.name, - 'planned_node_count': plan.get_role_node_count(role), - 'field': form['%s-count' % role.id] if form else '', - } - - if stack: - resources = stack.resources(role=role, with_joins=True) - nodes = [r.node for r in resources] - node_count = len(nodes) - - deployed_node_count = 0 - deploying_node_count = 0 - error_node_count = 0 - waiting_node_count = node_count - - status = 'warning' - if nodes: - deployed_node_count = sum(1 for node in nodes - if node.instance.status == 'ACTIVE') - deploying_node_count = sum(1 for node in nodes - if node.instance.status == 'BUILD') - error_node_count = sum(1 for node in nodes - if node.instance.status == 'ERROR') - waiting_node_count = (node_count - deployed_node_count - - deploying_node_count - error_node_count) - - if error_node_count or 'FAILED' in stack.stack_status: - status = 'danger' - elif deployed_node_count == data['planned_node_count']: - status = 'success' - else: - status = 'info' - - finished = deployed_node_count == data['planned_node_count'] - if finished: - icon = 'fa-check' - elif status in ('danger', 'warning'): - icon = 'fa-exclamation' - else: - icon = 'fa-spinner fa-spin' - - data.update({ - 'status': status, - 'finished': finished, - 'total_node_count': node_count, - 'deployed_node_count': deployed_node_count, - 'deploying_node_count': deploying_node_count, - 'waiting_node_count': waiting_node_count, - 'error_node_count': error_node_count, - 'icon': icon, - }) - - # TODO(rdopieralski) get this from ceilometer - # data['capacity'] = 20 - return data - - -class IndexView(horizon.forms.ModalFormView, views.StackMixin): - template_name = 'infrastructure/overview/index.html' - form_class = forms.EditPlan - success_url = reverse_lazy(INDEX_URL) - - def get_progress_update(self, request, data): - return { - 'progress': data.get('progress'), - 'show_last_events': data.get('show_last_events'), - 'last_events_title': unicode(data.get('last_events_title')), - 'last_events': [{ - 'event_time': event.event_time, - 'resource_name': event.resource_name, - 'resource_status': event.resource_status, - 'resource_status_reason': event.resource_status_reason, - } for event in data.get('last_events', [])], - 'roles': [{ - 'status': role.get('status', 'warning'), - 'finished': role.get('finished', False), - 'name': role.get('name', ''), - 'slug': django.utils.text.slugify(role.get('name', '')), - 'id': role.get('id', ''), - 'total_node_count': role.get('node_count', 0), - 'deployed_node_count': role.get('deployed_node_count', 0), - 'deploying_node_count': role.get('deploying_node_count', 0), - 'waiting_node_count': role.get('waiting_node_count', 0), - 'error_node_count': role.get('error_node_count', 0), - 'planned_node_count': role.get('planned_node_count', 0), - 'icon': role.get('icon', ''), - } for role in data.get('roles', [])], - } - - def get(self, request, *args, **kwargs): - if request.META.get('HTTP_X_HORIZON_PROGRESS', ''): - # If it's an AJAX call for progress update, send it. - data = self.get_data(request, {}) - return http.HttpResponse( - json.dumps(self.get_progress_update(request, data)), - content_type='application/json', - ) - return super(IndexView, self).get(request, *args, **kwargs) - - def get_form(self, form_class): - return form_class(self.request, **self.get_form_kwargs()) - - def get_context_data(self, *args, **kwargs): - context = super(IndexView, self).get_context_data(*args, **kwargs) - context.update(self.get_data(self.request, context)) - return context - - def get_data(self, request, context, *args, **kwargs): - plan = api.tuskar.Plan.get_the_plan(request) - stack = self.get_stack() - form = context.get('form') - - context['plan'] = plan - context['stack'] = stack - - roles = [_get_role_data(plan, stack, form, role) - for role in plan.role_list] - context['roles'] = roles - - if stack: - context['show_last_events'] = True - failed_events = [e for e in stack.events - if 'FAILED' in e.resource_status and - 'aborted' not in e.resource_status_reason][-3:] - - if failed_events: - context['last_events_title'] = _('Last failed events') - context['last_events'] = failed_events - else: - context['last_events_title'] = _('Last event') - context['last_events'] = [stack.events[0]] - - if stack.is_deleting or stack.is_delete_failed: - # TODO(lsmola) since at this point we don't have total number - # of nodes we will hack this around, till API can show this - # information. So it will actually show progress like the total - # number is 10, or it will show progress of 5%. Ugly, but - # workable. - total_num_nodes_count = 10 - - try: - resources_count = len( - stack.resources(with_joins=False)) - except heatclient.exc.HTTPNotFound: - # Immediately after undeploying has started, heat returns - # this exception so we can take it as kind of init of - # undeploying. - resources_count = total_num_nodes_count - - # TODO(lsmola) same as hack above - total_num_nodes_count = max( - resources_count, total_num_nodes_count) - - context['progress'] = min(95, max( - 5, 100 * float(resources_count) / total_num_nodes_count)) - elif stack.is_deploying or stack.is_updating: - total = sum(d['total_node_count'] for d in roles) - context['progress'] = min(95, max( - 5, 100 * sum(float(d.get('deployed_node_count', 0)) - for d in roles) / (total or 1) - )) - else: - # stack is active - if not stack.is_failed: - context['show_last_events'] = False - context['progress'] = 100 - controller_role = plan.get_role_by_name("Controller") - context['admin_password'] = plan.parameter_value( - controller_role.parameter_prefix + 'AdminPassword') - - context['dashboard_urls'] = stack.dashboard_urls - no_proxy = [urlparse.urlparse(url).hostname - for url in stack.dashboard_urls] - context['no_proxy'] = ",".join(no_proxy) - context['auth_url'] = stack.keystone_auth_url - else: - messages = forms.validate_plan(request, plan) - context['plan_messages'] = messages - context['plan_invalid'] = any(message.get('is_critical') - for message in messages) - context['steps_message'] = _steps_message(messages) - return context - - def post(self, request, *args, **kwargs): - """If the post comes from ajax, return validation results as json.""" - - if not request.META.get('HTTP_X_HORIZON_VALIDATE', ''): - return super(IndexView, self).post(request, *args, **kwargs) - form_class = self.get_form_class() - form = self.get_form(form_class) - if form.is_valid(): - handled = form.handle(self.request, form.cleaned_data) - else: - handled = False - if handled: - messages = forms.validate_plan(request, form.plan) - else: - messages = [{ - 'text': _(u"Error saving the plan."), - 'is_critical': True, - }] - messages.extend({ - 'text': repr(error), - } for error in form.non_field_errors) - messages.extend({ - 'text': repr(error), - } for field in form.fields for error in field.errors) - # We need to unlazify all the lazy urls and translations. - return http.HttpResponse(json.dumps({ - 'plan_invalid': any(m.get('is_critical') for m in messages), - 'steps_message': _steps_message(messages), - 'messages': [{ - 'text': unicode(m.get('text', '')), - 'is_critical': m.get('is_critical', False), - 'indent': m.get('indent', 0), - 'classes': m['classes'], - } for m in messages], - }), content_type='application/json') - - -class DeployConfirmationView(horizon.forms.ModalFormView, views.StackMixin): - form_class = forms.DeployOvercloud - template_name = 'infrastructure/overview/deploy_confirmation.html' - submit_label = _("Deploy") - - def get_context_data(self, **kwargs): - context = super(DeployConfirmationView, - self).get_context_data(**kwargs) - plan = api.tuskar.Plan.get_the_plan(self.request) - - context['autogenerated_parameters'] = ( - plan.list_generated_parameters(with_prefix=False).keys()) - return context - - def get_success_url(self): - return reverse(INDEX_URL) - - -class UndeployConfirmationView(horizon.forms.ModalFormView, views.StackMixin): - form_class = forms.UndeployOvercloud - template_name = 'infrastructure/overview/undeploy_confirmation.html' - submit_label = _("Undeploy") - - def get_success_url(self): - return reverse(INDEX_URL) - - def get_context_data(self, **kwargs): - context = super(UndeployConfirmationView, - self).get_context_data(**kwargs) - context['stack_id'] = self.get_stack().id - return context - - def get_initial(self, **kwargs): - initial = super(UndeployConfirmationView, self).get_initial(**kwargs) - initial['stack_id'] = self.get_stack().id - return initial - - -class PostDeployInitView(horizon.forms.ModalFormView, views.StackMixin): - form_class = forms.PostDeployInit - template_name = 'infrastructure/overview/post_deploy_init.html' - submit_label = _("Initialize") - - def get_success_url(self): - return reverse(INDEX_URL) - - def get_context_data(self, **kwargs): - context = super(PostDeployInitView, - self).get_context_data(**kwargs) - context['stack_id'] = self.get_stack().id - return context - - def get_initial(self, **kwargs): - initial = super(PostDeployInitView, self).get_initial(**kwargs) - initial['stack_id'] = self.get_stack().id - initial['admin_email'] = getattr(self.request.user, 'email', '') - return initial - - -class ScaleOutView(horizon.forms.ModalFormView, views.StackMixin): - form_class = forms.ScaleOut - template_name = "infrastructure/overview/scale_out.html" - submit_label = _("Deploy Changes") - - def get_success_url(self): - return reverse(INDEX_URL) - - def get_form(self, form_class): - return form_class(self.request, **self.get_form_kwargs()) - - def get_context_data(self, *args, **kwargs): - context = super(ScaleOutView, self).get_context_data(*args, **kwargs) - plan = api.tuskar.Plan.get_the_plan(self.request) - form = context.get('form') - roles = [_get_role_data(plan, None, form, role) - for role in plan.role_list] - context.update({ - 'roles': roles, - 'plan': plan, - }) - return context - - -def _get_openrc_credentials(request): - plan = api.tuskar.Plan.get_the_plan(request) - stack = api.heat.Stack.get_by_plan(request, plan) - no_proxy = [urlparse.urlparse(url).hostname - for url in stack.dashboard_urls] - controller_role = plan.get_role_by_name("Controller") - credentials = dict(tenant_name='admin', - auth_url=stack.keystone_auth_url, - admin_password=plan.parameter_value( - controller_role.parameter_prefix + 'AdminPassword'), - no_proxy=",".join(no_proxy)) - return credentials - - -def download_overcloudrc_file(request): - template = 'infrastructure/overview/overcloudrc.sh.template' - try: - context = _get_openrc_credentials(request) - - response = shortcuts.render(request, - template, - context, - content_type="text/plain") - response['Content-Disposition'] = ('attachment; ' - 'filename="overcloudrc"') - response['Content-Length'] = str(len(response.content)) - return response - - except Exception as e: - LOG.exception("Exception in DownloadOvercloudrcForm.") - messages.error(request, _('Error Downloading RC File: %s') % e) - return shortcuts.redirect(request.build_absolute_uri()) diff --git a/tuskar_ui/infrastructure/parameters/__init__.py b/tuskar_ui/infrastructure/parameters/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/tuskar_ui/infrastructure/parameters/forms.py b/tuskar_ui/infrastructure/parameters/forms.py deleted file mode 100644 index a0e91e639..000000000 --- a/tuskar_ui/infrastructure/parameters/forms.py +++ /dev/null @@ -1,281 +0,0 @@ -# -*- coding: utf8 -*- -# -# 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 json -import logging - -import django.forms -from django.utils.datastructures import SortedDict -from django.utils.translation import ugettext_lazy as _ -import horizon.exceptions -import horizon.forms -import horizon.messages - -from tuskar_ui import api -import tuskar_ui.forms -from tuskar_ui.utils import utils - - -LOG = logging.getLogger(__name__) - - -VIRT_TYPE_CHOICES = [ - ('qemu', _("Virtualized (qemu)")), - ('kvm', _("Baremetal (kvm)")), -] - -CINDER_ISCSI_HELPER_CHOICES = [ - ('tgtadm', _('tgtadm')), - ('lioadm', _('lioadm')), -] - - -class ParameterAwareMixin(object): - parameter = None - - -def parameter_fields(request, prefix=None, read_only=False): - fields = SortedDict() - plan = api.tuskar.Plan.get_the_plan(request) - parameters = plan.parameter_list(include_key_parameters=False) - - for p in parameters: - if prefix and not p.name.startswith(prefix): - continue - Field = django.forms.CharField - field_kwargs = {} - widget = None - if read_only: - if p.hidden: - widget = tuskar_ui.forms.StaticTextPasswordWidget - else: - widget = tuskar_ui.forms.StaticTextWidget - else: - if p.hidden: - widget = django.forms.PasswordInput(render_value=True) - elif p.parameter_type == 'number': - Field = django.forms.IntegerField - elif p.parameter_type == 'boolean': - Field = django.forms.BooleanField - elif (p.parameter_type == 'string' and - p.get_constraint_by_type('allowed_values')): - Field = django.forms.ChoiceField - field_kwargs['choices'] = [ - (choice, choice) for choice in - p.get_constraint_by_type('allowed_values')['definition']] - elif (p.parameter_type in ['json', 'comma_delimited_list'] or - 'Certificate' in p.name): - widget = django.forms.Textarea - - fields[p.name] = Field( - required=False, - label=_parameter_label(p), - initial=p.value, - widget=widget, - **field_kwargs - ) - fields[p.name].__class__ = type('ParameterAwareField', - (ParameterAwareMixin, Field), {}) - fields[p.name].parameter = p - return fields - - -def _parameter_label(parameter): - return tuskar_ui.forms.label_with_tooltip( - parameter.label or utils.de_camel_case(parameter.stripped_name), - parameter.description) - - -class ServiceConfig(horizon.forms.SelfHandlingForm): - def __init__(self, *args, **kwargs): - super(ServiceConfig, self).__init__(*args, **kwargs) - self.fields.update(parameter_fields(self.request, read_only=True)) - - def global_fieldset(self): - return tuskar_ui.forms.fieldset(self, prefix='^(?!.*::)') - - def controller_fieldset(self): - return tuskar_ui.forms.fieldset(self, prefix='Controller-1') - - def compute_fieldset(self): - return tuskar_ui.forms.fieldset(self, prefix='Compute-1') - - def block_storage_fieldset(self): - return tuskar_ui.forms.fieldset(self, prefix='Cinder-Storage-1') - - def object_storage_fieldset(self): - return tuskar_ui.forms.fieldset(self, prefix='Swift-Storage-1') - - def ceph_storage_fieldset(self): - return tuskar_ui.forms.fieldset(self, prefix='Ceph-Storage-1') - - def handle(): - pass - - -class AdvancedEditServiceConfig(ServiceConfig): - def __init__(self, *args, **kwargs): - super(AdvancedEditServiceConfig, self).__init__(*args, **kwargs) - self.fields.update(parameter_fields(self.request)) - - def handle(self, request, data): - plan = api.tuskar.Plan.get_the_plan(self.request) - - # TODO(bcrochet): Commenting this out. - # For advanced config, we should have a whitelist of which params - # must be synced across roles. - # data = self._sync_common_params_across_roles(plan, data) - - try: - plan.patch(request, plan.uuid, data) - except Exception as e: - horizon.exceptions.handle( - request, - _("Unable to update the service configuration.")) - LOG.exception(e) - return False - else: - horizon.messages.success( - request, - _("Service configuration updated.")) - return True - - @staticmethod - def _sync_common_params_across_roles(plan, parameters_dict): - for (p_key, p_value) in parameters_dict.iteritems(): - for role in plan.role_list: - role_parameter_key = (role.parameter_prefix + - api.tuskar.strip_prefix(p_key)) - if role_parameter_key in parameters_dict: - parameters_dict[role_parameter_key] = p_value - return parameters_dict - - -class SimpleEditServiceConfig(horizon.forms.SelfHandlingForm): - virt_type = django.forms.ChoiceField( - label=_("Deployment Type"), - choices=VIRT_TYPE_CHOICES, - required=True, - help_text=_('If you are testing OpenStack in a virtual machine, ' - 'you must configure Compute to use qemu without KVM ' - 'and hardware virtualization.')) - neutron_public_interface = django.forms.CharField( - label=_("Public Interface"), - required=True, - initial='eth0', - help_text=_('What interface to bridge onto br-ex for network nodes. ' - 'If you are testing OpenStack in a virtual machine' - 'you must configure interface to eth0.')) - snmp_password = django.forms.CharField( - label=_("SNMP Password"), - required=True, - help_text=_('The user password for SNMPd with readonly ' - 'rights running on all Overcloud nodes'), - widget=django.forms.PasswordInput(render_value=True)) - cloud_name = django.forms.CharField( - label=_("Cloud name"), - required=True, - initial="overcloud", - help_text=_('The DNS name of this cloud. ' - 'E.g. ci-overcloud.tripleo.org')) - cinder_iscsi_helper = django.forms.ChoiceField( - label=_("Cinder ISCSI helper"), - choices=CINDER_ISCSI_HELPER_CHOICES, - required=True, - help_text=_('The iSCSI helper to use with cinder.')) - ntp_server = django.forms.CharField( - label=_("NTP server"), - required=False, - initial="", - help_text=_('Address of the NTP server. If blank, public NTP servers ' - 'will be used.')) - extra_config = django.forms.CharField( - label=_("Extra Config"), - required=False, - widget=django.forms.Textarea(attrs={'rows': 2}), - help_text=("Additional configuration to inject into the cluster." - "The data format of this field is JSON." - "See http://git.io/PuwLXQ for more information.")) - - def clean_extra_config(self): - data = self.cleaned_data['extra_config'] - try: - json.loads(data) - except Exception as json_error: - raise django.forms.ValidationError( - _("%(err_msg)s"), params={'err_msg': json_error.message}) - return data - - @staticmethod - def _load_additional_parameters(plan, data, form_key, param_name): - params = {} - param_value = data.get(form_key) - # Set the same parameter and value in all roles. - for role in plan.role_list: - key = role.parameter_prefix + param_name - if key in [parameter.name - for parameter in role.parameter_list(plan)]: - params[key] = param_value - - return params - - def handle(self, request, data): - plan = api.tuskar.Plan.get_the_plan(self.request) - compute_prefix = plan.get_role_by_name('Compute').parameter_prefix - controller_prefix = plan.get_role_by_name( - 'Controller').parameter_prefix - cinder_prefix = plan.get_role_by_name( - 'Cinder-Storage').parameter_prefix - - virt_type = data.get('virt_type') - neutron_public_interface = data.get('neutron_public_interface') - cloud_name = data.get('cloud_name') - cinder_iscsi_helper = data.get('cinder_iscsi_helper') - ntp_server = data.get('ntp_server') - - parameters = { - compute_prefix + 'NovaComputeLibvirtType': virt_type, - controller_prefix + 'CinderISCSIHelper': cinder_iscsi_helper, - cinder_prefix + 'CinderISCSIHelper': cinder_iscsi_helper, - controller_prefix + 'CloudName': cloud_name, - controller_prefix + 'NeutronPublicInterface': - neutron_public_interface, - compute_prefix + 'NeutronPublicInterface': - neutron_public_interface, - controller_prefix + 'NtpServer': - ntp_server, - compute_prefix + 'NtpServer': - ntp_server, - } - - parameters.update(self._load_additional_parameters( - plan, data, - 'snmp_password', 'SnmpdReadonlyUserPassword')) - parameters.update(self._load_additional_parameters( - plan, data, - 'extra_config', 'ExtraConfig')) - - try: - plan.patch(request, plan.uuid, parameters) - except Exception as e: - horizon.exceptions.handle( - request, - _("Unable to update the service configuration.")) - LOG.exception(e) - return False - else: - horizon.messages.success( - request, - _("Service configuration updated.")) - return True diff --git a/tuskar_ui/infrastructure/parameters/panel.py b/tuskar_ui/infrastructure/parameters/panel.py deleted file mode 100644 index 3c085ae04..000000000 --- a/tuskar_ui/infrastructure/parameters/panel.py +++ /dev/null @@ -1,26 +0,0 @@ -# -*- coding: utf8 -*- -# -# 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 tuskar_ui.infrastructure import dashboard - - -class Parameters(horizon.Panel): - name = _("Service Configuration") - slug = "parameters" - - -dashboard.Infrastructure.register(Parameters) diff --git a/tuskar_ui/infrastructure/parameters/templates/parameters/_simple_service_config.html b/tuskar_ui/infrastructure/parameters/templates/parameters/_simple_service_config.html deleted file mode 100644 index f8788d2fd..000000000 --- a/tuskar_ui/infrastructure/parameters/templates/parameters/_simple_service_config.html +++ /dev/null @@ -1,26 +0,0 @@ -{% extends "horizon/common/_modal_form.html" %} -{% load i18n %} -{% load url from future %} - -{% block form_id %}configuration_form{% endblock %} -{% block form_action %}{% url 'horizon:infrastructure:parameters:simple_service_configuration' %}{% endblock %} - -{% block modal_id %}provision_modal{% endblock %} -{% block modal-header %}{% trans "Service Configuration" %}{% endblock %} - -{% block modal-body %} -
-
- {% include "horizon/common/_form_fields.html" %} -
-
-
-

{% trans "Description:" %}

-

- {% trans "Configure values that cannot be defaulted" %} -

-

- {% trans "These values cannot be defaulted. Please choose values for them and save them before you deploy your overcloud." %} -

-
-{% endblock %} diff --git a/tuskar_ui/infrastructure/parameters/templates/parameters/advanced_service_config.html b/tuskar_ui/infrastructure/parameters/templates/parameters/advanced_service_config.html deleted file mode 100644 index 9f9955be5..000000000 --- a/tuskar_ui/infrastructure/parameters/templates/parameters/advanced_service_config.html +++ /dev/null @@ -1,88 +0,0 @@ -{% extends "infrastructure/base.html" %} -{% load i18n %} -{% load url from future %} -{% block title %}{% trans "Advanced Service Configuration" %}{% endblock %} - -{% block page_header %} - {% include 'horizon/common/_items_count_domain_page_header.html' with title=_('Advanced Service Configuration') %} -{% endblock %} - -{% block main %} -
-
{% csrf_token %} - - {% include 'horizon/common/_form_errors.html' with form=form %} - -
-
-
- {% for field in form.global_fieldset %} - {% include 'horizon/common/_horizontal_field.html' with field=field %} - {% endfor %} -
-
- {% for field in form.controller_fieldset %} - {% include 'horizon/common/_horizontal_field.html' with field=field %} - {% endfor %} -
-
- {% for field in form.compute_fieldset %} - {% include 'horizon/common/_horizontal_field.html' with field=field %} - {% endfor %} -
-
- {% for field in form.block_storage_fieldset %} - {% include 'horizon/common/_horizontal_field.html' with field=field %} - {% endfor %} -
-
- {% for field in form.object_storage_fieldset %} - {% include 'horizon/common/_horizontal_field.html' with field=field %} - {% endfor %} -
-
- {% for field in form.ceph_storage_fieldset %} - {% include 'horizon/common/_horizontal_field.html' with field=field %} - {% endfor %} -
-
-
-
-
- - -{% endblock %} diff --git a/tuskar_ui/infrastructure/parameters/templates/parameters/index.html b/tuskar_ui/infrastructure/parameters/templates/parameters/index.html deleted file mode 100644 index 1671e7077..000000000 --- a/tuskar_ui/infrastructure/parameters/templates/parameters/index.html +++ /dev/null @@ -1,76 +0,0 @@ -{% extends "infrastructure/base.html" %} -{% load i18n %} -{% load url from future %} -{% block title %}{% trans "Service Configuration" %}{% endblock %} - -{% block page_header %} - {% include 'horizon/common/_items_count_domain_page_header.html' with title=_('Service Configuration') %} -{% endblock %} - -{% block main %} -
-
- {% include 'horizon/common/_form_errors.html' with form=form %} - -
-
-
- {% for field in form.global_fieldset %} - {% include 'horizon/common/_horizontal_field.html' with field=field %} - {% endfor %} -
-
- {% for field in form.controller_fieldset %} - {% include 'horizon/common/_horizontal_field.html' with field=field %} - {% endfor %} -
-
- {% for field in form.compute_fieldset %} - {% include 'horizon/common/_horizontal_field.html' with field=field %} - {% endfor %} -
-
- {% for field in form.block_storage_fieldset %} - {% include 'horizon/common/_horizontal_field.html' with field=field %} - {% endfor %} -
-
- {% for field in form.object_storage_fieldset %} - {% include 'horizon/common/_horizontal_field.html' with field=field %} - {% endfor %} -
-
- {% for field in form.ceph_storage_fieldset %} - {% include 'horizon/common/_horizontal_field.html' with field=field %} - {% endfor %} -
-
-
-
-
- - -{% endblock %} diff --git a/tuskar_ui/infrastructure/parameters/templates/parameters/simple_service_config.html b/tuskar_ui/infrastructure/parameters/templates/parameters/simple_service_config.html deleted file mode 100644 index 803fc3bb0..000000000 --- a/tuskar_ui/infrastructure/parameters/templates/parameters/simple_service_config.html +++ /dev/null @@ -1,11 +0,0 @@ -{% extends "base.html" %} -{% load i18n %} -{% block title %}{% trans "Simple Service Configuration" %}{% endblock %} - -{% block page_header %} - {% include "horizon/common/_page_header.html" with title=_("Simple Service Configuration") %} -{% endblock %} - -{% block main %} - {% include "infrastructure/parameters/_simple_service_config.html" %} -{% endblock %} diff --git a/tuskar_ui/infrastructure/parameters/tests.py b/tuskar_ui/infrastructure/parameters/tests.py deleted file mode 100644 index 0d008884e..000000000 --- a/tuskar_ui/infrastructure/parameters/tests.py +++ /dev/null @@ -1,130 +0,0 @@ -# -*- coding: utf8 -*- -# -# 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 contextlib - -from django.core import urlresolvers -from mock import patch, call, ANY # noqa -from openstack_dashboard.test.test_data import utils - -from tuskar_ui import api -from tuskar_ui.test import helpers as test -from tuskar_ui.test.test_data import tuskar_data - - -INDEX_URL = urlresolvers.reverse( - 'horizon:infrastructure:parameters:index') -SIMPLE_SERVICE_CONFIG_URL = urlresolvers.reverse( - 'horizon:infrastructure:parameters:simple_service_configuration') -ADVANCED_SERVICE_CONFIG_URL = urlresolvers.reverse( - 'horizon:infrastructure:parameters:advanced_service_configuration') - -TEST_DATA = utils.TestDataContainer() -tuskar_data.data(TEST_DATA) - - -class ParametersTest(test.BaseAdminViewTests): - - def test_index(self): - plans = [api.tuskar.Plan(plan) - for plan in self.tuskarclient_plans.list()] - roles = [api.tuskar.Role(role) - for role in self.tuskarclient_roles.list()] - - with contextlib.nested( - patch('tuskar_ui.api.tuskar.Plan.list', - return_value=plans), - patch('tuskar_ui.api.tuskar.Role.list', - return_value=roles), - ): - res = self.client.get(INDEX_URL) - - self.assertTemplateUsed(res, 'infrastructure/parameters/index.html') - - def test_simple_service_config_get(self): - plan = api.tuskar.Plan(self.tuskarclient_plans.first()) - role = api.tuskar.Role(self.tuskarclient_roles.first()) - with contextlib.nested( - patch('tuskar_ui.api.tuskar.Plan.get_the_plan', - return_value=plan), - patch('tuskar_ui.api.tuskar.Plan.get_role_by_name', - return_value=role), - ): - res = self.client.get(SIMPLE_SERVICE_CONFIG_URL) - self.assertTemplateUsed( - res, 'infrastructure/parameters/simple_service_config.html') - - def test_advanced_service_config_post(self): - plan = api.tuskar.Plan(self.tuskarclient_plans.first()) - roles = [api.tuskar.Role(role) - for role in self.tuskarclient_roles.list()] - parameters = [api.tuskar.Parameter(p, plan=self) - for p in plan.parameters] - - data = {p.name: unicode(p.value) for p in parameters} - - with contextlib.nested( - patch('tuskar_ui.api.tuskar.Plan.get_the_plan', - return_value=plan), - patch('tuskar_ui.api.tuskar.Plan.role_list', - return_value=roles), - patch('tuskar_ui.api.tuskar.Plan.parameter_list', - return_value=parameters), - patch('tuskar_ui.api.tuskar.Plan.patch', - return_value=plan), - ) as (get_the_plan, role_list, parameter_list, plan_patch): - res = self.client.post(ADVANCED_SERVICE_CONFIG_URL, data) - - self.assertRedirectsNoFollow(res, INDEX_URL) - - plan_patch.assert_called_once_with(ANY, plan.uuid, data) - - def test_simple_service_config_post(self): - plan = api.tuskar.Plan(self.tuskarclient_plans.first()) - roles = [api.tuskar.Role(role) for role in - self.tuskarclient_roles.list()] - plan.role_list = roles - - data = { - 'virt_type': 'qemu', - 'snmp_password': 'password', - 'cinder_iscsi_helper': 'lioadm', - 'cloud_name': 'cloud_name', - 'neutron_public_interface': 'eth0', - 'extra_config': '{}' - } - with contextlib.nested( - patch('tuskar_ui.api.tuskar.Plan.get_the_plan', - return_value=plan), - patch('tuskar_ui.api.tuskar.Plan.patch', - return_value=plan), - patch('tuskar_ui.api.tuskar.Plan.get_role_by_name', - return_value=roles[0]), - ) as (get_the_plan, plan_patch, get_role_by_name): - res = self.client.post(SIMPLE_SERVICE_CONFIG_URL, data) - - self.assertRedirectsNoFollow(res, INDEX_URL) - - plan_patch.assert_called_once_with(ANY, plan.uuid, { - 'Controller-1::CloudName': u'cloud_name', - 'Controller-1::SnmpdReadonlyUserPassword': u'password', - 'Controller-1::NeutronPublicInterface': u'eth0', - 'Controller-1::CinderISCSIHelper': u'lioadm', - 'Controller-1::NovaComputeLibvirtType': u'qemu', - 'Compute-1::SnmpdReadonlyUserPassword': u'password', - 'Controller-1::NtpServer': u'', - 'Controller-1::ExtraConfig': u'{}', - 'Compute-1::ExtraConfig': u'{}', - 'Block Storage-1::ExtraConfig': u'{}', - 'Object Storage-1::ExtraConfig': u'{}'}) diff --git a/tuskar_ui/infrastructure/parameters/urls.py b/tuskar_ui/infrastructure/parameters/urls.py deleted file mode 100644 index efadf7ea1..000000000 --- a/tuskar_ui/infrastructure/parameters/urls.py +++ /dev/null @@ -1,29 +0,0 @@ -# -*- coding: utf8 -*- -# -# 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 urls - -from tuskar_ui.infrastructure.parameters import views - - -urlpatterns = urls.patterns( - '', - urls.url(r'^$', views.IndexView.as_view(), name='index'), - urls.url(r'^simple-service-config$', - views.SimpleServiceConfigView.as_view(), - name='simple_service_configuration'), - urls.url(r'^advanced-service-config$', - views.AdvancedServiceConfigView.as_view(), - name='advanced_service_configuration'), -) diff --git a/tuskar_ui/infrastructure/parameters/views.py b/tuskar_ui/infrastructure/parameters/views.py deleted file mode 100644 index b7748299e..000000000 --- a/tuskar_ui/infrastructure/parameters/views.py +++ /dev/null @@ -1,107 +0,0 @@ -# -*- coding: utf8 -*- -# -# 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.core.urlresolvers import reverse_lazy -from django.utils.translation import ugettext_lazy as _ -import horizon.forms -import horizon.tables - -from tuskar_ui import api -from tuskar_ui.infrastructure.parameters import forms - - -class SimpleServiceConfigView(horizon.forms.ModalFormView): - form_class = forms.SimpleEditServiceConfig - success_url = reverse_lazy('horizon:infrastructure:parameters:index') - submit_label = _("Save Configuration") - template_name = "infrastructure/parameters/simple_service_config.html" - - def get_initial(self): - plan = api.tuskar.Plan.get_the_plan(self.request) - compute_prefix = plan.get_role_by_name('Compute').parameter_prefix - controller_prefix = plan.get_role_by_name( - 'Controller').parameter_prefix - - cinder_iscsi_helper = plan.parameter_value( - controller_prefix + 'CinderISCSIHelper') - cloud_name = plan.parameter_value( - controller_prefix + 'CloudName') - extra_config = plan.parameter_value( - controller_prefix + 'ExtraConfig') - neutron_public_interface = plan.parameter_value( - controller_prefix + 'NeutronPublicInterface') - ntp_server = plan.parameter_value( - controller_prefix + 'NtpServer') - snmp_password = plan.parameter_value( - controller_prefix + 'SnmpdReadonlyUserPassword') - virt_type = plan.parameter_value( - compute_prefix + 'NovaComputeLibvirtType') - return { - 'cinder_iscsi_helper': cinder_iscsi_helper, - 'cloud_name': cloud_name, - 'neutron_public_interface': neutron_public_interface, - 'ntp_server': ntp_server, - 'extra_config': extra_config, - 'neutron_public_interface': neutron_public_interface, - 'snmp_password': snmp_password, - 'virt_type': virt_type} - - -class IndexView(horizon.forms.ModalFormView): - form_class = forms.ServiceConfig - form_id = "service_config" - template_name = "infrastructure/parameters/index.html" - - def get_initial(self): - self.plan = api.tuskar.Plan.get_the_plan(self.request) - self.parameters = self.plan.parameter_list( - include_key_parameters=False) - return {p.name: p.value for p in self.parameters} - - def get_context_data(self, **kwargs): - context = super(IndexView, self).get_context_data(**kwargs) - advanced_edit_action = { - 'name': _('Advanced Configuration'), - 'url': reverse('horizon:infrastructure:parameters:' - 'advanced_service_configuration'), - 'icon': 'fa-pencil', - 'ajax_modal': False, - } - simplified_edit_action = { - 'name': _('Simplified Configuration'), - 'url': reverse('horizon:infrastructure:parameters:' - 'simple_service_configuration'), - 'icon': 'fa-pencil-square-o', - 'ajax_modal': True, - } - context['header_actions'] = [advanced_edit_action, - simplified_edit_action] - return context - - -class AdvancedServiceConfigView(IndexView): - form_class = forms.AdvancedEditServiceConfig - form_id = "advanced_service_config" - success_url = reverse_lazy('horizon:infrastructure:parameters:index') - submit_label = _("Save Configuration") - submit_url = reverse_lazy('horizon:infrastructure:parameters:' - 'advanced_service_configuration') - template_name = "infrastructure/parameters/advanced_service_config.html" - - def get_context_data(self, **kwargs): - context = super(AdvancedServiceConfigView, - self) .get_context_data(**kwargs) - context['header_actions'] = [] - return context diff --git a/tuskar_ui/infrastructure/roles/__init__.py b/tuskar_ui/infrastructure/roles/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/tuskar_ui/infrastructure/roles/panel.py b/tuskar_ui/infrastructure/roles/panel.py deleted file mode 100644 index 3a2041f63..000000000 --- a/tuskar_ui/infrastructure/roles/panel.py +++ /dev/null @@ -1,26 +0,0 @@ -# -*- coding: utf8 -*- -# -# 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 tuskar_ui.infrastructure import dashboard - - -class Roles(horizon.Panel): - name = _("Deployment Roles") - slug = "roles" - - -dashboard.Infrastructure.register(Roles) diff --git a/tuskar_ui/infrastructure/roles/tables.py b/tuskar_ui/infrastructure/roles/tables.py deleted file mode 100644 index 8da446ca4..000000000 --- a/tuskar_ui/infrastructure/roles/tables.py +++ /dev/null @@ -1,66 +0,0 @@ -# -*- coding: utf8 -*- -# -# 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 tables - -from tuskar_ui import api -from tuskar_ui.infrastructure.nodes import tables as nodes_tables - - -class UpdateRole(tables.LinkAction): - name = "update" - verbose_name = _("Edit Role") - url = "horizon:infrastructure:roles:update" - classes = ("ajax-modal",) - icon = "pencil" - - def allowed(self, request, datum): - plan = api.tuskar.Plan.get_the_plan(request) - - if datum.id in [role.id for role in plan.role_list]: - return True - return False - - -class RolesTable(tables.DataTable): - - name = tables.Column('name', - link="horizon:infrastructure:roles:detail", - verbose_name=_("Role")) - flavor = tables.Column('flavor', - verbose_name=_("Flavor")) - image = tables.Column('image', - verbose_name=_("Image")) - - def get_object_id(self, datum): - return datum.uuid - - class Meta(object): - name = "roles" - verbose_name = _("Deployment Roles") - table_actions = () - row_actions = (UpdateRole,) - template = "horizon/common/_enhanced_data_table.html" - - -class NodeTable(nodes_tables.ProvisionedNodesTable): - - class Meta(object): - name = "nodetable" - verbose_name = _("Nodes") - hidden_title = False - table_actions = () - row_actions = () - template = "horizon/common/_enhanced_data_table.html" diff --git a/tuskar_ui/infrastructure/roles/templates/roles/config.html b/tuskar_ui/infrastructure/roles/templates/roles/config.html deleted file mode 100644 index 854871b46..000000000 --- a/tuskar_ui/infrastructure/roles/templates/roles/config.html +++ /dev/null @@ -1,24 +0,0 @@ - -
-
-
- {% include "horizon/common/_horizontal_fields.html" %} -
-
-
- diff --git a/tuskar_ui/infrastructure/roles/templates/roles/detail.html b/tuskar_ui/infrastructure/roles/templates/roles/detail.html deleted file mode 100644 index 0839ca0d2..000000000 --- a/tuskar_ui/infrastructure/roles/templates/roles/detail.html +++ /dev/null @@ -1,39 +0,0 @@ -{% extends 'infrastructure/base.html' %} -{% load i18n %} -{% load url from future %} -{% block title %}{% trans 'Role' %}: {{ role.name }}{% endblock %} - -{% block page_header %} - {% include 'horizon/common/_domain_page_header.html' with title=_('Deployment Role: ')|add:role.name %} -{% endblock page_header %} - -{% block main %} - -
-
-

{% trans "Properties" %}

-

{% blocktrans count counter=nodes|length %}{{ counter }} instance{% plural %}{{ counter }} instances{% endblocktrans %}

-
-
{% trans 'Flavor' %}
- {% if flavor %} -
{{ flavor.name }} {{ flavor.get_keys.cpu_arch }} | {{ flavor.vcpus }} {% trans "CPU" %} | {{ flavor.ram }} {% trans "MB RAM" %} | {{ flavor.disk }} {% trans "GB HDD" %}
- {% else %} -
{% trans 'No flavor associated' %}
- {% endif %} -
{% trans 'Image' %}
- {% if image %} -
{{ image.name }}
- {% else %} -
{% trans 'No image associated' %}
- {% endif %} -
-
-
-

{% trans "Performance & Metrics" %}

- {% url 'horizon:infrastructure:roles:performance' role.uuid as node_perf_url %} - {% include "infrastructure/_performance_chart_box.html" with meter_conf=meter_conf node_perf_url=node_perf_url col_size=4 %} -
-
- -{{ table.render }} -{% endblock %} diff --git a/tuskar_ui/infrastructure/roles/templates/roles/index.html b/tuskar_ui/infrastructure/roles/templates/roles/index.html deleted file mode 100644 index b17ae2300..000000000 --- a/tuskar_ui/infrastructure/roles/templates/roles/index.html +++ /dev/null @@ -1,16 +0,0 @@ -{% extends 'infrastructure/base.html' %} -{% load i18n %} -{% block title %}{% trans 'Deployment Roles' %}{% endblock %} - -{% block page_header %} - {% include 'horizon/common/_items_count_domain_page_header.html' with title=_('Deployment Roles') %} -{% endblock page_header %} - -{% block main %} -
-
- {{ table.render }} -
-
- -{% endblock %} diff --git a/tuskar_ui/infrastructure/roles/templates/roles/info.html b/tuskar_ui/infrastructure/roles/templates/roles/info.html deleted file mode 100644 index 2909bd1cf..000000000 --- a/tuskar_ui/infrastructure/roles/templates/roles/info.html +++ /dev/null @@ -1,11 +0,0 @@ - -
-
-
- {% include "horizon/common/_horizontal_fields.html" %} -
-
-
- {{ step.get_help_text }} -
-
diff --git a/tuskar_ui/infrastructure/roles/tests.py b/tuskar_ui/infrastructure/roles/tests.py deleted file mode 100644 index d9ee2057b..000000000 --- a/tuskar_ui/infrastructure/roles/tests.py +++ /dev/null @@ -1,166 +0,0 @@ -# -*- coding: utf8 -*- -# -# 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 contextlib - -from django.core import urlresolvers -from openstack_dashboard.test.test_data import utils -from mock import patch, call # noqa - -from tuskar_ui import api -from tuskar_ui.test import helpers as test -from tuskar_ui.test.test_data import flavor_data -from tuskar_ui.test.test_data import heat_data -from tuskar_ui.test.test_data import node_data -from tuskar_ui.test.test_data import tuskar_data - - -INDEX_URL = urlresolvers.reverse( - 'horizon:infrastructure:roles:index') -DETAIL_URL = urlresolvers.reverse( - 'horizon:infrastructure:roles:detail', args=('role-1',)) -UPDATE_URL = urlresolvers.reverse( - 'horizon:infrastructure:roles:update', args=('role-1',)) - -TEST_DATA = utils.TestDataContainer() -flavor_data.data(TEST_DATA) -node_data.data(TEST_DATA) -heat_data.data(TEST_DATA) -tuskar_data.data(TEST_DATA) - - -class RolesTest(test.BaseAdminViewTests): - - def test_index_get(self): - roles = [api.tuskar.Role(role) - for role in self.tuskarclient_roles.list()] - plans = [api.tuskar.Plan(plan) - for plan in self.tuskarclient_plans.list()] - flavor = self.novaclient_flavors.first() - images = self.glanceclient_images.list() - - with contextlib.nested( - patch('tuskar_ui.api.tuskar.Plan.list', - return_value=plans), - patch('tuskar_ui.api.tuskar.Role.list', - return_value=roles), - patch('openstack_dashboard.api.glance.image_list_detailed', - return_value=[images]), - patch('tuskar_ui.api.flavor.Flavor.get_by_name', - return_value=flavor)): - res = self.client.get(INDEX_URL) - - self.assertTemplateUsed(res, 'infrastructure/roles/index.html') - - def test_detail_get(self): - roles = [api.tuskar.Role(role) - for role in self.tuskarclient_roles.list()] - plans = [api.tuskar.Plan(plan) - for plan in self.tuskarclient_plans.list()] - flavor = self.novaclient_flavors.first() - images = self.glanceclient_images.list() - stack = api.heat.Stack(TEST_DATA.heatclient_stacks.first()) - - with contextlib.nested( - patch('tuskar_ui.api.tuskar.Role.list', - return_value=roles), - patch('tuskar_ui.api.heat.Stack.get_by_plan', - return_value=stack), - patch('tuskar_ui.api.heat.Stack.events', - return_value=[]), - patch('tuskar_ui.api.heat.Stack.resources', - return_value=[]), - patch('tuskar_ui.api.tuskar.Plan.list', - return_value=plans), - patch('openstack_dashboard.api.glance.image_list_detailed', - return_value=[images]), - patch('tuskar_ui.api.flavor.Flavor.get_by_name', - return_value=flavor)): - res = self.client.get(DETAIL_URL) - - self.assertTemplateUsed( - res, 'infrastructure/roles/detail.html') - - def test_update_get(self): - roles = [api.tuskar.Role(role) - for role in self.tuskarclient_roles.list()] - plans = [api.tuskar.Plan(plan) - for plan in self.tuskarclient_plans.list()] - flavors = self.novaclient_flavors.list() - images = self.glanceclient_images.list() - stack = api.heat.Stack(TEST_DATA.heatclient_stacks.first()) - - with contextlib.nested( - patch('tuskar_ui.api.tuskar.Role.list', - return_value=roles), - patch('tuskar_ui.api.heat.Stack.get_by_plan', - return_value=stack), - patch('tuskar_ui.api.heat.Stack.events', - return_value=[]), - patch('tuskar_ui.api.heat.Stack.resources', - return_value=[]), - patch('tuskar_ui.api.tuskar.Plan.list', - return_value=plans), - patch('openstack_dashboard.api.glance.image_get', - return_value=images[0]), - patch('tuskar_ui.api.flavor.Flavor.list', - return_value=flavors), - patch('openstack_dashboard.api.glance.image_list_detailed', - return_value=[images])): - - res = self.client.get(UPDATE_URL) - - # Check that the expected fields are in the form: - self.assertIn('id="id_flavor" name="flavor"', res.content) - self.assertIn('id="id_image" name="image"', res.content) - self.assertIn('flavor-1', res.content) - self.assertIn('flavor-2', res.content) - - def test_update_post(self): - plan = api.tuskar.Plan(self.tuskarclient_plans.first()) - role = api.tuskar.Role(self.tuskarclient_roles.first()) - flavors = self.novaclient_flavors.list() - images = self.glanceclient_images.list() - - data = { - 'name': 'controller', - 'description': 'The controller node role.', - 'flavor': self.novaclient_flavors.first().name, - 'image': self.glanceclient_images.first().name, - 'nodes': '0', - } - - with contextlib.nested( - patch('tuskar_ui.api.flavor.Flavor.list', - return_value=flavors), - patch('openstack_dashboard.api.glance.image_list_detailed', - return_value=[images]), - patch('openstack_dashboard.api.glance.image_get', - return_value=images[0]), - patch('tuskar_ui.api.tuskar.Role.get', - return_value=role), - patch('tuskar_ui.api.tuskar.Plan.patch', - return_value=plan), - patch('tuskar_ui.api.tuskar.Plan.get_the_plan', - return_value=plan)) as mocks: - - mock_patch = mocks[4] - res = self.client.post(UPDATE_URL, data) - self.assertRedirectsNoFollow(res, INDEX_URL) - - self.assertEqual(len(mock_patch.call_args_list), 1) - args = mock_patch.call_args_list[0][0] - self.assertEqual(args[1], plan.id) - self.assertEqual(args[2]['Controller-1::Flavor'], u'flavor-1') - self.assertEqual(args[2]['Controller-1::Image'], u'overcloud-full') diff --git a/tuskar_ui/infrastructure/roles/urls.py b/tuskar_ui/infrastructure/roles/urls.py deleted file mode 100644 index 9dfdb7787..000000000 --- a/tuskar_ui/infrastructure/roles/urls.py +++ /dev/null @@ -1,29 +0,0 @@ -# -*- coding: utf8 -*- -# -# 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 urls - -from tuskar_ui.infrastructure.roles import views - - -urlpatterns = urls.patterns( - '', - urls.url(r'^$', views.IndexView.as_view(), name='index'), - urls.url(r'^(?P[^/]+)/$', views.DetailView.as_view(), - name='detail'), - urls.url(r'^(?P[^/]+)/edit$', views.UpdateView.as_view(), - name='update'), - urls.url(r'^(?P[^/]+)/performance/$', - views.PerformanceView.as_view(), name='performance'), -) diff --git a/tuskar_ui/infrastructure/roles/views.py b/tuskar_ui/infrastructure/roles/views.py deleted file mode 100644 index b5cb5801c..000000000 --- a/tuskar_ui/infrastructure/roles/views.py +++ /dev/null @@ -1,191 +0,0 @@ -# -*- coding: utf8 -*- -# -# 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 json - -from django.core.urlresolvers import reverse -from django import http -from django.utils.translation import ugettext_lazy as _ -from django.views.generic import base -from glanceclient import exc as glance_exc -from horizon import tables as horizon_tables -from horizon import utils -from horizon import workflows -from openstack_dashboard.api import base as api_base - -from tuskar_ui import api -from tuskar_ui.infrastructure.roles import tables -from tuskar_ui.infrastructure.roles import workflows as role_workflows -from tuskar_ui.infrastructure import views -from tuskar_ui.utils import metering as metering_utils - - -INDEX_URL = 'horizon:infrastructure:roles:index' - - -class IndexView(views.ItemCountMixin, horizon_tables.DataTableView): - table_class = tables.RolesTable - template_name = "infrastructure/roles/index.html" - - @utils.memoized.memoized - def get_data(self): - roles = api.tuskar.Role.list(self.request) - plan = api.tuskar.Plan.get_the_plan(self.request) - for role in roles: - role_flavor = role.flavor(plan) - try: - role_image = role.image(plan) - except glance_exc.HTTPNotFound: - # Glance returns a 404 if the image doesn't exist - role_image = None - if role_flavor: - role.flavor = role_flavor.name - else: - role.flavor = _('Unknown') - if role_image: - role.image = role_image.name - else: - role.image = _('Unknown') - - return roles - - -class DetailView(horizon_tables.DataTableView, views.RoleMixin, - views.StackMixin): - table_class = tables.NodeTable - template_name = 'infrastructure/roles/detail.html' - - @utils.memoized.memoized - def _get_nodes(self, stack, role): - resources = stack.resources(role=role, with_joins=True) - nodes = [r.node for r in resources] - for node in nodes: - try: - resource = api.heat.Resource.get_by_node(self.request, node) - except LookupError: - node.role_name = '-' - else: - node.role_name = resource.role.name - node.role_id = resource.role.id - node.stack_id = resource.stack.id - - return nodes - - def get_data(self): - redirect = reverse(INDEX_URL) - stack = self.get_stack() - if stack: - role = self.get_role(redirect) - return self._get_nodes(stack, role) - return [] - - def get_context_data(self, **kwargs): - context = super(DetailView, self).get_context_data(**kwargs) - redirect = reverse(INDEX_URL) - - plan = api.tuskar.Plan.get_the_plan(self.request) - stack = self.get_stack() - role = self.get_role(redirect) - - context['role'] = role - if stack: - context['nodes'] = self._get_nodes(stack, role) - else: - context['nodes'] = [] - context['flavor'] = role.flavor(plan) - context['image'] = role.image(plan) - - if stack: - if api_base.is_service_enabled(self.request, 'metering'): - # Meter configuration in the following format: - # (meter label, url part, barchart (True/False)) - context['meter_conf'] = ( - (_('System Load'), - metering_utils.url_part('hardware.cpu.load.1min', False), - None), - (_('CPU Utilization'), - metering_utils.url_part('hardware.system_stats.cpu.util', - True), - '100'), - (_('Swap Utilization'), - metering_utils.url_part('hardware.memory.swap.util', - True), - '100'), - ) - return context - - -class UpdateView(workflows.WorkflowView, views.StackMixin, views.RoleMixin): - workflow_class = role_workflows.UpdateRole - - def get_initial(self): - plan = self.get_plan() - role = self.get_role() - - stack = self.get_stack() - if stack: - resources = stack.resources(role=role, with_joins=True) - role_nodes = len(resources) - else: - role_nodes = 0 - - role_flavor = role.flavor(plan) - role_flavor = '' if role_flavor is None else role_flavor.name - - role_image = role.image(plan) - role_image = '' if role_image is None else role_image.name - - free_nodes = len(api.node.Node.list(self.request, associated=False, - maintenance=False)) - available_nodes = role_nodes + free_nodes - - return { - 'role_id': role.id, - 'name': role.name, - 'flavor': role_flavor, - 'image': role_image, - 'nodes': role_nodes, - 'available_nodes': available_nodes, - } - - -class PerformanceView(base.TemplateView, views.RoleMixin, views.StackMixin): - def get(self, request, *args, **kwargs): - meter = request.GET.get('meter') - date_options = request.GET.get('date_options') - date_from = request.GET.get('date_from') - date_to = request.GET.get('date_to') - stats_attr = request.GET.get('stats_attr', 'avg') - barchart = bool(request.GET.get('barchart')) - - role = self.get_role() - stack = self.get_stack() - instances = stack.resources(role=role, with_joins=True) - - if instances: - instance_uuids = [i.physical_resource_id for i in instances] - json_output = metering_utils.get_nodes_stats( - request=request, - node_uuid=None, - instance_uuids=instance_uuids, - meter=meter, - date_options=date_options, - date_from=date_from, - date_to=date_to, - stats_attr=stats_attr, - barchart=barchart) - else: - json_output = None - - return http.HttpResponse(json.dumps(json_output), - content_type='application/json') diff --git a/tuskar_ui/infrastructure/roles/workflows.py b/tuskar_ui/infrastructure/roles/workflows.py deleted file mode 100644 index b97861121..000000000 --- a/tuskar_ui/infrastructure/roles/workflows.py +++ /dev/null @@ -1,180 +0,0 @@ -# -*- coding: utf8 -*- -# -# 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_lazy -import django.forms -from django.utils.translation import ugettext_lazy as _ -from horizon import exceptions -from horizon import forms -from horizon import workflows -from openstack_dashboard.api import glance - -from tuskar_ui import api -import tuskar_ui.forms -from tuskar_ui.infrastructure.flavors import utils -from tuskar_ui.infrastructure.parameters import forms as parameters_forms -from tuskar_ui.utils import utils as tuskar_utils - - -class UpdateRoleInfoAction(workflows.Action): - # TODO(rdopiera) Make the name and description editable. - name = forms.CharField( - label=_("Name"), - required=False, - widget=tuskar_ui.forms.StaticTextWidget - ) - description = forms.CharField( - label=_("Description"), - required=False, - widget=tuskar_ui.forms.StaticTextWidget - ) - flavor = forms.ChoiceField( - label=_("Flavor"), - ) - image = forms.ChoiceField( - label=_("Image"), - ) - nodes = forms.IntegerField( - label=_("Number of Nodes"), - required=False, - initial=0, - ) - - class Meta(object): - name = _("Overall Settings") - slug = 'update_role_info' - help_text = _("Edit the role details.") - - def __init__(self, request, context, *args, **kwargs): - super(UpdateRoleInfoAction, self).__init__(request, context, *args, - **kwargs) - self.available_nodes = context['available_nodes'] - self.fields['nodes'].widget = tuskar_ui.forms.NumberInput(attrs={ - 'min': 0, - 'max': self.available_nodes, - }) - self.fields['nodes'].help_text = _( - "{0} nodes available").format(self.available_nodes) - if not utils.matching_deployment_mode(): - del self.fields['flavor'] - - def populate_flavor_choices(self, request, context): - flavors = api.flavor.Flavor.list(self.request) - choices = [(f.name, f.name) for f in flavors] - return [('', _('Unknown'))] + choices - - def populate_image_choices(self, request, context): - images = glance.image_list_detailed(self.request)[0] - images = [image for image in images - if tuskar_utils.check_image_type(image, - 'overcloud provisioning')] - choices = [(i.name, i.name) for i in images] - return [('', _('Unknown'))] + choices - - def clean_nodes(self): - new_count = int(self.cleaned_data['nodes'] or 0) - if new_count > self.available_nodes: - raise django.forms.ValidationError(_( - "There are only {0} nodes available " - "for the selected flavor." - ).format(self.available_nodes)) - return str(new_count) - - def handle(self, request, context): - return { - 'name': self.cleaned_data['name'], - 'description': self.cleaned_data['description'], - 'flavor': self.cleaned_data.get('flavor'), - 'image': self.cleaned_data['image'], - 'nodes': self.cleaned_data['nodes'], - } - - -class UpdateRoleConfigAction(workflows.Action): - class Meta(object): - name = _("Service Configuration") - slug = 'update_role_config' - help_text = _("Edit the role's services configuration.") - - def __init__(self, request, context, *args, **kwargs): - super(UpdateRoleConfigAction, self).__init__(request, context, - *args, **kwargs) - self.fields.update( - parameters_forms.parameter_fields( - request, - prefix='%s-1::' % context['name']), - ) - - def handle(self, request, context): - return {'parameters': self.cleaned_data} - - -class UpdateRoleInfo(workflows.Step): - action_class = UpdateRoleInfoAction - depends_on = ("role_id", "available_nodes") - contributes = ("name", "description", "flavor", "image", "nodes") - template_name = 'infrastructure/roles/info.html' - - -class UpdateRoleConfig(workflows.Step): - action_class = UpdateRoleConfigAction - depends_on = ("role_id", "name") - contributes = ("parameters",) - template_name = 'infrastructure/roles/config.html' - - -class UpdateRole(workflows.Workflow): - slug = "update_role" - finalize_button_name = _("Save") - success_message = _('Modified role "%s".') - failure_message = _('Unable to modify role "%s".') - index_url = "horizon:infrastructure:roles:index" - default_steps = ( - UpdateRoleInfo, - UpdateRoleConfig, - ) - success_url = reverse_lazy( - 'horizon:infrastructure:roles:index') - - def name(self): - # Use context_seed here, as context['name'] returns empty - # as it's one of the fields. - return _('Edit Role "%s"') % self.context_seed['name'] - - def format_status_message(self, message): - # Use context_seed here, as context['name'] returns empty - # as it's one of the fields. - return message % self.context_seed['name'] - - def handle(self, request, data): - # save it! - role_id = data['role_id'] - try: - # Get initial role information - plan = api.tuskar.Plan.get_the_plan(self.request) - role = api.tuskar.Role.get(self.request, role_id) - except Exception: - exceptions.handle( - self.request, - _('Unable to retrieve role details.'), - redirect=reverse_lazy(self.index_url)) - - parameters = data['parameters'] - parameters[role.image_parameter_name] = data['image'] - parameters[role.node_count_parameter_name] = data['nodes'] - if utils.matching_deployment_mode(): - parameters[role.flavor_parameter_name] = data['flavor'] - - plan.patch(request, plan.uuid, parameters) - # TODO(rdopiera) Find out how to update role's name and description. - return True diff --git a/tuskar_ui/infrastructure/static/infrastructure/images/chevron.png b/tuskar_ui/infrastructure/static/infrastructure/images/chevron.png deleted file mode 100644 index 4e4cef9e7..000000000 Binary files a/tuskar_ui/infrastructure/static/infrastructure/images/chevron.png and /dev/null differ diff --git a/tuskar_ui/infrastructure/static/infrastructure/images/power.png b/tuskar_ui/infrastructure/static/infrastructure/images/power.png deleted file mode 100644 index 8d3590dd8..000000000 Binary files a/tuskar_ui/infrastructure/static/infrastructure/images/power.png and /dev/null differ diff --git a/tuskar_ui/infrastructure/static/infrastructure/js/horizon.capacity.js b/tuskar_ui/infrastructure/static/infrastructure/js/horizon.capacity.js deleted file mode 100644 index d82b73095..000000000 --- a/tuskar_ui/infrastructure/static/infrastructure/js/horizon.capacity.js +++ /dev/null @@ -1,160 +0,0 @@ -/* - Used for animating and displaying capacity information using - D3js progress bars. - - Usage: - In order to have capacity bars that work with this, you need to have a - DOM structure like this in your Django template: - -
-
- - With this capacity bar, you then need to add some data- HTML attributes - to the div #your_capacity_bar_id. The available data- attributes are: - - data-chart-type="capacity_bar_chart" REQUIRED - Must be "capacity_bar_chart". - - data-capacity-used="integer" OPTIONAL - Integer representing the total number used by the user. - - data-capacity-limit="integer" OPTIONAL - Integer representing the total quota limit the user has. Note this IS - NOT the amount remaining they can use, but the total original capacity. - - data-average-capacity-used="integer" OPTIONAL - Integer representing the average usage of given capacity. -*/ - -horizon.Capacity = { - capacity_bars: [], - - // Determines the capacity bars to be used for capacity display. - init: function() { - this.capacity_bars = $('div[data-chart-type="capacity_bar_chart"]'); - - // Draw the capacity bars - this._initialCreation(this.capacity_bars); - }, - - - /* - Create a new d3 bar and populate it with the current amount used, - average used, and percentage label - */ - drawUsed: function(element, used_perc, used_px, average_perc) { - var w= "100%"; - var h= 15; - var lvl_curve= 3; - var bkgrnd= "#F2F2F2"; - var frgrnd= "grey"; - var usage_color = d3.scale.linear() - .domain([0, 50, 75, 90, 100]) - .range(["#669900", "#669900", "#FF9900", "#FF3300", "#CC0000"]); - - // Horizontal Bars - var bar = d3.select("#"+element).append("svg:svg") - .attr("class", "chart") - .attr("width", w) - .attr("height", h) - .style("background-color", "white") - .append("g"); - - // background - unused resources - bar.append("rect") - .attr("y", 0) - .attr("width", w) - .attr("height", h) - .attr("rx", lvl_curve) - .attr("ry", lvl_curve) - .style("fill", bkgrnd) - .style("stroke", "#bebebe") - .style("stroke-width", 1); - - // used resources - if (used_perc) { - bar.append("rect") - .attr("class", "usedbar") - .attr("y", 0) - .attr("id", "test") - .attr("width", 0) - .attr("height", h) - .attr("rx", lvl_curve) - .attr("ry", lvl_curve) - .style("fill", usage_color(used_perc)) - .style("stroke", "#a0a0a0") - .style("stroke-width", 1) - .attr("d", used_perc) - .transition() - .duration(500) - .attr("width", used_perc + "%"); - } - - // average - if (average_perc) { - bar.append("rect") - .attr("y",1) - .attr("x", 0) - .attr("class", "average") - .attr("height", h-2) - .attr("width", 1) - .style("fill", "black") - .transition() - .duration(500) - .attr("x", average_perc + "%"); - } - - // used text - if (used_perc) { - bar.append("text") - .text(used_perc + "%") - .attr("y", 8) - .attr("x", 3) - .attr("dominant-baseline", "middle") - .attr("font-size", 10) - .transition() - .duration(500) - .attr("x", function() { - // position the percentage label - if (used_perc > 99 && used_px > 25){ - return used_px - 30; - } else if (used_px > 25) { - return used_px - 25; - } else { - return used_px + 3; - } - }); - } - }, - - - // Draw the initial d3 bars - _initialCreation: function(bars) { - var scope = this; - - $(bars).each(function(index, element) { - var progress_element = $(element); - - var capacity_limit = parseInt(progress_element.attr('data-capacity-limit'), 10); - var capacity_used = parseInt(progress_element.attr('data-capacity-used'), 10); - var average_used = parseInt(progress_element.attr('data-average-capacity-used'), 10); - var percentage_used = 0; - var average_percentage = 0; - var _used_px = 0; - - if (!isNaN(capacity_limit) && !isNaN(average_used)) { - average_percentage = ((average_used / capacity_limit) * 100); - } - if (!isNaN(capacity_limit) && !isNaN(capacity_used)) { - percentage_used = Math.round((capacity_used / capacity_limit) * 100); - _used_px = progress_element.width() / 100 * percentage_used; - } - - scope.drawUsed($(element).attr('id'), percentage_used, _used_px, average_percentage); - }); - } -}; - -horizon.addInitFunction(function () { - horizon.Capacity.init(); -}); diff --git a/tuskar_ui/infrastructure/static/infrastructure/js/horizon.d3circleschart.js b/tuskar_ui/infrastructure/static/infrastructure/js/horizon.d3circleschart.js deleted file mode 100644 index 67547e185..000000000 --- a/tuskar_ui/infrastructure/static/infrastructure/js/horizon.d3circleschart.js +++ /dev/null @@ -1,282 +0,0 @@ -/* - Draw circles chart in d3. - - To use, a div is required with the data attributes - data-chart-type="circles_chart", data-url and data-size in the - div. - - data-chart-type - must be "circles_chart" so chart gets initialized - data-url - (string) url for the json data for the chart - data-time - (string) time parameter, gets appended to url as time=... - data-size - (integer) size of the circles in pixels - - If used in popup, initialization must be made manually e.g.: - addHorizonLoadEvent(function() { - horizon.d3_circles_chart.init('.html_element'); - horizon.d3_circles_chart.init('.health_chart'); - }); - - - Example: -
-
- - There are control elements for the cirles chart, implementing some commands - that will be executed over the chart. - - 1. The selectbox for time change, implements ChangeTime command. It has to - have data attribute data-circles-chart-command="change_time", with defined - receiver as jquery selector (selector can point to more elements and it will - execute the command on all of them) e.g. data-receiver="#rack_health_chart" - Option value is then appended to url and chart is refreshed. - - Example -
- -
-
- - 2. Bootstrap tabs for switching different circle_charts implement command - ChangeUrl. It has to have data attribute data-circles-chart-command="change_url", - with defined receiver as jquery selector (selector can point to more elements and - it will execute the command on all of them) e.g. data-receiver="#rack_health_chart" - - Inner li a element has to have attribute data-url, e.g. - data-url="/infrastructure/racks/1/top_communicating.json?type=alerts" - - The url of the chart is then switched and chart is refreshed. - - -*/ - - -horizon.d3_circles_chart = { - CirclesChart: function(chart_class, html_element){ - this.chart_class = chart_class; - this.html_element = html_element; - - var jquery_element = $(html_element); - this.size = jquery_element.data('size'); - this.time = jquery_element.data('time'); - this.url = jquery_element.data('url'); - - this.final_url = this.url; - if (this.final_url.indexOf('?') > -1){ - this.final_url += '&time=' + this.time; - }else{ - this.final_url += '?time=' + this.time; - } - - this.time = jquery_element.data('time'); - this.data = []; - - this.refresh = refresh; - function refresh(){ - var self = this; - this.jqxhr = $.getJSON( this.final_url, function() { - //FIXME add loader in the target element - }) - .done(function(data) { - // FIXME find a way how to only update graph with new data - // not delete and create - $(self.html_element).html(""); - - self.data = data.data; - self.settings = data.settings; - self.chart_class.render(self.html_element, self.size, self.data, self.settings); - }) - .fail(function() { - // FIXME add proper fail message - console.log( "error" ); }) - .always(function() { - // FIXME add behaviour that should be always done - }); - } - }, - init: function(selector, settings) { - var self = this; - $(selector).each(function() { - self.refresh(this); - }); - self.bind_commands(); - }, - refresh: function(html_element){ - var chart = new this.CirclesChart(this, html_element); - // FIXME save chart objects somewhere so I can use them again when - // e.g. I am swithing tabs, or if I want to update them - // via web sockets - // this.charts.add_or_update(chart) - chart.refresh(); - }, - render: function(html_element, size, data, settings){ - var self = this; - // FIXME rewrite to scatter plot once we have some cool D3 chart - // library - var width = size + 4, - height = size + 4, - round = size / 2, - center_x = width / 2, - center_y = height / 2; - - var svg = d3.select(html_element).selectAll("svg") - .data(data) - .enter().append("svg") - .attr("width", width) - .attr("height", height); - - // FIXME use some pretty tooltip from some library we will use - // this one is just temporary - var tooltip = d3.select(html_element).append("div") - .style("position", "absolute") - .style("z-index", "10") - .style("visibility", "hidden") - .style("min-width", "100px") - .style("max-width", "110px") - .style("min-height", "30px") - .style("border", "1px ridge grey") - .style("background-color", "white") - .text(function(d) { "a simple tooltip"; }); - - var circle = svg.append("circle") - .attr("r", round)//function(d) { return d.r; })// can be sent form server - .attr("cx", center_x) - .attr("cy", center_y) - .attr("stroke", "#cecece") - .attr("stroke-width", function (d) { - return 1; - }) - .style("fill", function (d) { - if (d.color) { - return d.color; - } else if (settings.scale == "linear_color_scale") { - return self.linear_color_scale(d.percentage, settings.domain, settings.range); - } - }) - .on("mouseover", function (d) { - if (d.tooltip) { - tooltip.html(d.tooltip); - } else { - tooltip.html(d.name + "
" + d.status); - } - tooltip.style("visibility", "visible"); - }) - .on("mousemove", function (d) { - tooltip.style("top", (event.pageY-10)+"px").style("left",(event.pageX+10)+"px"); - }) - .on("mouseout", function (d) { - tooltip.style("visibility", "hidden"); - }); - - /* - // or just d3 title element - circle.append("svg:title") - .text(function(d) { return d.x; }); - - */ - }, - linear_color_scale: function(percentage, domain, range){ - usage_color = d3.scale.linear() - .domain(domain) - .range(range); - return usage_color(percentage); - }, - bind_commands: function (){ - var change_time_command_selector = 'select[data-circles-chart-command="change_time"]'; - var change_url_command_selector = '[data-circles-chart-command="change_url"]'; - var self = this; - bind_change_time = function () { - $(change_time_command_selector).each(function() { - $(this).change(function () { - var invoker = $(this); - var command = new self.Command.ChangeTime(self, invoker); - command.execute(); - }); - }); - }; - bind_change_url = function(){ - $(change_url_command_selector + ' a').click(function (e) { - // Bootstrap tabs functionality - e.preventDefault(); - $(this).tab('show'); - - // Command for url change and refresh - var invoker = $(this); - var command = new self.Command.ChangeUrl(self, invoker); - command.execute(); - }); - }; - bind_change_time(); - bind_change_url(); - }, - Command: { - ChangeTime: function (chart_class, invoker){ - // Invoker of the command should know about it's receiver. - // Also invoker brings all parameters of the command. - this.receiver_selector = invoker.data('receiver'); - this.new_time = invoker.find("option:selected").val(); - - this.execute = execute; - function execute(){ - var self = this; - $(this.receiver_selector).each(function(){ - // change time of the chart - $(this).data('time', self.new_time); - // refresh the chart - chart_class.refresh(this); - }); - } - }, - ChangeUrl: function (chart_class, invoker, new_url){ - // Invoker of the command should know about it's receiver. - // Also invoker brings all parameters of the command. - this.receiver_selector = invoker.parents('ul').first().data('receiver'); - this.new_url = invoker.data('url'); - - this.execute = execute; - function execute(){ - var self = this; - $(this.receiver_selector).each(function(){ - // change time of the chart - $(this).data('url', self.new_url); - // refresh the chart - chart_class.refresh(this); - }); - } - } - } -}; - -/* init the graphs */ -horizon.addInitFunction(function () { - horizon.d3_circles_chart.init('div[data-chart-type="circles_chart"]'); -}); - diff --git a/tuskar_ui/infrastructure/static/infrastructure/js/horizon.d3singlebarchart.js b/tuskar_ui/infrastructure/static/infrastructure/js/horizon.d3singlebarchart.js deleted file mode 100644 index 2c681f289..000000000 --- a/tuskar_ui/infrastructure/static/infrastructure/js/horizon.d3singlebarchart.js +++ /dev/null @@ -1,475 +0,0 @@ -/* - Used for animating and displaying single-bar information using - D3js rect. - - Usage: - In order to have single bars that work with this, you need to have a - DOM structure like this in your Django template: - - Example: -
-
- - The available data- attributes are: - data-popup-free, data-popup-used, data-popup-average OPTIONAL - Html content of popups that will be displayed over this areas. - - data-single-bar-orientation REQUIRED - String representing orientation of the bar.Can be "horizontal" - or "vertical" - - data-single-bar-height REQUIRED - Integer or string with percent mark format e.g. "50%". Determines - the total height of the bar. - - data-single-bar-width="100%" REQUIRED - Integer or string with percent mark format e.g. "50%". Determines - the total width of the bar. - - data-single-bar-used="integer" REQUIRED - 1. Integer - Integer representing the percent used. - 2. Array - Array of following structure: - [{"popup_used": "Popup html 1", "used_instances": "5"}, - {"popup_used": "Popup html 2", "used_instances": "15"},....] - - used_instances: Integer representing the percent used. - popup_used: Html that will be displayed in popup window over - this area. - - data-single-bar-used-average="integer" OPTIONAL - Integer representing the average usage in percent of given - single-bar. - - data-single-bar-auto-scale-selector OPTIONAL - Jquery selector of bar elements that have Integer - data-single-bar-used attribute. It then takes maximum of these - values as 100% of the liner scale of the colors. - So the array representing linear scale interval is set - automatically.This then maps to data-single-bar-color-scale-range. - (arrays must have the same structure) - - single-bar-color-scale-range OPTIONAL - Array representing linear scale interval that is set manually. - E.g "[0,10]". This then maps to data-single-bar-color-scale-range. - (arrays must have the same structure) - - data-single-bar-color-scale-range OPTIONAL - Array representing linear scale of colors. - E.g '["#000060", "#99FFFF"]' - -*/ - -horizon.d3_single_bar_chart = { - SingleBarChart: function(chart_class, html_element){ - var self = this; - self.chart_class = chart_class; - - self.html_element = html_element; - self.jquery_element = $(self.html_element); - // Using only percent, so limit is 100% - self.single_bar_limit = 100; - - self.single_bar_used = $.parseJSON(self.jquery_element.attr('data-single-bar-used')); - self.average_used = parseInt(self.jquery_element.attr('data-single-bar-average-used'), 10); - - self.data = {}; - - // Percentage and used_px count - if ($.isArray(self.single_bar_used)){ - self.data.used_px = 0; - self.data.percentage_used = Array(); - self.data.tooltip_used_contents = Array(); - for (var i = 0; i < self.single_bar_used.length; ++i) { - if (!isNaN(self.single_bar_limit) && !isNaN(self.single_bar_used[i].used_instances)) { - used = Math.round((self.single_bar_used[i].used_instances / self.single_bar_limit) * 100); - - self.data.percentage_used.push(used); - // for multi-bar chart, tooltip is in the data - self.data.tooltip_used_contents.push(self.single_bar_used[i].popup_used); - - self.data.used_px += self.jquery_element.width() / 100 * used; - } else { // If NaN self.data.percentage_used is 0 - - } - } - - } - else { - if (!isNaN(self.single_bar_limit) && !isNaN(self.single_bar_used)) { - self.data.percentage_used = Math.round((self.single_bar_used / self.single_bar_limit) * 100); - self.data.used_px = self.jquery_element.width() / 100 * self.data.percentage_used; - - } else { // If NaN self.data.percentage_used is 0 - self.data.percentage_used = 0; - self.data.used_px = 0; - } - - if (!isNaN(self.single_bar_limit) && !isNaN(self.average_used)) { - self.data.percentage_average = ((self.average_used / self.single_bar_limit) * 100); - } else { - self.data.percentage_average = 0; - } - } - - // Width and height of bar - self.data.width = self.jquery_element.data('single-bar-width'); - self.data.height = self.jquery_element.data('single-bar-height'); - - // Color scales - self.auto_scale_selector = function () { - return self.jquery_element.data('single-bar-auto-scale-selector'); - }; - self.is_auto_scaling = function () { - return self.auto_scale_selector(); - }; - self.auto_scale = function () { - var max_scale = 0; - $(self.auto_scale_selector()).each(function() { - var scale = parseInt($(this).data('single-bar-used'), 10); - if (scale > max_scale) - max_scale = scale; - }); - return [0, max_scale]; - }; - - if (self.jquery_element.data('single-bar-color-scale-domain')) - self.data.color_scale_domain = - self.jquery_element.data('single-bar-color-scale-domain'); - else if (self.is_auto_scaling()) - // Dynamically set scale based on biggest value - self.data.color_scale_domain = self.auto_scale(); - else - self.data.color_scale_domain = [0,100]; - - if (self.jquery_element.data('single-bar-color-scale-range')) - self.data.color_scale_range = - self.jquery_element.data('single-bar-color-scale-range'); - else - self.data.color_scale_range = ["#000000", "#0000FF"]; - - // Tooltips data - self.data.popup_average = self.jquery_element.data('popup-average'); - self.data.popup_free = self.jquery_element.data('popup-free'); - self.data.popup_used = self.jquery_element.data('popup-used'); - - // Orientation of the Bar chart - self.data.orientation = self.jquery_element.data('single-bar-orientation'); - - // Refresh method - self.refresh = function (){ - self.chart_class.render(self.html_element, self.data); - }; - }, - BaseComponent: function(data){ - var self = this; - - self.data = data; - - self.w = data.width; - self.h = data.height; - self.lvl_curve = 3; - self.bkgrnd = "#F2F2F2"; - self.frgrnd = "grey"; - self.color_scale_max = 25; - - self.percentage_used = data.percentage_used; - self.total_used_perc = 0; - self.used_px = data.used_px; - self.percentage_average = data.percentage_average; - self.tooltip_used_contents = data.tooltip_used_contents; - - // set scales - self.usage_color = d3.scale.linear() - .domain(data.color_scale_domain) - .range(data.color_scale_range); - - // return true if it renders used percentage multiple in one chart - self.used_multi = function (){ - return ($.isArray(self.percentage_used)); - }; - - // deals with percentage if there should be multiple in one chart - self.used_multi_iterator = 0; - self.percentage_used_value = function(){ - if (self.used_multi()){ - return self.percentage_used[self.used_multi_iterator]; - } else { - return self.percentage_used; - } - }; - // deals with html tooltips if there should be multiple in one chart - self.tooltip_used_value = function (){ - if (self.used_multi()){ - return self.tooltip_used_contents[self.used_multi_iterator]; - } else { - return ""; - } - }; - - // return true if it chart is oriented horizontally - self.horizontal_orientation = function (){ - return (self.data.orientation == "horizontal"); - }; - - }, - UsedComponent: function(base_component){ - var self = this; - self.base_component = base_component; - - // FIXME would be good to abstract all attributes and resolve orientation inside - if (base_component.horizontal_orientation()){ - // Horizontal Bars - self.y = 0; - self.x = base_component.total_used_perc + "%"; - self.width = 0; - self.height = base_component.h; - self.trasition_attr = "width"; - self.trasition_value = base_component.percentage_used_value() + "%"; - } else { // Vertical Bars - self.y = base_component.h; - self.x = 0; - self.width = base_component.w; - self.height = base_component.percentage_used_value() + "%"; - self.trasition_attr = "y"; - self.trasition_value = 100 - base_component.percentage_used_value() + "%"; - } - - self.append = function(bar, tooltip){ - var used_component = self; - var base_component = self.base_component; - - bar.append("rect") - .attr("class", "usedbar") - .attr("y", used_component.y) - .attr("x", used_component.x) - .attr("width", used_component.width) - .attr("height", used_component.height) - //.attr("rx", base_component.lvl_curve) - //.attr("ry", base_component.lvl_curve) - .style("fill", base_component.usage_color(base_component.percentage_used_value())) - .style("stroke", "#bebebe") - .style("stroke-width", 0) - .attr("d", base_component.percentage_used_value()) - .attr("popup-used", base_component.tooltip_used_value()) - .on("mouseover", function(d){ - if ($(this).attr('popup-used')){ - tooltip.html($(this).attr('popup-used')); - } - tooltip.style("visibility", "visible");}) - .on("mousemove", function(d){tooltip.style("top", - (event.pageY-10)+"px").style("left",(event.pageX+10)+"px");}) - .on("mouseout", function(d){tooltip.style("visibility", "hidden");}) - .transition() - .duration(500) - .attr(used_component.trasition_attr, used_component.trasition_value); - }; - }, - AverageComponent: function(base_component){ - var self = this; - self.base_component = base_component; - - // FIXME would be good to abstract all attributes and resolve orientation inside - if (base_component.horizontal_orientation()){ - // Horizontal Bars - self.y = 1; - self.x = 0; - self.width = 1; - self.height = base_component.h; - self.trasition_attr = "x"; - self.trasition_value = base_component.percentage_average + "%"; - } else { // Vertical Bars - self.y = 0; - self.x = 0; - self.width = base_component.w; - self.height = 1; - self.trasition_attr = "y"; - self.trasition_value = 100 - base_component.percentage_average + "%"; - } - - self.append = function(bar, tooltip){ - var average_component = self; - var base_component = self.base_component; - - bar.append("rect") - .attr("y", average_component.y) - .attr("x", average_component.x) - .attr("class", "average") - .attr("height", average_component.height) - .attr("width", average_component.width) - .style("fill", "black") - .on("mouseover", function(){tooltip.style("visibility", "visible");}) - .on("mousemove", function(){tooltip.style("top", - (event.pageY-10)+"px").style("left",(event.pageX+10)+"px");}) - .on("mouseout", function(){tooltip.style("visibility", "hidden");}) - .transition() - .duration(500) - .attr(average_component.trasition_attr, average_component.trasition_value); - }; - }, - /* TODO rewrite below as components */ - /* FIXME use some pretty tooltip from some library we will use - * this one is just temporary */ - append_tooltip: function(tooltip, html_content){ - return tooltip - .style("position", "absolute") - .style("z-index", "10") - .style("visibility", "hidden") - .style("min-width", "100px") - .style("max-width", "200px") - .style("min-height", "30px") - .style("max-height", "150px") - .style("border", "1px ridge grey") - .style("padding", "8px") - .style("padding-top", "5px") - .style("background-color", "white") - .html(html_content); - }, - append_unused: function(bar, base_component, tooltip_free){ - bar.append("rect") - .attr("y", 0) - .attr("width", base_component.w) - .attr("height", base_component.h) - .attr("rx", base_component.lvl_curve) - .attr("ry", base_component.lvl_curve) - .style("fill", base_component.bkgrnd) - .style("stroke", "#e0e0e0") - .style("stroke-width", 1) - .on("mouseover", function(d){tooltip_free.style("visibility", "visible");}) - .on("mousemove", function(d){tooltip_free.style("top", - (event.pageY-10)+"px").style("left",(event.pageX+10)+"px");}) - .on("mouseout", function(d){tooltip_free.style("visibility", "hidden");}); - }, - // TODO This have to be enhanced, so this library can replace jtomasek capacity charts - append_text: function(bar, base_component, tooltip){ - bar.append("text") - .text("FREE") - .attr("y", base_component.h/2) - .attr("x", 3) - .attr("dominant-baseline", "middle") - .attr("font-size", 13) - .on("mouseover", function(d){tooltip.style("visibility", "visible");}) - .on("mousemove", function(d){tooltip.style("top", - (event.pageY-10)+"px").style("left",(event.pageX+10)+"px");}) - .on("mouseout", function(d){tooltip.style("visibility", "hidden");}) - .transition() - .duration(500) - .attr("x", function() { - // FIXME when another panel is active, this page is hidden and used_px return 0 - // text is then badly positioned, quick fix will be to refresh charts when panel - // is switched. Need to find better solution. - if (base_component.total_used_perc > 90 && base_component.used_px > 25) - return base_component.used_px - 20; - else - return base_component.used_px + 20; - }); - }, - append_border: function(bar){ - bar.append("rect") - .attr("x", 0) - .attr("y", 0) - .attr("height", '100%') - .attr("width", '100%') - .style("stroke", "#bebebe") - .style("fill", "none") - .style("stroke-width", 1); - }, - // INIT - init: function() { - var self = this; - this.single_bars = $('div[data-single-bar-used]'); - - this.single_bars.each(function() { - self.refresh(this); - }); - }, - refresh: function(html_element){ - var chart = new this.SingleBarChart(this, html_element); - // FIXME save chart objects somewhere so I can use them again when - // e.g. I am swithing tabs, or if I want to update them - // via web sockets - // this.charts.add_or_update(chart) - chart.refresh(); - }, - render: function(html_element, data) { - var jquery_element = $(html_element); - - // Initialize base_component - var base_component = new this.BaseComponent(data); - - // Bar - var bar_html = d3.select(html_element); - - // Tooltips - var tooltip_average = bar_html.append("div"); - if (data.popup_average) - tooltip_average = this.append_tooltip(tooltip_average, data.popup_average); - - var tooltip_free = bar_html.append("div"); - if (data.popup_free) - tooltip_free = this.append_tooltip(tooltip_free, data.popup_free); - - var tooltip_used = bar_html.append("div"); - if (data.popup_used) - tooltip_used = this.append_tooltip(tooltip_used, data.popup_used); - - // append layout for bar chart - var bar = bar_html.append("svg:svg") - .attr("class", "chart") - .attr("width", base_component.w) - .attr("height", base_component.h) - .style("background-color", "white") - .append("g"); - var used_component; - - // append Unused resources Bar - this.append_unused(bar, base_component, tooltip_free); - - if (base_component.used_multi()){ - // If Used is shown as multiple values in one chart - for (var i = 0; i < base_component.percentage_used.length; ++i) { - // FIXME write proper iterator - base_component.used_multi_iterator = i; - - // Use general tooltip, content of tooltip will be changed - // by inner used compoentnts on their hover - tooltip_used = this.append_tooltip(tooltip_used, ""); - - // append used so it will be shown as multiple values in one chart - used_component = new this.UsedComponent(base_component); - used_component.append(bar, tooltip_used); - - // append Used resources to Bar - base_component.total_used_perc += base_component.percentage_used_value(); - } - - // append Text to Bar - this.append_text(bar, base_component, tooltip_free); - - } else { - // used is show as one value it the chart - used_component = new this.UsedComponent(base_component); - used_component.append(bar, tooltip_used); - - // append average value to Bar - var average_component = new this.AverageComponent(base_component); - average_component.append(bar, tooltip_average); - } - // append border of whole Bar - this.append_border(bar); - } -}; - - -horizon.addInitFunction(function () { - horizon.d3_single_bar_chart.init(); -}); diff --git a/tuskar_ui/infrastructure/static/infrastructure/js/tuskar.deployment_live.js b/tuskar_ui/infrastructure/static/infrastructure/js/tuskar.deployment_live.js deleted file mode 100644 index 22e4f2f55..000000000 --- a/tuskar_ui/infrastructure/static/infrastructure/js/tuskar.deployment_live.js +++ /dev/null @@ -1,17 +0,0 @@ -tuskar.deployment_live = (function () { - 'use strict'; - - var module = {}; - - module.init = function () { - $("#overcloudrc").on("hide.bs.collapse", function(){ - $("span.overcloudrc").html('Show '); - }); - $("#overcloudrc").on("show.bs.collapse", function(){ - $("span.overcloudrc").html('Hide '); - }); - }; - - horizon.addInitFunction(module.init); - return module; -} ()); diff --git a/tuskar_ui/infrastructure/static/infrastructure/js/tuskar.deployment_progress.js b/tuskar_ui/infrastructure/static/infrastructure/js/tuskar.deployment_progress.js deleted file mode 100644 index 668293790..000000000 --- a/tuskar_ui/infrastructure/static/infrastructure/js/tuskar.deployment_progress.js +++ /dev/null @@ -1,43 +0,0 @@ -tuskar.deployment_progress = (function () { - 'use strict'; - - var module = {}; - - module.init = function () { - if (!$('div.deployment-box div.progress').length) { return; } - this.interval = setInterval(function () { - module.check_progress(); - }, 30000); - module.events_template = Hogan.compile($('#events-template').html() || ''); - module.roles_template = Hogan.compile($('#roles-template').html() || ''); - }; - - module.check_progress = function () { - var $form = $('form.deployment-roles-form'); - $.ajax({ - type: 'GET', - headers: {'X-Horizon-Progress': 'true'}, - url: $form.attr('action'), - dataType: 'json', - async: true, - success: this.update_progress - }); - }; - - module.update_progress = function (data) { - if (data.progress >= 100 || data.progress <= 0) { - window.location.reload(true); - } - var $bar = $('div.deployment-box div.progress div.progress-bar'); - $bar.css('width', '' + data.progress + '%'); - if (data.show_last_events) { - $('div.deploy-last-events').html(module.events_template.render(data)); - } else { - $('div.deploy-last-events').html(''); - } - $('div.deploy-role-status').html(module.roles_template.render(data)); - }; - - horizon.addInitFunction(module.init); - return module; -} ()); diff --git a/tuskar_ui/infrastructure/static/infrastructure/js/tuskar.edit_plan.js b/tuskar_ui/infrastructure/static/infrastructure/js/tuskar.edit_plan.js deleted file mode 100644 index 96ab54597..000000000 --- a/tuskar_ui/infrastructure/static/infrastructure/js/tuskar.edit_plan.js +++ /dev/null @@ -1,74 +0,0 @@ -tuskar.edit_plan = (function () { - 'use strict'; - - var module = {}; - - module.debounce_timer = null; - module.ICON_CLASSES = ( - 'fa-spinner ' + - 'fa-spin ' + - 'fa-cloud ' + - 'fa-exclamation-circle ' + - 'fa-check-circle ' + - '' - ); - - module.init = function () { - if (!$('form.deployment-roles-form').length) { return; } - // Attach event listeners and hide the submit button. - $('form.deployment-roles-form input.number-picker' - ).change(module.on_change); - $('form.deployment-roles-form [type=submit]').hide(); - // Compile the templates. - module.message_template = Hogan.compile( - $('#message-template').html() || ''); - module.title_template = Hogan.compile( - $('#title-template').html() || ''); - }; - - module.on_change = function () { - // Only save when there was no activity for half a second. - window.clearTimeout(module.debounce_timer); - module.debounce_timer = window.setTimeout(module.save_form, 500); - }; - - module.save_form = function () { - // Save the current plan and get validation results. - var $form = $('form.deployment-roles-form'); - module.update_messages(null); - $.ajax({ - type: 'POST', - headers: {'X-Horizon-Validate': 'true'}, - url: $form.attr('action'), - data: $form.serialize(), - dataType: 'json', - async: true, - success: module.update_messages, - }); - }; - - module.update_messages = function (data) { - if (data === null) { - $('div.deployment-buttons a.btn-primary').addClass('disabled'); - $('div.deployment-icon i').removeClass(module.ICON_CLASSES - ).addClass('fa-spinner fa-spin'); - data = {validating:true}; - } else if (data.plan_invalid) { - $('div.deployment-buttons a.btn-primary').addClass('disabled'); - $('div.deployment-icon i').removeClass(module.ICON_CLASSES - ).addClass('fa-exclamation-circle'); - } else { - $('div.deployment-buttons a.btn-primary').removeClass('disabled'); - $('div.deployment-icon i').removeClass(module.ICON_CLASSES - ).addClass('fa-check-circle'); - } - $('div.deployment-box h4').replaceWith( - module.title_template.render(data)); - $('div.deployment-box ul').replaceWith( - module.message_template.render(data)); - $('div.deployment-box a#collapse-steps').text(data.steps_message); - }; - - horizon.addInitFunction(module.init); - return module; -} ()); diff --git a/tuskar_ui/infrastructure/static/infrastructure/js/tuskar.formset_table.js b/tuskar_ui/infrastructure/static/infrastructure/js/tuskar.formset_table.js deleted file mode 100644 index 4cf225f1b..000000000 --- a/tuskar_ui/infrastructure/static/infrastructure/js/tuskar.formset_table.js +++ /dev/null @@ -1,87 +0,0 @@ -tuskar.formset_table = (function () { - 'use strict'; - - var module = {}; - - - // go through the whole table and fix the numbering of rows - module.reenumerate_rows = function (table, prefix) { - var count = 0; - var input_name_re = new RegExp('^' + prefix + '-(\\d+|__prefix__)-'); - var input_id_re = new RegExp('^id_' + prefix + '-(\\d+|__prefix__)-'); - - table.find('tbody tr').each(function () { - $(this).find('input').each(function () { - var input = $(this); - input.attr('name', input.attr('name').replace( - input_name_re, prefix + '-' + count + '-')); - input.attr('id', input.attr('id').replace( - input_id_re, 'id_' + prefix + '-' + count + '-')); - }); - count += 1; - }); - $('#id_' + prefix + '-TOTAL_FORMS').val(count); - }; - - // mark a row as deleted and hide it - module.delete_row = function (e) { - $(this).closest('tr').hide(); - $(this).prev('input[name$="-DELETE"]').attr('checked', true); - }; - - // replace the "Delete" checkboxes with × for deleting rows - module.replace_delete = function (where) { - where.find('input[name$="-DELETE"]').hide().after( - $('×').click(module.delete_row) - ); - }; - - // add more empty rows in the flavors table - module.add_row = function (table, prefix, empty_row_html) { - var new_row = $(empty_row_html); - module.replace_delete(new_row); - table.find('tbody').append(new_row); - module.reenumerate_rows(table, prefix); - }; - - // prepare all the javascript for formset table - module.init = function (prefix, empty_row_html, add_label) { - - var table = $('table#' + prefix); - - module.replace_delete(table); - - // if there are extra empty rows, add the button for new rows - if (add_label) { - var button = $('' + - add_label + ''); - table.find('tfoot td').append(button); - button.click(function () { - module.add_row(table, prefix, empty_row_html); - }); - } - - // if the formset is not empty and has no errors, - // delete the empty extra rows from the end - var initial_forms = +$('#id_' + prefix + '-INITIAL_FORMS').val(); - var total_forms = +$('#id_' + prefix + '-TOTAL_FORMS').val(); - - if (table.find('tbody tr').length > 1 && - table.find('tbody td.error').length === 0 && - total_forms > initial_forms) { - table.find('tbody tr').each(function (index) { - if (index >= initial_forms) { - $(this).remove(); - } - }); - module.reenumerate_rows(table, prefix); - $('#id_' + prefix + '-INITIAL_FORMS').val( - $('#id_' + prefix + '-TOTAL_FORMS').val()); - } - - // enable tooltips - table.find('td.error[title]').tooltip(); - }; - - return module; -} ()); diff --git a/tuskar_ui/infrastructure/static/infrastructure/js/tuskar.js b/tuskar_ui/infrastructure/static/infrastructure/js/tuskar.js deleted file mode 100644 index c9f29de63..000000000 --- a/tuskar_ui/infrastructure/static/infrastructure/js/tuskar.js +++ /dev/null @@ -1,6 +0,0 @@ -var Tuskar = function () { - var tuskar = {}; - return tuskar; -}; - -var tuskar = new Tuskar(); diff --git a/tuskar_ui/infrastructure/static/infrastructure/js/tuskar.menu_formset.js b/tuskar_ui/infrastructure/static/infrastructure/js/tuskar.menu_formset.js deleted file mode 100644 index a7021fe70..000000000 --- a/tuskar_ui/infrastructure/static/infrastructure/js/tuskar.menu_formset.js +++ /dev/null @@ -1,87 +0,0 @@ -tuskar.menu_formset = (function () { - 'use strict'; - - var module = {}; - - module.init = function (prefix, empty_form_html) { - var input_name_re = new RegExp('^' + prefix + '-(\\d+|__prefix__)-'); - var input_id_re = new RegExp('^id_' + prefix + '-(\\d+|__prefix__)-'); - var $content = $('#formset-' + prefix +' .tab-content'); - var $nav = $('#formset-' + prefix + ' .nav'); - var activated = false; - - function renumber_form($form, prefix, count) { - $form.find('input, textarea, select').each(function () { - var input = $(this); - input.attr('name', input.attr('name').replace( - input_name_re, prefix + '-' + count + '-')); - input.attr('id', input.attr('id').replace( - input_id_re, 'id_' + prefix + '-' + count + '-')); - }); - } - - function reenumerate_forms($forms, prefix) { - var count = 0; - $forms.each(function () { - renumber_form($(this), prefix, count); - count += 1; - }); - $('#id_' + prefix + '-TOTAL_FORMS').val(count); - return count; - } - - function add_delete_link($nav_item) { - var $form = $content.find($nav_item.find('a').attr('href')); - $nav_item.prepend(''); - $nav_item.find('span.delete-icon:first').click(function () { - var count; - $form.remove(); - $nav_item.remove(); - count = reenumerate_forms($content.find('.tab-pane'), prefix); - if (count === 0) { add_node(); } - }); - } - - function add_node() { - var $new_form = $(empty_form_html); - var count, id, $new_nav; - $content.append($new_form); - $new_form = $content.find('.tab-pane:last'); - count = reenumerate_forms($content.find('.tab-pane'), prefix); - id = 'tab-' + prefix + '-' + count; - $new_form.attr('id', id); - $nav.append('
  • Undefined node
  • '); - $new_nav = $nav.find('li > a:last'); - add_delete_link($new_nav.parent()); - $new_nav.click(function () { - $(this).tab('show'); - $('select.switchable').trigger('change'); - }); - $new_nav.tab('show'); - $('select.switchable').trigger('change'); - horizon.forms.add_password_fields_reveal_buttons($new_form); - } - - // Connect all signals. - $('#add-node-link').click(add_node); - $nav.find('li').each(function () { - add_delete_link($(this)); - }); - $nav.find('li a').click(function () { - window.setTimeout(function () { - $('select.switchable').trigger('change'); - }, 0); - }); - - // Activate the first field that has errors. - $content.find('.control-group.error').each(function () { - if (!activated) { - $nav.find('a[href="#' + $(this).closest('.tab-pane').attr('id') + '"]').tab('show'); - activated = true; - } - }); - - }; - - return module; -} ()); diff --git a/tuskar_ui/infrastructure/static/infrastructure/js/tuskar.number_picker.js b/tuskar_ui/infrastructure/static/infrastructure/js/tuskar.number_picker.js deleted file mode 100644 index ac6ffda0e..000000000 --- a/tuskar_ui/infrastructure/static/infrastructure/js/tuskar.number_picker.js +++ /dev/null @@ -1,51 +0,0 @@ -tuskar.number_picker = (function () { - 'use strict'; - - var module = {}; - - module.init = function () { - $('input.number-picker').removeClass( - 'form-control').wrap( - '
    ').before( - '' + - '').after( - '' + - '').each( - function () { - var $this = $(this); - var $right_arrow = $this.next('a.arrow-right'); - var $left_arrow = $this.prev('a.arrow-left'); - if ($this.attr('readonly')) { - $this.parent().addClass('readonly'); - } - function change(step) { - var value = +$this.val(); - var maximum = +$this.attr('max'); - var minimum = +$this.attr('min'); - value += step; - if (!isNaN(maximum)) { value = Math.min(maximum, value); } - if (!isNaN(minimum)) { value = Math.max(minimum, value); } - $right_arrow.toggleClass('disabled', (value === maximum)); - $left_arrow.toggleClass('disabled', (value === minimum)); - $this.val(value); - $this.trigger('change'); - } - $right_arrow.click(function () { - var step = +($this.attr('step') || 1); - change(step); - }); - $left_arrow.click(function () { - var step = -($this.attr('step') || 1); - change(step); - }); - change(0); - var step = +($this.attr('step') || 1); - if (step !== 1) { - $this.after('+' + step + ''); - } - }); - }; - - horizon.addInitFunction(module.init); - return module; -} ()); diff --git a/tuskar_ui/infrastructure/static/infrastructure/js/tuskar.performance.js b/tuskar_ui/infrastructure/static/infrastructure/js/tuskar.performance.js deleted file mode 100644 index 23658f102..000000000 --- a/tuskar_ui/infrastructure/static/infrastructure/js/tuskar.performance.js +++ /dev/null @@ -1,25 +0,0 @@ -tuskar.performance = (function () { - 'use strict'; - - var module = {}; - - module.show_hide_datepickers = function () { - var date_options = $("#date_options"); - date_options.change(function(evt) { - if ($(this).find("option:selected").val() === "other"){ - evt.stopPropagation(); - $("#date_from, #date_to").val(''); - $("#date_from_group, #date_to_group").show(); - } else { - $("#date_from_group, #date_to_group").hide(); - } - }); - if (date_options.find("option:selected").val() === "other"){ - $("#date_from_group, #date_to_group").show(); - } else { - $("#date_from_group, #date_to_group").hide(); - } - }; - - return module; -} ()); diff --git a/tuskar_ui/infrastructure/static/infrastructure/js/tuskar.templates.js b/tuskar_ui/infrastructure/static/infrastructure/js/tuskar.templates.js deleted file mode 100644 index 5f8e68628..000000000 --- a/tuskar_ui/infrastructure/static/infrastructure/js/tuskar.templates.js +++ /dev/null @@ -1,16 +0,0 @@ -/* Namespace for core functionality related to client-side templating. */ -tuskar.templates = { - template_ids: ["#modal_chart_template"], -}; - -/* Pre-loads and compiles the client-side templates. */ -tuskar.templates.compile_templates = function () { - $.each(tuskar.templates.template_ids, function (ind, template_id) { - horizon.templates.compiled_templates[template_id] = Hogan.compile($(template_id).html()); - }); -}; - -horizon.addInitFunction(function () { - // Load client-side template fragments and compile them. - tuskar.templates.compile_templates(); -}); diff --git a/tuskar_ui/infrastructure/static/infrastructure/scss/_breadcrumbs.scss b/tuskar_ui/infrastructure/static/infrastructure/scss/_breadcrumbs.scss deleted file mode 100644 index 6a4e24f71..000000000 --- a/tuskar_ui/infrastructure/static/infrastructure/scss/_breadcrumbs.scss +++ /dev/null @@ -1,31 +0,0 @@ -// breadcrumbs - -.breadcrumbs { - font-size: 85%; - margin: 0 0 25px 0; - - a { - color: rgb(170, 170, 170); - text-decoration: underline; - - &:hover { - text-decoration: none; - color: rgb(100, 100, 100); - } - } - - .separator { - color: rgb(200, 200, 200); - - &:before { - display: inline; - content: ">>"; - margin: 0 7px; - } - - &:last-child:after { - display: inline; - content: "..."; - } - } -} diff --git a/tuskar_ui/infrastructure/static/infrastructure/scss/_buttons.scss b/tuskar_ui/infrastructure/static/infrastructure/scss/_buttons.scss deleted file mode 100644 index 0a62aa053..000000000 --- a/tuskar_ui/infrastructure/static/infrastructure/scss/_buttons.scss +++ /dev/null @@ -1,15 +0,0 @@ -// buttons -.btn-default:not(.btn-danger, .btn-primary) { - background: rgb(243, 243, 243); - &:hover { - background: rgb( 235, 235, 235); - } -} - -.btn-toolbar.pull-right { - margin: 0; -} - -.btn-no-border { - border: none; -} diff --git a/tuskar_ui/infrastructure/static/infrastructure/scss/_capacities.scss b/tuskar_ui/infrastructure/static/infrastructure/scss/_capacities.scss deleted file mode 100644 index 91b2a1f55..000000000 --- a/tuskar_ui/infrastructure/static/infrastructure/scss/_capacities.scss +++ /dev/null @@ -1,24 +0,0 @@ -// capacities -table.capacities { - &.overall_usage { - margin-top: 5px; - } - - td { - padding: 3px; - - &.capacity_label { - width: 60px; - padding-right: 5px; - color: rgb(160,160,160); - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - } - - & div.capacity_bar { - line-height: 0; - width: 120px; - } - } -} diff --git a/tuskar_ui/infrastructure/static/infrastructure/scss/_charts.scss b/tuskar_ui/infrastructure/static/infrastructure/scss/_charts.scss deleted file mode 100644 index 1a20c71c8..000000000 --- a/tuskar_ui/infrastructure/static/infrastructure/scss/_charts.scss +++ /dev/null @@ -1,15 +0,0 @@ -form.performance_charts { - .pull-right { - input { - margin-left: 0; - } - } -} -.overview_chart { - .chart_container { - .spinner_wrapper { - margin-top: 12px; - margin-left: 12px; - } - } -} diff --git a/tuskar_ui/infrastructure/static/infrastructure/scss/_detail_pages.scss b/tuskar_ui/infrastructure/static/infrastructure/scss/_detail_pages.scss deleted file mode 100644 index 7c3d096a3..000000000 --- a/tuskar_ui/infrastructure/static/infrastructure/scss/_detail_pages.scss +++ /dev/null @@ -1,12 +0,0 @@ -.dl-horizontal-left { - dt { - width: 120px; - text-align: left; - color: rgb(160, 160, 160); - font-weight: normal; - } - dd { - margin-left: 130px; - padding-bottom: .5em; - } -} diff --git a/tuskar_ui/infrastructure/static/infrastructure/scss/_flavor_usages.scss b/tuskar_ui/infrastructure/static/infrastructure/scss/_flavor_usages.scss deleted file mode 100644 index 639be9ecf..000000000 --- a/tuskar_ui/infrastructure/static/infrastructure/scss/_flavor_usages.scss +++ /dev/null @@ -1,89 +0,0 @@ -// flavor usages -div.flavor_usage_bar { - max-width: 550px; -} - -table.flavor_usages { - width: 100%; - max-width: 550px; - - td { - text-align: center; - padding: 3px; - - &.flavor_usage_label { - width: 60px; - text-align: center; - } - - &.flavor_usage_text { - width: 60px; - text-align: center; - line-height: 1; - } - - & div.flavor_usage_bar { - width: auto; - text-align: center; - line-height: 0; - height: 120px; - } - } -} - -#interval_selector { - position: absolute; - right: 15px; - top: 15px; - - li { - display: inline; - list-style-type: none; - padding-right: 5px; - - &.active { - font-weight: bold; - - a { - color: black; - - &:hover { - text-decoration:none; - } - } - } - } -} - -svg { - .axis { - path, line { - fill: none; - stroke: #000; - shape-rendering: crispEdges; - } - } -} - -.communication_chart_wrapper { - display:inline-block; - vertical-align: middle; - width: 38%; -} - -.communication_chart_connection { - display:inline-block; - width: 60px; - height: 30px; - vertical-align: middle; - background: url('/static/dashboard/img/communication_flow.png') no-repeat 50% 50%; - background-size: 40px 20px; -} - -.circles_chart_time_picker { - float: right; -} - -.csv_rack_table { - padding-top: 40px; -} diff --git a/tuskar_ui/infrastructure/static/infrastructure/scss/_formsets.scss b/tuskar_ui/infrastructure/static/infrastructure/scss/_formsets.scss deleted file mode 100644 index cb1f3b596..000000000 --- a/tuskar_ui/infrastructure/static/infrastructure/scss/_formsets.scss +++ /dev/null @@ -1,34 +0,0 @@ -// formsets -.datatable { - th.narrow { - width: 1em; - } - - input { - padding: 2px 5px; - margin: 0; - } - - input.number_input_slim { - width: 4em; - text-align: right; - } - - th span.required:after { - // Copied from horizon, because there is no way to reuse their class. - content: "*"; - font-weight: bold; - line-height: 0; - padding-left: 4px; - color: #428bca; - } -} - -.param-section { - padding-bottom: 15px; -} - -#collapse-upload-form { - text-align: center; - padding-bottom: 30px; -} diff --git a/tuskar_ui/infrastructure/static/infrastructure/scss/_icons.scss b/tuskar_ui/infrastructure/static/infrastructure/scss/_icons.scss deleted file mode 100644 index e5ea1f783..000000000 --- a/tuskar_ui/infrastructure/static/infrastructure/scss/_icons.scss +++ /dev/null @@ -1,5 +0,0 @@ -.text-warning .fa, -.alert-warning .fa, -.text-warning.fa { - color: #eea236; -} diff --git a/tuskar_ui/infrastructure/static/infrastructure/scss/_individual_pages.scss b/tuskar_ui/infrastructure/static/infrastructure/scss/_individual_pages.scss deleted file mode 100644 index b9cdfcdd9..000000000 --- a/tuskar_ui/infrastructure/static/infrastructure/scss/_individual_pages.scss +++ /dev/null @@ -1,271 +0,0 @@ -#nodes-formset-datatable .datatable tbody { - input { - padding: 2px 1px; - } - input.number_input_slim { - width: 3em; - } - td { - padding: 2px; - text-align: center; - a.close { - margin-right: 4px; - } - } -} - -$link-color: #428bca; - -// Register nodes formset -.register-nodes-formset { - a.node-icon { - display: block; - float: right; - padding-left: 10px; - } - - .nav-pills > .active > a { - color: #fff; - background-color: $link-color; - } - - ul.nav-pills > li span.delete-icon { - display: none; - } - - ul.nav-pills > li.active span.delete-icon { - display: block; - cursor: default; - color: #fff; - z-index: 1000; - position: absolute; - right: 16px; - top: 10px; - } - - ul.nav-pills > li { - position: relative; - } - - ul.nav-pills > li.active { - width: 107%; - } - - ul.nav-pills > li.active:after { - display: block; - content: ''; - position: absolute; - top: 1px; - right: -7px; - border-top: 18px solid transparent; - border-bottom: 18px solid transparent; - border-left: 8px solid $link-color; - } - - .register-nav-head { - margin-top: 19px; - margin-bottom: 5px; - - h4 { - margin: 0; - } - } - - .form h5 { - margin: 0.5em 0 0.75em 0; - } - - .form label.checkbox { - font-weight: normal; - } - - .panel { - margin: 8px -8px 8px -8px; - padding: 8px; - .panel-heading { - margin: -8px -8px 0 -8px; - } - } - - fieldset .form-group { - width: 100%; - input, textarea, select { - width: 100%; - margin-left: 5px; - } - } - - .required label:after { - content: "*"; - font-weight: bold; - line-height: 0; - padding-left: 4px; - color: #428bca; - } -} - - -#detail__overview, #collapseParameters { - ul { - list-style: disc; - margin: 0 0 9px 25px; - } -} - -// Power state icons -.fa.powerstate { - display: inline-block; - vertical-align: middle; - &.fa-play { - color: $brand-success; - } -} - -// Node detail view -.node-details { - .chart svg { - path.undefined { - stroke-width: 1px; - stroke: #ff7f0e; - } - - .y_ticks text { - display: none; - } - } -} - -// Node overview - -.nodes { - .widget-info { - padding: 0 30px 0 30px; - text-align: center; - span { - font-size: 300%; - } - } - - .d3_pie_chart_distribution { - .legend { - position: relative; - } - } -} - -// Plan overview - -.deployment-icon { - vertical-align: middle; - float: left; - text-align: center; - color: #ddd; - background: #fff; -} - -.deployment-box { - border-left: 4px solid #eee; - margin-left: 24px; - margin-bottom: 12px; - padding-left: 20px; -} - -.deployment-buttons { - margin-top: 12px; -} - -.deployment-roles-label { - font-weight: bold; - display: block; - margin: 14px 0 0 0; -} - -.deploy-role-icon { - a { - display: inline-block; - margin: 7px 0 0 0; - } - i { - display: inline-block; - margin: 14px 0 0 0; - } -} -.deploy-role-count { - text-align: right; - vertical-align: middle; - font-size: 32px; - small { - font-size: 28px; - } -} - -#collapseDriver { - margin-left: -50px; -} - -/* Configuration parameters */ -table .data-table-config-label { - width: 20em; - vertical-align: top; -} -table .data-table-config-value { - font-weight: bold; -} -table.definition-list-table > thead { - display: none; -} -table.definition-list-table > tbody > tr > td { - border: 0; -} - - -ul.nav-arrow { - padding-top: 30px; - & > li { - z-index: 1000; - position: relative; - margin-right: -30px; - & > a { - border-top-right-radius: 0; - border-bottom-right-radius: 0; - } - &.active { - margin-right: -31px; - &:after { - display: block; - content: ''; - position: absolute; - top: 0px; - right: -12px; - border-top: 19px solid transparent; - border-bottom: 19px solid transparent; - border-left: 12px solid $link-color; - } - } - } -} - -.configuration-panel { - padding: 15px 0 0px 30px !important; - border: none; - border-left: 1px solid $border-color; - border-radius: 0; - box-shadow: none; -} - -.password-button + div.popover { - white-space: pre-wrap; - word-wrap: break-word; -} - -.form-horizontal label .popover { - min-width: 250px; - .popover-content { - font-weight: normal; - } -} - -// hacky positioning of advanced service config form buttons next to header -.page_form_actions { - margin-top: -5em; -} diff --git a/tuskar_ui/infrastructure/static/infrastructure/scss/_numberpicker.scss b/tuskar_ui/infrastructure/static/infrastructure/scss/_numberpicker.scss deleted file mode 100644 index d443c41ee..000000000 --- a/tuskar_ui/infrastructure/static/infrastructure/scss/_numberpicker.scss +++ /dev/null @@ -1,49 +0,0 @@ -// Number picker widget -div.number_picker { - text-align: left; - margin: 5px 14px; - width: 35px; - height: 35px; - padding: 0; - display: inline-block; - position: relative; - vertical-align: middle; - input { - border: 0; - width: 20px; - display: block; - margin: 8px auto; - padding: 0; - text-align: center; - outline: 0; - } - a { - display: block; - width: 14px; - position: absolute; - top: 8px; - cursor: default; - &.disabled { - cursor: default; - } - &.arrow-left { - left: -12px; - } - &.arrow-right { - right: -16px; - } - } - a.disabled { - opacity: 0.25; - } - &.readonly a { - display: none; - } - span.step { - display: block; - font-size: 75%; - position: absolute; - top: -2px; - right: -14px; - } -} diff --git a/tuskar_ui/infrastructure/static/infrastructure/scss/_tables.scss b/tuskar_ui/infrastructure/static/infrastructure/scss/_tables.scss deleted file mode 100644 index d881ef4cc..000000000 --- a/tuskar_ui/infrastructure/static/infrastructure/scss/_tables.scss +++ /dev/null @@ -1,74 +0,0 @@ -tr.table_actions_row { - th.table_header { - background: #f9f9f9; - border-top-width: 2px !important; - .table_actions { - float: none; - .table_search, .table_filter { - .filter_select { - display: inline-block; - position: relative; - background: rgb(243, 243, 243); - margin-right: -4px; - &:after, &:before { - left: 100%; - top: 50%; - border: solid transparent; - content: " "; - height: 0; - width: 0; - position: absolute; - pointer-events: none; - z-index: 3; - } - - &:after { - border-color: rgba(243, 243, 243, 0); - border-left-color: #f3f3f3; - border-width: 14px; - margin-top: -14px; - } - &:before { - border-color: rgba(204, 204, 204, 0); - border-left-color: #cccccc; - border-width: 16px; - margin-top: -16px; - } - select { - overflow:hidden; - width: 120%; - border-right: none; - box-shadow: none; - background: transparent; - border-top-right-radius: 0; - border-bottom-right-radius: 0; - } - .caret { - position: absolute; - right: 0; - top: 47%; - } - } - input[type="text"] { - padding-right: 5px; - } - input.filter_select_input { - border-top-left-radius: 0; - border-bottom-left-radius: 0; - border-left: none; - padding-left: 20px; - &:focus { - box-shadow: none; - } - } - } - } - } -} - -.table > thead > tr > th { - border-bottom: none; -} -tr.column_headers th { - border: 1px solid $gray-light; -} diff --git a/tuskar_ui/infrastructure/static/infrastructure/scss/infrastructure.scss b/tuskar_ui/infrastructure/static/infrastructure/scss/infrastructure.scss deleted file mode 100644 index 3194e008b..000000000 --- a/tuskar_ui/infrastructure/static/infrastructure/scss/infrastructure.scss +++ /dev/null @@ -1,15 +0,0 @@ -/* Additional CSS for infrastructure. */ -@import "/dashboard/scss/variables"; -@import "/bootstrap/scss/bootstrap/variables"; - -@import "numberpicker"; -@import "breadcrumbs"; -@import "buttons"; -@import "capacities"; -@import "flavor_usages"; -@import "formsets"; -@import "individual_pages"; -@import "tables"; -@import "charts"; -@import "icons"; -@import "detail_pages" diff --git a/tuskar_ui/infrastructure/static/infrastructure/tests/formset_table.js b/tuskar_ui/infrastructure/static/infrastructure/tests/formset_table.js deleted file mode 100644 index 184d7706b..000000000 --- a/tuskar_ui/infrastructure/static/infrastructure/tests/formset_table.js +++ /dev/null @@ -1,60 +0,0 @@ -horizon.addInitFunction(function () { - module("Formset table (tuskar.formset_table.js)"); - - test("Reenumerate rows", function () { - var html = $('#qunit-fixture'); - var table = html.find('table'); - var input = table.find('tbody tr#flavors__row__14 input').first(); - - input.attr('id', 'id_flavors-3-name'); - tuskar.formset_table.reenumerate_rows(table, 'flavors'); - equal(input.attr('id'), 'id_flavors-0-name', "Enumerate old rows ids"); - input.attr('id', 'id_flavors-__prefix__-name'); - tuskar.formset_table.reenumerate_rows(table, 'flavors'); - equal(input.attr('id'), 'id_flavors-0-name', "Enumerate new rows ids"); - }); - - test("Delete row", function () { - var html = $('#qunit-fixture'); - var table = html.find('table'); - var row = table.find('tbody tr').first(); - var input = row.find('input#id_flavors-0-DELETE'); - - equal(row.css("display"), 'table-row'); - equal(input.attr('checked'), undefined); - tuskar.formset_table.replace_delete(row); - var x = input.next('a'); - tuskar.formset_table.delete_row.call(x); - equal(row.css("display"), 'none'); - equal(input.attr('checked'), 'checked'); - }); - - test("Add row", function() { - var html = $('#qunit-fixture'); - var table = html.find('table'); - var empty_row_html = ''; - - equal(table.find('tbody tr').length, 3); - equal(html.find('#id_flavors-TOTAL_FORMS').val(), 3); - tuskar.formset_table.add_row(table, 'flavors', empty_row_html); - equal(table.find('tbody tr').length, 4); - equal(table.find('tbody tr:last input').attr('id'), 'id_flavors-3-name'); - equal(html.find('#id_flavors-TOTAL_FORMS').val(), 4); - }); - - test("Init formset table", function() { - var html = $('#qunit-fixture'); - var table = html.find('table'); - - tuskar.formset_table.init('flavors', '', 'Add row'); - equal(table.find('tfoot tr a').html(), 'Add row'); - }); - - test("Init formset table -- no add", function() { - var html = $('#qunit-fixture'); - var table = html.find('table'); - - tuskar.formset_table.init('flavors', '', ''); - equal(table.find('tfoot tr a').length, 0); - }); -}); diff --git a/tuskar_ui/infrastructure/templates/client_side/_modal_chart.html b/tuskar_ui/infrastructure/templates/client_side/_modal_chart.html deleted file mode 100644 index e26a7d4d9..000000000 --- a/tuskar_ui/infrastructure/templates/client_side/_modal_chart.html +++ /dev/null @@ -1,19 +0,0 @@ -{% extends "horizon/client_side/template.html" %} -{% load horizon %} - -{% block id %}modal_chart_template{% endblock %} - -{% block template %} -{% jstemplate %} -
    - - -
    -{% endjstemplate %} -{% endblock %} diff --git a/tuskar_ui/infrastructure/templates/client_side/templates.html b/tuskar_ui/infrastructure/templates/client_side/templates.html deleted file mode 100644 index c16465859..000000000 --- a/tuskar_ui/infrastructure/templates/client_side/templates.html +++ /dev/null @@ -1 +0,0 @@ -{% include "client_side/_modal_chart.html" %} diff --git a/tuskar_ui/infrastructure/templates/formset_table/_row.html b/tuskar_ui/infrastructure/templates/formset_table/_row.html deleted file mode 100644 index 5409a0b51..000000000 --- a/tuskar_ui/infrastructure/templates/formset_table/_row.html +++ /dev/null @@ -1,24 +0,0 @@ - - {% for cell in row %} - - {% if cell.field %} - {{ cell.field }} - {% else %} - {%if cell.wrap_list %}
      {% endif %}{{ cell.value }}{%if cell.wrap_list %}
    {% endif %} - {% endif %} - {% if forloop.first %} - {% for field in row.form.hidden_fields %} - {{ field }} - {% for error in field.errors %} - {{ field.name }}: {{ error }} - {% endfor %} - {% endfor %} - {% if row.form.non_field_errors %} -
    - {{ row.form.non_field_errors }} -
    - {% endif %} - {% endif %} - - {% endfor %} - diff --git a/tuskar_ui/infrastructure/templates/formset_table/_table.html b/tuskar_ui/infrastructure/templates/formset_table/_table.html deleted file mode 100644 index be90b7865..000000000 --- a/tuskar_ui/infrastructure/templates/formset_table/_table.html +++ /dev/null @@ -1,43 +0,0 @@ -{% extends 'horizon/common/_data_table.html' %} -{% load i18n %} - -{% block table_columns %} - {% if not table.is_browser_table %} - - {% for column in columns %} - {{ column }} - {% endfor %} - - {% endif %} -{% endblock table_columns %} - -{% block table %} - {% with table.get_formset as formset %} - {{ formset.management_form }} - {% if formset.non_field_errors %} -
    - {{ formset.non_field_errors }} -
    - {% endif %} - {% endwith %} - {{ block.super }} - - -{% endblock table %} diff --git a/tuskar_ui/infrastructure/templates/formset_table/menu_formset.html b/tuskar_ui/infrastructure/templates/formset_table/menu_formset.html deleted file mode 100644 index e95680669..000000000 --- a/tuskar_ui/infrastructure/templates/formset_table/menu_formset.html +++ /dev/null @@ -1,44 +0,0 @@ -{% load i18n %} -{{ formset.management_form }} -{% for error in formset.non_form_errors %} -
    {{ error }}
    -{% endfor %} -
    -
    -
    - - - - - - -

    Nodes to register

    -
    -
    - {% include 'infrastructure/nodes/_upload.html' with form=upload_form %} -
    - -
    -
    -
    - {% for form in formset %} - {% include form_template with form=form active=forloop.first %} - {% endfor %} -
    -
    -
    - diff --git a/tuskar_ui/infrastructure/templates/horizon/common/_data_table_row_action_dropdown.html b/tuskar_ui/infrastructure/templates/horizon/common/_data_table_row_action_dropdown.html deleted file mode 100644 index d719b5d8c..000000000 --- a/tuskar_ui/infrastructure/templates/horizon/common/_data_table_row_action_dropdown.html +++ /dev/null @@ -1,11 +0,0 @@ -{% if action.method != "GET" %} - -{% else %} - - {% if action.icon != None %} {% endif %} - {{ action.verbose_name }} - -{% endif %} diff --git a/tuskar_ui/infrastructure/templates/horizon/common/_data_table_row_action_dropdown_first_item.html b/tuskar_ui/infrastructure/templates/horizon/common/_data_table_row_action_dropdown_first_item.html deleted file mode 100644 index 1596e247f..000000000 --- a/tuskar_ui/infrastructure/templates/horizon/common/_data_table_row_action_dropdown_first_item.html +++ /dev/null @@ -1,17 +0,0 @@ -{% if action.method != "GET" %} - -{% else %} - - {% if action.icon != None %} - - {% else %} - {{ action.verbose_name }} - {% endif %} - -{% endif %} diff --git a/tuskar_ui/infrastructure/templates/horizon/common/_data_table_row_actions_dropdown.html b/tuskar_ui/infrastructure/templates/horizon/common/_data_table_row_actions_dropdown.html deleted file mode 100644 index 3c842b685..000000000 --- a/tuskar_ui/infrastructure/templates/horizon/common/_data_table_row_actions_dropdown.html +++ /dev/null @@ -1,27 +0,0 @@ -{% load horizon i18n %} - -{% spaceless %} {# This makes sure whitespace doesn't affect positioning for dropdown. #} -{% if row_actions|length > 1 %} -
    - {% for action in row_actions %} - {% if forloop.first %} - {% include "horizon/common/_data_table_row_action_dropdown_first_item.html" %} - - - - - {% endif %} - {% endfor %} -
    -{% endif %} -{% if row_actions|length == 1%} - {% include "horizon/common/_data_table_row_action_dropdown_first_item.html" with action=row_actions.0%} -{% endif %} -{% endspaceless %} diff --git a/tuskar_ui/infrastructure/templates/horizon/common/_data_table_table_actions.html b/tuskar_ui/infrastructure/templates/horizon/common/_data_table_table_actions.html deleted file mode 100644 index 4cc4afe3b..000000000 --- a/tuskar_ui/infrastructure/templates/horizon/common/_data_table_table_actions.html +++ /dev/null @@ -1,68 +0,0 @@ -{% load i18n %} -
    -{% block table_filter %} - {% if filter.filter_type == 'fixed' %} -
    - {% for button in filter.fixed_buttons %} - - {% endfor %} -
    - {% elif filter.filter_type == 'query' %} - - {% elif filter.filter_type == 'server' %} - - {% endif %} -{% endblock table_filter %} -{% block table_actions %} -
    - {% for action in table_actions_buttons %} - {% include "horizon/common/_data_table_table_action.html" %} - {% endfor %} - {% if table_actions_menu|length > 0 %} -
    - - {% if table_actions_buttons|length > 0 %} - {% trans "More Actions" %} - {% else %} - {% trans "Actions" %} - {% endif %} - - - -
    - {% endif %} -
    -{% endblock table_actions %} -
    diff --git a/tuskar_ui/infrastructure/templates/horizon/common/_definition_list_data_table.html b/tuskar_ui/infrastructure/templates/horizon/common/_definition_list_data_table.html deleted file mode 100644 index 064c5ac6e..000000000 --- a/tuskar_ui/infrastructure/templates/horizon/common/_definition_list_data_table.html +++ /dev/null @@ -1,5 +0,0 @@ -{% extends "horizon/common/_enhanced_data_table.html" %} - -{% block table_css_classes %}table table-condensed definition-list-table {{ table.css_classes }}{% endblock %} - -{% block table_footer %}{% endblock %} diff --git a/tuskar_ui/infrastructure/templates/horizon/common/_enhanced_data_table.html b/tuskar_ui/infrastructure/templates/horizon/common/_enhanced_data_table.html deleted file mode 100644 index 0fb7fccb2..000000000 --- a/tuskar_ui/infrastructure/templates/horizon/common/_enhanced_data_table.html +++ /dev/null @@ -1,54 +0,0 @@ -{% extends "horizon/common/_data_table.html" %} - - -{% block table %} - - - {% block table_caption %} - {% if not hidden_title %} - - - - {% endif %} - {% if table.get_table_actions %} - - - - {% endif %} - {% endblock table_caption %} - - {% block table_breadcrumb %} - {{ block.super }} - {% endblock table_breadcrumb %} - - {% block table_columns %} - {% if not table.is_browser_table %} - - {% for column in columns %} - - {% endfor %} - - {% endif %} - {% endblock table_columns %} - - - {% block table_body %} - {{ block.super }} - {% endblock table_body %} - - {% block table_footer %} - {{ block.super }} - {% endblock table_footer %} -
    -

    {{ table }}

    -
    - {{ table.render_table_actions }} -
    - {{ column }} - {% if column.help_text %} - - - - {% endif %} -
    -{% endblock table %} diff --git a/tuskar_ui/infrastructure/templates/horizon/common/_horizontal_field.html b/tuskar_ui/infrastructure/templates/horizon/common/_horizontal_field.html deleted file mode 100644 index f15f92848..000000000 --- a/tuskar_ui/infrastructure/templates/horizon/common/_horizontal_field.html +++ /dev/null @@ -1,20 +0,0 @@ -{% load form_helpers %} - -
    - -
    - {{ field|add_bootstrap_class }} - {% for error in field.errors %} - {{ error }} - {% empty %} - {% comment %} - Escape help_text a second time here, to avoid an XSS issue in bootstrap.js. - This can most likely be removed once we upgrade bootstrap.js past 2.0.2. - Note: the spaces are necessary here. - {% endcomment %} - {% if field.help_text %} - {% filter force_escape %} {{ field.help_text }} {% endfilter %} - {% endif %} - {% endfor %} -
    -
    diff --git a/tuskar_ui/infrastructure/templates/horizon/common/_items_count_domain_page_header.html b/tuskar_ui/infrastructure/templates/horizon/common/_items_count_domain_page_header.html deleted file mode 100644 index dafe4007b..000000000 --- a/tuskar_ui/infrastructure/templates/horizon/common/_items_count_domain_page_header.html +++ /dev/null @@ -1,17 +0,0 @@ -{% load i18n %} -{% block page_header %} - -{% endblock %} diff --git a/tuskar_ui/infrastructure/templates/horizon/common/_items_count_tab_group.html b/tuskar_ui/infrastructure/templates/horizon/common/_items_count_tab_group.html deleted file mode 100644 index 1d8731337..000000000 --- a/tuskar_ui/infrastructure/templates/horizon/common/_items_count_tab_group.html +++ /dev/null @@ -1,25 +0,0 @@ -{% with tab_group.get_tabs as tabs %} -{% if tabs %} - - {# Tab Navigation #} - - - {# Tab Content #} -
    - {% for tab in tabs %} -
    - {{ tab.render }} -
    - {% endfor %} -
    - -{% endif %} -{% endwith %} diff --git a/tuskar_ui/infrastructure/templates/infrastructure/_fullscreen_workflow.html b/tuskar_ui/infrastructure/templates/infrastructure/_fullscreen_workflow.html deleted file mode 100644 index f7870a1b8..000000000 --- a/tuskar_ui/infrastructure/templates/infrastructure/_fullscreen_workflow.html +++ /dev/null @@ -1,37 +0,0 @@ -{% load i18n %} -{% with workflow.get_entry_point as entry_point %} -
    -
    - {% csrf_token %} - {% if REDIRECT_URL %}{% endif %} -
    -
    - {% block workflow-buttons %} - - {% endblock %} -
    - {% block workflow-body %} - -
    - {% for step in workflow.steps %} -
    - {{ step.render }} -
    - {% if not forloop.last %} - - {% endif %} - {% endfor %} -
    - {% endblock %} -
    -
    -
    -{% endwith %} diff --git a/tuskar_ui/infrastructure/templates/infrastructure/_fullscreen_workflow_base.html b/tuskar_ui/infrastructure/templates/infrastructure/_fullscreen_workflow_base.html deleted file mode 100644 index 35dc70e79..000000000 --- a/tuskar_ui/infrastructure/templates/infrastructure/_fullscreen_workflow_base.html +++ /dev/null @@ -1,11 +0,0 @@ -{% extends 'infrastructure/base.html' %} -{% load i18n %} -{% block title %}{% trans workflow.name %}{% endblock %} - -{% block page_header %} - {% include "horizon/common/_page_header.html" with title=workflow.name %} -{% endblock page_header %} - -{% block main %} - {% include 'infrastructure/_fullscreen_workflow.html' %} -{% endblock %} diff --git a/tuskar_ui/infrastructure/templates/infrastructure/_performance_chart.html b/tuskar_ui/infrastructure/templates/infrastructure/_performance_chart.html deleted file mode 100644 index f3806712b..000000000 --- a/tuskar_ui/infrastructure/templates/infrastructure/_performance_chart.html +++ /dev/null @@ -1,16 +0,0 @@ -

    {{ label }}

    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    diff --git a/tuskar_ui/infrastructure/templates/infrastructure/_performance_chart_box.html b/tuskar_ui/infrastructure/templates/infrastructure/_performance_chart_box.html deleted file mode 100644 index 1ad016077..000000000 --- a/tuskar_ui/infrastructure/templates/infrastructure/_performance_chart_box.html +++ /dev/null @@ -1,68 +0,0 @@ -{% load i18n %} -{% load url from future%} - -{% if meter_conf %} -
    -
    -
    -
    -
    -
    -
    - -
    -
    -
    -
    -
    {% trans "From" %}
    - -
    -
    -
    -
    -
    {% trans "To" %}
    - -
    -
    -
    -
    -
    -
    - - - -
    - {% for meter_label, url_part, y_max in meter_conf %} -
    - {% include "infrastructure/_performance_chart.html" with label=meter_label y_max=y_max url=node_perf_url|add:"?"|add:url_part only %} -
    - {% endfor %} -
    -{% else %} -

    {% trans 'Metering service is not enabled.' %}

    -{% endif %} diff --git a/tuskar_ui/infrastructure/templates/infrastructure/_scripts.html b/tuskar_ui/infrastructure/templates/infrastructure/_scripts.html deleted file mode 100644 index ff190f013..000000000 --- a/tuskar_ui/infrastructure/templates/infrastructure/_scripts.html +++ /dev/null @@ -1,17 +0,0 @@ -{% block custom_js_files %} - - - - - - - - - - - - -{% endblock %} - -{% comment %} Tuskar-UI Client-side Templates (These should *not* be inside the "compress" tag.) {% endcomment %} -{% include "client_side/templates.html" %} diff --git a/tuskar_ui/infrastructure/templates/infrastructure/_top_5_box.html b/tuskar_ui/infrastructure/templates/infrastructure/_top_5_box.html deleted file mode 100644 index 1ac664d79..000000000 --- a/tuskar_ui/infrastructure/templates/infrastructure/_top_5_box.html +++ /dev/null @@ -1,8 +0,0 @@ -
    -
    {% include "infrastructure/_top_5_chart.html" with top_5=top_5.fan%}
    -
    {% include "infrastructure/_top_5_chart.html" with top_5=top_5.voltage %}
    -
    {% include "infrastructure/_top_5_chart.html" with top_5=top_5.temperature%}
    -
    -
    -
    {% include "infrastructure/_top_5_chart.html" with top_5=top_5.current %}
    -
    diff --git a/tuskar_ui/infrastructure/templates/infrastructure/_top_5_chart.html b/tuskar_ui/infrastructure/templates/infrastructure/_top_5_chart.html deleted file mode 100644 index d4d730cfa..000000000 --- a/tuskar_ui/infrastructure/templates/infrastructure/_top_5_chart.html +++ /dev/null @@ -1,29 +0,0 @@ -{% load i18n %} -{% load chart_helpers %} - -

    - {% trans 'Top 5 Nodes' %} ({{ top_5.label }}): -

    -{% if top_5.data %} - - {% for d in top_5.data %} - - - - - - {% endfor %} -
    - - {{ d.node_uuid|truncatechars:6 }} - - - {{ d.value}} {{ top_5.unit }} - - {%if d.direction %} - - {% endif %} -
    -{% else %} -{% trans 'No data available.' %} -{% endif %} diff --git a/tuskar_ui/infrastructure/templates/infrastructure/_workflow_base.html b/tuskar_ui/infrastructure/templates/infrastructure/_workflow_base.html deleted file mode 100644 index 5aa1a0501..000000000 --- a/tuskar_ui/infrastructure/templates/infrastructure/_workflow_base.html +++ /dev/null @@ -1,11 +0,0 @@ -{% extends 'infrastructure/base.html' %} -{% load i18n %} -{% block title %}{% trans workflow.name %}{% endblock %} - -{% block page_header %} - {% include "horizon/common/_page_header.html" with title=workflow.name %} -{% endblock page_header %} - -{% block main %} - {% include 'horizon/common/_workflow.html' %} -{% endblock %} diff --git a/tuskar_ui/infrastructure/templates/infrastructure/base.html b/tuskar_ui/infrastructure/templates/infrastructure/base.html deleted file mode 100644 index c5ebbc4c5..000000000 --- a/tuskar_ui/infrastructure/templates/infrastructure/base.html +++ /dev/null @@ -1,17 +0,0 @@ -{% extends 'base.html' %} - -{% block css %} - {{block.super}} - - {% load compress %} - {% compress css %} - - - - {% endcompress %} -{% endblock %} - -{% block js %} - {{ block.super }} - {% include "infrastructure/_scripts.html" %} -{% endblock %} diff --git a/tuskar_ui/infrastructure/templates/infrastructure/base_detail.html b/tuskar_ui/infrastructure/templates/infrastructure/base_detail.html deleted file mode 100644 index 591c5362e..000000000 --- a/tuskar_ui/infrastructure/templates/infrastructure/base_detail.html +++ /dev/null @@ -1,27 +0,0 @@ -{% extends 'infrastructure/base.html' %} - -{% block main %} - -
    -
    - {% block breadcrumbs %}{% endblock %} - -
    - {% block actions %}{% endblock %} -
    - -

    {% block name %}{% endblock %}

    -
    -
    - -
    -
    -
    - {% block overall_usage %}{% endblock %} -
    - - {{ tab_group.render }} -
    -
    - -{% endblock %} diff --git a/tuskar_ui/infrastructure/templates/infrastructure/header_actions.html b/tuskar_ui/infrastructure/templates/infrastructure/header_actions.html deleted file mode 100644 index 6544f2955..000000000 --- a/tuskar_ui/infrastructure/templates/infrastructure/header_actions.html +++ /dev/null @@ -1,10 +0,0 @@ - diff --git a/tuskar_ui/infrastructure/templates/infrastructure/nodes/_auto_discover_csv.html b/tuskar_ui/infrastructure/templates/infrastructure/nodes/_auto_discover_csv.html deleted file mode 100644 index b6d7f14b2..000000000 --- a/tuskar_ui/infrastructure/templates/infrastructure/nodes/_auto_discover_csv.html +++ /dev/null @@ -1,23 +0,0 @@ -{% extends "horizon/common/_modal_form.html" %} -{% load i18n %} -{% load url from future %} - -{% block form_id %}autodiscover_csv_nodes_form{% endblock %} -{% block form_action %}{% url 'horizon:infrastructure:nodes:register' %}{% endblock %} - -{% block modal_id %}autodiscover_csv_nodes_modal{% endblock %} -{% block modal-header %}{% trans "Upload Nodes" %}{% endblock %} -{% block form_attrs %}enctype="multipart/form-data"{% endblock %} - -{% block modal-body %} - {% include "horizon/common/_form_fields.html" %} -{% endblock %} - -{% block modal-footer %} - - {% trans "Cancel" %} -{% endblock %} diff --git a/tuskar_ui/infrastructure/templates/infrastructure/nodes/_detail_overview.html b/tuskar_ui/infrastructure/templates/infrastructure/nodes/_detail_overview.html deleted file mode 100644 index e000da055..000000000 --- a/tuskar_ui/infrastructure/templates/infrastructure/nodes/_detail_overview.html +++ /dev/null @@ -1,105 +0,0 @@ -{% load i18n %} -{% load icon_helpers %} - -
    -
    -

    {{ node.power_state|iconized_ironic_node_state|safe }}

    - -

    {% trans "Inventory" %}

    -
    -
    {% trans "Node UUID" %}
    -
    {{ node.uuid|default:"—" }}
    -
    {% trans "Driver" %}
    -
    - - {{ node.driver|default:"—" }} - - -
    -
    - {% if node.driver == 'pxe_ssh' %} -
    IP Address
    {{ node.driver_info.ssh_address|default:"—" }}
    -
    IPMI User
    {{ node.driver_info.ssh_username|default:"—" }}
    -
    SSH Key
    {{ node.driver_info.ssh_key_contents|default:"—" }}
    - {% else %} -
    IP Address
    {{ node.driver_info.ipmi_address|default:"—" }}
    -
    IPMI User
    {{ node.driver_info.ipmi_username|default:"—" }}
    -
    IPMI Password
    -
    - -
    - {% endif %} -
    -
    -
    -
    {% trans "Network Cards" %}
    -
    - - {% blocktrans count addresses_length=node.addresses|length %} - {{ addresses_length }} interface - {% plural %} - {{ addresses_length }} interfaces - {% endblocktrans %} - - -
    -
      - {% for address in node.addresses %} -
    • {{ address }}
    • - {% endfor %} -
    -
    -
    -
    {% trans "Registered HW" %}
    -
    - {{ node.cpu_arch|default:"—" }}
    - {{ node.cpus|default:"—" }} {% trans "CPU" %}
    - {{ node.memory_mb|default:"—" }} {% trans "RAM (MB)" %}
    - {{ node.local_gb|default:"—" }} {% trans "HDD (GB)" %} -
    -
    - -

    {% trans "Deployment" %}

    -
    -
    {% trans "Deployment Role" %}
    - {% if stack and role %} -
    {{ role.name }}
    - {% else %} -
    - {% endif %} -
    {% trans "Provisioning" %}
    -
    - {{ node.provisioning_status|default:"—" }} - {% if node.instance_uuid %} -
    {{ node.instance.created }} - {% endif %} -
    -
    {% trans "Image" %}
    -
    {{ node.image_name|default:"—" }}
    -
    {% trans "Instance UUID" %}
    -
    {{ node.instance_uuid|default:"—" }}
    -
    - -

    {% trans "Deployment Images" %}

    -
    -
    {% trans "Kernel" %}
    -
    {{ kernel_image.name|default:"—" }}
    -
    {% trans "Ramdisk" %}
    -
    {{ ramdisk_image.name|default:"—" }}
    -
    - -
    -
    -

    {% trans "Performance & Metrics" %}

    - {% url 'horizon:infrastructure:nodes:performance' node.uuid as node_perf_url %} - {% include "infrastructure/_performance_chart_box.html" with meter_conf=meter_conf node_perf_url=node_perf_url col_size=6 %} -
    -
    - -{% block additional_data %} -{% endblock %} \ No newline at end of file diff --git a/tuskar_ui/infrastructure/templates/infrastructure/nodes/_nodes_formset_field.html b/tuskar_ui/infrastructure/templates/infrastructure/nodes/_nodes_formset_field.html deleted file mode 100644 index 1b1c489a6..000000000 --- a/tuskar_ui/infrastructure/templates/infrastructure/nodes/_nodes_formset_field.html +++ /dev/null @@ -1,15 +0,0 @@ -{% load form_helpers %} -
    -
    - -
    {{ field|add_bootstrap_class }}
    -
    {{ extra_text|default:'' }}
    -
    - {% if field.errors %} -
    - {% for error in field.errors %} - {{ error }} - {% endfor %} -
    - {% endif %} -
    diff --git a/tuskar_ui/infrastructure/templates/infrastructure/nodes/_nodes_formset_form.html b/tuskar_ui/infrastructure/templates/infrastructure/nodes/_nodes_formset_form.html deleted file mode 100644 index c828125db..000000000 --- a/tuskar_ui/infrastructure/templates/infrastructure/nodes/_nodes_formset_form.html +++ /dev/null @@ -1,49 +0,0 @@ -{% load i18n %} -{% load form_helpers %} - -
    -
    - {% include 'horizon/common/_form_errors.html' with form=form %} -

    {% trans "Node Detail" %}

    -
    -
    {% trans "Power Management" %}
    - {% include 'infrastructure/nodes/_nodes_formset_field.html' with field=form.driver required=True %} - {% include 'infrastructure/nodes/_nodes_formset_field.html' with field=form.ipmi_address required=True %} - {% include 'infrastructure/nodes/_nodes_formset_field.html' with field=form.ipmi_username %} - {% include 'infrastructure/nodes/_nodes_formset_field.html' with field=form.ipmi_password %} - {% include 'infrastructure/nodes/_nodes_formset_field.html' with field=form.ssh_address required=True %} - {% include 'infrastructure/nodes/_nodes_formset_field.html' with field=form.ssh_username required=True %} - {% include 'infrastructure/nodes/_nodes_formset_field.html' with field=form.ssh_key_contents required=True %} -
    -
    -
    {% trans "Networking" %}
    - {% include 'infrastructure/nodes/_nodes_formset_field.html' with field=form.mac_addresses required=True %} -
    -
    -
    {% trans "Hardware" %}
    - {% include 'infrastructure/nodes/_nodes_formset_field.html' with field=form.cpu_arch %} - {% include 'infrastructure/nodes/_nodes_formset_field.html' with field=form.cpus extra_text=_('units') %} - {% include 'infrastructure/nodes/_nodes_formset_field.html' with field=form.memory_mb extra_text=_('MB') %} - {% include 'infrastructure/nodes/_nodes_formset_field.html' with field=form.local_gb extra_text=_('GB') %} -
    -
    -
    {% trans "Deployment Images" %}
    - {% include 'infrastructure/nodes/_nodes_formset_field.html' with field=form.deployment_kernel %} - {% include 'infrastructure/nodes/_nodes_formset_field.html' with field=form.deployment_ramdisk %} -
    -
    -
    - - diff --git a/tuskar_ui/infrastructure/templates/infrastructure/nodes/_overview.html b/tuskar_ui/infrastructure/templates/infrastructure/nodes/_overview.html deleted file mode 100644 index 373084afb..000000000 --- a/tuskar_ui/infrastructure/templates/infrastructure/nodes/_overview.html +++ /dev/null @@ -1,146 +0,0 @@ -{% load i18n %} -{% load url from future%} - -
    -
    -

    {% trans 'Hardware Inventory' %}

    -
    - -
    -

    {{ cpus }} {% trans 'CPU cores' %}

    -

    {{ memory_gb|floatformat:"0" }} {% trans 'GB of memory' %}

    -

    {{ local_gb|floatformat:"0" }} {% trans 'GB of storage' %}

    -
    -
    -
    -

    {% trans 'Nodes Status' %}

    -
    -
    -
    -
    -

    {% trans 'Power Status' %}

    -
    -
    -
    -
    - -

    {% trans "Provisioned nodes" %}

    -
    -
    -
    -
    - - {{ nodes_provisioned_count }} -

    {% trans 'Provisioned' %}

    {% trans 'Nodes' %}

    -
    - {% if nodes_provisioning_count %} -
    - - - {% blocktrans count nodes_provisioning_count as counter %} - {{ counter }} node - {% plural %} - {{ counter }} nodes - {% endblocktrans %} - - {% blocktrans count nodes_provisioning_count as counter %} - is being provisioned - {% plural %} - are being provisioned - {% endblocktrans %} -
    - {% endif %} - {% if nodes_deleting_count %} -
    - - - {% blocktrans count nodes_deleting_count as counter %} - {{ counter }} node - {% plural %} - {{ counter }} nodes - {% endblocktrans %} - - {% blocktrans count nodes_deleting_count as counter %} - is being deleted - {% plural %} - are being deleted - {% endblocktrans %} -
    - {% endif %} - {% if nodes_error_count %} - - {% endif %} -
    -
    -
    - {% if nodes_provisioned_count or nodes_provisioning_count %} - {% url 'horizon:infrastructure:nodes:nodes_performance' as node_perf_url %} - {% include "infrastructure/_performance_chart_box.html" with meter_conf=meter_conf node_perf_url=node_perf_url col_size=2 %} - {% endif %} -
    -
    -{% if nodes_provisioned_count or nodes_provisioning_count %} -{% include "infrastructure/_top_5_box.html" %} -{% endif %} -{% if nodes_on_discovery_count or nodes_discovered_count or nodes_discovery_failed_count %} -

    {% trans "Nodes Discovery" %}

    -
    -
    -
    -
    - {% if nodes_discovered_count %} - - {{ nodes_discovered_count }} -

    {% trans 'Discovered' %}

    {% trans 'Nodes' %}

    -
    - ({{ nodes_discovered_count }} {% trans 'waiting for activation' %}) - {% endif %} - {% if nodes_on_discovery_count %} -
    - - - {% blocktrans count nodes_on_discovery_count as counter %} - {{ counter }} node - {% plural %} - {{ counter }} nodes - {% endblocktrans %} - - {% blocktrans count nodes_on_discovery_count as counter %} - is being discovered - {% plural %} - are being discovered - {% endblocktrans %} -
    - {% endif %} - {% if nodes_discovery_failed_count %} - - {% endif %} -
    -
    -
    -{% endif %} diff --git a/tuskar_ui/infrastructure/templates/infrastructure/nodes/_register.html b/tuskar_ui/infrastructure/templates/infrastructure/nodes/_register.html deleted file mode 100644 index 1a1486a81..000000000 --- a/tuskar_ui/infrastructure/templates/infrastructure/nodes/_register.html +++ /dev/null @@ -1,13 +0,0 @@ -{% extends "horizon/common/_modal_form.html" %} -{% load i18n %} -{% load url from future %} - -{% block form_id %}register_nodes_form{% endblock %} -{% block form_action %}{% url 'horizon:infrastructure:nodes:register' %}{% endblock %} -{% block modal_id %}register_nodes_modal{% endblock %} -{% block modal-header %}{% trans "Register Nodes" %}{% endblock %} -{% block form_attrs %}enctype="multipart/form-data"{% endblock %} - -{% block modal-body %} -{% include "formset_table/menu_formset.html" with formset=form form_template="infrastructure/nodes/_nodes_formset_form.html" %} -{% endblock %} diff --git a/tuskar_ui/infrastructure/templates/infrastructure/nodes/_upload.html b/tuskar_ui/infrastructure/templates/infrastructure/nodes/_upload.html deleted file mode 100644 index 7de46ec66..000000000 --- a/tuskar_ui/infrastructure/templates/infrastructure/nodes/_upload.html +++ /dev/null @@ -1,7 +0,0 @@ -{% load i18n %} -
    - {% include 'horizon/common/_form_field.html' with field=form.csv_file %} -
    - - -{% trans 'Cancel' %} diff --git a/tuskar_ui/infrastructure/templates/infrastructure/nodes/detail.html b/tuskar_ui/infrastructure/templates/infrastructure/nodes/detail.html deleted file mode 100644 index bbbe8fe1e..000000000 --- a/tuskar_ui/infrastructure/templates/infrastructure/nodes/detail.html +++ /dev/null @@ -1,13 +0,0 @@ -{% extends 'infrastructure/base.html' %} -{% load i18n %} -{% block title %}{% trans "Node Details" %}{% endblock %} -{% block page_header %} -{% include "horizon/common/_page_header.html" %} -{% endblock page_header %} -{% block main %} -
    -
    - {{ tab_group.render }} -
    -
    -{% endblock %} \ No newline at end of file diff --git a/tuskar_ui/infrastructure/templates/infrastructure/nodes/index.html b/tuskar_ui/infrastructure/templates/infrastructure/nodes/index.html deleted file mode 100644 index 302d44189..000000000 --- a/tuskar_ui/infrastructure/templates/infrastructure/nodes/index.html +++ /dev/null @@ -1,16 +0,0 @@ -{% extends 'infrastructure/base.html' %} -{% load i18n %} -{% load url from future %} -{% block title %}{% trans 'Nodes' %}{% endblock %} - -{% block page_header %} - {% include 'horizon/common/_items_count_domain_page_header.html' with title=_('Nodes') %} -{% endblock page_header %} - -{% block main %} -
    -
    - {{ tab_group.render }} -
    -
    -{% endblock %} diff --git a/tuskar_ui/infrastructure/templates/infrastructure/nodes/register.html b/tuskar_ui/infrastructure/templates/infrastructure/nodes/register.html deleted file mode 100644 index 3e9cb1f5a..000000000 --- a/tuskar_ui/infrastructure/templates/infrastructure/nodes/register.html +++ /dev/null @@ -1,11 +0,0 @@ -{% extends 'infrastructure/base.html' %} -{% load i18n %} -{% block title %}{% trans "Register Nodes" %}{% endblock %} - -{% block page_header %} - {% include "horizon/common/_page_header.html" with title=_("Register Nodes") %} -{% endblock %} - -{% block main %} - {% include "infrastructure/nodes/_register.html" %} -{% endblock %} diff --git a/tuskar_ui/infrastructure/templates/infrastructure/overview/_deploy_confirmation.html b/tuskar_ui/infrastructure/templates/infrastructure/overview/_deploy_confirmation.html deleted file mode 100644 index b3daed670..000000000 --- a/tuskar_ui/infrastructure/templates/infrastructure/overview/_deploy_confirmation.html +++ /dev/null @@ -1,40 +0,0 @@ -{% extends "horizon/common/_modal_form.html" %} -{% load i18n %} -{% load url from future %} - -{% block form_id %}provision_form{% endblock %} -{% block form_action %}{% url 'horizon:infrastructure:overview:deploy_confirmation' %}{% endblock %} - -{% block modal_id %}provision_modal{% endblock %} -{% block modal-header %}{% trans "Deployment Confirmation" %}{% endblock %} - -{% block modal-body %} -
    -

    {% trans "You are about deploy your overcloud." %}

    - {% if autogenerated_parameters %} -   - {{ autogenerated_parameters|length }} parameters will be randomly generated. - - - -
    -
    • - {{ autogenerated_parameters|join:"
    • " }} -
    -
    - {% endif %} -
    -
    -
    -
    - {% include "horizon/common/_form_fields.html" %} -
    -
    -

    -
    - -

    {% trans "This operation cannot be undone." %}

    -

    {% trans "Are you sure you want to deploy changes?" %}

    -
    -
    -{% endblock %} diff --git a/tuskar_ui/infrastructure/templates/infrastructure/overview/_last_events.html b/tuskar_ui/infrastructure/templates/infrastructure/overview/_last_events.html deleted file mode 100644 index e0659d6ad..000000000 --- a/tuskar_ui/infrastructure/templates/infrastructure/overview/_last_events.html +++ /dev/null @@ -1,18 +0,0 @@ -{% if show_last_events %} -{{ last_events_title }} -
    -
    - {% for event in last_events %} -
    - -
    - {{ event.resource_name }} | - {{ event.resource_status }} | - {{ event.resource_status_reason }} -
    -
    - {% endfor %} -
    -
    -

    See full log

    -{% endif %} diff --git a/tuskar_ui/infrastructure/templates/infrastructure/overview/_post_deploy_init.html b/tuskar_ui/infrastructure/templates/infrastructure/overview/_post_deploy_init.html deleted file mode 100644 index d0f9c52a5..000000000 --- a/tuskar_ui/infrastructure/templates/infrastructure/overview/_post_deploy_init.html +++ /dev/null @@ -1,22 +0,0 @@ -{% extends "horizon/common/_modal_form.html" %} -{% load i18n %} -{% load url from future %} - -{% block form_id %}post_deploy_init_form{% endblock %} -{% block form_action %}{% url 'horizon:infrastructure:overview:post_deploy_init' %}{% endblock %} - -{% block modal_id %}provision_modal{% endblock %} -{% block modal-header %}{% trans "Initialize Overcloud" %}{% endblock %} - -{% block modal-body %} -
    -
    -
    - {% include "horizon/common/_form_fields.html" %} -
    -
    - {% trans "Your OpenStack cloud nodes are deployed. They need to be initialized before your cloud will be live."%} -
    -
    -
    -{% endblock %} diff --git a/tuskar_ui/infrastructure/templates/infrastructure/overview/_scale_out.html b/tuskar_ui/infrastructure/templates/infrastructure/overview/_scale_out.html deleted file mode 100644 index 550aa42dd..000000000 --- a/tuskar_ui/infrastructure/templates/infrastructure/overview/_scale_out.html +++ /dev/null @@ -1,41 +0,0 @@ -{% extends "horizon/common/_modal_form.html" %} -{% load i18n %} -{% load url from future %} -{% load form_helpers %} - -{% block form_id %}scale_out_form{% endblock %} -{% block form_action %}{% url 'horizon:infrastructure:overview:scale_out' %}{% endblock %} - -{% block modal_id %}scale_out_modal{% endblock %} -{% block modal-header %}{% trans "Scale-out Deployment" %}{% endblock %} - -{% block modal-body %} -
    - {% include 'horizon/common/_form_errors.html' with form=form %} -{% for role in roles %} -
    -
    - {{ role.name|capfirst }} - {% for error in role.field.errors %} - - {{ error }} - - {% endfor %} -
    -
    - {{ role.planned_node_count }} → -
    -
    - {{ role.field|add_bootstrap_class }} -
    -
    -{% endfor %} -
    - - - -{% endblock %} diff --git a/tuskar_ui/infrastructure/templates/infrastructure/overview/_undeploy_confirmation.html b/tuskar_ui/infrastructure/templates/infrastructure/overview/_undeploy_confirmation.html deleted file mode 100644 index 50b2c9430..000000000 --- a/tuskar_ui/infrastructure/templates/infrastructure/overview/_undeploy_confirmation.html +++ /dev/null @@ -1,20 +0,0 @@ -{% extends "horizon/common/_modal_form.html" %} -{% load i18n %} -{% load url from future %} - -{% block form_id %}provision_form{% endblock %} -{% block form_action %}{% url 'horizon:infrastructure:overview:undeploy_confirmation' %}{% endblock %} - -{% block modal_id %}provision_modal{% endblock %} -{% block modal-header %}{% trans "Undeployment Confirmation" %}{% endblock %} - -{% block modal-body %} -
    -

    {% trans "You are about undeploy your overcloud" %} -

    -

    {% trans "This operation cannot be undone. Are you sure you want to do that?" %}

    -
    - {% include "horizon/common/_form_fields.html" %} -
    -
    -{% endblock %} diff --git a/tuskar_ui/infrastructure/templates/infrastructure/overview/deploy_confirmation.html b/tuskar_ui/infrastructure/templates/infrastructure/overview/deploy_confirmation.html deleted file mode 100644 index 74f6dc867..000000000 --- a/tuskar_ui/infrastructure/templates/infrastructure/overview/deploy_confirmation.html +++ /dev/null @@ -1,11 +0,0 @@ -{% extends 'infrastructure/base.html' %} -{% load i18n %} -{% block title %}{% trans "Deploy overcloud" %}{% endblock %} - -{% block page_header %} - {% include "horizon/common/_page_header.html" with title=_("Deploy overcloud") %} -{% endblock page_header %} - -{% block infrastructure_main %} - {% include "infrastructure/overview/_deploy_confirmation.html" %} -{% endblock %} diff --git a/tuskar_ui/infrastructure/templates/infrastructure/overview/deployment_base.html b/tuskar_ui/infrastructure/templates/infrastructure/overview/deployment_base.html deleted file mode 100644 index 04bf8eefb..000000000 --- a/tuskar_ui/infrastructure/templates/infrastructure/overview/deployment_base.html +++ /dev/null @@ -1,27 +0,0 @@ -{% load i18n %} -{% load url from future%} - -
    - {% block deployment-heading %}{% endblock %} -
    -
    -
    - -
    -
    -

    {% block deployment-title %}{% endblock %}

    - {% block deployment-info %}{% endblock %} -
    - {% block deployment-buttons %} - - {% trans "Undeploy" %} - - {% endblock %} -
    -
    - -{% block deployment-overcloudrc %}{% endblock %} - -{% block templates %}{% endblock %} diff --git a/tuskar_ui/infrastructure/templates/infrastructure/overview/deployment_failed.html b/tuskar_ui/infrastructure/templates/infrastructure/overview/deployment_failed.html deleted file mode 100644 index c78acc230..000000000 --- a/tuskar_ui/infrastructure/templates/infrastructure/overview/deployment_failed.html +++ /dev/null @@ -1,25 +0,0 @@ -{% extends "infrastructure/overview/deployment_base.html" %} - -{% load i18n %} -{% load url from future%} - -{% block deployment-icon %}fa-exclamation-circle{% endblock %} - -{% block deployment-title %} - {% if stack.is_delete_failed %} - {% trans "Undeploying failed" %} - {% elif stack.is_failed %} - {% trans "Deployment failed" %} - {% else %} - {% trans "Failure" %} - {% endif %} -{% endblock %} - -{% block deployment-info %} -{% include "infrastructure/overview/_last_events.html" %} -{% endblock %} - -{% block deployment-buttons %} - {{ block.super }} -{% endblock %} - diff --git a/tuskar_ui/infrastructure/templates/infrastructure/overview/deployment_initialize.html b/tuskar_ui/infrastructure/templates/infrastructure/overview/deployment_initialize.html deleted file mode 100644 index af29031e4..000000000 --- a/tuskar_ui/infrastructure/templates/infrastructure/overview/deployment_initialize.html +++ /dev/null @@ -1,21 +0,0 @@ -{% extends "infrastructure/overview/deployment_base.html" %} - -{% load i18n %} -{% load url from future%} - -{% block deployment-icon %}fa-exclamation-triangle{% endblock %} - -{% block deployment-title %}{% trans "Initialization Needed" %}{% endblock %} - -{% block deployment-info %} -

    {% trans "Your OpenStack cloud is successfully deployed. Before using the cloud you must now initialize it." %}

    -{% endblock %} - -{% block deployment-buttons %} - {{ block.super }} - - - {% trans "Initialize" %} - -{% endblock %} diff --git a/tuskar_ui/infrastructure/templates/infrastructure/overview/deployment_live.html b/tuskar_ui/infrastructure/templates/infrastructure/overview/deployment_live.html deleted file mode 100644 index af6dfa812..000000000 --- a/tuskar_ui/infrastructure/templates/infrastructure/overview/deployment_live.html +++ /dev/null @@ -1,113 +0,0 @@ -{% extends "infrastructure/overview/deployment_base.html" %} - -{% load i18n %} -{% load url from future %} - -{% block deployment-heading %} -
    - -
    -
    -

    {% trans "Deployment is live" %}

    -
    -{% endblock %} - -{% block deployment-icon %}fa-key{% endblock %} -{% block deployment-title %}{% trans "Access Information" %}{% endblock %} -{% block deployment-info %} -
    - {% for dashboard_url in dashboard_urls %} -
    -
    {% trans "Horizon URL:" %}
    - -
    - {% endfor %} -
    -
    {% trans "User name:" %}
    -
    admin
    -
    -
    -
    {% trans "Password" %}
    -
    - {% trans "Reveal" %} -
    -
    -
    - - -{% endblock %} - -{% block deployment-buttons %} - {{ block.super }} - - {% trans "Scale-out" %} - - -{% endblock %} - -{% block deployment-overcloudrc %} -
    -
    - -
    -
    -

    {% trans "Overcloudrc Information" %}

    -
    -
    -
    NOVA_VERSION:
    -
    1.1
    -
    -
    -
    OS_USERNAME:
    -
    admin
    -
    -
    -
    OS_PASSWORD:
    -
    {{ admin_password }}
    -
    -
    -
    OS_AUTH_URL:
    -
    {{ auth_url }}
    -
    -
    -
    OS_TENANT_NAME:
    -
    admin
    -
    -
    -
    COMPUTE_API_VERSION:
    -
    1.1
    -
    -
    -
    OS_NO_CACHE:
    -
    True
    -
    -
    -
    no_proxy:
    -
    {{ no_proxy }}
    -
    -
    -
    {% trans "Download" %}
    - -
    -
    -
    -{% endblock %} diff --git a/tuskar_ui/infrastructure/templates/infrastructure/overview/deployment_plan.html b/tuskar_ui/infrastructure/templates/infrastructure/overview/deployment_plan.html deleted file mode 100644 index 52b3c275a..000000000 --- a/tuskar_ui/infrastructure/templates/infrastructure/overview/deployment_plan.html +++ /dev/null @@ -1,51 +0,0 @@ -{% extends "infrastructure/overview/deployment_base.html" %} - -{% load i18n %} -{% load url from future%} -{% load horizon %} - -{% block deployment-icon %} -{% if plan_invalid %}fa-exclamation-circle{% else %}fa-check-circle{% endif %} -{% endblock %} - -{% block deployment-title %} - {% trans "Deployment Checklist" %} -{% endblock %} - -{% block deployment-info %} - -
      - {% for message in plan_messages %} -
    • - - {{ message.text }} -
    • - {% endfor %} -
    -{% endblock %} - -{% block deployment-buttons %} - - {% trans "Verify and Deploy" %} - -{% endblock %} - -{% block templates %} - - -{% endblock %} diff --git a/tuskar_ui/infrastructure/templates/infrastructure/overview/deployment_progress.html b/tuskar_ui/infrastructure/templates/infrastructure/overview/deployment_progress.html deleted file mode 100644 index acfdd9ddf..000000000 --- a/tuskar_ui/infrastructure/templates/infrastructure/overview/deployment_progress.html +++ /dev/null @@ -1,69 +0,0 @@ -{% extends "infrastructure/overview/deployment_base.html" %} - -{% load i18n %} -{% load url from future%} -{% load horizon %} - -{% block deployment-icon %}fa-spinner fa-spin{% endblock %} - -{% block deployment-title %} - {% if stack.is_deleting %} - {% trans "Undeploying..." %} - {% elif stack.is_deploying %} - {% trans "Deploying..." %} - {% elif stack.is_updating %} - {% trans "Updating..." %} - {% endif %} -{% endblock %} - -{% block deployment-info %} -{% if progress %} -
    -
    {{ progress }}% {% trans "Complete" %}
    -
    -{% endif %} -
    - {% include "infrastructure/overview/_last_events.html" %} -
    -{% endblock %} - -{% block deployment-buttons %} - {% if stack.is_deploying or stack.is_updating %} - - - {% trans "Stop" %} - - {% endif %} -{% endblock %} - -{% block templates %} - -{% endblock %} diff --git a/tuskar_ui/infrastructure/templates/infrastructure/overview/index.html b/tuskar_ui/infrastructure/templates/infrastructure/overview/index.html deleted file mode 100644 index fa4273cc6..000000000 --- a/tuskar_ui/infrastructure/templates/infrastructure/overview/index.html +++ /dev/null @@ -1,36 +0,0 @@ -{% extends 'infrastructure/base.html' %} -{% load i18n %} -{% load url from future %} - -{% block title %}{% trans 'My OpenStack Deployment' %}{% endblock %} - -{% block page_header %} - {% include 'horizon/common/_domain_page_header.html' with title=_('My OpenStack Deployment') %} -{% endblock page_header %} - -{% block main %} -
    -
    - {% if stack %} - {% if stack.is_deleting or stack.is_deploying or stack.is_updating %} - {% include "infrastructure/overview/deployment_progress.html" %} - {% elif stack.is_delete_failed or stack.is_failed %} - {% include "infrastructure/overview/deployment_failed.html" %} - {% elif stack.is_deployed and not stack.is_initialized %} - {% include "infrastructure/overview/deployment_initialize.html" %} - {% else %} - {% include "infrastructure/overview/deployment_live.html" %} - {% endif %} - {% else %} - {% include "infrastructure/overview/deployment_plan.html" %} - {% endif %} -
    -
    - {% if stack %} - {% include "infrastructure/overview/role_nodes_status.html" %} - {% else %} - {% include "infrastructure/overview/role_nodes_edit.html" %} - {% endif %} -
    -
    -{% endblock %} diff --git a/tuskar_ui/infrastructure/templates/infrastructure/overview/overcloudrc.sh.template b/tuskar_ui/infrastructure/templates/infrastructure/overview/overcloudrc.sh.template deleted file mode 100644 index 5977bb37d..000000000 --- a/tuskar_ui/infrastructure/templates/infrastructure/overview/overcloudrc.sh.template +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/bash -export NOVA_VERSION=1.1 -export OS_AUTH_URL={{ auth_url }} -export OS_TENANT_NAME="admin" -export OS_USERNAME="admin" -export OS_PASSWORD={{ admin_password }} -export COMPUTE_API_VERSION=1.1 -export OS_NO_CACHE=True -export no_proxy={{ no_proxy }} diff --git a/tuskar_ui/infrastructure/templates/infrastructure/overview/post_deploy_init.html b/tuskar_ui/infrastructure/templates/infrastructure/overview/post_deploy_init.html deleted file mode 100644 index 835c0e524..000000000 --- a/tuskar_ui/infrastructure/templates/infrastructure/overview/post_deploy_init.html +++ /dev/null @@ -1,11 +0,0 @@ -{% extends 'infrastructure/base.html' %} -{% load i18n %} -{% block title %}{% trans "Initialize" %}{% endblock %} - -{% block page_header %} - {% include "horizon/common/_page_header.html" with title=_("Initialize Overcloud") %} -{% endblock page_header %} - -{% block infrastructure_main %} - {% include "infrastructure/overview/_post_deploy_init.html" %} -{% endblock %} diff --git a/tuskar_ui/infrastructure/templates/infrastructure/overview/role_nodes_edit.html b/tuskar_ui/infrastructure/templates/infrastructure/overview/role_nodes_edit.html deleted file mode 100644 index 5a47a8f61..000000000 --- a/tuskar_ui/infrastructure/templates/infrastructure/overview/role_nodes_edit.html +++ /dev/null @@ -1,38 +0,0 @@ -{% load i18n %} -{% load url from future %} -{% load form_helpers %} - -

    {% trans "Deployment Roles" %}

    -
    -{% csrf_token %} -{% include 'horizon/common/_form_errors.html' with form=form %} -{% for role in roles %} -
    -
    - -
    -
    - {{ role.name }} - {% for error in role.field.errors %} - - {{ error }} - - {% endfor %} -
    -
    - {{ role.field|add_bootstrap_class }} -
    -
    -{% endfor %} -
    - -
    diff --git a/tuskar_ui/infrastructure/templates/infrastructure/overview/role_nodes_status.html b/tuskar_ui/infrastructure/templates/infrastructure/overview/role_nodes_status.html deleted file mode 100644 index f31c6babc..000000000 --- a/tuskar_ui/infrastructure/templates/infrastructure/overview/role_nodes_status.html +++ /dev/null @@ -1,50 +0,0 @@ -{% load i18n %} -{% load url from future %} -{% load horizon %} - -

    {% trans "Deployment Roles" %}

    -
    -{% for role in roles %} -
    -
    - {% if role.planned_node_count %} - - {% endif %} -
    -
    - {{ role.name }} -
    -
    - {% if role.finished %} - {{ role.deployed_node_count }} - {% else %} - {{ role.deployed_node_count }}/{{ role.planned_node_count }} - {% endif %} -
    -
    -{% endfor %} -
    - - diff --git a/tuskar_ui/infrastructure/templates/infrastructure/overview/scale_out.html b/tuskar_ui/infrastructure/templates/infrastructure/overview/scale_out.html deleted file mode 100644 index 4ef31c202..000000000 --- a/tuskar_ui/infrastructure/templates/infrastructure/overview/scale_out.html +++ /dev/null @@ -1,11 +0,0 @@ -{% extends 'infrastructure/base.html' %} -{% load i18n %} -{% block title %}{% trans "Scale-out Deployment" %}{% endblock %} - -{% block page_header %} - {% include "horizon/common/_page_header.html" with title=_("Scale-out Deployment") %} -{% endblock page_header %} - -{% block infrastructure_main %} - {% include "infrastructure/overview/_scale_out.html" %} -{% endblock %} diff --git a/tuskar_ui/infrastructure/templates/infrastructure/overview/undeploy_confirmation.html b/tuskar_ui/infrastructure/templates/infrastructure/overview/undeploy_confirmation.html deleted file mode 100644 index b234ac95b..000000000 --- a/tuskar_ui/infrastructure/templates/infrastructure/overview/undeploy_confirmation.html +++ /dev/null @@ -1,11 +0,0 @@ -{% extends 'infrastructure/base.html' %} -{% load i18n %} -{% block title %}{% trans "Undeploy overcloud" %}{% endblock %} - -{% block page_header %} - {% include "horizon/common/_page_header.html" with title=_("Undeploy overcloud") %} -{% endblock page_header %} - -{% block infrastructure_main %} - {% include "infrastructure/overview/_undeploy_confirmation.html" %} -{% endblock %} diff --git a/tuskar_ui/infrastructure/templates/infrastructure/qunit.html b/tuskar_ui/infrastructure/templates/infrastructure/qunit.html deleted file mode 100644 index 71a7815fe..000000000 --- a/tuskar_ui/infrastructure/templates/infrastructure/qunit.html +++ /dev/null @@ -1,189 +0,0 @@ - - - - - Tuskar QUnit Test Suite - - - - {% include "horizon/_conf.html" %} - - {% comment %}Load test modules here.{% endcomment %} - - {% comment %}End test modules.{% endcomment %} - - {% include "horizon/_scripts.html" %} - {% include "infrastructure/_scripts.html" %} - - -

    Tuskar JavaScript Tests

    -

    -
    -

    -
      -
      - - -
      - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      -

      Flavors

      - -
      -
      Flavor NameVCPURAM (MB)Root Disk (GB)Ephemeral Disk - (GB)Swap Disk - (MB)Max. - VMs - Delete
      -
      -
      -
      Displaying 3 - items
      -
      - -
      - - diff --git a/tuskar_ui/infrastructure/templatetags/__init__.py b/tuskar_ui/infrastructure/templatetags/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/tuskar_ui/infrastructure/templatetags/chart_helpers.py b/tuskar_ui/infrastructure/templatetags/chart_helpers.py deleted file mode 100644 index 2a3e0e205..000000000 --- a/tuskar_ui/infrastructure/templatetags/chart_helpers.py +++ /dev/null @@ -1,55 +0,0 @@ -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -import json - -from django import template - - -register = template.Library() - - -@register.filter() -def remaining_capacity_by_flavors(obj): - flavors = obj.list_flavors - - decorated_obj = " ".join( - [("

      {0} {1}

      ").format( - str(flavor.used_instances), - flavor.name) - for flavor in flavors]) - - decorated_obj = ("

      Capacity remaining by flavors:

      " + - decorated_obj) - - return decorated_obj - - -@register.filter() -def all_used_instances(obj): - flavors = obj.list_flavors - - all_used_instances_info = [] - for flavor in flavors: - info = {} - info['popup_used'] = ( - '

      {0}% total,' - ' {1} instances of {2}

      '.format( - flavor.used_instances, - flavor.used_instances, - flavor.name)) - info['used_instances'] = str(flavor.used_instances) - - all_used_instances_info.append(info) - - return json.dumps(all_used_instances_info) diff --git a/tuskar_ui/infrastructure/templatetags/icon_helpers.py b/tuskar_ui/infrastructure/templatetags/icon_helpers.py deleted file mode 100644 index b3a88476a..000000000 --- a/tuskar_ui/infrastructure/templatetags/icon_helpers.py +++ /dev/null @@ -1,58 +0,0 @@ -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -from django import template -from django.utils.translation import ugettext_lazy as _ - - -register = template.Library() - -IRONIC_NODE_STATE_STRING_DICT = { - 'active': _('powered on'), - 'wait call-back': _('waiting'), - 'deploying': _('deploying'), - 'deploy failed': _('deployment failed'), - 'deploy complete': _('deployment complete'), - 'deleting': _('deleting'), - 'deleted': _('deleted'), - 'error': _('error'), - 'rebuild': _('rebuilding'), - 'power on': _('powered on'), - 'power off': _('powered off'), - 'rebooting': _('rebooting'), -} - -IRONIC_NODE_STATE_ICON_DICT = { - 'active': 'fa-play', - 'wait call-back': 'fa-spinner', - 'deploying': 'fa-spinner', - 'deploy failed': 'fa-warning', - 'deploy complete': 'fa-ok', - 'deleting': 'fa-spinner', - 'deleted': 'fa-cancel', - 'error': 'fa-warning', - 'rebuild': 'fa-spinner', - 'power on': 'fa-play', - 'power off': 'fa-stop', - 'rebooting': 'fa-spinner', -} - - -@register.filter(is_safe=True) -def iconized_ironic_node_state(node_power_state): - state = IRONIC_NODE_STATE_STRING_DICT.get(node_power_state, "—") - icon = IRONIC_NODE_STATE_ICON_DICT.get(node_power_state, 'fa-question') - html_string = (u"""""" - u"""%s """) % (icon, unicode(state)) - - return html_string diff --git a/tuskar_ui/infrastructure/templatetags/tests.py b/tuskar_ui/infrastructure/templatetags/tests.py deleted file mode 100644 index 3d338c47d..000000000 --- a/tuskar_ui/infrastructure/templatetags/tests.py +++ /dev/null @@ -1,73 +0,0 @@ -# -*- coding: utf8 -*- -# -# 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 collections -import json - -from tuskar_ui.infrastructure.templatetags import chart_helpers -from tuskar_ui.infrastructure.templatetags import icon_helpers -from tuskar_ui.test import helpers - - -Flavor = collections.namedtuple('Flavor', 'name used_instances') -Flavors = collections.namedtuple('Flavors', 'list_flavors') - - -class ChartHelpersTest(helpers.TestCase): - def test_remaining_capacity_by_flavors(self): - flavors = Flavors([ - Flavor('a', 0), - Flavor('b', 1), - ]) - ret = chart_helpers.remaining_capacity_by_flavors(flavors) - self.assertEqual( - ret, - '

      Capacity remaining by flavors:

      ' - '

      0 a

      ' - '

      1 b

      ' - ) - - def test_all_used_instances(self): - flavors = Flavors([ - Flavor('a', 0), - Flavor('b', 1), - ]) - ret = chart_helpers.all_used_instances(flavors) - self.assertEqual(ret, json.dumps([ - { - 'popup_used': '

      0% total, ' - ' 0 instances of a

      ', - 'used_instances': '0', - }, { - 'popup_used': '

      1% total, ' - ' 1 instances of b

      ', - 'used_instances': '1', - }, - ])) - - -class IconHelpersTest(helpers.TestCase): - def test_iconized_ironic_node_state(self): - ret = icon_helpers.iconized_ironic_node_state('active') - self.assertEqual( - ret, - u'' - 'powered on ', - ) - ret = icon_helpers.iconized_ironic_node_state('') - self.assertEqual( - ret, - u'' - ' ', - ) diff --git a/tuskar_ui/infrastructure/views.py b/tuskar_ui/infrastructure/views.py deleted file mode 100644 index b22aade3b..000000000 --- a/tuskar_ui/infrastructure/views.py +++ /dev/null @@ -1,46 +0,0 @@ -# -*- coding: utf8 -*- -# -# 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 horizon.utils import memoized - -from tuskar_ui import api - - -class ItemCountMixin(object): - def get_items_count(self): - return len(self.get_data()) - - def get_context_data(self, **kwargs): - context = super(ItemCountMixin, self).get_context_data(**kwargs) - context['items_count'] = self.get_items_count() - return context - - -class StackMixin(object): - @memoized.memoized - def get_plan(self): - return api.tuskar.Plan.get_the_plan(self.request) - - @memoized.memoized - def get_stack(self): - return api.heat.Stack.get_by_plan(self.request, self.get_plan()) - - -class RoleMixin(object): - @memoized.memoized - def get_role(self, redirect=None): - role_id = self.kwargs['role_id'] - role = api.tuskar.Role.get(self.request, role_id, - _error_redirect=redirect) - return role diff --git a/tuskar_ui/tables.py b/tuskar_ui/tables.py deleted file mode 100644 index c780bbb5e..000000000 --- a/tuskar_ui/tables.py +++ /dev/null @@ -1,177 +0,0 @@ -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -import itertools -import logging -import sys - -from django import template -from django.template import loader -from django.utils import datastructures -from horizon.tables import base as horizon_tables - - -LOG = logging.getLogger(__name__) -STRING_SEPARATOR = "__" - - -class FormsetCell(horizon_tables.Cell): - """A DataTable cell that knows about its field from the fieldset.""" - - def __init__(self, *args, **kwargs): - super(FormsetCell, self).__init__(*args, **kwargs) - try: - self.field = (self.row.form or {})[self.column.name] - except KeyError: - self.field = None - else: - if self.field.errors: - self.attrs['class'] = ( - '%s error control-group' % self.attrs.get('class', '')) - self.attrs['title'] = ' '.join( - unicode(error) for error in self.field.errors) - - -class FormsetRow(horizon_tables.Row): - """A DataTable row that knows about its form from the fieldset.""" - - template_path = 'formset_table/_row.html' - - def __init__(self, column, datum, form): - self.form = form - super(FormsetRow, self).__init__(column, datum) - if self.cells == []: - # We need to be able to handle empty rows, because there may - # be extra empty forms in a formset. The original DataTable breaks - # on this, because it sets self.cells to [], but later expects a - # SortedDict. We just fill self.cells with empty Cells. - cells = [] - for column in self.table.columns.values(): - cell = self.table._meta.cell_class(None, column, self) - cells.append((column.name or column.auto, cell)) - self.cells = datastructures.SortedDict(cells) - - def render(self): - return loader.render_to_string(self.template_path, - {"row": self, "form": self.form}) - - -class FormsetDataTableMixin(object): - """A mixin for DataTable to support Django Formsets. - - This works the same as the ``FormsetDataTable`` below, but can be used - to add to existing DataTable subclasses. - """ - formset_class = None - - def __init__(self, *args, **kwargs): - super(FormsetDataTableMixin, self).__init__(*args, **kwargs) - self._formset = None - - # Override Meta settings, because we need custom Form and Cell classes, - # and also our own template. - self._meta.row_class = FormsetRow - self._meta.cell_class = FormsetCell - self._meta.template = 'formset_table/_table.html' - - def get_required_columns(self): - """Lists names of columns that have required fields.""" - required_columns = [] - if self.formset_class: - empty_form = self.get_formset().empty_form - for column in self.columns.values(): - field = empty_form.fields.get(column.name) - if field and field.required: - required_columns.append(column.name) - return required_columns - - def _get_formset_data(self): - """Formats the self.filtered_data in a way suitable for a formset.""" - data = [] - for datum in self.filtered_data: - form_data = {} - for column in self.columns.values(): - value = column.get_data(datum) - form_data[column.name] = value - form_data['id'] = self.get_object_id(datum) - data.append(form_data) - return data - - def get_formset(self): - """Provide the formset corresponding to this DataTable. - - Use this to validate the formset and to get the submitted data back. - """ - if self._formset is None: - self._formset = self.formset_class( - self.request.POST or None, - initial=self._get_formset_data(), - prefix=self._meta.name) - return self._formset - - def get_empty_row(self): - """Return a row with no data, for adding at the end of the table.""" - return self._meta.row_class(self, None, self.get_formset().empty_form) - - def get_rows(self): - """Return the row data for this table broken out by columns. - - The row objects get an additional ``form`` parameter, with the - formset form corresponding to that row. - """ - try: - rows = [] - if self.formset_class is None: - formset = [] - else: - formset = self.get_formset() - formset.is_valid() - for datum, form in itertools.izip_longest(self.filtered_data, - formset): - row = self._meta.row_class(self, datum, form) - if self.get_object_id(datum) == self.current_item_id: - self.selected = True - row.classes.append('current_selected') - rows.append(row) - except Exception: - # Exceptions can be swallowed at the template level here, - # re-raising as a TemplateSyntaxError makes them visible. - LOG.exception("Error while rendering table rows.") - exc_info = sys.exc_info() - raise template.TemplateSyntaxError, exc_info[1], exc_info[2] - return rows - - def get_object_id(self, datum): - # We need to support ``None`` when there are more forms than data. - if datum is None: - return None - return super(FormsetDataTableMixin, self).get_object_id(datum) - - -class FormsetDataTable(FormsetDataTableMixin, horizon_tables.DataTable): - """A DataTable with support for Django Formsets. - - Note that :attr:`~horizon.tables.DataTableOptions.row_class` and - :attr:`~horizon.tables.DataTaleOptions.cell_class` are overwritten in this - class, so setting them in ``Meta`` has no effect. - - .. attribute:: formset_class - - A classs made with :function:`~django.forms.formsets.formset_factory` - containing the definition of the formset to use with this data table. - - The columns that are named the same as the formset fields will be - replaced with form widgets in the table. Any hidden fields from the - formset will also be included. The fields that are not hidden and - don't correspond to any column will not be included in the form. - """ diff --git a/tuskar_ui/test/__init__.py b/tuskar_ui/test/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/tuskar_ui/test/api_tests/__init__.py b/tuskar_ui/test/api_tests/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/tuskar_ui/test/api_tests/heat_tests.py b/tuskar_ui/test/api_tests/heat_tests.py deleted file mode 100644 index 235ba8f15..000000000 --- a/tuskar_ui/test/api_tests/heat_tests.py +++ /dev/null @@ -1,134 +0,0 @@ -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -from __future__ import absolute_import - -from django.utils import unittest -from heatclient.v1 import events -import mock -from mock import patch # noqa - -from tuskar_ui import api -import tuskar_ui.api.heat # noqa -from tuskar_ui.test import helpers as test - - -class HeatAPITests(test.APITestCase): - def test_stack_list(self): - stacks = self.heatclient_stacks.list() - - with patch('openstack_dashboard.api.heat.stacks_list', - return_value=(stacks, None, None)): - stacks = api.heat.Stack.list(self.request) - for stack in stacks: - self.assertIsInstance(stack, api.heat.Stack) - self.assertEqual(1, len(stacks)) - - def test_stack_get(self): - stack = self.heatclient_stacks.first() - - with patch('openstack_dashboard.api.heat.stack_get', - return_value=stack): - ret_val = api.heat.Stack.get(self.request, stack.id) - self.assertIsInstance(ret_val, api.heat.Stack) - - def test_stack_plan(self): - stack = api.heat.Stack(self.heatclient_stacks.first(), - self.request) - plan = self.tuskarclient_plans.first() - - with patch('tuskarclient.v2.plans.PlanManager.list', - return_value=[plan]): - ret_val = stack.plan - self.assertIsInstance(ret_val, api.tuskar.Plan) - - def test_stack_events(self): - event_list = self.heatclient_events.list() - stack = self.heatclient_stacks.first() - - with patch('openstack_dashboard.api.heat.events_list', - return_value=event_list): - ret_val = api.heat.Stack(stack).events - for e in ret_val: - self.assertIsInstance(e, events.Event) - self.assertEqual(8, len(ret_val)) - - def test_stack_is_deployed(self): - stack = api.heat.Stack(self.heatclient_stacks.first()) - ret_val = stack.is_deployed - self.assertFalse(ret_val) - - def test_stack_is_updating(self): - stack = api.heat.Stack(self.heatclient_stacks.first()) - ret_val = stack.is_updating - self.assertFalse(ret_val) - - def test_stack_is_deploying(self): - stack = api.heat.Stack(self.heatclient_stacks.first()) - ret_val = stack.is_deploying - self.assertFalse(ret_val) - - @unittest.skip("Add appropriate test data to deal with nested stacks.") - def test_stack_resources(self): - stack = api.heat.Stack(self.heatclient_stacks.first(), - request=self.request) - resources = self.heatclient_resources.list() - nodes = self.baremetalclient_nodes.list() - instances = [] - - with patch('openstack_dashboard.api.base.is_service_enabled', - return_value=False): - with patch('openstack_dashboard.api.heat.resources_list', - return_value=resources): - with patch('openstack_dashboard.api.nova.server_list', - return_value=(instances, None)): - with patch('novaclient.v2.contrib.baremetal.' - 'BareMetalNodeManager.list', - return_value=nodes): - ret_val = stack.resources() - - for i in ret_val: - self.assertIsInstance(i, api.heat.Resource) - self.assertEqual(4, len(ret_val)) - - def test_stack_keystone_ip(self): - stack = api.heat.Stack(self.heatclient_stacks.first()) - - self.assertEqual('192.0.2.23', stack.keystone_ip) - - def test_stack_dashboard_url(self): - stack = api.heat.Stack(self.heatclient_stacks.first()) - stack.plan = api.tuskar.Plan(self.tuskarclient_plans.first()) - - mocked_service = mock.Mock(id='horizon_id') - mocked_service.name = 'horizon' - - services = [mocked_service] - endpoints = [mock.Mock(service_id='horizon_id', - adminurl='http://192.0.2.23:/admin'), ] - - services_obj = mock.Mock( - **{'list.return_value': services, }) - - endpoints_obj = mock.Mock( - **{'list.return_value': endpoints, }) - - overcloud_keystone_client = mock.Mock( - services=services_obj, - endpoints=endpoints_obj) - - with patch('tuskar_ui.api.heat.overcloud_keystoneclient', - return_value=overcloud_keystone_client) as client_get: - self.assertEqual(['http://192.0.2.23:/admin'], - stack.dashboard_urls) - self.assertEqual(client_get.call_count, 1) diff --git a/tuskar_ui/test/api_tests/node_tests.py b/tuskar_ui/test/api_tests/node_tests.py deleted file mode 100644 index 7bde594bf..000000000 --- a/tuskar_ui/test/api_tests/node_tests.py +++ /dev/null @@ -1,161 +0,0 @@ -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -from __future__ import absolute_import - -import mock - -from novaclient.v2 import servers - -from tuskar_ui import api -import tuskar_ui.api.node -from tuskar_ui.test import helpers as test - - -def mock_ironicclient(node=None, nodes=()): - return mock.patch( - 'tuskar_ui.api.node.ironicclient', - return_value=mock.MagicMock(node=mock.MagicMock(**{ - 'create.return_value': node, - 'get.return_value': node, - 'get_by_instance_uuid.return_value': node, - 'find.return_value': node, - 'list.return_value': nodes, - 'delete.return_value': None, - })), - ) - - -class NodeAPITests(test.APITestCase): - def test_node_create(self): - node = tuskar_ui.api.node.Node(self.ironicclient_nodes.first()) - - with mock_ironicclient(node=node): - ret_val = api.node.Node.create( - self.request, - node.driver_info['ipmi_address'], - 'i386', - node.cpus, - node.memory_mb, - node.local_gb, - ['aa:aa:aa:aa:aa:aa'], - ipmi_username='admin', - ipmi_password='password') - - self.assertIsInstance(ret_val, api.node.Node) - - def test_node_get(self): - node = self.ironicclient_nodes.first() - instance = self.novaclient_servers.first() - - with mock_ironicclient(node=node), mock.patch( - 'openstack_dashboard.api.nova.server_list', - return_value=([instance], False), - ), mock.patch( - 'openstack_dashboard.api.nova.server_get', - return_value=instance, - ): - ret_val = api.node.Node.get(self.request, node.uuid) - ret_instance = ret_val.instance - - self.assertIsInstance(ret_val, api.node.Node) - self.assertIsInstance(ret_instance, servers.Server) - - def test_node_get_by_instance_uuid(self): - instance = self.novaclient_servers.first() - node = self.ironicclient_nodes.first() - nodes = self.ironicclient_nodes.list() - - with mock_ironicclient( - node=node, - nodes=nodes, - ), mock.patch( - 'openstack_dashboard.api.nova.server_get', - return_value=instance, - ), mock.patch( - 'openstack_dashboard.api.nova.server_list', - return_value=([instance], False), - ): - ret_val = api.node.Node.get_by_instance_uuid(self.request, - node.instance_uuid) - ret_instance = ret_val.instance - self.assertIsInstance(ret_val, api.node.Node) - self.assertIsInstance(ret_instance, servers.Server) - - def test_node_list(self): - instances = self.novaclient_servers.list() - node = self.ironicclient_nodes.first() - nodes = self.ironicclient_nodes.list() - - with mock_ironicclient( - node=node, - nodes=nodes - ), mock.patch( - 'openstack_dashboard.api.nova.server_list', - return_value=(instances, None), - ), mock.patch( - 'openstack_dashboard.api.nova.server_get', - return_value=instances[0], - ): - ret_val = api.node.Node.list(self.request) - - for node in ret_val: - self.assertIsInstance(node, api.node.Node) - self.assertEqual(9, len(ret_val)) - - def test_node_delete(self): - node = self.ironicclient_nodes.first() - with mock_ironicclient(node=node): - api.node.Node.delete(self.request, node.uuid) - - def test_node_set_maintenance(self): - node = self.ironicclient_nodes.first() - with mock_ironicclient(node=node): - api.node.Node.set_maintenance(self.request, node.uuid, False) - - def test_node_set_power_state(self): - node = self.ironicclient_nodes.first() - with mock_ironicclient(node=node): - api.node.Node.set_power_state(self.request, node.uuid, 'on') - - def test_node_instance(self): - node = self.ironicclient_nodes.first() - instance = self.novaclient_servers.first() - - with mock.patch( - 'openstack_dashboard.api.nova.server_get', - return_value=instance, - ), mock.patch( - 'openstack_dashboard.api.nova.server_list', - return_value=([instance], False), - ): - ret_val = api.node.Node(node).instance - self.assertIsInstance(ret_val, servers.Server) - - def test_node_image_name(self): - node = self.ironicclient_nodes.first() - instance = self.novaclient_servers.first() - image = self.glanceclient_images.first() - - with mock.patch( - 'openstack_dashboard.api.nova.server_get', - return_value=instance, - ), mock.patch( - 'openstack_dashboard.api.glance.image_get', - return_value=image, - ), mock.patch( - 'openstack_dashboard.api.nova.server_list', - return_value=([instance], False), - ): - ret_val = api.node.Node(node).image_name - self.assertEqual(ret_val, 'overcloud-full') diff --git a/tuskar_ui/test/api_tests/tuskar_tests.py b/tuskar_ui/test/api_tests/tuskar_tests.py deleted file mode 100644 index 3979bd732..000000000 --- a/tuskar_ui/test/api_tests/tuskar_tests.py +++ /dev/null @@ -1,321 +0,0 @@ -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -from __future__ import absolute_import -import contextlib - -from mock import patch, call # noqa - -from tuskar_ui import api -from tuskar_ui.test import helpers as test - - -class TuskarAPITests(test.APITestCase): - def test_plan_create(self): - plan = self.tuskarclient_plans.first() - - with patch('tuskarclient.v2.plans.PlanManager.create', - return_value=plan): - ret_val = api.tuskar.Plan.create(self.request, {}, {}) - self.assertIsInstance(ret_val, api.tuskar.Plan) - - def test_plan_list(self): - plans = self.tuskarclient_plans.list() - - with patch('tuskarclient.v2.plans.PlanManager.list', - return_value=plans): - ret_val = api.tuskar.Plan.list(self.request) - for plan in ret_val: - self.assertIsInstance(plan, api.tuskar.Plan) - self.assertEqual(1, len(ret_val)) - - def test_plan_get(self): - plan = self.tuskarclient_plans.first() - - with patch('tuskarclient.v2.plans.PlanManager.get', - return_value=plan): - ret_val = api.tuskar.Plan.get(self.request, plan.uuid) - - self.assertIsInstance(ret_val, api.tuskar.Plan) - - def test_plan_get_the_plan(self): - plan = self.tuskarclient_plans.first() - - with patch('tuskarclient.v2.plans.PlanManager.list', - return_value=[plan]): - with patch('tuskarclient.v2.plans.PlanManager.create', - return_value=plan): - ret_val = api.tuskar.Plan.get_the_plan(self.request) - - self.assertIsInstance(ret_val, api.tuskar.Plan) - - def test_plan_delete(self): - plan = self.tuskarclient_plans.first() - - with patch('tuskarclient.v2.plans.PlanManager.delete', - return_value=None): - api.tuskar.Plan.delete(self.request, plan.uuid) - - def test_plan_role_list(self): - with patch('tuskarclient.v2.plans.PlanManager.get', - return_value=[]): - plan = api.tuskar.Plan(self.tuskarclient_plans.first(), - self.request) - - with patch('tuskarclient.v2.roles.RoleManager.list', - return_value=self.tuskarclient_roles.list()): - ret_val = plan.role_list - self.assertEqual(4, len(ret_val)) - for r in ret_val: - self.assertIsInstance(r, api.tuskar.Role) - - def test_role_list(self): - roles = self.tuskarclient_roles.list() - - with patch('tuskarclient.v2.roles.RoleManager.list', - return_value=roles): - ret_val = api.tuskar.Role.list(self.request) - - for r in ret_val: - self.assertIsInstance(r, api.tuskar.Role) - self.assertEqual(4, len(ret_val)) - - def test_role_get(self): - roles = self.tuskarclient_roles.list() - - with patch('tuskarclient.v2.roles.RoleManager.list', - return_value=roles): - ret_val = api.tuskar.Role.get(self.request, - roles[0].uuid) - self.assertIsInstance(ret_val, api.tuskar.Role) - - def test_role_get_by_image(self): - plan = api.tuskar.Plan(self.tuskarclient_plans.first()) - image = self.glanceclient_images.first() - roles = self.tuskarclient_roles.list() - - with patch('tuskarclient.v2.roles.RoleManager.list', - return_value=roles): - ret_val = api.tuskar.Role.get_by_image( - self.request, plan, image) - self.assertIsInstance(ret_val, list) - self.assertEqual(len(ret_val), 3) - - def test_parameter_stripped_name(self): - plan = api.tuskar.Plan(self.tuskarclient_plans.first()) - param = plan.parameter('Controller-1::count') - - ret_val = param.stripped_name - self.assertEqual(ret_val, 'count') - - def test_parameter_role(self): - plan = api.tuskar.Plan(self.tuskarclient_plans.first(), - request=self.request) - param = plan.parameter('Controller-1::count') - roles = self.tuskarclient_roles.list() - - with patch('tuskarclient.v2.roles.RoleManager.list', - return_value=roles): - ret_val = param.role - self.assertIsInstance(ret_val, api.tuskar.Role) - self.assertEqual(ret_val.name, 'Controller') - - def test_list_generated_parameters(self): - plan = api.tuskar.Plan(self.tuskarclient_plans.first()) - with contextlib.nested( - patch('tuskar_ui.api.tuskar.Plan.parameter_list', - return_value=plan.parameters), - ) as (mock_parameter_list, ): - ret_val = plan.list_generated_parameters() - - self.assertEqual( - ret_val, - {'Controller-1::KeystoneCACertificate': { - 'description': 'Keystone CA CertificateAdmin', - 'hidden': True, - 'name': 'Controller-1::KeystoneCACertificate', - 'value': 'unset', - 'default': '', - 'label': 'Keystone CA CertificateAdmin', - 'parameter_type': 'string', - 'constraints': []}, - 'Controller-1::SnmpdReadonlyUserPassword': { - 'description': 'Snmpd password', - 'hidden': True, - 'name': 'Controller-1::SnmpdReadonlyUserPassword', - 'value': '', - 'default': '', - 'label': 'Snmpd password', - 'parameter_type': 'string', - 'constraints': []}, - 'Controller-1::AdminPassword': { - 'description': 'Admin password', - 'hidden': True, - 'name': 'Controller-1::AdminPassword', - 'value': 'unset', - 'default': '', - 'label': 'Admin Password', - 'parameter_type': 'string', - 'constraints': []}, - 'Controller-1::AdminToken': { - 'description': 'Admin Token', - 'hidden': True, - 'name': 'Controller-1::AdminToken', - 'value': '', - 'default': '', - 'label': 'Admin Token', - 'parameter_type': 'string', - 'constraints': []}, - 'Compute-1::SnmpdReadonlyUserPassword': { - 'description': 'Snmpd password', - 'hidden': True, - 'name': 'Compute-1::SnmpdReadonlyUserPassword', - 'value': 'unset', - 'default': '', - 'label': 'Snmpd password', - 'parameter_type': 'string', - 'constraints': []}}) - - mock_parameter_list.assert_called_once_with() - - def test_list_generated_parameters_without_prefix(self): - plan = api.tuskar.Plan(self.tuskarclient_plans.first()) - with contextlib.nested( - patch('tuskar_ui.api.tuskar.Plan.parameter_list', - return_value=plan.parameters), - ) as (mock_parameter_list, ): - ret_val = plan.list_generated_parameters(with_prefix=False) - - self.assertEqual( - ret_val, - {'SnmpdReadonlyUserPassword': { - 'description': 'Snmpd password', - 'hidden': True, - 'name': 'Compute-1::SnmpdReadonlyUserPassword', - 'value': 'unset', - 'default': '', - 'label': 'Snmpd password', - 'parameter_type': 'string', - 'constraints': []}, - 'KeystoneCACertificate': { - 'description': 'Keystone CA CertificateAdmin', - 'hidden': True, - 'name': 'Controller-1::KeystoneCACertificate', - 'value': 'unset', - 'default': '', - 'label': 'Keystone CA CertificateAdmin', - 'parameter_type': 'string', - 'constraints': []}, - 'AdminToken': { - 'description': 'Admin Token', - 'hidden': True, - 'name': 'Controller-1::AdminToken', - 'value': '', - 'default': '', - 'label': 'Admin Token', - 'parameter_type': 'string', - 'constraints': []}, - 'AdminPassword': { - 'description': 'Admin password', - 'hidden': True, - 'name': 'Controller-1::AdminPassword', - 'value': 'unset', - 'default': '', - 'label': 'Admin Password', - 'parameter_type': 'string', - 'constraints': []}}) - - mock_parameter_list.assert_called_once_with() - - def test_make_keystone_certificates(self): - plan = api.tuskar.Plan(self.tuskarclient_plans.first()) - with contextlib.nested( - patch('os_cloud_config.keystone_pki.create_ca_pair', - return_value=('ca_key_pem', 'ca_cert_pem')), - patch('os_cloud_config.keystone_pki.create_signing_pair', - return_value=('signing_key_pem', 'signing_cert_pem')) - ) as (mock_create_ca_pair, mock_create_signing_pair): - ret_val = plan._make_keystone_certificates( - {'KeystoneSigningCertificate': {}}) - - self.assertEqual( - ret_val, - {'KeystoneCACertificate': 'ca_cert_pem', - 'KeystoneSigningCertificate': 'signing_cert_pem', - 'KeystoneSigningKey': 'signing_key_pem'}) - - mock_create_ca_pair.assert_called_once_with() - mock_create_signing_pair.assert_called_once_with( - 'ca_key_pem', 'ca_cert_pem') - - def test_make_generated_parameters(self): - plan = api.tuskar.Plan(self.tuskarclient_plans.first()) - - with contextlib.nested( - patch('tuskar_ui.api.tuskar.Plan.parameter_list', - return_value=plan.parameters), - patch('tuskar_ui.api.tuskar.Plan._make_keystone_certificates', - return_value={'KeystoneCACertificate': 'ca_cert_pem'}), - patch('tuskar_ui.api.tuskar.password_generator', - return_value='generated_password') - ) as (mock_parameter_list, mock_make_keystone_certificates, - mock_password_generator): - ret_val = plan.make_generated_parameters() - - self.assertEqual( - ret_val, - {'Controller-1::KeystoneCACertificate': 'ca_cert_pem', - 'Controller-1::SnmpdReadonlyUserPassword': 'generated_password', - 'Controller-1::AdminPassword': 'generated_password', - 'Controller-1::AdminToken': 'generated_password', - 'Compute-1::SnmpdReadonlyUserPassword': 'generated_password'}) - - mock_parameter_list.assert_has_calls([ - call(), call()]) - mock_make_keystone_certificates.assert_called_once_with({ - 'SnmpdReadonlyUserPassword': { - 'description': 'Snmpd password', - 'hidden': True, - 'name': 'Compute-1::SnmpdReadonlyUserPassword', - 'value': 'unset', - 'default': '', - 'label': 'Snmpd password', - 'parameter_type': 'string', - 'constraints': []}, - 'KeystoneCACertificate': { - 'description': 'Keystone CA CertificateAdmin', - 'hidden': True, 'name': - 'Controller-1::KeystoneCACertificate', - 'value': 'unset', - 'default': '', - 'label': 'Keystone CA CertificateAdmin', - 'parameter_type': 'string', - 'constraints': []}, - 'AdminToken': { - 'description': 'Admin Token', - 'hidden': True, - 'name': 'Controller-1::AdminToken', - 'value': '', - 'default': '', - 'label': 'Admin Token', - 'parameter_type': 'string', - 'constraints': []}, - 'AdminPassword': { - 'description': 'Admin password', - 'hidden': True, - 'name': 'Controller-1::AdminPassword', - 'value': 'unset', - 'default': '', - 'label': 'Admin Password', - 'parameter_type': 'string', - 'constraints': []}}) diff --git a/tuskar_ui/test/formset_table_tests.py b/tuskar_ui/test/formset_table_tests.py deleted file mode 100644 index d50eb6af1..000000000 --- a/tuskar_ui/test/formset_table_tests.py +++ /dev/null @@ -1,57 +0,0 @@ -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -import django.forms -from horizon import tables - -import tuskar_ui.tables -from tuskar_ui.test import helpers as test - - -class FormsetTableTests(test.TestCase): - - def test_populate(self): - """Create a FormsetDataTable and populate it with data.""" - - class TableObj(object): - pass - - obj = TableObj() - obj.name = 'test object' - obj.value = 42 - obj.id = 4 - - class TableForm(django.forms.Form): - name = django.forms.CharField() - value = django.forms.IntegerField() - - TableFormset = django.forms.formsets.formset_factory(TableForm, - extra=0) - - class Table(tuskar_ui.tables.FormsetDataTable): - formset_class = TableFormset - - name = tables.Column('name') - value = tables.Column('value') - - class Meta(object): - name = 'table' - - table = Table(self.request) - table.data = [obj] - formset = table.get_formset() - self.assertEqual(len(formset), 1) - form = formset[0] - form_data = form.initial - self.assertEqual(form_data['name'], 'test object') - self.assertEqual(form_data['value'], 42) diff --git a/tuskar_ui/test/helpers.py b/tuskar_ui/test/helpers.py deleted file mode 100644 index aa5d16956..000000000 --- a/tuskar_ui/test/helpers.py +++ /dev/null @@ -1,53 +0,0 @@ -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -import os -import warnings - -from django.utils import unittest -from openstack_dashboard.test import helpers - -from tuskar_ui.test.test_data import utils - - -# Silences the warning about with statements. -warnings.filterwarnings('ignore', 'With-statements now directly support ' - 'multiple context managers', DeprecationWarning, - r'^tuskar_ui[.].*[._]tests$') - - -def create_stubs(stubs_to_create={}): - return helpers.create_stubs(stubs_to_create) - - -class TuskarTestsMixin(object): - def _setup_test_data(self): - super(TuskarTestsMixin, self)._setup_test_data() - utils.load_test_data(self) - - def add_panel_mocks(self): - pass - - -@unittest.skipIf(os.environ.get('SKIP_UNITTESTS', False), - "The SKIP_UNITTESTS env variable is set.") -class TestCase(TuskarTestsMixin, helpers.TestCase): - pass - - -class BaseAdminViewTests(TuskarTestsMixin, helpers.BaseAdminViewTests): - pass - - -class APITestCase(TuskarTestsMixin, helpers.APITestCase): - pass diff --git a/tuskar_ui/test/selenium.py b/tuskar_ui/test/selenium.py deleted file mode 100644 index 2eebd8cae..000000000 --- a/tuskar_ui/test/selenium.py +++ /dev/null @@ -1,55 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# 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. - -from __future__ import absolute_import - -import logging - -from horizon.test import helpers as test -from selenium.common import exceptions as selenium_exceptions - - -class BrowserTests(test.SeleniumTestCase): - def test_qunit(self): - url = "%s%s" % (self.live_server_url, "/qunit_tuskar/") - self.selenium.get(url) - wait = self.ui.WebDriverWait(self.selenium, 10) - - def qunit_done(driver): - text = driver.find_element_by_id("qunit-testresult").text - return "Tests completed" in text - - wait.until(qunit_done) - - failed_elem = self.selenium.find_element_by_class_name("failed") - failed = int(failed_elem.text) - if failed: - self.log_failure_messages() - self.assertEqual(failed, 0) - - def log_failure_messages(self): - logger = logging.getLogger('selenium') - logger.error("Errors found during qunit test:") - fail_elems = self.selenium.find_elements_by_class_name("fail") - for elem in fail_elems: - try: - module = elem.find_element_by_class_name("module-name").text - except selenium_exceptions.NoSuchElementException: - continue - message = elem.find_element_by_class_name("test-message").text - source = elem.find_element_by_tag_name("pre").text - logger.error("Module: %s, message: %s, source: %s" % ( - module, message, source)) diff --git a/tuskar_ui/test/settings.py b/tuskar_ui/test/settings.py deleted file mode 100644 index 33f52bb09..000000000 --- a/tuskar_ui/test/settings.py +++ /dev/null @@ -1,161 +0,0 @@ -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -import os - -from django.utils.translation import ugettext_lazy as _ - -from horizon.test.settings import * # noqa -from horizon.utils import secret_key as secret_key_utils - -from tuskar_ui import exceptions - - -DEBUG = True -TEMPLATE_DEBUG = DEBUG - -TEST_DIR = os.path.dirname(os.path.abspath(__file__)) -ROOT_PATH = os.path.abspath(os.path.join(TEST_DIR, "..")) - -MEDIA_ROOT = os.path.abspath(os.path.join(ROOT_PATH, '..', 'media')) -MEDIA_URL = '/media/' -STATIC_ROOT = os.path.abspath(os.path.join(ROOT_PATH, '..', 'static')) -STATIC_URL = '/static/' - -SECRET_KEY = secret_key_utils.generate_or_read_from_file( - os.path.join(TEST_DIR, '.secret_key_store')) -ROOT_URLCONF = 'tuskar_ui.test.urls' -TEMPLATE_DIRS = ( - os.path.join(TEST_DIR, 'templates'), -) - -TEMPLATE_CONTEXT_PROCESSORS += ( - 'openstack_dashboard.context_processors.openstack', -) - -INSTALLED_APPS = ( - 'django.contrib.contenttypes', - 'django.contrib.auth', - 'django.contrib.sessions', - 'django.contrib.staticfiles', - 'django.contrib.messages', - 'django.contrib.humanize', - 'django_nose', - 'openstack_auth', - 'compressor', - 'horizon', - 'openstack_dashboard', - 'tuskar_ui.infrastructure', - 'openstack_dashboard.dashboards.settings', -) - -AUTHENTICATION_BACKENDS = ('openstack_auth.backend.KeystoneBackend',) - -SITE_BRANDING = 'OpenStack' - -HORIZON_CONFIG = { - 'dashboards': ('infrastructure', 'settings'), - 'default_dashboard': 'infrastructure', - "password_validator": { - "regex": '^.{8,18}$', - "help_text": _("Password must be between 8 and 18 characters.") - }, - 'user_home': None, - 'help_url': "http://docs.openstack.org", - 'exceptions': {'recoverable': exceptions.RECOVERABLE, - 'not_found': exceptions.NOT_FOUND, - 'unauthorized': exceptions.UNAUTHORIZED}, -} - -# Set to True to allow users to upload images to glance via Horizon server. -# When enabled, a file form field will appear on the create image form. -# See documentation for deployment considerations. -HORIZON_IMAGES_ALLOW_UPLOAD = True - -AVAILABLE_REGIONS = [ - ('http://localhost:5000/v2.0', 'local'), - ('http://remote:5000/v2.0', 'remote'), -] - -OPENSTACK_KEYSTONE_URL = "http://localhost:5000/v2.0" -OPENSTACK_KEYSTONE_DEFAULT_ROLE = "Member" - -OPENSTACK_KEYSTONE_MULTIDOMAIN_SUPPORT = True -OPENSTACK_KEYSTONE_DEFAULT_DOMAIN = 'test_domain' - -OPENSTACK_KEYSTONE_BACKEND = { - 'name': 'native', - 'can_edit_user': True, - 'can_edit_group': True, - 'can_edit_project': True, - 'can_edit_domain': True, - 'can_edit_role': True -} - -OPENSTACK_NEUTRON_NETWORK = { - 'enable_lb': True -} - -OPENSTACK_HYPERVISOR_FEATURES = { - 'can_set_mount_point': True, - - # NOTE: as of Grizzly this is not yet supported in Nova so enabling this - # setting will not do anything useful - 'can_encrypt_volumes': False -} - -LOGGING['loggers']['openstack_dashboard'] = { - 'handlers': ['test'], - 'propagate': False, -} - -LOGGING['loggers']['selenium'] = { - 'handlers': ['test'], - 'propagate': False, -} - -LOGGING['loggers']['tuskar_ui'] = { - 'handlers': ['test'], - 'propagate': False, -} - -SECURITY_GROUP_RULES = { - 'all_tcp': { - 'name': 'ALL TCP', - 'ip_protocol': 'tcp', - 'from_port': '1', - 'to_port': '65535', - }, - 'http': { - 'name': 'HTTP', - 'ip_protocol': 'tcp', - 'from_port': '80', - 'to_port': '80', - }, -} - -NOSE_ARGS = ['--nocapture', - '--nologcapture', - '--cover-package=openstack_dashboard', - '--cover-inclusive', - '--all-modules'] - -TUSKAR_ENDPOINT_URL = "http://127.0.0.1:8585" - -OVERCLOUD_CREDS = { - 'enabled': True, - 'user': 'admin', - 'password': 'password', - 'tenant': 'admin', - 'auth_url': 'http://localhost:5000/v2.0/', -} diff --git a/tuskar_ui/test/test_data/__init__.py b/tuskar_ui/test/test_data/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/tuskar_ui/test/test_data/exceptions.py b/tuskar_ui/test/test_data/exceptions.py deleted file mode 100644 index 2b56032ad..000000000 --- a/tuskar_ui/test/test_data/exceptions.py +++ /dev/null @@ -1,23 +0,0 @@ -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -from openstack_dashboard.test.test_data import exceptions -from tuskarclient.openstack.common.apiclient import exceptions as tuskarclient - - -def data(TEST): - TEST.exceptions = exceptions.data - - tuskar_exception = tuskarclient.ClientException - TEST.exceptions.tuskar = exceptions.create_stubbed_exception( - tuskar_exception) diff --git a/tuskar_ui/test/test_data/flavor_data.py b/tuskar_ui/test/test_data/flavor_data.py deleted file mode 100644 index 290a11e62..000000000 --- a/tuskar_ui/test/test_data/flavor_data.py +++ /dev/null @@ -1,38 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -from novaclient.v2 import flavors -from openstack_dashboard.test.test_data import utils as test_data_utils - - -def data(TEST): - - # Nova flavors - # Do not include fields irrelevant for our usage - TEST.novaclient_flavors = test_data_utils.TestDataContainer() - flavor_1 = flavors.Flavor( - flavors.FlavorManager(None), - {'id': '1', - 'name': 'flavor-1', - 'vcpus': 2, - 'ram': 2048, - 'disk': 20}) - flavor_1.get_keys = lambda: {'cpu_arch': 'amd64'} - flavor_2 = flavors.Flavor( - flavors.FlavorManager(None), - {'id': '2', - 'name': 'flavor-2', - 'vcpus': 4, - 'ram': 4096, - 'disk': 60}) - flavor_2.get_keys = lambda: {'cpu_arch': 'i386'} - TEST.novaclient_flavors.add(flavor_1, flavor_2) diff --git a/tuskar_ui/test/test_data/heat_data.py b/tuskar_ui/test/test_data/heat_data.py deleted file mode 100644 index 303bcc0ab..000000000 --- a/tuskar_ui/test/test_data/heat_data.py +++ /dev/null @@ -1,248 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -from glanceclient.v1 import images -from heatclient.v1 import events -from heatclient.v1 import resources -from heatclient.v1 import stacks -from novaclient.v2 import servers -from openstack_dashboard.test.test_data import utils as test_data_utils - - -def data(TEST): - - # Stack - TEST.heatclient_stacks = test_data_utils.TestDataContainer() - stack_1 = stacks.Stack( - stacks.StackManager(None), - {'id': 'stack-id-1', - 'stack_name': 'overcloud', - 'stack_status': 'RUNNING', - 'outputs': [{ - 'output_key': 'KeystoneURL', - 'output_value': 'http://192.0.2.23:5000/v2', - }], - 'parameters': { - 'plan_id': 'plan-1', - 'one': 'one', - 'two': 'two', - }}) - TEST.heatclient_stacks.add(stack_1) - - # Events - TEST.heatclient_events = test_data_utils.TestDataContainer() - event_1 = events.Event( - events.EventManager(None), - {'id': 1, - 'stack_id': 'stack-id-1', - 'resource_name': 'Controller', - 'resource_status': 'CREATE_IN_PROGRESS', - 'resource_status_reason': 'state changed', - 'event_time': '2014-01-01T07:26:15Z'}) - event_2 = events.Event( - events.EventManager(None), - {'id': 2, - 'stack_id': 'stack-id-1', - 'resource_name': 'Compute0', - 'resource_status': 'CREATE_IN_PROGRESS', - 'resource_status_reason': 'state changed', - 'event_time': '2014-01-01T07:26:27Z'}) - event_3 = events.Event( - events.EventManager(None), - {'id': 3, - 'stack_id': 'stack-id-1', - 'resource_name': 'Compute1', - 'resource_status': 'CREATE_IN_PROGRESS', - 'resource_status_reason': 'state changed', - 'event_time': '2014-01-01T07:26:44Z'}) - event_4 = events.Event( - events.EventManager(None), - {'id': 4, - 'stack_id': 'stack-id-1', - 'resource_name': 'Compute0', - 'resource_status': 'CREATE_COMPLETE', - 'resource_status_reason': 'state changed', - 'event_time': '2014-01-01T07:27:14Z'}) - event_5 = events.Event( - events.EventManager(None), - {'id': 5, - 'stack_id': 'stack-id-1', - 'resource_name': 'Compute2', - 'resource_status': 'CREATE_IN_PROGRESS', - 'resource_status_reason': 'state changed', - 'event_time': '2014-01-01T07:27:31Z'}) - event_6 = events.Event( - events.EventManager(None), - {'id': 6, - 'stack_id': 'stack-id-1', - 'resource_name': 'Compute1', - 'resource_status': 'CREATE_COMPLETE', - 'resource_status_reason': 'state changed', - 'event_time': '2014-01-01T07:28:01Z'}) - event_7 = events.Event( - events.EventManager(None), - {'id': 7, - 'stack_id': 'stack-id-1', - 'resource_name': 'Controller', - 'resource_status': 'CREATE_COMPLETE', - 'resource_status_reason': 'state changed', - 'event_time': '2014-01-01T07:28:59Z'}) - event_8 = events.Event( - events.EventManager(None), - {'id': 8, - 'stack_id': 'stack-id-1', - 'resource_name': 'Compute2', - 'resource_status': 'CREATE_COMPLETE', - 'resource_status_reason': 'state changed', - 'event_time': '2014-01-01T07:29:11Z'}) - TEST.heatclient_events.add(event_1, event_2, event_3, event_4, - event_5, event_6, event_7, event_8) - - # Resource - TEST.heatclient_resources = test_data_utils.TestDataContainer() - resource_1 = resources.Resource( - resources.ResourceManager(None), - {'id': '1-resource-id', - 'stack_id': 'stack-id-1', - 'resource_name': 'Compute0', - 'logical_resource_id': 'Compute0', - 'physical_resource_id': 'aa', - 'resource_status': 'CREATE_COMPLETE', - 'resource_type': 'OS::Nova::Server'}) - resource_2 = resources.Resource( - resources.ResourceManager(None), - {'id': '2-resource-id', - 'stack_id': 'stack-id-1', - 'resource_name': 'Controller', - 'logical_resource_id': 'Controller', - 'physical_resource_id': 'bb', - 'resource_status': 'CREATE_COMPLETE', - 'resource_type': 'OS::Nova::Server'}) - resource_3 = resources.Resource( - resources.ResourceManager(None), - {'id': '3-resource-id', - 'stack_id': 'stack-id-1', - 'resource_name': 'Compute1', - 'logical_resource_id': 'Compute1', - 'physical_resource_id': 'cc', - 'resource_status': 'CREATE_COMPLETE', - 'resource_type': 'OS::Nova::Server'}) - resource_4 = resources.Resource( - resources.ResourceManager(None), - {'id': '4-resource-id', - 'stack_id': 'stack-id-4', - 'resource_name': 'Compute2', - 'logical_resource_id': 'Compute2', - 'physical_resource_id': 'dd', - 'resource_status': 'CREATE_COMPLETE', - 'resource_type': 'OS::Nova::Server'}) - TEST.heatclient_resources.add(resource_1, - resource_2, - resource_3, - resource_4) - - # Server - TEST.novaclient_servers = test_data_utils.TestDataContainer() - s_1 = servers.Server( - servers.ServerManager(None), - {'id': 'aa', - 'name': 'Compute', - 'created': '2014-06-26T20:38:06Z', - 'image': {'id': '1'}, - 'flavor': { - 'id': '1', - }, - 'status': 'ACTIVE', - 'public_ip': '192.168.1.1'}) - s_2 = servers.Server( - servers.ServerManager(None), - {'id': 'bb', - 'name': 'Controller', - 'created': '2014-06-27T20:38:06Z', - 'image': {'id': '2'}, - 'flavor': { - 'id': '2', - }, - 'status': 'ACTIVE', - 'public_ip': '192.168.1.2'}) - s_3 = servers.Server( - servers.ServerManager(None), - {'id': 'cc', - 'name': 'Compute', - 'created': '2014-06-28T20:38:06Z', - 'image': {'id': '1'}, - 'flavor': { - 'id': '1', - }, - 'status': 'BUILD', - 'public_ip': '192.168.1.3'}) - s_4 = servers.Server( - servers.ServerManager(None), - {'id': 'dd', - 'name': 'Compute', - 'created': '2014-06-29T20:38:06Z', - 'image': {'id': '1'}, - 'flavor': { - 'id': '1', - }, - 'status': 'ERROR', - 'public_ip': '192.168.1.4'}) - TEST.novaclient_servers.add(s_1, s_2, s_3, s_4) - - # Image - TEST.glanceclient_images = test_data_utils.TestDataContainer() - image_1 = images.Image( - images.ImageManager(None), - {'id': '1', - 'name': 'overcloud-full', - 'is_public': True, - 'protected': False, - 'properties': { - 'type': 'overcloud provisioning' - }}) - image_2 = images.Image( - images.ImageManager(None), - {'id': '2', - 'name': 'Discovery Ramdisk', - 'is_public': True, - 'protected': False, - 'properties': { - 'type': 'discovery ramdisk' - }}) - image_3 = images.Image( - images.ImageManager(None), - {'id': '3', - 'name': 'Discovery Kernel', - 'is_public': True, - 'protected': False, - 'properties': { - 'type': 'discovery kernel' - }}) - image_4 = images.Image( - images.ImageManager(None), - {'id': '4', - 'name': 'Baremetal Deployment Kernel', - 'is_public': True, - 'protected': False, - 'properties': { - 'type': 'deploy kernel' - }}) - image_5 = images.Image( - images.ImageManager(None), - {'id': '5', - 'name': 'Baremetal Deployment Ramdisk', - 'is_public': True, - 'protected': False, - 'properties': { - 'type': 'deploy ramdisk' - }}) - TEST.glanceclient_images.add(image_1, image_2, image_3, image_4, image_5) diff --git a/tuskar_ui/test/test_data/keystone_data.py b/tuskar_ui/test/test_data/keystone_data.py deleted file mode 100644 index 356c2d6f6..000000000 --- a/tuskar_ui/test/test_data/keystone_data.py +++ /dev/null @@ -1,30 +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. - - -def data(TEST): - - # Add tuskar to the keystone data - TEST.service_catalog.append( - {"type": "management", - "name": "tuskar", - "endpoints_links": [], - "endpoints": [ - {"region": "RegionOne", - "adminURL": "http://admin.tuskar.example.com:8585/v2", - "internalURL": "http://int.tuskar.example.com:8585/v2", - "publicURL": "http://public.tuskar.example.com:8585/v2"}, - {"region": "RegionTwo", - "adminURL": "http://admin.tuskar2.example.com:8585/v2", - "internalURL": "http://int.tuskar2.example.com:8585/v2", - "publicURL": "http://public.tuskar2.example.com:8585/v2"}]}, - ) diff --git a/tuskar_ui/test/test_data/node_data.py b/tuskar_ui/test/test_data/node_data.py deleted file mode 100644 index 7b54b0795..000000000 --- a/tuskar_ui/test/test_data/node_data.py +++ /dev/null @@ -1,289 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -from ironicclient.v1 import node -from ironicclient.v1 import port -from openstack_dashboard.test.test_data import utils as test_data_utils - - -def data(TEST): - # IronicNode - TEST.ironicclient_nodes = test_data_utils.TestDataContainer() - node_1 = node.Node( - node.NodeManager(None), - {'id': '1', - 'uuid': 'aa-11', - 'instance_uuid': 'aa', - 'driver': 'pxe_ipmitool', - 'driver_info': { - 'ipmi_address': '1.1.1.1', - 'ipmi_username': 'admin', - 'ipmi_password': 'password', - 'ip_address': '1.2.2.2', - 'deploy_kernel': 'deploy-kernel-uuid', - 'deploy_ramdisk': 'deploy-ramdisk-uuid', - }, - 'properties': { - 'cpus': '8', - 'memory_mb': '4096', - 'local_gb': '10', - 'cpu_arch': 'x86_64', - }, - 'power_state': 'on', - 'target_power_state': 'on', - 'maintenance': None, - 'newly_discovered': None, - 'provision_state': 'active', - 'extra': {} - }) - node_2 = node.Node( - node.NodeManager(None), - {'id': '2', - 'uuid': 'bb-22', - 'instance_uuid': 'bb', - 'driver': 'pxe_ipmitool', - 'driver_info': { - 'ipmi_address': '2.2.2.2', - 'ipmi_username': 'admin', - 'ipmi_password': 'password', - 'ip_address': '1.2.2.3', - 'deploy_kernel': 'deploy-kernel-uuid', - 'deploy_ramdisk': 'deploy-ramdisk-uuid', - }, - 'properties': { - 'cpus': '16', - 'memory_mb': '4096', - 'local_gb': '100', - 'cpu_arch': 'x86_64', - }, - 'power_state': 'on', - 'target_power_state': 'on', - 'maintenance': None, - 'newly_discovered': None, - 'provision_state': 'active', - 'extra': {} - }) - node_3 = node.Node( - node.NodeManager(None), - {'id': '3', - 'uuid': 'cc-33', - 'instance_uuid': 'cc', - 'driver': 'pxe_ipmitool', - 'driver_info': { - 'ipmi_address': '3.3.3.3', - 'ipmi_username': 'admin', - 'ipmi_password': 'password', - 'ip_address': '1.2.2.4', - 'deploy_kernel': 'deploy-kernel-uuid', - 'deploy_ramdisk': 'deploy-ramdisk-uuid', - }, - 'properties': { - 'cpus': '32', - 'memory_mb': '8192', - 'local_gb': '1', - 'cpu_arch': 'x86_64', - }, - 'power_state': 'rebooting', - 'target_power_state': 'on', - 'maintenance': None, - 'newly_discovered': None, - 'provision_state': 'deploying', - 'extra': {} - }) - node_4 = node.Node( - node.NodeManager(None), - {'id': '4', - 'uuid': 'cc-44', - 'instance_uuid': 'cc', - 'driver': 'pxe_ipmitool', - 'driver_info': { - 'ipmi_address': '4.4.4.4', - 'ipmi_username': 'admin', - 'ipmi_password': 'password', - 'ip_address': '1.2.2.5', - 'deploy_kernel': 'deploy-kernel-uuid', - 'deploy_ramdisk': 'deploy-ramdisk-uuid', - }, - 'properties': { - 'cpus': '8', - 'memory_mb': '4096', - 'local_gb': '10', - 'cpu_arch': 'x86_64', - }, - 'power_state': 'on', - 'target_power_state': 'on', - 'maintenance': None, - 'newly_discovered': None, - 'provision_state': 'deploying', - 'extra': {} - }) - node_5 = node.Node( - node.NodeManager(None), - {'id': '5', - 'uuid': 'dd-55', - 'instance_uuid': 'dd', - 'driver': 'pxe_ipmitool', - 'driver_info': { - 'ipmi_address': '5.5.5.5', - 'ipmi_username': 'admin', - 'ipmi_password': 'password', - 'ip_address': '1.2.2.6', - 'deploy_kernel': 'deploy-kernel-uuid', - 'deploy_ramdisk': 'deploy-ramdisk-uuid', - }, - 'properties': { - 'cpus': '8', - 'memory_mb': '4096', - 'local_gb': '10', - 'cpu_arch': 'x86_64', - }, - 'power_state': 'error', - 'target_power_state': 'on', - 'provision_state': 'error', - 'maintenance': None, - 'newly_discovered': None, - 'extra': {} - }) - node_6 = node.Node( - node.NodeManager(None), - {'id': '6', - 'uuid': 'ff-66', - 'instance_uuid': None, - 'driver': 'pxe_ipmitool', - 'driver_info': { - 'ipmi_address': '5.5.5.5', - 'ipmi_username': 'admin', - 'ipmi_password': 'password', - 'ip_address': '1.2.2.6', - 'deploy_kernel': 'deploy-kernel-uuid', - 'deploy_ramdisk': 'deploy-ramdisk-uuid', - }, - 'properties': { - 'cpus': '8', - 'memory_mb': '4096', - 'local_gb': '10', - 'cpu_arch': 'x86_64', - }, - 'power_state': 'on', - 'target_power_state': 'on', - 'maintenance': None, - 'newly_discovered': None, - 'provision_state': 'active', - 'extra': {} - }) - node_7 = node.Node( - node.NodeManager(None), - {'id': '7', - 'uuid': 'gg-77', - 'instance_uuid': None, - 'driver': 'pxe_ipmitool', - 'driver_info': { - 'ipmi_address': '7.7.7.7', - 'ipmi_username': 'admin', - 'ipmi_password': 'password', - 'ip_address': '1.2.2.7', - 'deploy_kernel': 'deploy-kernel-uuid', - 'deploy_ramdisk': 'deploy-ramdisk-uuid', - }, - 'properties': { - 'cpus': '8', - 'memory_mb': '4096', - 'local_gb': '10', - 'cpu_arch': 'x86_64', - }, - 'power_state': 'off', - 'target_power_state': 'on', - 'maintenance': True, - 'newly_discovered': None, - 'provision_state': 'deploying', - 'extra': {} - }) - node_8 = node.Node( - node.NodeManager(None), - {'id': '8', - 'uuid': 'hh-88', - 'instance_uuid': None, - 'driver': 'pxe_ipmitool', - 'driver_info': { - 'ipmi_address': '8.8.8.8', - 'ipmi_username': 'admin', - 'ipmi_password': 'password', - 'ip_address': '1.2.2.8', - 'deploy_kernel': 'deploy-kernel-uuid', - 'deploy_ramdisk': 'deploy-ramdisk-uuid', - }, - 'properties': { - 'cpus': '8', - 'memory_mb': '4096', - 'local_gb': '10', - 'cpu_arch': 'x86_64', - }, - 'power_state': 'on', - 'target_power_state': 'on', - 'maintenance': True, - 'newly_discovered': True, - 'provision_state': 'active', - 'extra': {} - }) - node_9 = node.Node( - node.NodeManager(None), - {'id': '9', - 'uuid': 'ii-99', - 'instance_uuid': None, - 'driver': 'pxe_ipmitool', - 'driver_info': { - 'ipmi_address': '9.9.9.9', - 'ipmi_username': 'admin', - 'ipmi_password': 'password', - 'ip_address': '1.2.2.9', - 'deploy_kernel': 'deploy-kernel-uuid', - 'deploy_ramdisk': 'deploy-ramdisk-uuid', - }, - 'properties': { - 'cpus': '16', - 'memory_mb': '8192', - 'local_gb': '1000', - 'cpu_arch': 'x86_64', - }, - 'power_state': 'on', - 'target_power_state': 'on', - 'maintenance': True, - 'newly_discovered': True, - 'provision_state': 'active', - 'extra': {} - }) - TEST.ironicclient_nodes.add(node_1, node_2, node_3, node_4, node_5, node_6, - node_7, node_8, node_9) - - # Ports - TEST.ironicclient_ports = test_data_utils.TestDataContainer() - port_1 = port.Port( - port.PortManager(None), - {'id': '1-port-id', - 'type': 'port', - 'address': 'aa:aa:aa:aa:aa:aa'}) - port_2 = port.Port( - port.PortManager(None), - {'id': '2-port-id', - 'type': 'port', - 'address': 'bb:bb:bb:bb:bb:bb'}) - port_3 = port.Port( - port.PortManager(None), - {'id': '3-port-id', - 'type': 'port', - 'address': 'cc:cc:cc:cc:cc:cc'}) - port_4 = port.Port( - port.PortManager(None), - {'id': '4-port-id', - 'type': 'port', - 'address': 'dd:dd:dd:dd:dd:dd'}) - TEST.ironicclient_ports.add(port_1, port_2, port_3, port_4) diff --git a/tuskar_ui/test/test_data/tuskar_data.py b/tuskar_ui/test/test_data/tuskar_data.py deleted file mode 100644 index dae72f1cf..000000000 --- a/tuskar_ui/test/test_data/tuskar_data.py +++ /dev/null @@ -1,246 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -from openstack_dashboard.test.test_data import utils as test_data_utils -from tuskarclient.v2 import plans -from tuskarclient.v2 import roles - -planmanager = plans.PlanManager(None) -rolemanager = roles.RoleManager(None) - - -def data(TEST): - - # Plan - TEST.tuskarclient_plans = test_data_utils.TestDataContainer() - plan_1 = plans.Plan(planmanager, { - 'uuid': 'plan-1', - 'name': 'overcloud', - 'description': 'this is an overcloud deployment plan', - 'template': '', - 'created_at': '2014-05-27T21:11:09Z', - 'modified_at': '2014-05-30T21:11:09Z', - 'roles': [ - { - 'uuid': 'role-1', - 'name': 'Controller', - 'version': 1, - }, { - 'uuid': 'role-2', - 'name': 'Compute', - 'version': 1, - }, { - 'uuid': 'role-3', - 'name': 'Object Storage', - 'version': 1, - }, { - 'uuid': 'role-4', - 'name': 'Block Storage', - 'version': 1, - }], - 'parameters': [{ - 'name': 'Controller-1::count', - 'label': 'Controller Node Count', - 'description': 'Controller node count', - 'hidden': False, - 'default': '', - 'value': 1, - 'parameter_type': 'string', - 'constraints': [], - }, { - 'name': 'Compute-1::count', - 'label': 'Compute Node Count', - 'description': 'Compute node count', - 'hidden': False, - 'default': '', - 'value': 42, - 'parameter_type': 'string', - 'constraints': [], - }, { - 'name': 'Block Storage-1::count', - 'label': 'Block Sorage Node Count', - 'description': 'Block storage node count', - 'hidden': False, - 'default': '', - 'value': 5, - 'parameter_type': 'string', - 'constraints': [], - }, { - 'name': 'Controller-1::Flavor', - 'label': 'Controller Flavor', - 'description': 'Controller flavor', - 'hidden': False, - 'default': '', - 'value': 'flavor-1', - 'parameter_type': 'string', - 'constraints': [], - }, { - 'name': 'Compute-1::Flavor', - 'label': 'Compute Flavor', - 'description': 'Compute flavor', - 'hidden': False, - 'default': '', - 'value': 'flavor-1', - 'parameter_type': 'string', - 'constraints': [], - }, { - 'name': 'Block Storage-1::Flavor', - 'label': 'Block Storage Flavor', - 'description': 'Block storage flavor', - 'hidden': False, - 'default': '', - 'value': 'flavor-2', - 'parameter_type': 'string', - 'constraints': [], - }, { - 'name': 'Controller-1::Image', - 'label': 'Controller Image ID', - 'description': 'Controller image ID', - 'hidden': False, - 'default': '', - 'value': 'overcloud-full', - 'parameter_type': 'string', - 'constraints': [], - }, { - 'name': 'Compute-1::Image', - 'label': 'Compute Image ID', - 'description': 'Compute image ID', - 'hidden': False, - 'default': '', - 'value': 'overcloud-full', - 'parameter_type': 'string', - 'constraints': [], - }, { - 'name': 'Block Storage-1::Image', - 'label': 'Block Storage Image ID', - 'description': 'Block storage image ID', - 'hidden': False, - 'default': '', - 'value': 'overcloud-full', - 'parameter_type': 'string', - 'constraints': [], - }, { - 'name': 'Controller-1::KeystoneCACertificate', - 'label': 'Keystone CA CertificateAdmin', - 'description': 'Keystone CA CertificateAdmin', - 'hidden': True, - 'default': '', - 'value': 'unset', - 'parameter_type': 'string', - 'constraints': [], - }, { - 'name': 'Controller-1::AdminPassword', - 'label': 'Admin Password', - 'description': 'Admin password', - 'hidden': True, - 'default': '', - 'value': 'unset', - 'parameter_type': 'string', - 'constraints': [], - }, { - 'name': 'Controller-1::AdminToken', - 'label': 'Admin Token', - 'description': 'Admin Token', - 'hidden': True, - 'default': '', - 'value': '', - 'parameter_type': 'string', - 'constraints': [], - }, { - 'name': 'Controller-1::SnmpdReadonlyUserPassword', - 'label': 'Snmpd password', - 'description': 'Snmpd password', - 'hidden': True, - 'default': '', - 'value': '', - 'parameter_type': 'string', - 'constraints': [], - }, { - 'name': 'Compute-1::SnmpdReadonlyUserPassword', - 'label': 'Snmpd password', - 'description': 'Snmpd password', - 'hidden': True, - 'default': '', - 'value': 'unset', - 'parameter_type': 'string', - 'constraints': [], - }, { - 'name': 'Controller-1::ExtraConfig', - 'label': 'Extra Config', - 'description': 'Extra Config', - 'hidden': False, - 'default': '', - 'value': '{}', - 'parameter_type': 'string', - 'constraints': [], - }, { - 'name': 'Compute-1::ExtraConfig', - 'label': 'Extra Config', - 'description': 'Extra Config', - 'hidden': False, - 'default': '', - 'value': '{}', - 'parameter_type': 'string', - 'constraints': [], - }, { - 'name': 'Block Storage-1::ExtraConfig', - 'label': 'Extra Config', - 'description': 'Extra Config', - 'hidden': False, - 'default': '', - 'value': '{}', - 'parameter_type': 'string', - 'constraints': [], - }, { - 'name': 'Object Storage-1::ExtraConfig', - 'label': 'Extra Config', - 'description': 'Extra Config', - 'hidden': False, - 'default': '', - 'value': '{}', - 'parameter_type': 'string', - 'constraints': [], - }], - }) - TEST.tuskarclient_plans.add(plan_1) - - # Role - TEST.tuskarclient_roles = test_data_utils.TestDataContainer() - r_1 = roles.Role(rolemanager, { - 'uuid': 'role-1', - 'name': 'Controller', - 'version': 1, - 'description': 'controller role', - 'created_at': '2014-05-27T21:11:09Z' - }) - r_2 = roles.Role(rolemanager, { - 'uuid': 'role-2', - 'name': 'Compute', - 'version': 1, - 'description': 'compute role', - 'created_at': '2014-05-27T21:11:09Z' - }) - r_3 = roles.Role(rolemanager, { - 'uuid': 'role-3', - 'name': 'Object Storage', - 'version': 1, - 'description': 'object storage role', - 'created_at': '2014-05-27T21:11:09Z' - }) - r_4 = roles.Role(rolemanager, { - 'uuid': 'role-4', - 'name': 'Block Storage', - 'version': 1, - 'description': 'block storage role', - 'created_at': '2014-05-27T21:11:09Z' - }) - TEST.tuskarclient_roles.add(r_1, r_2, r_3, r_4) diff --git a/tuskar_ui/test/test_data/utils.py b/tuskar_ui/test/test_data/utils.py deleted file mode 100644 index ef61ed0d6..000000000 --- a/tuskar_ui/test/test_data/utils.py +++ /dev/null @@ -1,52 +0,0 @@ -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -from openstack_dashboard.test.test_data import utils - - -def load_test_data(load_onto=None): - from openstack_dashboard.test.test_data import cinder_data - from openstack_dashboard.test.test_data import glance_data - from openstack_dashboard.test.test_data import heat_data - from openstack_dashboard.test.test_data import keystone_data - from openstack_dashboard.test.test_data import neutron_data - from openstack_dashboard.test.test_data import nova_data - from openstack_dashboard.test.test_data import swift_data - - from tuskar_ui.test.test_data import exceptions - from tuskar_ui.test.test_data import flavor_data - from tuskar_ui.test.test_data import heat_data as tuskar_heat_data - from tuskar_ui.test.test_data import keystone_data as tuskar_keystone_data - from tuskar_ui.test.test_data import node_data - from tuskar_ui.test.test_data import tuskar_data - - # The order of these loaders matters, some depend on others. - loaders = (exceptions.data, - keystone_data.data, - glance_data.data, - nova_data.data, - cinder_data.data, - neutron_data.data, - swift_data.data, - heat_data.data, - flavor_data.data, - node_data.data, - tuskar_heat_data.data, - tuskar_keystone_data.data, - tuskar_data.data) - if load_onto: - for data_func in loaders: - data_func(load_onto) - return load_onto - else: - return utils.TestData(*loaders) diff --git a/tuskar_ui/test/test_forms.py b/tuskar_ui/test/test_forms.py deleted file mode 100644 index 9b9a0cd3f..000000000 --- a/tuskar_ui/test/test_forms.py +++ /dev/null @@ -1,68 +0,0 @@ -# -*- coding: utf8 -*- -# -# 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 tuskar_ui import forms -from tuskar_ui.test import helpers as test - - -class MultiMACFieldTests(test.TestCase): - def test_empty(self): - field = forms.MultiMACField(required=False) - cleaned = field.clean("") - self.assertEqual(cleaned, "") - - def test_required(self): - field = forms.MultiMACField(required=True) - with self.assertRaises(forms.forms.ValidationError) as raised: - field.clean("") - self.assertEqual(unicode(raised.exception.messages[0]), - unicode(_('This field is required.'))) - - def test_malformed(self): - field = forms.MultiMACField(required=True) - with self.assertRaises(forms.forms.ValidationError) as raised: - field.clean("de.ad:be.ef:ca.fe") - self.assertEqual( - unicode(raised.exception.messages[0]), - unicode(_(u"'de.ad:be.ef:ca.fe' is not a valid MAC address.")), - ) - - def test_single(self): - field = forms.MultiMACField(required=False) - cleaned = field.clean("de:AD:be:ef:Ca:FE") - self.assertEqual(cleaned, "DE:AD:BE:EF:CA:FE") - - def test_multiple(self): - field = forms.MultiMACField(required=False) - cleaned = field.clean( - "de:AD:be:ef:Ca:FC, de:AD:be:ef:Ca:FD de:AD:be:ef:Ca:FE\n" - "de:AD:be:ef:Ca:FF", - ) - self.assertEqual( - cleaned, - "DE:AD:BE:EF:CA:FC DE:AD:BE:EF:CA:FD DE:AD:BE:EF:CA:FE " - "DE:AD:BE:EF:CA:FF", - ) - - def test_duplicated(self): - field = forms.MultiMACField(required=False) - cleaned = field.clean("DE:AD:BE:EF:CA:FC DE:AD:BE:EF:CA:FC") - - self.assertEqual( - cleaned, - "DE:AD:BE:EF:CA:FC", - ) diff --git a/tuskar_ui/test/urls.py b/tuskar_ui/test/urls.py deleted file mode 100644 index 8f09bbfb3..000000000 --- a/tuskar_ui/test/urls.py +++ /dev/null @@ -1,26 +0,0 @@ -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -from django.conf import urls -from django.views import generic -import openstack_dashboard.urls - -urlpatterns = urls.patterns( - '', - urls.url( - r'^qunit_tuskar', - generic.TemplateView.as_view( - template_name="infrastructure/qunit.html"), - name='qunit_tests'), - urls.url(r'', urls.include(openstack_dashboard.urls)) -) diff --git a/tuskar_ui/utils/__init__.py b/tuskar_ui/utils/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/tuskar_ui/utils/metering.py b/tuskar_ui/utils/metering.py deleted file mode 100644 index 2f9927efd..000000000 --- a/tuskar_ui/utils/metering.py +++ /dev/null @@ -1,289 +0,0 @@ -# -*- coding: utf8 -*- -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -import copy - -from django.utils.http import urlencode -from django.utils.translation import ugettext_lazy as _ -from horizon import exceptions -from openstack_dashboard.api import ceilometer -from openstack_dashboard.utils import metering - -SETTINGS = { - 'settings': { - 'renderer': 'StaticAxes', - 'xMin': None, - 'xMax': None, - 'higlight_last_point': True, - 'auto_size': False, - 'auto_resize': False, - 'axes_x': False, - 'axes_y': True, - 'axes_y_label': False, - 'bar_chart_settings': { - 'orientation': 'vertical', - 'used_label_placement': 'left', - 'width': 30, - 'color_scale_domain': [0, 80, 80, 100], - 'color_scale_range': [ - '#0000FF', - '#0000FF', - '#FF0000', - '#FF0000' - ], - 'average_color_scale_domain': [0, 100], - 'average_color_scale_range': ['#0000FF', '#0000FF'] - } - }, - 'stats': { - 'average': None, - 'used': None, - 'tooltip_average': None, - } -} - -LABELS = { - 'hardware.cpu.load.1min': _("CPU load 1 min average"), - 'hardware.system_stats.cpu.util': _("CPU utilization"), - 'hardware.system_stats.io.outgoing.blocks': _("IO raw sent"), - 'hardware.system_stats.io.incoming.blocks': _("IO raw received"), - 'hardware.network.ip.outgoing.datagrams': _("IP out requests"), - 'hardware.network.ip.incoming.datagrams': _("IP in requests"), - 'hardware.memory.swap.util': _("Swap utilization"), - 'hardware.ipmi.fan': _("Fan Speed"), - 'hardware.ipmi.voltage': _("Voltage"), - 'hardware.ipmi.temperature': _("Temperature"), - 'hardware.ipmi.current': _("Current") -} - - -# TODO(lsmola) this should probably live in Horizon common -def query_data(request, - date_from, - date_to, - group_by, - meter, - period=None, - query=None, - additional_query=None): - - if not period: - period = metering.calc_period(date_from, date_to, 50) - if additional_query is None: - additional_query = [] - if date_from: - additional_query += [{'field': 'timestamp', - 'op': 'ge', - 'value': date_from}] - if date_to: - additional_query += [{'field': 'timestamp', - 'op': 'le', - 'value': date_to}] - - # TODO(lsmola) replace this by logic implemented in I1 in bugs - # 1226479 and 1226482, this is just a quick fix for RC1 - ceilometer_usage = ceilometer.CeilometerUsage(request) - try: - if group_by: - resources = ceilometer_usage.resource_aggregates_with_statistics( - query, [meter], period=period, stats_attr=None, - additional_query=additional_query) - else: - resources = ceilometer_usage.resources_with_statistics( - query, [meter], period=period, stats_attr=None, - additional_query=additional_query) - except Exception: - resources = [] - exceptions.handle(request, - _('Unable to retrieve statistics.')) - return resources - - -def url_part(meter_name, barchart): - d = {'meter': meter_name} - if barchart: - d['barchart'] = True - return urlencode(d) - - -def get_meter_name(meter): - return meter.replace('.', '_') - - -def get_meter_list_and_unit(request, meter): - try: - meter_list = [m for m in ceilometer.meter_list(request) - if m.name == meter] - unit = meter_list[0].unit - except Exception: - meter_list = [] - unit = '' - - return meter_list, unit - - -def get_meters(meters): - return [(m, get_meter_name(m)) for m in meters] - - -def get_barchart_stats(series, unit): - values = [point['y'] for point in series[0]['data']] - average = sum(values) / len(values) - used = values[-1] - first_date = series[0]['data'][0]['x'] - last_date = series[0]['data'][-1]['x'] - tooltip_average = _('Average %(average)s %(unit)s
      From: ' - '%(first_date)s, to: %(last_date)s') % ( - dict(average=average, unit=unit, - first_date=first_date, - last_date=last_date)) - return average, used, tooltip_average - - -def create_json_output(series, barchart, unit, date_from, date_to): - start_datetime = end_datetime = '' - if date_from: - start_datetime = date_from.strftime("%Y-%m-%dT%H:%M:%S") - if date_to: - end_datetime = date_to.strftime("%Y-%m-%dT%H:%M:%S") - - settings = copy.deepcopy(SETTINGS) - settings['settings']['xMin'] = start_datetime - settings['settings']['xMax'] = end_datetime - - if series and barchart: - average, used, tooltip_average = get_barchart_stats(series, unit) - settings['settings']['yMin'] = 0 - settings['settings']['yMax'] = 100 - settings['stats']['average'] = average - settings['stats']['used'] = used - settings['stats']['tooltip_average'] = tooltip_average - else: - del settings['settings']['bar_chart_settings'] - del settings['stats'] - - json_output = {'series': series} - json_output = dict(json_output.items() + settings.items()) - return json_output - - -def get_nodes_stats(request, node_uuid, instance_uuids, meter, - date_options=None, date_from=None, date_to=None, - stats_attr=None, barchart=None, group_by=None): - series = [] - meter_list, unit = get_meter_list_and_unit(request, meter) - - if instance_uuids: - if 'ipmi' in meter: - # For IPMI metrics, a resource ID is made of node UUID concatenated - # with the metric description. E.g: - # 1dcf1896-f581-4027-9efa-973eef3380d2-fan_2a_tach_(0x42) - resource_ids = [m.resource_id for m in meter_list - if m.resource_id.startswith(node_uuid)] - queries = [ - [{'field': 'resource_id', - 'op': 'eq', - 'value': resource_id}] - for resource_id in resource_ids - ] - else: - # For SNMP metrics, a resource ID matches exactly the UUID of the - # associated instance - queries = [ - [{'field': 'resource_id', - 'op': 'eq', - 'value': instance_uuid}] - for instance_uuid in instance_uuids - ] - else: - # query will be aggregated across all resources - group_by = "all" - query = {'all': []} - queries = [query] - - # Disk and Network I/O: data from 2 meters in one chart - if meter == 'disk-io': - meters = get_meters([ - 'hardware.system_stats.io.outgoing.blocks', - 'hardware.system_stats.io.incoming.blocks' - ]) - elif meter == 'network-io': - meters = get_meters([ - 'hardware.network.ip.outgoing.datagrams', - 'hardware.network.ip.incoming.datagrams' - ]) - else: - meters = get_meters([meter]) - - date_from, date_to = metering.calc_date_args( - date_from, - date_to, - date_options) - - for meter_id, meter_name in meters: - label = unicode(LABELS.get(meter_id, meter_name)) - - for query in queries: - resources = query_data( - request=request, - date_from=date_from, - date_to=date_to, - group_by=group_by, - meter=meter_id, - query=query) - s = metering.series_for_meter(request, resources, group_by, - meter_id, meter_name, stats_attr, - unit, label) - series += s - - series = metering.normalize_series_by_unit(series) - - json_output = create_json_output( - series, - barchart, - unit, - date_from, - date_to) - - return json_output - - -def get_top_5(request, meter): - ceilometer_usage = ceilometer.CeilometerUsage(request) - meter_list, unit = get_meter_list_and_unit(request, meter) - top_5 = {'unit': unit, 'label': LABELS.get(meter, meter)} - data = [] - - for m in meter_list: - query = [{'field': 'resource_id', 'op': 'eq', 'value': m.resource_id}] - resource = ceilometer_usage.resources_with_statistics(query, [m.name], - period=600)[0] - node_uuid = resource.metadata['node'] - old_value, value = resource.get_meter(get_meter_name(m.name))[-2:] - old_value, value = old_value.max, value.max - - if value > old_value: - direction = 'up' - elif value < old_value: - direction = 'down' - else: - direction = None - - data.append({ - 'node_uuid': node_uuid, - 'value': value, - 'direction': direction - }) - - top_5['data'] = sorted(data, key=lambda d: d['value'], reverse=True)[:5] - return top_5 diff --git a/tuskar_ui/utils/tests.py b/tuskar_ui/utils/tests.py deleted file mode 100644 index 71a59e304..000000000 --- a/tuskar_ui/utils/tests.py +++ /dev/null @@ -1,298 +0,0 @@ -# -*- coding: utf8 -*- -# -# 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 collections -import datetime - -from django.utils.translation import ugettext_lazy as _ -import mock - -from tuskar_ui.test import helpers -from tuskar_ui.utils import metering -from tuskar_ui.utils import utils - - -class UtilsTests(helpers.TestCase): - def test_de_camel_case(self): - ret = utils.de_camel_case('CamelCaseString') - self.assertEqual(ret, 'Camel Case String') - ret = utils.de_camel_case('SecureSSLConnection') - self.assertEqual(ret, 'Secure SSL Connection') - ret = utils.de_camel_case('xxXXxx') - self.assertEqual(ret, 'xx X Xxx') - ret = utils.de_camel_case('XXX') - self.assertEqual(ret, 'XXX') - ret = utils.de_camel_case('NON Camel Case') - self.assertEqual(ret, 'NON Camel Case') - - def test_list_to_dict(self): - Item = collections.namedtuple('Item', 'id') - ret = utils.list_to_dict([Item('foo'), Item('bar'), Item('bar')]) - self.assertEqual(ret, {'foo': Item('foo'), 'bar': Item('bar')}) - - def test_length(self): - ret = utils.length(iter([])) - self.assertEqual(ret, 0) - ret = utils.length(iter([1, 2, 3])) - self.assertEqual(ret, 3) - - def test_check_image_type(self): - Image = collections.namedtuple('Image', 'properties') - ret = utils.check_image_type(Image({'type': 'Picasso'}), 'Picasso') - self.assertTrue(ret) - ret = utils.check_image_type(Image({'type': 'Picasso'}), 'Van Gogh') - self.assertFalse(ret) - ret = utils.check_image_type(Image({}), 'Van Gogh') - self.assertTrue(ret) - - def test_filter_items(self): - Item = collections.namedtuple('Item', 'index') - items = [Item(i) for i in range(7)] - ret = list(utils.filter_items(items, index=1)) - self.assertEqual(ret, [Item(1)]) - ret = list(utils.filter_items(items, index__in=(1, 2, 3))) - self.assertEqual(ret, [Item(1), Item(2), Item(3)]) - ret = list(utils.filter_items(items, index__not_in=(1, 2, 3))) - self.assertEqual(ret, [Item(0), Item(4), Item(5), Item(6)]) - - def test_safe_int_cast(self): - ret = utils.safe_int_cast(1) - self.assertEqual(ret, 1) - ret = utils.safe_int_cast('1') - self.assertEqual(ret, 1) - ret = utils.safe_int_cast('') - self.assertEqual(ret, 0) - ret = utils.safe_int_cast(None) - self.assertEqual(ret, 0) - ret = utils.safe_int_cast(object()) - self.assertEqual(ret, 0) - - def test_parse_correct_csv_file(self): - correct_file = [ - 'pxe_ipmitool,ipmi_address,ipmi_username,ipmi_password,' - 'mac_addresses,cpu_arch,cpus,memory_mb,local_gb', - 'pxe_ipmitool,,,,MAC_ADDRESS,,CPUS,,LOCAL_GB', - 'pxe_ssh,ssh_address,ssh_username,ssh_key_contents,mac_addresses' - ',cpu_arch,cpus,memory_mb,local_gb', - 'pxe_ssh,SSH,USER,KEY', - 'pxe_ssh,SSH,USER,,,CPU_ARCH', - ] - - correct_data = utils.parse_csv_file(correct_file) - - self.assertSequenceEqual( - correct_data, [ - { - 'driver': 'pxe_ipmitool', - 'ipmi_address': 'ipmi_address', - 'ipmi_username': 'ipmi_username', - 'ipmi_password': 'ipmi_password', - 'mac_addresses': 'mac_addresses', - 'cpu_arch': 'cpu_arch', - 'cpus': 'cpus', - 'memory_mb': 'memory_mb', - 'local_gb': 'local_gb', - }, { - 'driver': 'pxe_ipmitool', - 'ipmi_address': '', - 'ipmi_username': '', - 'ipmi_password': '', - 'mac_addresses': 'MAC_ADDRESS', - 'cpu_arch': '', - 'cpus': 'CPUS', - 'memory_mb': '', - 'local_gb': 'LOCAL_GB', - }, { - 'driver': 'pxe_ssh', - 'ssh_address': 'ssh_address', - 'ssh_username': 'ssh_username', - 'ssh_key_contents': 'ssh_key_contents', - 'mac_addresses': 'mac_addresses', - 'cpu_arch': 'cpu_arch', - 'cpus': 'cpus', - 'memory_mb': 'memory_mb', - 'local_gb': 'local_gb', - }, - { - 'driver': 'pxe_ssh', - 'ssh_address': 'SSH', - 'ssh_username': 'USER', - 'ssh_key_contents': 'KEY', - }, - { - 'driver': 'pxe_ssh', - 'ssh_address': 'SSH', - 'ssh_username': 'USER', - 'ssh_key_contents': '', - 'mac_addresses': '', - 'cpu_arch': 'CPU_ARCH', - }, - ] - ) - - def test_parse_csv_file_wrong(self): - no_csv_file = [ - '', - 'File with first empty line -- it\'s not a CSV file.', - ] - - with self.assertRaises(ValueError) as raised: - utils.parse_csv_file(no_csv_file) - - self.assertEqual(unicode(raised.exception.message), - unicode(_("Unable to parse the CSV file."))) - - def test_parse_wrong_driver_file(self): - wrong_driver_file = [ - 'wrong_driver,ssh_address,ssh_user', - ] - - with self.assertRaises(ValueError) as raised: - utils.parse_csv_file(wrong_driver_file) - - self.assertEqual(unicode(raised.exception.message), - unicode(_("Unknown driver: %s.") % 'wrong_driver')) - - -class MeteringTests(helpers.TestCase): - def test_query_data(self): - Meter = collections.namedtuple('Meter', 'name unit') - request = 'request' - from_date = datetime.datetime(2015, 1, 1, 13, 45) - to_date = datetime.datetime(2015, 1, 2, 13, 45) - with mock.patch( - 'openstack_dashboard.api.ceilometer.meter_list', - return_value=[Meter('foo.bar', u'µD')], - ), mock.patch( - 'openstack_dashboard.api.ceilometer.CeilometerUsage', - return_value=mock.MagicMock(**{ - 'resource_aggregates_with_statistics.return_value': 'plonk', - }), - ): - ret = metering.query_data(request, from_date, to_date, - 'all', 'foo.bar') - self.assertEqual(ret, 'plonk') - - def test_url_part(self): - ret = metering.url_part('foo_bar_baz', True) - self.assertTrue('meter=foo_bar_baz' in ret) - self.assertTrue('barchart=True' in ret) - ret = metering.url_part('foo_bar_baz', False) - self.assertTrue('meter=foo_bar_baz' in ret) - self.assertFalse('barchart=True' in ret) - - def test_get_meter_name(self): - ret = metering.get_meter_name('foo.bar.baz') - self.assertEqual(ret, 'foo_bar_baz') - - def test_get_meters(self): - ret = metering.get_meters(['foo.bar', 'foo.baz']) - self.assertEqual(ret, [('foo.bar', 'foo_bar'), ('foo.baz', 'foo_baz')]) - - def test_get_barchart_stats(self): - series = [ - {'data': [{'x': 1, 'y': 1}, {'x': 4, 'y': 4}]}, - {'data': [{'x': 2, 'y': 2}, {'x': 5, 'y': 5}]}, - {'data': [{'x': 3, 'y': 3}, {'x': 6, 'y': 6}]}, - ] - # Arrogance is measured in IT in micro-Dijkstras, µD. - average, used, tooltip_average = metering.get_barchart_stats(series, - u'µD') - self.assertEqual(average, 2) - self.assertEqual(used, 4) - self.assertEqual(tooltip_average, u'Average 2 µD
      From: 1, to: 4') - - def test_create_json_output(self): - ret = metering.create_json_output([], False, u'µD', None, None) - self.assertEqual(ret, { - 'series': [], - 'settings': { - 'higlight_last_point': True, - 'axes_x': False, - 'axes_y': True, - 'xMin': '', - 'renderer': 'StaticAxes', - 'xMax': '', - 'axes_y_label': False, - 'auto_size': False, - 'auto_resize': False, - }, - }) - - series = [ - {'data': [{'x': 1, 'y': 1}, {'x': 4, 'y': 4}]}, - {'data': [{'x': 2, 'y': 2}, {'x': 5, 'y': 5}]}, - {'data': [{'x': 3, 'y': 3}, {'x': 6, 'y': 6}]}, - ] - ret = metering.create_json_output(series, True, u'µD', None, None) - self.assertEqual(ret, { - 'series': series, - 'stats': { - 'average': 2, - 'used': 4, - 'tooltip_average': u'Average 2 µD
      From: 1, to: 4', - }, - 'settings': { - 'yMin': 0, - 'yMax': 100, - 'higlight_last_point': True, - 'axes_x': False, - 'axes_y': True, - 'bar_chart_settings': { - 'color_scale_domain': [0, 80, 80, 100], - 'orientation': 'vertical', - 'color_scale_range': [ - '#0000FF', - '#0000FF', - '#FF0000', - '#FF0000', - ], - 'width': 30, - 'average_color_scale_domain': [0, 100], - 'used_label_placement': 'left', - 'average_color_scale_range': ['#0000FF', '#0000FF'], - }, - 'xMin': '', - 'renderer': 'StaticAxes', - 'xMax': '', - 'axes_y_label': False, - 'auto_size': False, - 'auto_resize': False, - }, - }) - - def test_get_nodes_stats(self): - request = 'request' - with mock.patch( - 'tuskar_ui.utils.metering.create_json_output', - return_value='', - ) as create_json_output, mock.patch( - 'tuskar_ui.utils.metering.query_data', - return_value=[], - ), mock.patch( - 'openstack_dashboard.utils.metering.series_for_meter', - return_value=[], - ), mock.patch( - 'openstack_dashboard.utils.metering.calc_date_args', - return_value=('from date', 'to date'), - ): - ret = metering.get_nodes_stats( - request=request, - node_uuid='abc', - instance_uuids=['def'], - meter='foo.bar') - self.assertEqual(ret, '') - self.assertEqual(create_json_output.call_args_list, [ - mock.call([], None, '', 'from date', 'to date') - ]) diff --git a/tuskar_ui/utils/utils.py b/tuskar_ui/utils/utils.py deleted file mode 100644 index 8921d09b9..000000000 --- a/tuskar_ui/utils/utils.py +++ /dev/null @@ -1,152 +0,0 @@ -# -*- coding: utf8 -*- -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -import base64 -import csv -from itertools import izip -import os -import re -import struct -import time - -from django.utils.translation import ugettext_lazy as _ - -CAMEL_RE = re.compile(r'([A-Z][a-z]+|[A-Z]+(?=[A-Z\s]|$))') - - -def de_camel_case(text): - """Convert CamelCase names to human-readable format.""" - return ' '.join(w.strip() for w in CAMEL_RE.split(text) if w.strip()) - - -def list_to_dict(object_list, key_attribute='id'): - """Converts an object list to a dict - - :param object_list: list of objects to be put into a dict - :type object_list: list - - :param key_attribute: object attribute used as index by dict - :type key_attribute: str - - :return: dict containing the objects in the list - :rtype: dict - """ - return dict((getattr(o, key_attribute), o) for o in object_list) - - -def length(iterator): - """A length function for iterators - - Returns the number of items in the specified iterator. Note that this - function consumes the iterator in the process. - """ - return sum(1 for _item in iterator) - - -def check_image_type(image, type): - """Check if image 'type' property matches passed-in type. - - If image has no 'type' property' return True, as we cannot - be sure what type of image it is. - """ - - return (image.properties.get('type', type) == type) - - -def filter_items(items, **kwargs): - """Filters the list of items and returns the filtered list. - - Example usage: - >>> class Item(object): - ... def __init__(self, index): - ... self.index = index - ... def __repr__(self): - ... return '' % self.index - >>> items = [Item(i) for i in range(7)] - >>> list(filter_items(items, index=1)) - [] - >>> list(filter_items(items, index__in=(1, 2, 3))) - [, , ] - >>> list(filter_items(items, index__not_in=(1, 2, 3))) - [, , , ] - """ - for item in items: - for name, value in kwargs.items(): - if name.endswith('__in'): - if getattr(item, name[:-len('__in')]) not in value: - break - elif name.endswith('__not_in'): - if getattr(item, name[:-len('__not_in')]) in value: - break - else: - if getattr(item, name) != value: - break - else: - yield item - - -def safe_int_cast(value): - try: - return int(value) - except (TypeError, ValueError): - return 0 - - -def parse_csv_file(csv_file): - """Parses given CSV file. - - If there is no error, it returns list of dicts. When something went wrong, - list is empty, but warning contains appropriate information about - possible problems. - """ - - parsed_data = [] - - for row in csv.reader(csv_file): - try: - driver = row[0].strip() - except IndexError: - raise ValueError(_("Unable to parse the CSV file.")) - - if driver in ('pxe_ssh', 'pxe_ipmitool'): - node_keys = ( - 'mac_addresses', 'cpu_arch', 'cpus', 'memory_mb', 'local_gb') - - if driver == 'pxe_ssh': - driver_keys = ( - 'driver', 'ssh_address', 'ssh_username', - 'ssh_key_contents' - ) - - elif driver == 'pxe_ipmitool': - driver_keys = ( - 'driver', 'ipmi_address', 'ipmi_username', - 'ipmi_password' - ) - - node = dict(izip(driver_keys+node_keys, row)) - - parsed_data.append(node) - - else: - raise ValueError(_("Unknown driver: %s.") % driver) - - return parsed_data - - -def create_cephx_key(): - # NOTE(gfidente): Taken from - # https://github.com/ceph/ceph-deploy/blob/master/ceph_deploy/new.py#L21 - key = os.urandom(16) - header = struct.pack("