commit 8005e175c8769299765f2ca366a34bef24c33fa1 Author: Kazuhiro Suzuki Date: Mon Jun 12 20:02:55 2017 +0900 Initial Commit. diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000..9fb8f2c --- /dev/null +++ b/.eslintrc @@ -0,0 +1,59 @@ +# Set up globals +globals: + angular: false + +extends: openstack + +# Most environment options are not explicitly enabled or disabled, only +# included here for completeness' sake. They are commented out, because the +# global updates.py script would otherwise override them during a global +# requirements synchronization. +# +# Individual projects should choose which platforms they deploy to. + +env: + # browser global variables. + browser: true + + # Adds all of the Jasmine testing global variables for version 1.3 and 2.0. + jasmine: true + +# Enable eslint-plugin-angular +plugins: + - angular + +# Below we adjust rules specific to horizon's usage of openstack's linting +# rules, and its own plugin inclusions. +rules: + ############################################################################# + # Disabled Rules from eslint-config-openstack + ############################################################################# + valid-jsdoc: [1, { + requireParamDescription: false + }] + brace-style: 1 + block-scoped-var: 1 + callback-return: 1 + consistent-return: 1 + guard-for-in: 1 + no-extra-parens: 1 + no-new: 1 + no-redeclare: 1 + no-undefined: 1 + no-unneeded-ternary: 1 + no-use-before-define: 1 + quote-props: 0 + semi-spacing: 1 + space-in-parens: 1 + + ############################################################################# + # Angular Plugin Customization + ############################################################################# + + angular/controller-as-vm: + - 1 + - "ctrl" + + # Remove after migrating to angular 1.4 or later. + angular/no-cookiestore: + - 1 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..58a83d3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +AUTHORS +ChangeLog +build +cover +doc/source/sourcecode +node_modules +npm-debug.log +releasenotes/build +neutron_taas_dashboard/test/.secret_key_store +.coverage* +.jshintrc +.project +.pydevproject +.settings +.tox +.venv +*.egg* +*.lock +*.mo +*.py[cod] +*.swp diff --git a/.gitreview b/.gitreview new file mode 100644 index 0000000..829fd02 --- /dev/null +++ b/.gitreview @@ -0,0 +1,4 @@ +[gerrit] +host=review.openstack.org +port=29418 +project=openstack/tap-as-a-service-dashboard.git diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst new file mode 100644 index 0000000..626cfe8 --- /dev/null +++ b/CONTRIBUTING.rst @@ -0,0 +1,17 @@ +If you would like to contribute to the development of OpenStack, you must +follow the steps in this page: + + http://docs.openstack.org/infra/manual/developers.html + +If you already have a good understanding of how the system works and your +OpenStack accounts are set up, you can skip to the development workflow +section of this documentation to learn how changes to OpenStack should be +submitted for review via the Gerrit tool: + + http://docs.openstack.org/infra/manual/developers.html#development-workflow + +Pull requests submitted through GitHub will be ignored. + +Bugs should be filed on Launchpad, not GitHub: + + https://bugs.launchpad.net/tap-as-a-service-dashboard diff --git a/HACKING.rst b/HACKING.rst new file mode 100644 index 0000000..b8a3c0b --- /dev/null +++ b/HACKING.rst @@ -0,0 +1,4 @@ +tap-as-a-service-dashboard Style Commandments +=============================================== + +Read the OpenStack Style Commandments http://docs.openstack.org/developer/hacking/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..68c771a --- /dev/null +++ b/LICENSE @@ -0,0 +1,176 @@ + + 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 new file mode 100644 index 0000000..707472f --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,9 @@ +include AUTHORS +include ChangeLog +exclude .gitignore +exclude .gitreview +include setup.py + +recursive-include neutron_taas_dashboard *.js *.html *.scss + +global-exclude *.pyc diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..1361554 --- /dev/null +++ b/README.rst @@ -0,0 +1,64 @@ +=============================== +Tap-as-a-Service Dashboard +=============================== + +Tap-as-a-Service Horizon plugin. + +* Free software: Apache license +* Source: http://git.openstack.org/cgit/openstack/tap-as-a-service-dashboard +* Bugs: http://bugs.launchpad.net/tap-as-a-service-dashboard + +Features +-------- + +* TODO + +Enabling in DevStack +-------------------- + +Add this repo as an external repository into your ``local.conf`` file:: + + [[local|localrc]] + enable_plugin neutron_taas_dashboard https://github.com/openstack/tap-as-a-service-dashboard + +Manual Installation +------------------- + +Begin by cloning the Horizon and Tap-as-a-Service Dashboard repositories:: + + git clone https://github.com/openstack/horizon + git clone https://github.com/openstack/tap-as-a-service-dashboard + +Create a virtual environment and install Horizon dependencies:: + + cd horizon + python tools/install_venv.py + +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 Tap-as-a-Service Dashboard with all dependencies in your virtual environment:: + + tools/with_venv.sh pip install -e ../tap-as-a-service-dashboard/ + +And enable it in Horizon:: + + ln -s ../tap-as-a-service-dashboard/neutron_taas_dashboard/enabled/_90_project_taps_panelgroup.py openstack_dashboard/local/enabled + ln -s ../tap-as-a-service-dashboard/neutron_taas_dashboard/enabled/_91_project_taps_taps_panel.py openstack_dashboard/local/enabled + +To run horizon with the newly enabled Tap-as-a-Service Dashboard plugin run:: + + ./run_tests.sh --runserver 0.0.0.0:8080 + +to have the application start on port 8080 and the horizon dashboard will be +available in your browser at http://localhost:8080/ diff --git a/babel-django.cfg b/babel-django.cfg new file mode 100644 index 0000000..ad09d34 --- /dev/null +++ b/babel-django.cfg @@ -0,0 +1,5 @@ +[extractors] +django = django_babel.extract:extract_django + +[python: **.py] +[django: templates/**.html] diff --git a/babel-djangojs.cfg b/babel-djangojs.cfg new file mode 100644 index 0000000..a8273b6 --- /dev/null +++ b/babel-djangojs.cfg @@ -0,0 +1,14 @@ +[extractors] +# We use a custom extractor to find translatable strings in AngularJS +# templates. The extractor is included in horizon.utils for now. +# See http://babel.pocoo.org/docs/messages/#referencing-extraction-methods for +# details on how this works. +angular = horizon.utils.babel_extract_angular:extract_angular + +[javascript: **.js] + +# We need to look into all static folders for HTML files. +# The **/static ensures that we also search within +# /openstack_dashboard/dashboards/XYZ/static which will ensure +# that plugins are also translated. +[angular: **/static/**.html] diff --git a/devstack/README.rst b/devstack/README.rst new file mode 100644 index 0000000..5f33fdf --- /dev/null +++ b/devstack/README.rst @@ -0,0 +1,10 @@ +========================================== +Tap as a Service Dashboard devStack plugin +========================================== + +This is the tap-as-a-service-dashboard devstack plugin. + +A `local.conf` recipe to enable tap-as-a-service-dashboard: + + [[local|localrc]] + enable_plugin tap-as-a-service-dashboard https://git.openstack.org/openstack/tap-as-a-service-dashboard diff --git a/devstack/plugin.sh b/devstack/plugin.sh new file mode 100644 index 0000000..eb4fb79 --- /dev/null +++ b/devstack/plugin.sh @@ -0,0 +1,59 @@ +#!/bin/bash + +# Copyright 2016 FUJITSU LABORATORIES LTD. +# +# 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 script is meant to be sourced from devstack. It is a wrapper of +# devmido scripts that allows proper exporting of environment variables. + + +function neutron_taas_dashboard_install { + setup_develop $NEUTRON_TAAS_DASHBOARD_DIR +} + +function neutron_taas_dashboard_configure { + cp $NEUTRON_TAAS_DASHBOARD_ENABLE_FILE \ + $HORIZON_DIR/openstack_dashboard/local/enabled/ + + local local_settings=$HORIZON_DIR/openstack_dashboard/local/local_settings.py +} + +if is_service_enabled horizon && is_service_enabled taas; then + if [[ "$1" == "stack" && "$2" == "install" ]]; then + # Perform installation of service source + echo_summary "Installing neutron-taas-dashboard" + neutron_taas_dashboard_install + elif [[ "$1" == "stack" && "$2" == "post-config" ]]; then + echo_summary "Configuring neutron-taas-dashboard" + neutron_taas_dashboard_configure + elif [[ "$1" == "stack" && "$2" == "extra" ]]; then + # Initialize and start the TaaS service + echo_summary "Initializing neutron-taas-dashboard" + fi +fi + +if [[ "$1" == "unstack" ]]; then + # Shut down TaaS dashboard services + : +fi + +if [[ "$1" == "clean" ]]; then + # Remove state and transient data + # Remember clean.sh first calls unstack.sh + + # Remove taas-dashboard enabled file and pyc + # rm -f ${NEUTRON_TAAS_DASHBOARD_ENABLE_FILE}* + ENABLE_FILE="${NEUTRON_TAAS_DASHBOARD_ENABLE_FILE##*/}" + rm -f ${NEUTRON_TAAS_DASHBOARD_ENABLE_FILE}/$ENABLE_FILE* +fi diff --git a/devstack/settings b/devstack/settings new file mode 100644 index 0000000..b171aff --- /dev/null +++ b/devstack/settings @@ -0,0 +1,3 @@ +# Devstack settings +NEUTRON_TAAS_DASHBOARD_DIR=$DEST/tap-as-a-service-dashboard +NEUTRON_TAAS_DASHBOARD_ENABLE_FILE=$NEUTRON_TAAS_DASHBOARD_DIR/neutron_taas_dashboard/enabled/_90_project_tapservices_panel.py diff --git a/doc/Makefile b/doc/Makefile new file mode 100644 index 0000000..142faf2 --- /dev/null +++ b/doc/Makefile @@ -0,0 +1,152 @@ +# 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/Tap-as-a-Service-Dashboard.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Tap-as-a-Service-Dashboard.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/Tap-as-a-Service-Dashboard" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Tap-as-a-Service-Dashboard" + @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/source/conf.py b/doc/source/conf.py new file mode 100644 index 0000000..ee67597 --- /dev/null +++ b/doc/source/conf.py @@ -0,0 +1,441 @@ +# 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. +# +# Horizon documentation build configuration file, created by +# sphinx-quickstart on Thu Oct 27 11:38:59 2011. +# +# 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. + +from __future__ import print_function + +import os +import subprocess +import sys + +BASE_DIR = os.path.dirname(os.path.abspath(__file__)) +ROOT = os.path.abspath(os.path.join(BASE_DIR, "..", "..")) + +sys.path.insert(0, ROOT) + +# This is required for ReadTheDocs.org, but isn't a bad idea anyway. +os.environ.setdefault('DJANGO_SETTINGS_MODULE', + 'neutron_taas_dashboard.test.settings') + +from neutron_taas_dashboard \ + import version as ui_ver + + +def write_autodoc_index(): + + def find_autodoc_modules(module_name, sourcedir): + """returns a list of modules in the SOURCE directory.""" + modlist = [] + os.chdir(os.path.join(sourcedir, module_name)) + print("SEARCHING %s" % sourcedir) + for root, dirs, files in os.walk("."): + for filename in files: + if filename == 'tests.py': + continue + if filename.endswith(".py"): + # remove the pieces of the root + elements = root.split(os.path.sep) + # replace the leading "." with the module name + elements[0] = module_name + # and get the base module name + base, extension = os.path.splitext(filename) + if not (base == "__init__"): + elements.append(base) + result = ".".join(elements) + # print result + modlist.append(result) + return modlist + + RSTDIR = os.path.abspath(os.path.join(BASE_DIR, "sourcecode")) + SRCS = [('neutron_taas_dashboard', ROOT), ] + + EXCLUDED_MODULES = () + CURRENT_SOURCES = {} + + if not(os.path.exists(RSTDIR)): + os.mkdir(RSTDIR) + CURRENT_SOURCES[RSTDIR] = ['autoindex.rst'] + + INDEXOUT = open(os.path.join(RSTDIR, "autoindex.rst"), "w") + INDEXOUT.write(""" +================= +Source Code Index +================= + +.. contents:: + :depth: 1 + :local: + +""") + + for modulename, path in SRCS: + sys.stdout.write("Generating source documentation for %s\n" % + modulename) + INDEXOUT.write("\n%s\n" % modulename.capitalize()) + INDEXOUT.write("%s\n" % ("=" * len(modulename),)) + INDEXOUT.write(".. toctree::\n") + INDEXOUT.write(" :maxdepth: 1\n") + INDEXOUT.write("\n") + + MOD_DIR = os.path.join(RSTDIR, modulename) + CURRENT_SOURCES[MOD_DIR] = [] + if not(os.path.exists(MOD_DIR)): + os.mkdir(MOD_DIR) + for module in find_autodoc_modules(modulename, path): + if any([module.startswith(exclude) for exclude + in EXCLUDED_MODULES]): + print("Excluded module %s." % module) + continue + mod_path = os.path.join(path, *module.split(".")) + generated_file = os.path.join(MOD_DIR, "%s.rst" % module) + + INDEXOUT.write(" %s/%s\n" % (modulename, module)) + + # Find the __init__.py module if this is a directory + if os.path.isdir(mod_path): + source_file = ".".join((os.path.join(mod_path, "__init__"), + "py",)) + else: + source_file = ".".join((os.path.join(mod_path), "py")) + + CURRENT_SOURCES[MOD_DIR].append("%s.rst" % module) + # Only generate a new file if the source has changed or we don't + # have a doc file to begin with. + if not os.access(generated_file, os.F_OK) or ( + os.stat(generated_file).st_mtime < + os.stat(source_file).st_mtime): + print("Module %s updated, generating new documentation." + % module) + FILEOUT = open(generated_file, "w") + header = "The :mod:`%s` Module" % module + FILEOUT.write("%s\n" % ("=" * len(header),)) + FILEOUT.write("%s\n" % header) + FILEOUT.write("%s\n" % ("=" * len(header),)) + FILEOUT.write(".. automodule:: %s\n" % module) + FILEOUT.write(" :members:\n") + FILEOUT.write(" :undoc-members:\n") + FILEOUT.write(" :show-inheritance:\n") + FILEOUT.write(" :noindex:\n") + FILEOUT.close() + + INDEXOUT.close() + + # Delete auto-generated .rst files for sources which no longer exist + for directory, subdirs, files in list(os.walk(RSTDIR)): + for old_file in files: + if old_file not in CURRENT_SOURCES.get(directory, []): + print("Removing outdated file for %s" % old_file) + os.remove(os.path.join(directory, old_file)) + + +write_autodoc_index() + +# 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.todo', + 'sphinx.ext.coverage', + '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'Tap-as-a-Service Dashboard' +copyright = u'2016, OpenStack Foundation' + +# 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 = ui_ver.version_info.version_string() +# The full version, including alpha/beta/rc tags. +release = ui_ver.version_info.release_string() + +# 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 = [] + +primary_domain = 'py' +nitpicky = False + + +# -- Options for HTML output -------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# html_theme_path = ['.'] +# html_theme = '_theme' + +# 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 = { + "nosidebar": "false" +} + +# 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' +git_cmd = ["git", "log", "--pretty=format:'%ad, commit %h'", "--date=local", + "-n1"] +html_last_updated_fmt = subprocess.check_output(git_cmd, + stdin=subprocess.PIPE) + +# 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 = 'Horizondoc' + + +# -- 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', 'Horizon.tex', u'Horizon Documentation', + u'OpenStack Foundation', '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', u'Tap-as-a-Service Dashboard Documentation', + 'Documentation for the Tap-as-a-Service Dashboard plugin to the Openstack\ + Dashboard (Horizon)', + [u'OpenStack'], 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', 'Horizon', u'Horizon Documentation', u'OpenStack', + 'Horizon', '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' + + +# -- Options for Epub output -------------------------------------------------- + +# Bibliographic Dublin Core info. +epub_title = u'Horizon' +epub_author = u'OpenStack' +epub_publisher = u'OpenStack' +epub_copyright = u'2012, OpenStack' + +# The language of the text. It defaults to the language option +# or en if the language is not set. +# epub_language = '' + +# The scheme of the identifier. Typical schemes are ISBN or URL. +# epub_scheme = '' + +# The unique identifier of the text. This can be an ISBN number +# or the project homepage. +# epub_identifier = '' + +# A unique identification for the text. +# epub_uid = '' + +# A tuple containing the cover image and cover page html template filenames. +# epub_cover = () + +# HTML files that should be inserted before the pages created by sphinx. +# The format is a list of tuples containing the path and title. +# epub_pre_files = [] + +# HTML files shat should be inserted after the pages created by sphinx. +# The format is a list of tuples containing the path and title. +# epub_post_files = [] + +# A list of files that should not be packed into the epub file. +# epub_exclude_files = [] + +# The depth of the table of contents in toc.ncx. +# epub_tocdepth = 3 + +# Allow duplicate toc entries. +# epub_tocdup = True diff --git a/doc/source/index.rst b/doc/source/index.rst new file mode 100644 index 0000000..b0f327a --- /dev/null +++ b/doc/source/index.rst @@ -0,0 +1,84 @@ +=============================== +Tap-as-a-Service Dashboard +=============================== + +Tap-as-a-Service Horizon plugin. + +* Free software: Apache license +* Source: http://git.openstack.org/cgit/openstack/tap-as-a-service-dashboard +* Bugs: http://bugs.launchpad.net/tap-as-a-service-dashboard + +Features +-------- + +* TODO + +Enabling in DevStack +-------------------- + +Add this repo as an external repository into your ``local.conf`` file:: + + [[local|localrc]] + enable_plugin neutron_taas_dashboard https://github.com/openstack/tap-as-a-service-dashboard + +Manual Installation +------------------- + +Begin by cloning the Horizon and Tap-as-a-Service Dashboard repositories:: + + git clone https://github.com/openstack/horizon + git clone https://github.com/openstack/tap-as-a-service-dashboard + +Create a virtual environment and install Horizon dependencies:: + + cd horizon + python tools/install_venv.py + +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 Tap-as-a-Service Dashboard with all dependencies in your virtual environment:: + + tools/with_venv.sh pip install -e ../tap-as-a-service-dashboard/ + +And enable it in Horizon:: + + ln -s ../tap-as-a-service-dashboard/neutron_taas_dashboard/enabled/_90_project_taps_panelgroup.py openstack_dashboard/local/enabled + ln -s ../tap-as-a-service-dashboard/neutron_taas_dashboard/enabled/_91_project_taps_taps_panel.py openstack_dashboard/local/enabled + +To run horizon with the newly enabled Tap-as-a-Service Dashboard plugin run:: + + ./run_tests.sh --runserver 0.0.0.0:8080 + +to have the application start on port 8080 and the horizon dashboard will be +available in your browser at http://localhost:8080/ + +Release Notes +============= + +.. toctree:: + :glob: + :maxdepth: 1 + + releases/* + +Source Code Reference +===================== + +.. toctree:: + :glob: + :maxdepth: 1 + + sourcecode/autoindex + + diff --git a/doc/source/releases/0.1.0.rst b/doc/source/releases/0.1.0.rst new file mode 100644 index 0000000..50b44b2 --- /dev/null +++ b/doc/source/releases/0.1.0.rst @@ -0,0 +1,2 @@ +Tap-as-a-Service Dashboard 0.1.0 +=============================== diff --git a/manage.py b/manage.py new file mode 100755 index 0000000..9528e29 --- /dev/null +++ b/manage.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python + +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import os +import sys + +from django.core.management import execute_from_command_line # noqa + +if __name__ == "__main__": + os.environ.setdefault("DJANGO_SETTINGS_MODULE", + "neutron_taas_dashboard.test.settings") + execute_from_command_line(sys.argv) diff --git a/neutron_taas_dashboard/__init__.py b/neutron_taas_dashboard/__init__.py new file mode 100644 index 0000000..4c8be2b --- /dev/null +++ b/neutron_taas_dashboard/__init__.py @@ -0,0 +1,18 @@ +# Copyright 2016, FUJITSU LABORATORIES LTD. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import pbr.version + +__version__ = pbr.version.VersionInfo( + 'neutron_taas_dashboard').version_string() diff --git a/neutron_taas_dashboard/api/__init__.py b/neutron_taas_dashboard/api/__init__.py new file mode 100644 index 0000000..dd3f72a --- /dev/null +++ b/neutron_taas_dashboard/api/__init__.py @@ -0,0 +1,15 @@ +# Copyright 2016, FUJITSU LABORATORIES LTD. +# +# 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 . import taas # noqa diff --git a/neutron_taas_dashboard/api/taas.py b/neutron_taas_dashboard/api/taas.py new file mode 100644 index 0000000..720c8c8 --- /dev/null +++ b/neutron_taas_dashboard/api/taas.py @@ -0,0 +1,169 @@ +# Copyright 2016, FUJITSU LABORATORIES LTD. +# +# 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.conf import settings + +from openstack_dashboard.api import neutron + +import logging + +LOG = logging.getLogger(__name__) + +neutronclient = neutron.neutronclient + + +class TapService(neutron.NeutronAPIDictWrapper): + """Wrapper for neutron tap services.""" + + def __init__(self, apiresource): + super(TapService, self).__init__(apiresource) + + +class TapFlow(neutron.NeutronAPIDictWrapper): + """Wrapper for neutron tap flows.""" + + def __init__(self, apiresource): + super(TapFlow, self).__init__(apiresource) + + +def add_taas_enable(): + network_config = getattr(settings, 'OPENSTACK_NEUTRON_NETWORK', {}) + + if not ('enable_taas' in network_config): + network_config['enable_taas'] = True + + +def taas_supported(request): + network_config = getattr(settings, 'OPENSTACK_NEUTRON_NETWORK', {}) + return network_config.get('enable_taas', True) + + +def tap_service_exist(request): + res = neutronclient(request).get('/taas/tap_services') + + tap_services = res['tap_services'] + if tap_services: + return True + else: + return False + + +def is_tapservice(request, port_id): + res = neutronclient(request).get('/taas/tap_services') + + tap_service_id = '' + tap_services = res['tap_services'] + for tap_service in tap_services: + if port_id == tap_service['port_id']: + tap_service_id = tap_service['id'] + break + if not tap_service_id == '': + return True + else: + return False + + +def port_has_tapflow(request, port_id): + res = neutronclient(request).get("/taas/tap_flows") + + tap_flow_id = '' + tap_flows = res['tap_flows'] + for tap_flow in tap_flows: + if port_id == tap_flow['source_port']: + tap_flow_id = tap_flow['id'] + break + if not tap_flow_id == '': + return True + else: + return False + + +def port_has_tapservice(request, port_id): + res = neutronclient(request).get("/taas/tap_services") + + tap_service_id = '' + tap_services = res['tap_services'] + for tap_service in tap_services: + if port_id == tap_service['port_id']: + tap_service_id = tap_service['id'] + break + if not tap_service_id == '': + return True + else: + return False + + +def list_tap_flow(request, tap_service_id): + LOG.debug("list_tap_flow()") + tap_flows = neutronclient(request).get("/taas/tap_flows") + return [TapFlow(tf) for tf in tap_flows.get('tap_flows')] + + +def show_tap_flow(request, tap_flow_id): + LOG.debug("tap_flow_show(): tapflowid=%s" % tap_flow_id) + tap_flow = neutronclient(request).get("/taas/tap_flows/%s" % tap_flow_id) + return TapFlow(tap_flow.get('tap_flow')) + + +def create_tap_flow(request, direction, tap_service_id, port_id, **params): + req_data = {} + req_data['tap_flow'] = { + 'name': params['name'], + 'description': params['description'], + 'source_port': port_id, + 'direction': direction, + 'tap_service_id': tap_service_id + } + + res = neutronclient(request).post("/taas/tap_flows", req_data) + tap_flow = res.get('tap_flow') + return TapFlow(tap_flow) + + +def delete_tap_flow(request, tap_flow_id): + LOG.debug("tap_flow_delete(): tapflowid=%s" % tap_flow_id) + neutronclient(request).delete("/taas/tap_flows/%s" % tap_flow_id) + + +def list_tap_service(request): + LOG.debug("list_tap_service()") + tap_services = neutronclient(request).get('/taas/tap_services') + return [TapService(ts) for ts in tap_services.get('tap_services')] + + +def show_tap_service(request, tap_service_id): + LOG.debug("tap_service_show(): tapserviceid=%s" % tap_service_id) + tap_service = neutronclient(request).get('/taas/tap_services/%s' % + tap_service_id) + return TapService(tap_service.get('tap_service')) + + +def create_tap_service(request, port_id, **params): + req_data = {} + req_data['tap_service'] = { + 'name': params['name'], + 'description': params['description'], + 'port_id': port_id, + } + + res = neutronclient(request).post("/taas/tap_services", req_data) + tap_service = res.get('tap_service') + return TapService(tap_service) + + +def delete_tap_service(request, tap_service_id): + LOG.debug("tap_service_delete(): tapserviceid=%s" % tap_service_id) + neutronclient(request).delete("/taas/tap_services/%s" % + tap_service_id) diff --git a/neutron_taas_dashboard/dashboards/__init__.py b/neutron_taas_dashboard/dashboards/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/neutron_taas_dashboard/dashboards/project/__init__.py b/neutron_taas_dashboard/dashboards/project/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/neutron_taas_dashboard/dashboards/project/tapservices/__init__.py b/neutron_taas_dashboard/dashboards/project/tapservices/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/neutron_taas_dashboard/dashboards/project/tapservices/panel.py b/neutron_taas_dashboard/dashboards/project/tapservices/panel.py new file mode 100644 index 0000000..ce76969 --- /dev/null +++ b/neutron_taas_dashboard/dashboards/project/tapservices/panel.py @@ -0,0 +1,44 @@ +# Copyright 2016, FUJITSU LABORATORIES LTD. +# +# 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 openstack_dashboard.api import neutron + +import horizon +import logging + +LOG = logging.getLogger(__name__) + + +class TapServices(horizon.Panel): + name = _("Tap Services") + slug = 'tapservices' + permissions = ('openstack.services.network',) + + def allowed(self, context): + request = context['request'] + if not request.user.has_perms(self.permissions): + return False + try: + if not neutron.is_service_enabled(request, + config_name='enable_taas', + ext_name='taas'): + return False + except Exception: + LOG.error("Call to list enabled services failed. This is likely " + "due to a problem communicating with the Neutron " + "endpoint. Tap Services panel will not be displayed.") + return False + ret = super(TapServices, self).allowed(context) + return ret diff --git a/neutron_taas_dashboard/dashboards/project/tapservices/tables.py b/neutron_taas_dashboard/dashboards/project/tapservices/tables.py new file mode 100644 index 0000000..d669c9f --- /dev/null +++ b/neutron_taas_dashboard/dashboards/project/tapservices/tables.py @@ -0,0 +1,109 @@ +# Copyright 2016, FUJITSU LABORATORIES LTD. +# +# 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 django.utils.translation import ungettext_lazy + +from horizon import exceptions +from horizon import tables + +from neutron_taas_dashboard import api + +import logging + +LOG = logging.getLogger(__name__) + + +class TapFilterAction(tables.FilterAction): + + def filter(self, table, tap_services, filter_string): + """Naive case-insensitive search.""" + query = filter_string.lower() + return [tap_service for tap_service in tap_services + if query in tap_service.id.lower()] + + +class CreateTapService(tables.LinkAction): + name = "create" + verbose_name = _("Create Tap Service") + url = "horizon:project:tapservices:create" + classes = ("ajax-modal",) + icon = "plus" + policy_rules = (("network", "create_tap_service"),) + + def allowed(self, request, datum=None): + return True + + +class DeleteTapService(tables.DeleteAction): + @staticmethod + def action_present(count): + return ungettext_lazy( + u"Delete Tap Service", + u"Delete Tap Services", + count + ) + + @staticmethod + def action_past(count): + return ungettext_lazy( + u"Deleted Tap Service", + u"Deleted Tap Services", + count + ) + + policy_rules = (("network", "delete_tap_service"),) + + def delete(self, request, tap_service_id): + tap_service_name = tap_service_id + try: + api.taas.delete_tap_service(request, tap_service_id) + LOG.debug('Deleted tap service %s successfully', tap_service_id) + except Exception: + msg = _('Failed to delete tap service %s') + LOG.info(msg, tap_service_id) + redirect = reverse("horizon:project:tapservices:index") + exceptions.handle(request, + msg % tap_service_name, + redirect=redirect) + + +class CreateTapFlow(tables.LinkAction): + name = "tapflow" + verbose_name = _("Create Tap Flow") + url = "horizon:project:tapservices:createtapflow" + classes = ("ajax-modal",) + icon = "plus" + policy_rules = (("network", "create_tap_flow"),) + + def allowed(self, request, datum=None): + return True + + +class TapServicesTable(tables.DataTable): + name = tables.Column("name_or_id", + verbose_name=_("Name"), + link="horizon:project:tapservices:detail") + port_id = tables.Column("port_id", + verbose_name=_("Port ID"),) + + def get_object_display(self, obj): + return obj.id + + class Meta(object): + name = "tap_service" + verbose_name = _("Tap Services") + table_actions = (CreateTapService, DeleteTapService, TapFilterAction) + row_actions = (CreateTapFlow, DeleteTapService,) diff --git a/neutron_taas_dashboard/dashboards/project/tapservices/tapflows/__init__.py b/neutron_taas_dashboard/dashboards/project/tapservices/tapflows/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/neutron_taas_dashboard/dashboards/project/tapservices/tapflows/tables.py b/neutron_taas_dashboard/dashboards/project/tapservices/tapflows/tables.py new file mode 100644 index 0000000..ef73232 --- /dev/null +++ b/neutron_taas_dashboard/dashboards/project/tapservices/tapflows/tables.py @@ -0,0 +1,94 @@ +# Copyright 2016, FUJITSU LABORATORIES LTD. +# +# 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 django.utils.translation import ungettext_lazy + +from horizon import exceptions +from horizon import tables + +from neutron_taas_dashboard import api + +import logging + +LOG = logging.getLogger(__name__) + + +class CreateTapFlow(tables.LinkAction): + name = "create" + verbose_name = _("Create Tap Flow") + url = "horizon:project:tapservices:createtapflow" + classes = ("ajax-modal",) + icon = "plus" + policy_rules = (("network", "create_tap_flow"),) + + def get_link_url(self, datum=None): + tap_service_id = self.table.kwargs['tap_service_id'] + return reverse(self.url, args=(tap_service_id,)) + + def allowed(self, request, datum=None): + return True + + +class DeleteTapFlow(tables.DeleteAction): + @staticmethod + def action_present(count): + return ungettext_lazy( + u"Delete Tap Flow", + u"Delete Tap Flows", + count + ) + + @staticmethod + def action_past(count): + return ungettext_lazy( + u"Deleted Tap Flow", + u"Deleted Tap Flows", + count + ) + + policy_rules = (("network", "delete_tap_flow"),) + + def delete(self, request, tap_flow_id): + tap_service_id = self.table.kwargs['tap_service_id'] + tap_flow_name = tap_flow_id + try: + api.taas.delete_tap_flow(request, tap_flow_id) + except Exception: + msg = _('Failed to delete tap flow %s') + LOG.info(msg, tap_flow_id) + redirect = reverse("horizon:project:tapservices:detail", + args=[tap_service_id]) + exceptions.handle(request, msg % tap_flow_name, redirect=redirect) + + +class TapFlowsTable(tables.DataTable): + name = tables.Column("name_or_id", + verbose_name=_("Name"), + link="horizon:project:tapservices:tapflows:detail") + port_id = tables.Column("source_port", + verbose_name=_("Port ID")) + tap_service_id = tables.Column("tap_service_id", + verbose_name=_("Tap Service ID")) + + def get_object_display(self, obj): + return obj.id + + class Meta(object): + name = "tapflows" + verbose_name = _("Tap Flows") + table_actions = (CreateTapFlow, DeleteTapFlow,) + row_actions = (DeleteTapFlow,) + hidden_title = False diff --git a/neutron_taas_dashboard/dashboards/project/tapservices/tapflows/tabs.py b/neutron_taas_dashboard/dashboards/project/tapservices/tapflows/tabs.py new file mode 100644 index 0000000..94e7504 --- /dev/null +++ b/neutron_taas_dashboard/dashboards/project/tapservices/tapflows/tabs.py @@ -0,0 +1,31 @@ +# Copyright 2016, FUJITSU LABORATORIES LTD. +# +# 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 tabs + + +class OverviewTab(tabs.Tab): + name = _("Overview") + slug = "overview" + template_name = "project/tapservices/tapflows/_detail_overview.html" + + def get_context_data(self, request): + tap_flow = self.tab_group.kwargs['tap_flow'] + return {'tap_flow': tap_flow} + + +class TapFlowDetailTabs(tabs.TabGroup): + slug = "tapflow_details" + tabs = (OverviewTab,) diff --git a/neutron_taas_dashboard/dashboards/project/tapservices/tapflows/urls.py b/neutron_taas_dashboard/dashboards/project/tapservices/tapflows/urls.py new file mode 100644 index 0000000..3eafb00 --- /dev/null +++ b/neutron_taas_dashboard/dashboards/project/tapservices/tapflows/urls.py @@ -0,0 +1,28 @@ +# Copyright 2016, FUJITSU LABORATORIES LTD. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from django.conf.urls import patterns +from django.conf.urls import url + +from neutron_taas_dashboard.dashboards.project.tapservices.tapflows \ + import views + + +TAP_FLOWS = r'^(?P[^/]+)/%s$' +VIEW_MOD = 'openstack_dashboard.dashboards.project.tapservices.tapflows.views' + +urlpatterns = patterns( + VIEW_MOD, + url(TAP_FLOWS % 'detail', views.DetailView.as_view(), name='detail'), +) diff --git a/neutron_taas_dashboard/dashboards/project/tapservices/tapflows/views.py b/neutron_taas_dashboard/dashboards/project/tapservices/tapflows/views.py new file mode 100644 index 0000000..267d09d --- /dev/null +++ b/neutron_taas_dashboard/dashboards/project/tapservices/tapflows/views.py @@ -0,0 +1,84 @@ +# Copyright 2016, FUJITSU LABORATORIES LTD. +# +# 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 exceptions +from horizon import tables +from horizon.utils import memoized +from horizon import workflows + +from neutron_taas_dashboard import api +from neutron_taas_dashboard.dashboards.project.tapservices.tapflows \ + import tables as tf_tables +from neutron_taas_dashboard.dashboards.project.tapservices.tapflows \ + import workflows as tf_workflows + + +class CreateView(workflows.WorkflowView): + workflow_class = tf_workflows.CreateTapFlow + ajax_template_name = 'project/tapservices/tapflows/create.html' + + @memoized.memoized_method + def get_object(self): + try: + tap_service_id = self.kwargs["tap_service_id"] + tap_service = api.taas.show_tap_service(self.request, + tap_service_id) + return tap_service + except Exception: + redirect = reverse('horizon:project:tapservices:index') + msg = _('Unable to retrieve details for tap service "%s".') \ + % (tap_service_id) + exceptions.handle(self.request, msg, redirect=redirect) + + def get_initial(self): + return {"tap_service_id": self.kwargs['tap_service_id']} + + +class DetailView(tables.DataTableView): + table_class = tf_tables.TapFlowsTable + template_name = 'project/tapservices/tapflows/detail.html' + page_title = _("Tap Flow Details: {{ tap_flow.name }}") + + @memoized.memoized_method + def get_data(self): + tap_flow_id = self.kwargs['tap_flow_id'] + + try: + tap_flow = api.taas.show_tap_flow(self.request, tap_flow_id) + except Exception: + tap_flow = [] + redirect = self.get_redirect_url() + msg = _('Unable to retrieve details for tap flow \"%s\".') \ + % (tap_flow_id) + exceptions.handle(self.request, msg, redirect=redirect) + + return tap_flow + + def get_context_data(self, **kwargs): + context = super(DetailView, self).get_context_data(**kwargs) + tap_flow = self.get_data() + context["tap_flow"] = tap_flow + context["url"] = self.get_redirect_url() + return context + + def get_tabs(self, request, *args, **kwargs): + tap_flow = self.get_data() + return self.tab_group_class(request, tap_flow=tap_flow, **kwargs) + + @staticmethod + def get_redirect_url(): + return reverse('horizon:project:tapservices:index') diff --git a/neutron_taas_dashboard/dashboards/project/tapservices/tapflows/workflows.py b/neutron_taas_dashboard/dashboards/project/tapservices/tapflows/workflows.py new file mode 100644 index 0000000..f57c1c5 --- /dev/null +++ b/neutron_taas_dashboard/dashboards/project/tapservices/tapflows/workflows.py @@ -0,0 +1,135 @@ +# Copyright 2016, FUJITSU LABORATORIES LTD. +# +# 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 django.views.decorators.debug import sensitive_variables + +from horizon import exceptions +from horizon import forms +from horizon import workflows + +from neutron_taas_dashboard import api +from neutron_taas_dashboard.dashboards.project.tapservices \ + import utils as ts_utils +from neutron_taas_dashboard.dashboards.project.tapservices \ + import workflows as ts_workflows + + +class CreateTapFlowInfoAction(workflows.Action): + tap_flow_name = forms.CharField(max_length=255, + label=_("Tap Flow Name"), + required=False) + description = forms.CharField(max_length=255, + label=_("Description"), + required=False) + direction = forms.ChoiceField(choices=[('BOTH', _('Both')), + ('IN', _('Ingress')), + ('OUT', _('Egress'))], + label=_("Direction"), + help_text=_("Whether to mirror the traffic" + " leaving or ariving" + " at the source port.")) + + class Meta(object): + name = _("Information") + slug = 'instance_info' + help_text = _("Create a new tap flow.") + + +class CreateTapFlowInfo(workflows.Step): + action_class = CreateTapFlowInfoAction + contributes = ("tap_flow_name", "description", "direction") + depends_on = ("tap_service_id",) + + +class SelectPortAction(workflows.Action): + port = forms.ChoiceField(label=_("Port"), + help_text=_("Create tap flow with" + " this port"), + widget=forms.SelectWidget( + transform=lambda + x: ("%s (%s) %s" % + (x.name, + x.id, + x["fixed_ips"][0]["ip_address"])))) + + def __init__(self, request, *args, **kwargs): + super(SelectPortAction, self).__init__(request, *args, **kwargs) + port_list = self.fields["port"].choices + if len(port_list) == 1: + self.fields['port'].initial = [port_list[0][0]] + + class Meta(object): + name = _("Port") + permissions = ('openstack.services.network',) + help_text = _("Select port for your tap flow.") + + def populate_port_choices(self, request, context): + return ts_utils.port_field_data(request,) + + +class SelectPort(workflows.Step): + action_class = SelectPortAction + contributes = ("port",) + + +class CreateTapFlow(ts_workflows.CreateTapService): + slug = "create_tapflow" + name = _("Create Tap Flow") + finalize_button_name = _("Create") + success_message = _('Created tap flow "%s".') + failure_message = _('Unable to create tap flow "%s".') + default_steps = (CreateTapFlowInfo, + SelectPort,) + wizard = True + + def get_success_url(self): + return reverse("horizon:project:tapservices:detail", + args=(self.context.get('tap_service_id'),)) + + def get_failure_url(self): + return reverse("horizon:project:tapservices:detail", + args=(self.context.get('tap_service_id'),)) + + def format_status_message(self, message): + name = self.context.get('tap_flow_name') or \ + self.context.get('tap_flow_id', '') + return message % name + + def _create_tap_flow(self, request, data): + try: + params = {'name': data['tap_flow_name'], + 'description': data['description']} + direction = data['direction'] + tap_service_id = self.context.get('tap_service_id') + port_id = data['port'] + tap_flow = api.taas.create_tap_flow(request, + direction, + tap_service_id, + port_id, + **params) + self.context['tap_flow_id'] = tap_flow.id + return True + except Exception: + exceptions.handle(request) + return False + + @sensitive_variables('context') + def handle(self, request, context): + tap_flow = self._create_tap_flow(request, context) + if not tap_flow: + return False + else: + return True diff --git a/neutron_taas_dashboard/dashboards/project/tapservices/templates/tapservices/_detail_overview.html b/neutron_taas_dashboard/dashboards/project/tapservices/templates/tapservices/_detail_overview.html new file mode 100644 index 0000000..94f878d --- /dev/null +++ b/neutron_taas_dashboard/dashboards/project/tapservices/templates/tapservices/_detail_overview.html @@ -0,0 +1,17 @@ +{% load i18n sizeformat %} + +

{% trans "Tap Service Overview" %}

+ +
+
+
{% trans "Name" %}
+
{{ tap_service.name|default:_("None") }}
+
{% trans "ID" %}
+
{{ tap_service.id|default:_("None") }}
+
{% trans "Project ID" %}
+
{{ tap_service.tenant_id|default:_("-") }}
+ {% url 'horizon:project:networks:ports:detail' tap_service.port_id as port_url %} +
{% trans "Port ID" %}
+
{{ tap_service.port_id|default:_("None") }}
+
+
diff --git a/neutron_taas_dashboard/dashboards/project/tapservices/templates/tapservices/create.html b/neutron_taas_dashboard/dashboards/project/tapservices/templates/tapservices/create.html new file mode 100644 index 0000000..d2d14ff --- /dev/null +++ b/neutron_taas_dashboard/dashboards/project/tapservices/templates/tapservices/create.html @@ -0,0 +1,16 @@ +{% extends 'horizon/common/_workflow.html' %} +{% load i18n %} + +{% block modal-footer %} + {% if workflow.wizard %} + + {% else %} + + {% if modal %}{% trans "Cancel" %}{% endif %} + {% endif %} +{% endblock %} diff --git a/neutron_taas_dashboard/dashboards/project/tapservices/templates/tapservices/detail.html b/neutron_taas_dashboard/dashboards/project/tapservices/templates/tapservices/detail.html new file mode 100644 index 0000000..c866092 --- /dev/null +++ b/neutron_taas_dashboard/dashboards/project/tapservices/templates/tapservices/detail.html @@ -0,0 +1,16 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "Tap Service Details"%}{% endblock %} + +{% block main %} +
+
+ {% include "project/tapservices/_detail_overview.html" %} +
+
+ {{ tapflows_table.render }} +
+
+
+{% endblock %} + diff --git a/neutron_taas_dashboard/dashboards/project/tapservices/templates/tapservices/index.html b/neutron_taas_dashboard/dashboards/project/tapservices/templates/tapservices/index.html new file mode 100644 index 0000000..e427744 --- /dev/null +++ b/neutron_taas_dashboard/dashboards/project/tapservices/templates/tapservices/index.html @@ -0,0 +1,7 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "Tap Services" %}{% endblock %} + +{% block main %} + {{ table.render }} +{% endblock %} diff --git a/neutron_taas_dashboard/dashboards/project/tapservices/templates/tapservices/tapflows/_detail_overview.html b/neutron_taas_dashboard/dashboards/project/tapservices/templates/tapservices/tapflows/_detail_overview.html new file mode 100644 index 0000000..10a5a80 --- /dev/null +++ b/neutron_taas_dashboard/dashboards/project/tapservices/templates/tapservices/tapflows/_detail_overview.html @@ -0,0 +1,18 @@ +{% load i18n sizeformat %} + +
+
+
{% trans "Name" %}
+
{{ tap_flow.name|default:_("None") }}
+
{% trans "ID" %}
+
{{ tap_flow.id|default:_("None") }}
+
{% trans "Direction" %}
+
{{ tap_flow.direction|default:_("Both") }}
+
{% trans "Tap Service ID" %}
+
{{ tap_flow.tap_service_id|default:_("-") }}
+
{% trans "Port ID" %}
+
{{ tap_flow.source_port|default:_("-") }}
+
{% trans "Project ID" %}
+
{{ tap_flow.tenant_id|default:_("-") }}
+
+
diff --git a/neutron_taas_dashboard/dashboards/project/tapservices/templates/tapservices/tapflows/create.html b/neutron_taas_dashboard/dashboards/project/tapservices/templates/tapservices/tapflows/create.html new file mode 100644 index 0000000..d2d14ff --- /dev/null +++ b/neutron_taas_dashboard/dashboards/project/tapservices/templates/tapservices/tapflows/create.html @@ -0,0 +1,16 @@ +{% extends 'horizon/common/_workflow.html' %} +{% load i18n %} + +{% block modal-footer %} + {% if workflow.wizard %} + + {% else %} + + {% if modal %}{% trans "Cancel" %}{% endif %} + {% endif %} +{% endblock %} diff --git a/neutron_taas_dashboard/dashboards/project/tapservices/templates/tapservices/tapflows/detail.html b/neutron_taas_dashboard/dashboards/project/tapservices/templates/tapservices/tapflows/detail.html new file mode 100644 index 0000000..1ab79be --- /dev/null +++ b/neutron_taas_dashboard/dashboards/project/tapservices/templates/tapservices/tapflows/detail.html @@ -0,0 +1,15 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "Tap Flow Details"%}{% endblock %} + +{% block main %} +
+
+ {% include "project/tapservices/tapflows/_detail_overview.html" %} +
+
+ {{ tap_flow.render }} +
+
+
+{% endblock %} diff --git a/neutron_taas_dashboard/dashboards/project/tapservices/urls.py b/neutron_taas_dashboard/dashboards/project/tapservices/urls.py new file mode 100644 index 0000000..82b50c5 --- /dev/null +++ b/neutron_taas_dashboard/dashboards/project/tapservices/urls.py @@ -0,0 +1,36 @@ +# Copyright 2016, FUJITSU LABORATORIES LTD. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from django.conf.urls import include +from django.conf.urls import patterns +from django.conf.urls import url + +from neutron_taas_dashboard.dashboards.project.tapservices.tapflows \ + import urls as tapflow_urls +from neutron_taas_dashboard.dashboards.project.tapservices.tapflows \ + import views as tf_views +from neutron_taas_dashboard.dashboards.project.tapservices \ + import views + +TAP_SERVICES = r'^(?P[^/]+)/%s$' + +urlpatterns = patterns( + '', + url(r'^$', views.IndexView.as_view(), name='index'), + url(r'^create/$', views.CreateView.as_view(), name='create'), + url(TAP_SERVICES % 'detail', views.DetailView.as_view(), name='detail'), + url(TAP_SERVICES % 'tapflows/create', tf_views.CreateView.as_view(), + name='createtapflow'), + url(r'^tapflows/', include(tapflow_urls, namespace='tapflows')), +) diff --git a/neutron_taas_dashboard/dashboards/project/tapservices/utils.py b/neutron_taas_dashboard/dashboards/project/tapservices/utils.py new file mode 100644 index 0000000..a9aa039 --- /dev/null +++ b/neutron_taas_dashboard/dashboards/project/tapservices/utils.py @@ -0,0 +1,79 @@ +# Copyright 2016, FUJITSU LABORATORIES LTD. +# +# 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 _ +import six + +from horizon import exceptions + +from neutron_taas_dashboard import api + +from openstack_dashboard import api as o_api + +LOG = logging.getLogger(__name__) + + +def port_field_data(request, instance_id=None, include_empty_option=False): + """Returns a list of tuples of all ports. + + Generates a list of ports available to the user (request). And returns + a list of (id, name) tuples. + + :param request: django http request object + :param include_empty_option: flag to include a empty tuple in the front of + the list + :return: list of (id, name) tuples + """ + ports = [] + try: + ports_pre = o_api.neutron.port_list(request,) + for port in ports_pre: + if not instance_id or port.device_id == instance_id: + ports = ports + [port] + ports = [(n.id, n) for n in ports] + ports.sort(key=lambda obj: obj[1].name_or_id) + except Exception as e: + msg = _('Failed to get port list {0}').format(six.text_type(e)) + exceptions.handle(request, msg) + + if not ports: + if include_empty_option: + return [("", _("No ports available")), ] + return [] + + if include_empty_option: + return [("", _("Select Port")), ] + ports + return ports + + +def tap_service_field_data(request, include_empty_option=False): + tap_services = [] + try: + tap_services = api.taas.list_tap_service(request) + tap_services = [(n.id, n) for n in tap_services] + tap_services.sort(key=lambda obj: obj[1]) + except Exception as e: + msg = _('Failed to get tap service list {0}').format(six.text_type(e)) + exceptions.handle(request, msg) + + if not tap_services: + if include_empty_option: + return [("", _("No tap services available")), ] + return [] + + if include_empty_option: + return [("", _("Select Tap Service")), ] + tap_services + return tap_services diff --git a/neutron_taas_dashboard/dashboards/project/tapservices/views.py b/neutron_taas_dashboard/dashboards/project/tapservices/views.py new file mode 100644 index 0000000..9415f11 --- /dev/null +++ b/neutron_taas_dashboard/dashboards/project/tapservices/views.py @@ -0,0 +1,107 @@ +# Copyright 2016, FUJITSU LABORATORIES LTD. +# +# 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 exceptions +from horizon import tables +from horizon.utils import memoized +from horizon import workflows + +from neutron_taas_dashboard import api +from neutron_taas_dashboard.dashboards.project.tapservices \ + import tables as ts_tables +from neutron_taas_dashboard.dashboards.project.tapservices.tapflows \ + import tables as tf_tables +from neutron_taas_dashboard.dashboards.project.tapservices \ + import workflows as ts_workflows + +import logging +LOG = logging.getLogger(__name__) + + +class IndexView(tables.DataTableView): + table_class = ts_tables.TapServicesTable + template_name = 'project/tapservices/index.html' + page_title = _("Tap Services") + + @memoized.memoized_method + def get_data(self): + + try: + tap_service = api.taas.list_tap_service(self.request) + except Exception: + tap_service = [] + msg = _('Tap service list can not be retrieved.') + exceptions.handle(self.request, msg) + + return tap_service + + +class CreateView(workflows.WorkflowView): + workflow_class = ts_workflows.CreateTapService + ajax_template_name = 'project/tapservices/create.html' + + +class DetailView(tables.DataTableView): + table_class = tf_tables.TapFlowsTable + template_name = 'project/tapservices/detail.html' + page_title = _("Tap Service Details: {{ tap_service.name }}") + + def get_data(self): + try: + tap_service = self._get_data() + tap_service_id = tap_service.id + tap_flows_pre = api.taas.list_tap_flow(self.request, + tap_service_id) + tap_flows = [] + for tap_flow in tap_flows_pre: + tap_flow_tap_service_id = tap_flow['tap_service_id'] + if tap_flow_tap_service_id == tap_service_id: + tap_flows = tap_flows + [tap_flow] + except Exception: + tap_flows = [] + msg = _('Tap flow list can not be retrieved.') + exceptions.handle(self.request, msg) + LOG.debug("GET_TAPFLOWS_DATA") + + return tap_flows + + @memoized.memoized_method + def _get_data(self): + try: + tap_service_id = self.kwargs['tap_service_id'] + tap_service = api.taas.show_tap_service(self.request, + tap_service_id) + except Exception: + msg = _('Unable to retrieve details for tap service "%s".') \ + % (tap_service_id) + exceptions.handle(self.request, msg, + redirect=self.get_redirect_url()) + return tap_service + + def get_context_data(self, **kwargs): + context = super(DetailView, self).get_context_data(**kwargs) + tap_service = self._get_data() + context["tap_service"] = tap_service + table = ts_tables.TapServicesTable(self.request) + context["url"] = self.get_redirect_url() + context["actions"] = table.render_row_actions(tap_service) + LOG.debug("GET_CONTEXT_DATA") + return context + + @staticmethod + def get_redirect_url(): + return reverse('horizon:project:tapservices:index') diff --git a/neutron_taas_dashboard/dashboards/project/tapservices/workflows.py b/neutron_taas_dashboard/dashboards/project/tapservices/workflows.py new file mode 100644 index 0000000..d03ff63 --- /dev/null +++ b/neutron_taas_dashboard/dashboards/project/tapservices/workflows.py @@ -0,0 +1,117 @@ +# Copyright 2016, FUJITSU LABORATORIES LTD. +# +# 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 django.views.decorators.debug import sensitive_variables + +from horizon import exceptions +from horizon import forms +from horizon import workflows + +from neutron_taas_dashboard import api +from neutron_taas_dashboard.dashboards.project.tapservices \ + import utils as ts_utils + + +class CreateTapInfoAction(workflows.Action): + tap_service_name = forms.CharField(max_length=255, + label=_("Tap Service Name"), + required=False) + description = forms.CharField(max_length=255, label=_("Description"), + required=False) + + class Meta(object): + name = _("Tap Service") + help_text = _("Create a new tap service.") + + +class CreateTapInfo(workflows.Step): + action_class = CreateTapInfoAction + contributes = ("tap_service_name", "description") + + +class CreateTapDetailAction(workflows.Action): + port = forms.ChoiceField(label=_("Ports"), + help_text=_("Create tap service with" + " this port"), + widget=forms.SelectWidget( + transform=lambda + x: ("%s (%s) %s" % + (x.name, + x.id, + x["fixed_ips"][0]["ip_address"])))) + + def __init__(self, request, *args, **kwargs): + super(CreateTapDetailAction, self).__init__(request, *args, **kwargs) + port_list = self.fields["port"].choices + if len(port_list) == 1: + self.fields['port'].initial = [port_list[0][0]] + + class Meta(object): + name = _("Port") + permissions = ('openstack.services.network',) + help_text = _("Select port for your tap service.") + + def populate_port_choices(self, request, context): + return ts_utils.port_field_data(request) + + +class CreateTapDetail(workflows.Step): + action_class = CreateTapDetailAction + contributes = ("port",) + + +class CreateTapService(workflows.Workflow): + slug = "create_tapservice" + name = _("Create Tap Service") + finalize_button_name = _("Create") + success_message = _('Created tap service "%s".') + failure_message = _('Unable to create tap service "%s".') + default_steps = (CreateTapInfo, + CreateTapDetail) + wizard = True + + def get_success_url(self): + return reverse("horizon:project:tapservices:index") + + def get_failure_url(self): + return reverse("horizon:project:tapservices:index") + + def format_status_message(self, message): + name = self.context.get('tap_service_name') or \ + self.context.get('tap_service_id', '') + return message % name + + def _create_tap_service(self, request, data): + try: + params = {'name': data['tap_service_name'], + 'description': data['description']} + port_id = data['port'] + tap_service = api.taas.create_tap_service(request, + port_id, + **params) + self.context['tap_service_id'] = tap_service.id + return True + except Exception: + exceptions.handle(request) + return False + + @sensitive_variables('context') + def handle(self, request, context): + tap_service = self._create_tap_service(request, context) + if not tap_service: + return False + else: + return True diff --git a/neutron_taas_dashboard/enabled/_90_project_tapservices_panel.py b/neutron_taas_dashboard/enabled/_90_project_tapservices_panel.py new file mode 100644 index 0000000..36d9655 --- /dev/null +++ b/neutron_taas_dashboard/enabled/_90_project_tapservices_panel.py @@ -0,0 +1,28 @@ +# Copyright 2016, FUJITSU LABORATORIES LTD. +# +# 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. + +# The slug of the panel to be added to HORIZON_CONFIG. Required. +PANEL = 'tapservices' +# The slug of the dashboard the PANEL associated with. Required. +PANEL_DASHBOARD = 'project' +# The slug of the panel group the PANEL is associated with. +PANEL_GROUP = 'network' + +# Python panel class of the PANEL to be added. +ADD_PANEL = 'neutron_taas_dashboard.dashboards.' \ + 'project.tapservices.panel.TapServices' + +ADD_INSTALLED_APPS = ['neutron_taas_dashboard'] + +AUTO_DISCOVER_STATIC_FILES = True diff --git a/neutron_taas_dashboard/enabled/__init__.py b/neutron_taas_dashboard/enabled/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/neutron_taas_dashboard/karma.conf.js b/neutron_taas_dashboard/karma.conf.js new file mode 100644 index 0000000..d7c4e44 --- /dev/null +++ b/neutron_taas_dashboard/karma.conf.js @@ -0,0 +1,151 @@ +/* + * 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. + */ + +'use strict'; + +var fs = require('fs'); +var path = require('path'); + +module.exports = function (config) { + // This tox venv is setup in the post-install npm step + var toxPath = '../.tox/py27/lib/python2.7/site-packages/'; + + config.set({ + preprocessors: { + // Used to collect templates for preprocessing. + // NOTE: the templates must also be listed in the files section below. + './static/**/*.html': ['ng-html2js'], + // Used to indicate files requiring coverage reports. + './static/**/!(*.spec).js': ['coverage'], + }, + + // Sets up module to process templates. + ngHtml2JsPreprocessor: { + prependPrefix: '/', + moduleName: 'templates' + }, + + basePath: './', + + // Contains both source and test files. + files: [ + /* + * shim, partly stolen from /i18n/js/horizon/ + * Contains expected items not provided elsewhere (dynamically by + * Django or via jasmine template. + */ + '../test-shim.js', + + // from jasmine.html + toxPath + 'xstatic/pkg/jquery/data/jquery.js', + toxPath + 'xstatic/pkg/angular/data/angular.js', + toxPath + 'xstatic/pkg/angular/data/angular-route.js', + toxPath + 'xstatic/pkg/angular/data/angular-mocks.js', + toxPath + 'xstatic/pkg/angular/data/angular-cookies.js', + toxPath + 'xstatic/pkg/angular_bootstrap/data/angular-bootstrap.js', + toxPath + 'xstatic/pkg/angular_gettext/data/angular-gettext.js', + toxPath + 'xstatic/pkg/angular/data/angular-sanitize.js', + toxPath + 'xstatic/pkg/d3/data/d3.js', + toxPath + 'xstatic/pkg/rickshaw/data/rickshaw.js', + toxPath + 'xstatic/pkg/angular_smart_table/data/smart-table.js', + toxPath + 'xstatic/pkg/angular_lrdragndrop/data/lrdragndrop.js', + toxPath + 'xstatic/pkg/spin/data/spin.js', + toxPath + 'xstatic/pkg/spin/data/spin.jquery.js', + toxPath + 'xstatic/pkg/tv4/data/tv4.js', + toxPath + 'xstatic/pkg/objectpath/data/ObjectPath.js', + toxPath + 'xstatic/pkg/angular_schema_form/data/schema-form.js', + toxPath + 'xstatic/pkg/angular_fileupload/data/ng-file-upload.js', + + + // TODO: These should be mocked. + toxPath + '/horizon/static/horizon/js/horizon.js', + + /** + * Include framework source code from horizon that we need. + * Otherwise, karma will not be able to find them when testing. + * These files should be mocked in the foreseeable future. + */ + toxPath + 'horizon/static/framework/**/*.module.js', + toxPath + 'horizon/static/framework/**/!(*.spec|*.mock).js', + toxPath + 'openstack_dashboard/static/**/*.module.js', + toxPath + 'openstack_dashboard/static/**/!(*.spec|*.mock).js', + toxPath + 'openstack_dashboard/dashboards/**/static/**/*.module.js', + toxPath + 'openstack_dashboard/dashboards/**/static/**/!(*.spec|*.mock).js', + + /** + * First, list all the files that defines application's angular modules. + * Those files have extension of `.module.js`. The order among them is + * not significant. + */ + './static/**/*.module.js', + + /** + * Followed by other JavaScript files that defines angular providers + * on the modules defined in files listed above. And they are not mock + * files or spec files defined below. The order among them is not + * significant. + */ + './static/**/!(*.spec|*.mock).js', + + /** + * Then, list files for mocks with `mock.js` extension. The order + * among them should not be significant. + */ + toxPath + 'openstack_dashboard/static/**/*.mock.js', + + /** + * Finally, list files for spec with `spec.js` extension. The order + * among them should not be significant. + */ + './static/**/*.spec.js', + + /** + * Angular external templates + */ + './static/**/*.html' + ], + + autoWatch: true, + + frameworks: ['jasmine'], + + browsers: ['Chrome'], + + browserNoActivityTimeout: 60000, + + reporters: ['progress', 'coverage', 'threshold'], + + plugins: [ + 'karma-chrome-launcher', + 'karma-jasmine', + 'karma-ng-html2js-preprocessor', + 'karma-coverage', + 'karma-threshold-reporter' + ], + + // Places coverage report in HTML format in the subdirectory below. + coverageReporter: { + type: 'html', + dir: '../cover/karma/' + }, + + // Coverage threshold values. + thresholdReporter: { + statements: 10, // target 100 + branches: 0, // target 100 + functions: 10, // target 100 + lines: 10 // target 100 + } + }); +}; diff --git a/neutron_taas_dashboard/post_install.sh b/neutron_taas_dashboard/post_install.sh new file mode 100644 index 0000000..e9d81e2 --- /dev/null +++ b/neutron_taas_dashboard/post_install.sh @@ -0,0 +1,13 @@ +#!/bin/bash -x + +# This script will be executed inside npm postinstall task, see package.json + +# pull down the test shim from horizon master because it's not +# included in the installed horizon packages +if [ ! -f test-shim.js ]; +then + wget -nv -t 3 https://raw.githubusercontent.com/openstack/horizon/master/test-shim.js +fi + +echo "Creating a tox env which will contain xStatic libraries, horizon, and openstack_dashboard" +tox -epy27 --notest; diff --git a/neutron_taas_dashboard/test/__init__.py b/neutron_taas_dashboard/test/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/neutron_taas_dashboard/test/base.py b/neutron_taas_dashboard/test/base.py new file mode 100644 index 0000000..2159808 --- /dev/null +++ b/neutron_taas_dashboard/test/base.py @@ -0,0 +1,21 @@ +# Copyright 2010-2011 OpenStack Foundation +# 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. + +from oslotest import base + + +class TestCase(base.BaseTestCase): + + """Test case base class for all unit tests.""" diff --git a/neutron_taas_dashboard/test/integration_tests/__init__.py b/neutron_taas_dashboard/test/integration_tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/neutron_taas_dashboard/test/settings.py b/neutron_taas_dashboard/test/settings.py new file mode 100644 index 0000000..498cd9e --- /dev/null +++ b/neutron_taas_dashboard/test/settings.py @@ -0,0 +1,37 @@ +# 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. + +# Default to Horizons test settings to avoid any missing keys +from horizon.test.settings import * # noqa +from openstack_dashboard.test.settings import * # noqa + +# pop these keys to avoid log warnings about deprecation +# update_dashboards will populate them anyway +HORIZON_CONFIG.pop('dashboards', None) +HORIZON_CONFIG.pop('default_dashboard', None) + +# Update the dashboards with neutron_taas_dashboard +import neutron_taas_dashboard.enabled +import openstack_dashboard.enabled +from openstack_dashboard.utils import settings + +settings.update_dashboards( + [ + neutron_taas_dashboard.enabled, + openstack_dashboard.enabled, + ], + HORIZON_CONFIG, + INSTALLED_APPS +) + +# Ensure any duplicate apps are removed after the update_dashboards call +INSTALLED_APPS = list(set(INSTALLED_APPS)) diff --git a/neutron_taas_dashboard/test/test_neutron_taas_dashboard.py b/neutron_taas_dashboard/test/test_neutron_taas_dashboard.py new file mode 100644 index 0000000..ca75670 --- /dev/null +++ b/neutron_taas_dashboard/test/test_neutron_taas_dashboard.py @@ -0,0 +1,28 @@ +# Copyright 2016, FUJITSU LABORATORIES LTD. +# +# 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. + +""" +test_neutron_taas_dashboard +---------------------------------- + +Tests for `neutron_taas_dashboard` module. +""" + +from neutron_taas_dashboard.test import base + + +class TestNeutron_taas_dashboard(base.TestCase): + + def test_something(self): + pass diff --git a/package.json b/package.json new file mode 100644 index 0000000..9280c62 --- /dev/null +++ b/package.json @@ -0,0 +1,32 @@ +{ + "name": "tap-as-a-service-dashboard", + "description": "Tap-as-a-Service Dashboard JavaScript tests", + "repository": { + "type": "git", + "url": "git://git.openstack.org/openstack/tap-as-a-service-dashboard" + }, + "version": "0.0.0", + "private": true, + "license": "Apache 2.0", + "author": "Openstack ", + "devDependencies": { + "eslint": "^1.10.3", + "eslint-config-openstack": "^1.2.4", + "eslint-plugin-angular": "1.0.1", + "jasmine-core": "2.4.1", + "karma": "1.1.2", + "karma-chrome-launcher": "1.0.1", + "karma-cli": "1.0.1", + "karma-coverage": "1.1.1", + "karma-jasmine": "1.0.2", + "karma-ng-html2js-preprocessor": "1.0.0", + "karma-threshold-reporter": "0.1.15" + }, + "dependencies": {}, + "scripts": { + "postinstall": "if [ ! -d .tox ] || [ ! -d .tox/py27 ]; then tox -epy27 --notest; fi", + "lint": "eslint --no-color neutron_taas_dashboard/static", + "lintq": "eslint --quiet neutron_taas_dashboard/static", + "test": "karma start neutron_taas_dashboard/karma.conf.js --single-run" + } +} diff --git a/releasenotes/notes/.placeholder b/releasenotes/notes/.placeholder new file mode 100644 index 0000000..e69de29 diff --git a/releasenotes/source/_static/.placeholder b/releasenotes/source/_static/.placeholder new file mode 100644 index 0000000..e69de29 diff --git a/releasenotes/source/_templates/.placeholder b/releasenotes/source/_templates/.placeholder new file mode 100644 index 0000000..e69de29 diff --git a/releasenotes/source/conf.py b/releasenotes/source/conf.py new file mode 100644 index 0000000..abdd422 --- /dev/null +++ b/releasenotes/source/conf.py @@ -0,0 +1,281 @@ +# -*- coding: utf-8 -*- +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Glance Release Notes documentation build configuration file, created by +# sphinx-quickstart on Tue Nov 3 17:40:50 2015. +# +# This file is execfile()d with the current directory set to its +# containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# sys.path.insert(0, os.path.abspath('.')) + +# -- General configuration ------------------------------------------------ + +# If your documentation needs a minimal Sphinx version, state it here. +# needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + 'oslosphinx', + 'reno.sphinxext', +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix 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'Tap-as-a-Service Dashboard Release Notes' +copyright = u'2016, OpenStack Foundation' + +# 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. +# The full version, including alpha/beta/rc tags. +release = '' +# The short X.Y version. +version = '' + +# 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 = [] + +# If true, keep warnings as "system message" paragraphs in the built documents. +# keep_warnings = False + + +# -- Options for HTML output ---------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +html_theme = 'default' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +# html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +# html_theme_path = [] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +# html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +# html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +# html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +# html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# Add any extra paths that contain custom files (such as robots.txt or +# .htaccess) here, relative to this directory. These files are copied +# directly to the root of the documentation. +# html_extra_path = [] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +# html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +# html_use_smartypants = True + +# Custom sidebar templates, 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 = 'Tap-as-a-Service-DashboardReleaseNotesdoc' + + +# -- 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, or own class]). +latex_documents = [ + ('index', + 'Tap-as-a-Service-DashboardReleaseNotes.tex', + u'Tap-as-a-Service Dashboard Release Notes Documentation', + u'Tap-as-a-Service Dashboard Developers', + 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +# latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +# latex_use_parts = False + +# If true, show page references after internal links. +# latex_show_pagerefs = False + +# If true, show URL addresses after external links. +# latex_show_urls = False + +# Documents to append as an appendix to all manuals. +# latex_appendices = [] + +# If false, no module index is generated. +# latex_domain_indices = True + + +# -- Options for manual page output --------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + ('index', 'neutron_taas_dashboardreleasenotes', + u'Tap-as-a-Service Dashboard Release Notes Documentation', + [u'Tap-as-a-Service Dashboard Developers'], 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', 'Tap-as-a-Service-DashboardReleaseNotes', + u'Tap-as-a-Service Dashboard Release Notes Documentation', + u'Tap-as-a-Service Dashboard Developers', + 'Tap-as-a-Service-DashboardReleaseNotes', + 'One line description of project.', + 'Miscellaneous'), +] + +# Documents to append as an appendix to all manuals. +# texinfo_appendices = [] + +# If false, no module index is generated. +# texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +# texinfo_show_urls = 'footnote' + +# If true, do not generate a @detailmenu in the "Top" node's menu. +# texinfo_no_detailmenu = False + +# -- Options for Internationalization output ------------------------------ +locale_dirs = ['locale/'] diff --git a/releasenotes/source/index.rst b/releasenotes/source/index.rst new file mode 100644 index 0000000..6271dcb --- /dev/null +++ b/releasenotes/source/index.rst @@ -0,0 +1,8 @@ +============================================ + neutron_taas_dashboard Release Notes +============================================ + +.. toctree:: + :maxdepth: 1 + + unreleased diff --git a/releasenotes/source/unreleased.rst b/releasenotes/source/unreleased.rst new file mode 100644 index 0000000..cd22aab --- /dev/null +++ b/releasenotes/source/unreleased.rst @@ -0,0 +1,5 @@ +============================== + Current Series Release Notes +============================== + +.. release-notes:: diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..8c407ec --- /dev/null +++ b/requirements.txt @@ -0,0 +1,18 @@ +# 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. +# Order matters to the pip dependency resolver, so sorting this file +# changes how packages are installed. New dependencies should be +# added in alphabetical order, however, some dependencies may need to +# be installed in a specific order. +# +# PBR should always appear first +pbr>=2.0.0 # Apache-2.0 +# If python-higginsclient will be created, we will use it. +#python-higginsclient>=0.1.0 # Apache-2.0 +Babel>=2.3.4 # BSD +Django<1.9,>=1.8 # BSD +django-babel>=0.5.1 # BSD +django-compressor>=2.0 # MIT +django-openstack-auth>=2.2.0 # Apache-2.0 +django-pyscss>=2.0.2 # BSD License (2 clause) diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..0bd575f --- /dev/null +++ b/setup.cfg @@ -0,0 +1,28 @@ +[metadata] +name = tap-as-a-service-dashboard +summary = Tap-as-a-Service Horizon plugin. +description-file = + README.rst +author = OpenStack +author-email = openstack-dev@lists.openstack.org +home-page = http://www.openstack.org/ +classifier = + Environment :: OpenStack + Intended Audience :: Information Technology + Intended Audience :: System Administrators + License :: OSI Approved :: Apache Software License + Operating System :: POSIX :: Linux + Programming Language :: Python + Programming Language :: Python :: 2 + Programming Language :: Python :: 2.7 + Programming Language :: Python :: 3 + Programming Language :: Python :: 3.4 + +[files] +packages = + neutron_taas_dashboard + +[build_sphinx] +all_files = 1 +build-dir = doc/build +source-dir = doc/source diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..d74ff58 --- /dev/null +++ b/setup.py @@ -0,0 +1,27 @@ +# 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>=2.0.0'], + pbr=True) diff --git a/test-requirements.txt b/test-requirements.txt new file mode 100644 index 0000000..92f3bf0 --- /dev/null +++ b/test-requirements.txt @@ -0,0 +1,31 @@ +# 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. +# Order matters to the pip dependency resolver, so sorting this file +# changes how packages are installed. New dependencies should be +# added in alphabetical order, however, some dependencies may need to +# be installed in a specific order. +# +# Hacking should appear first in case something else depends on pep8 +hacking>=0.12.0,!=0.13.0,<0.14 # Apache-2.0 +# +coverage>=3.6 # Apache-2.0 +django-nose>=1.2 # BSD +mock>=2.0 # BSD +mox3>=0.7.0 # Apache-2.0 +nodeenv>=0.9.4 # BSD License # BSD +nose # LGPL +nose-exclude # LGPL +nosehtmloutput>=0.0.3 # Apache-2.0 +nosexcover # BSD +openstack.nose-plugin>=0.7 # Apache-2.0 +oslosphinx!=3.4.0,>=2.5.0 # Apache-2.0 +oslotest>=1.10.0 # Apache-2.0 +reno>=1.8.0 # Apache2 +selenium>=2.50.1 # Apache-2.0 +sphinx!=1.3b1,<1.3,>=1.2.1 # BSD +testtools>=1.4.0 # MIT +# This also needs xvfb library installed on your OS +xvfbwrapper>=0.1.3 #license: MIT +# Include horizon as test requirement +http://tarballs.openstack.org/horizon/horizon-master.tar.gz#egg=horizon diff --git a/test-shim.js b/test-shim.js new file mode 100644 index 0000000..5b364ba --- /dev/null +++ b/test-shim.js @@ -0,0 +1,96 @@ +/* + * Shim for Javascript unit tests; supplying expected global features. + * This should be removed from the codebase once i18n services are provided. + * Taken from default i18n file provided by Django. + */ + +var horizonPlugInModules = []; + + +(function (globals) { + + var django = globals.django || (globals.django = {}); + + + django.pluralidx = function (count) { return (count == 1) ? 0 : 1; }; + + /* gettext identity library */ + + django.gettext = function (msgid) { return msgid; }; + django.ngettext = function (singular, plural, count) { return (count == 1) ? singular : plural; }; + django.gettext_noop = function (msgid) { return msgid; }; + django.pgettext = function (context, msgid) { return msgid; }; + django.npgettext = function (context, singular, plural, count) { return (count == 1) ? singular : plural; }; + + + django.interpolate = function (fmt, obj, named) { + if (named) { + return fmt.replace(/%\(\w+\)s/g, function(match){return String(obj[match.slice(2,-2)])}); + } else { + return fmt.replace(/%s/g, function(match){return String(obj.shift())}); + } + }; + + + /* formatting library */ + + django.formats = { + "DATETIME_FORMAT": "N j, Y, P", + "DATETIME_INPUT_FORMATS": [ + "%Y-%m-%d %H:%M:%S", + "%Y-%m-%d %H:%M:%S.%f", + "%Y-%m-%d %H:%M", + "%Y-%m-%d", + "%m/%d/%Y %H:%M:%S", + "%m/%d/%Y %H:%M:%S.%f", + "%m/%d/%Y %H:%M", + "%m/%d/%Y", + "%m/%d/%y %H:%M:%S", + "%m/%d/%y %H:%M:%S.%f", + "%m/%d/%y %H:%M", + "%m/%d/%y" + ], + "DATE_FORMAT": "N j, Y", + "DATE_INPUT_FORMATS": [ + "%Y-%m-%d", + "%m/%d/%Y", + "%m/%d/%y" + ], + "DECIMAL_SEPARATOR": ".", + "FIRST_DAY_OF_WEEK": "0", + "MONTH_DAY_FORMAT": "F j", + "NUMBER_GROUPING": "3", + "SHORT_DATETIME_FORMAT": "m/d/Y P", + "SHORT_DATE_FORMAT": "m/d/Y", + "THOUSAND_SEPARATOR": ",", + "TIME_FORMAT": "P", + "TIME_INPUT_FORMATS": [ + "%H:%M:%S", + "%H:%M:%S.%f", + "%H:%M" + ], + "YEAR_MONTH_FORMAT": "F Y" + }; + + django.get_format = function (format_type) { + var value = django.formats[format_type]; + if (typeof(value) == 'undefined') { + return format_type; + } else { + return value; + } + }; + + /* add to global namespace */ + globals.pluralidx = django.pluralidx; + globals.gettext = django.gettext; + globals.ngettext = django.ngettext; + globals.gettext_noop = django.gettext_noop; + globals.pgettext = django.pgettext; + globals.npgettext = django.npgettext; + globals.interpolate = django.interpolate; + globals.get_format = django.get_format; + globals.STATIC_URL = '/static/'; + globals.WEBROOT = '/'; + +}(this)); diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..5e1d3c1 --- /dev/null +++ b/tox.ini @@ -0,0 +1,90 @@ +[tox] +envlist = py35,py27,py27dj18,pep8 +minversion = 2.0 +skipsdist = True + +[testenv] +usedevelop = True +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 +install_command = pip install -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} -U {opts} {packages} +deps = -r{toxinidir}/requirements.txt + -r{toxinidir}/test-requirements.txt +commands = python manage.py test {posargs} --settings=neutron_taas_dashboard.test.settings + +[testenv:pep8] +commands = flake8 {posargs} + +[testenv:venv] +commands = {posargs} + +[testenv:cover] +commands = + coverage erase + coverage run {toxinidir}/manage.py test neutron_taas_dashboard --settings=neutron_taas_dashboard.test.settings {posargs} --exclude-dir=neutron_taas_dashboard/test/integration_tests {posargs} + coverage xml --omit '.tox/cover/*' -o 'cover/coverage.xml' + coverage html --omit '.tox/cover/*' -d 'cover/htmlcov' + +[testenv:py27dj18] +basepython = python2.7 +commands = + pip install django>=1.8,<1.9 + python manage.py test {posargs} --settings=neutron_taas_dashboard.test.settings + +[testenv:eslint] +whitelist_externals = npm +commands = + npm install + npm run postinstall + npm run lint + +[testenv:karma] +# NOTE(shu-mutou): Until PhantomJS setup get reliable, we use +# Chromium for JS test. And npm can't launch Chromium via tox. +#whitelist_externals = npm +#commands = +# npm install +# npm run postinstall +# npm run test +whitelist_externals = echo +commands = + echo "npm can't launch Chromium via tox." + echo "nexecute `npm run test`" + +[testenv:docs] +setenv = DJANGO_SETTINGS_MODULE=neutron_taas_dashboard.test.settings +commands = python setup.py build_sphinx + +[testenv:releasenotes] +commands = sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html + +[flake8] +exclude = .venv,.git,.tox,dist,*lib/python*,*egg,build,node_modules +max-complexity = 20 + +[hacking] +import_exceptions = collections.defaultdict, + django.conf.settings, + django.conf.urls.include, + django.conf.urls.patterns, + django.conf.urls.url, + django.core.urlresolvers.reverse, + django.core.urlresolvers.reverse_lazy, + django.template.loader.render_to_string, + django.test.utils.override_settings, + django.utils.datastructures.SortedDict, + django.utils.encoding.force_text, + django.utils.html.conditional_escape, + django.utils.html.escape, + django.utils.http.urlencode, + django.utils.safestring.mark_safe, + django.utils.translation.npgettext_lazy, + django.utils.translation.pgettext_lazy, + django.utils.translation.ugettext_lazy, + django.utils.translation.ungettext_lazy, + operator.attrgetter, + StringIO.StringIO