Retire Sahara: remove repo content

Sahara project is retiring
- https://review.opendev.org/c/openstack/governance/+/919374

this commit remove the content of this project repo

Depends-On: https://review.opendev.org/c/openstack/project-config/+/919376
Change-Id: I4eb4556e448b4eacdbf1b5cc4164bcf6a4ccbed0
This commit is contained in:
Ghanshyam Mann 2024-05-10 17:27:28 -07:00
parent 744efb4fc5
commit af37b9efbf
334 changed files with 8 additions and 43375 deletions

View File

@ -1,13 +0,0 @@
[run]
branch = True
source = sahara_dashboard
omit =
.tox/*
sahara_dashboard/test/*
[paths]
source = sahara_dashboard
[report]
ignore_errors = True
precision = 3

39
.gitignore vendored
View File

@ -1,39 +0,0 @@
*.py[co]
*.egg
*.egg-info
*.lock
dist
build
eggs
parts
var
sdist
develop-eggs
.installed.cfg
pip-log.txt
.tox
*.mo
.mr.developer.cfg
.DS_Store
Thumbs.db
.venv
.idea
out
target
*.iml
*.ipr
*.iws
doc/html
doc/source/apidoc
doc/source/api
doc/build
*.db
.coverage
nosetests.xml
pylint-report.txt
ChangeLog
cscope.out
horizon
sahara_dashboard/test/.secret_key_store
sahara_dashboard/test/configs/config.conf
AUTHORS

View File

@ -1,53 +0,0 @@
- project:
templates:
- check-requirements
- horizon-non-primary-django-jobs
- openstack-python3-jobs-horizon
- release-notes-jobs-python3
experimental:
jobs:
- sahara-dashboard-integration
- job:
name: sahara-dashboard-tox-base
parent: openstack-tox
required-projects:
- openstack/sahara-dashboard
irrelevant-files:
- ^.*\.rst$
- ^doc/.*$
- ^releasenotes/.*$
- ^sahara_dashboard/locale/.*$
- job:
name: sahara-dashboard-integration
parent: devstack
required-projects:
- openstack/heat
- openstack/horizon
- openstack/sahara
- openstack/sahara-plugin-ambari
- openstack/sahara-plugin-cdh
- openstack/sahara-plugin-mapr
- openstack/sahara-plugin-spark
- openstack/sahara-plugin-storm
- openstack/sahara-plugin-vanilla
- openstack/sahara-dashboard
roles:
- zuul: openstack/horizon
vars:
devstack_plugins:
sahara: 'https://opendev.org/openstack/sahara'
sahara-dashboard: 'https://opendev.org/openstack/sahara-dashboard'
heat: 'https://opendev.org/openstack/heat'
devstack_services:
horizon: true
tls-proxy: false
devstack_localrc:
USE_PYTHON3: True
pre-run: playbooks/sahara-dashboard-integration/pre.yaml
run: playbooks/sahara-dashboard-integration/run.yaml
irrelevant-files:
- ^.*\.rst$
- ^doc/.*$
- ^releasenotes/.*$

View File

@ -1,19 +0,0 @@
The source repository for this project can be found at:
https://opendev.org/openstack/sahara-dashboard
Pull requests submitted through GitHub are not monitored.
To start contributing to OpenStack, follow the steps in the contribution guide
to set up and use Gerrit:
https://docs.openstack.org/contributors/code-and-documentation/quick-start.html
Bugs should be filed on Storyboard:
https://storyboard.openstack.org/#!/project/openstack/sahara-dashboard
For more specific information about contributing to this repository, see the
sahara contributor guide:
https://docs.openstack.org/sahara/latest/contributor/contributing.html

View File

@ -1,12 +0,0 @@
Sahara Style Commandments
==========================
- Step 1: Read the OpenStack Style Commandments
https://docs.openstack.org/hacking/latest/
- Step 2: Read on
Sahara Specific Commandments
-----------------------------
None so far

176
LICENSE
View File

@ -1,176 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.

View File

@ -1,4 +0,0 @@
recursive-include sahara_dashboard *.html *.scss *.js
include AUTHORS
include ChangeLog

View File

@ -1,58 +1,10 @@
========================
Team and repository tags
========================
This project is no longer maintained.
.. image:: https://governance.openstack.org/tc/badges/sahara-dashboard.svg
:target: https://governance.openstack.org/tc/reference/tags/index.html
The contents of this repository are still available in the Git
source code management system. To see the contents of this
repository before it reached its end of life, please check out the
previous commit with "git checkout HEAD^1".
.. Change things from this point on
OpenStack Dashboard plugin for Sahara project
=============================================
How to use with Horizon on server:
----------------------------------
Use pip to install the package on the server running Horizon. Then either copy
or link the files in sahara_dashboard/enabled to
openstack_dashboard/local/enabled. This step will cause the Horizon service to
pick up the Sahara plugin when it starts.
How to use with devstack:
-------------------------
Add the following to your devstack ``local.conf`` file::
enable_plugin sahara-dashboard https://opendev.org/openstack/sahara-dashboard
To run unit tests:
------------------
./run_tests.sh
NOTE:
=====
As of the Mitaka release, the dashboard for sahara is now maintained
outside of the horizon codebase, in the repository.
Links:
------
Sahara project: https://opendev.org/openstack/sahara
Storyboard project: https://storyboard.openstack.org/#!/project/936
Sahara docs site: https://docs.openstack.org/sahara/latest/
Quickstart guide: https://docs.openstack.org/sahara/latest/user/quickstart.html
How to participate: https://docs.openstack.org/sahara/latest/contributor/how-to-participate.html
Release notes: https://docs.openstack.org/releasenotes/sahara-dashboard/
License
-------
Apache License Version 2.0 http://www.apache.org/licenses/LICENSE-2.0
For any further questions, please email
openstack-discuss@lists.openstack.org or join #openstack-dev on
OFTC.

View File

@ -1,2 +0,0 @@
[python: **.py]
[django: **/templates/**.html]

View File

@ -1,2 +0,0 @@
[javascript: **.js]
[angular: **/static/**.html]

View File

@ -1,41 +0,0 @@
# This is a cross-platform list tracking distribution packages needed by tests;
# see https://docs.openstack.org/infra/bindep/ for additional information.
build-essential [platform:dpkg]
gawk
# gettext and graphviz are needed by doc builds only. For transition,
# have them in both doc and test.
gettext [doc test]
graphviz [doc test]
language-pack-en [platform:ubuntu]
libevent-dev [platform:dpkg]
libevent-devel [platform:rpm]
libffi-dev [platform:dpkg]
libffi-devel [platform:rpm]
libjpeg-dev [platform:dpkg]
libjpeg-turbo-devel [platform:rpm]
libsasl2-dev [platform:dpkg]
libselinux-python [platform:rpm]
libsqlite3-dev [platform:dpkg]
libuuid-devel [platform:rpm]
locales [platform:debian]
pkg-config [platform:dpkg]
pkgconfig [platform:rpm]
python3-all-dev [platform:ubuntu !platform:ubuntu-precise]
python3-dev [platform:dpkg]
python3-devel [platform:fedora]
python3.4 [platform:ubuntu-trusty]
python34-devel [platform:centos]
python3.5 [platform:ubuntu-xenial]
uuid-dev [platform:dpkg]
zlib-devel [platform:rpm]
zlib1g-dev [platform:dpkg]
# integration tests
firefox [integrationtests]
dbus [integrationtests platform:redhat]
dbus-1 [integrationtests platform:suse]
xvfb [integrationtests platform:dpkg]
# already part of xorg-x11-server on openSUSE
xorg-x11-server-Xvfb [integrationtests platform:redhat]
libav-tools [integrationtests platform:dpkg]

View File

@ -1,55 +0,0 @@
# plugin.sh - DevStack plugin.sh dispatch script sahara-dashboard
SAHARA_DASH_DIR=$(cd $(dirname $BASH_SOURCE)/.. && pwd)
function install_sahara_dashboard {
setup_develop ${SAHARA_DASH_DIR}
}
function configure_sahara_dashboard {
cp -a ${SAHARA_DASH_DIR}/sahara_dashboard/enabled/* ${DEST}/horizon/openstack_dashboard/local/enabled/
cp -a ${SAHARA_DASH_DIR}/sahara_dashboard/local_settings.d/* ${DEST}/horizon/openstack_dashboard/local/local_settings.d/
# NOTE: If locale directory does not exist, compilemessages will fail,
# so check for an existence of locale directory is required.
if [ -d ${SAHARA_DASH_DIR}/sahara_dashboard/locale ]; then
(cd ${SAHARA_DASH_DIR}/sahara_dashboard; DJANGO_SETTINGS_MODULE=openstack_dashboard.settings $PYTHON ../manage.py compilemessages)
fi
}
# check for service enabled
if is_service_enabled sahara-dashboard; then
if [[ "$1" == "stack" && "$2" == "pre-install" ]]; then
# Set up system services
# no-op
:
elif [[ "$1" == "stack" && "$2" == "install" ]]; then
# Perform installation of service source
echo_summary "Installing Sahara Dashboard"
install_sahara_dashboard
elif [[ "$1" == "stack" && "$2" == "post-config" ]]; then
# Configure after the other layer 1 and 2 services have been configured
echo_summary "Configuring Sahara Dashboard"
configure_sahara_dashboard
elif [[ "$1" == "stack" && "$2" == "extra" ]]; then
# Initialize and start the app-catalog-ui service
# no-op
:
fi
if [[ "$1" == "unstack" ]]; then
# Shut down app-catalog-ui services
# no-op
:
fi
if [[ "$1" == "clean" ]]; then
# Remove state and transient data
# Remember clean.sh first calls unstack.sh
# no-op
:
fi
fi

View File

@ -1,2 +0,0 @@
# settings file for sahara-dashboard plugin
enable_service sahara-dashboard

View File

@ -1,7 +0,0 @@
# The order of packages is significant, because pip processes them in the order
# of appearance. Changing the order has an impact on the overall integration
# process, which may cause wedges in the gate later.
sphinx>=2.0.0,!=2.1.0 # BSD
openstackdocstheme>=2.2.1 # Apache-2.0
reno>=3.1.0 # Apache-2.0

View File

@ -1,23 +0,0 @@
#!/usr/bin/env python
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import os
import sys
from django.core.management import execute_from_command_line
if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE",
"sahara_dashboard.test.settings")
execute_from_command_line(sys.argv)

View File

@ -1,8 +0,0 @@
---
- hosts: controller
roles:
- role: bindep
bindep_profile: integrationtests
become: true
- setup-selenium-tests
- setup-sahara-ui-integration

View File

@ -1,13 +0,0 @@
---
- hosts: all
strategy: linear
roles:
- orchestrate-devstack
- hosts: controller
roles:
- post-devstack-sahara-ui-integration
- role: tox
tox_environment:
AVCONV_INSTALLED: 1
tox_envlist: py27integration

View File

@ -1,6 +0,0 @@
---
features:
- There is now support for APIv2 in sahara-dashboard. Enable it by setting
the "data-processing" API version to "2" in the `OPENSTACK_API_VERSIONS`
dictionary found in Horizon's local_settings.py. Some Sahara features are
only exposed through APIv2.

View File

@ -1,3 +0,0 @@
---
fixes:
- Fixed issue with retrieving details for public clusters.

View File

@ -1,3 +0,0 @@
---
features:
- Added integration with Designate for hostname resolution.

View File

@ -1,7 +0,0 @@
---
security:
- |
Django 1.10 introduced a new var : DATA_UPLOAD_MAX_NUMBER_FIELDS which
prevent DOS on data received via GET and POST methods. Default values
is set to 2000 instead of upstream default of 1000 to keep the security
feature and to not cause issue with the Sahara Dashboard forms.

View File

@ -1,6 +0,0 @@
---
upgrade:
- |
Python 2.7 support has been dropped. Last release of sahara-dashboard
to support python 2.7 is OpenStack Train. The minimum version of Python now
supported by sahara-dashboard is Python 3.6.

View File

@ -1,5 +0,0 @@
---
upgrade:
- |
Python 3.6 & 3.7 support has been dropped. The minimum version of Python now
supported is Python 3.8.

View File

@ -1,5 +0,0 @@
---
fixes:
- |
Increasing DATA_UPLOAD_MAX_NUMBER_FIELDS django configuration to allow creation
of CDH node group templates.

View File

@ -1,12 +0,0 @@
---
features:
- |
Support for the new boot from volume options introduced in the
Stein release of Sahara is added.
other:
- |
Coinciding with the official stability of Sahara APIv2, operators
should feel especially encouraged to set "data-processing" API
version to "2" in the `OPENSTACK_API_VERSIONS` dictionary found in
Horizon's local_settings.py. As previously stated, some Sahara
features are only exposed through APIv2.

View File

@ -1,9 +0,0 @@
---
prelude: >
The config parameter 'SAHARA_FLOATING_IP_DISABLED' replaces
'SAHARA_AUTO_IP_ALLOCATION_ENABLED'.
deprecations:
- |
The 'SAHARA_AUTO_IP_ALLOCATION_ENABLED' config parameter in Sahara
was deprecated and replaced by 'SAHARA_FLOATING_IP_DISABLED'
which has a less confusing name.

View File

@ -1,3 +0,0 @@
---
other:
- Start using reno for release notes management.

View File

@ -1,6 +0,0 @@
===========================
2023.1 Series Release Notes
===========================
.. release-notes::
:branch: stable/2023.1

View File

@ -1,6 +0,0 @@
===========================
2023.2 Series Release Notes
===========================
.. release-notes::
:branch: stable/2023.2

View File

@ -1,239 +0,0 @@
# -*- coding: utf-8 -*-
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# -- 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 = [
'openstackdocstheme',
'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.
copyright = '2013, Sahara Developers'
# openstackdocstheme options
openstackdocs_repo_name = 'openstack/sahara-dashboard'
openstackdocs_use_storyboard = True
# 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 = 'native'
# 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 = 'openstackdocs'
# 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
# "<project> v<release> 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 true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
# html_use_smartypants = True
# Custom sidebar templates, maps document names to template names.
# html_sidebars = {}
# Additional templates that should be rendered to pages, maps page names to
# template names.
# html_additional_pages = {}
# If false, no module index is generated.
# html_domain_indices = True
# If false, no index is generated.
# html_use_index = True
# If true, the index is split into individual pages for each letter.
# html_split_index = False
# If true, links to the reST sources are added to the pages.
# html_show_sourcelink = True
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
# html_show_sphinx = True
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
# html_show_copyright = True
# If true, an OpenSearch description file will be output, and all pages will
# contain a <link> tag referring to it. The value of this option must be the
# base URL from which the finished HTML is served.
# html_use_opensearch = ''
# This is the file name suffix for HTML files (e.g. ".xhtml").
# html_file_suffix = None
# Output file base name for HTML help builder.
htmlhelp_basename = 'SaharaDashboardReleaseNotesdoc'
# -- Options for LaTeX output ---------------------------------------------
# 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', 'SaharaDashboardReleaseNotes.tex',
'Sahara Dashboard Release Notes Documentation',
'Sahara 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', 'saharadashboardreleasenotes',
'Sahara Dashboard Release Notes Documentation',
['Sahara 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', 'SaharaDashboardReleaseNotes',
'Sahara Dashboard Release Notes Documentation',
'Sahara Developers', 'SaharaDashboardReleaseNotes',
'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/']

View File

@ -1,24 +0,0 @@
===============================
Sahara Dashboard Release Notes
===============================
.. toctree::
:maxdepth: 1
unreleased
2023.2
2023.1
zed
yoga
xena
wallaby
victoria
ussuri
train
stein
rocky
queens
pike
ocata
newton
mitaka

View File

@ -1,138 +0,0 @@
# Robert Simai <robert.simai@suse.com>, 2016. #zanata
# Robert Simai <robert.simai@suse.com>, 2017. #zanata
# Robert Simai <robert.simai@suse.com>, 2018. #zanata
# Andreas Jaeger <jaegerandi@gmail.com>, 2019. #zanata
msgid ""
msgstr ""
"Project-Id-Version: sahara-dashboard\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-10-26 16:02+0000\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"PO-Revision-Date: 2019-09-28 07:55+0000\n"
"Last-Translator: Andreas Jaeger <jaegerandi@gmail.com>\n"
"Language-Team: German\n"
"Language: de\n"
"X-Generator: Zanata 4.3.3\n"
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
msgid "10.0.0"
msgstr "10.0.0"
msgid "4.1.2"
msgstr "4.1.2"
msgid "5.0.0"
msgstr "5.0.0"
msgid "5.0.0.0b2"
msgstr "5.0.0.0b2"
msgid "5.0.0.0b3"
msgstr "5.0.0.0b3"
msgid "7.0.2"
msgstr "7.0.2"
msgid "8.0.0"
msgstr "8.0.0"
msgid "9.0.0"
msgstr "9.0.0"
msgid "Added integration with Designate for hostname resolution."
msgstr "Integration mit Designate zur Auflösung von Hostnamen hinzugefügt."
msgid "Bug Fixes"
msgstr "Fehlerkorrekturen"
msgid "Current Series Release Notes"
msgstr "Aktuelle Serie Releasenotes"
msgid "Deprecation Notes"
msgstr "Ablaufwarnungen"
msgid ""
"Django 1.10 introduced a new var : DATA_UPLOAD_MAX_NUMBER_FIELDS which "
"prevent DOS on data received via GET and POST methods. Default values is set "
"to 2000 instead of upstream default of 1000 to keep the security feature and "
"to not cause issue with the Sahara Dashboard forms."
msgstr ""
"Django 1.10 führt eine neue Variable ein: DATA_UPLOAD_MAX_NUMBER_FIELDS, "
"welche DOS bei durch GET und POST Methoden empfangene Daten verhindert. Der "
"Standardwert ist 2000, anstelle des Upstream-Standardwertes 1000, um das "
"Sicherheitsmerkmal zu behalten ohne Schwierigkeiten mit den Sahara Dashboard "
"Formularen hervorzurufen."
msgid "Fixed issue with retrieving details for public clusters."
msgstr "Problem beim abrufen von Details von öffentlichen Clustern beseitigt."
msgid "Mitaka Series Release Notes"
msgstr "Mitaka Series Releasenotes"
msgid "New Features"
msgstr "Neue Funktionen"
msgid "Newton Series Release Notes"
msgstr "Newton Series Releasenotes"
msgid "Ocata Series Release Notes"
msgstr "Ocata Serie Releasenotes"
msgid "Other Notes"
msgstr "Andere Notizen"
msgid "Pike Series Release Notes"
msgstr "Pike Serie Release Notes"
msgid "Prelude"
msgstr "Einleitung"
msgid "Queens Series Release Notes"
msgstr "Queens Serie Release Notes"
msgid "Rocky Series Release Notes"
msgstr "Rocky Serie Releasenotes"
msgid "Sahara Dashboard Release Notes"
msgstr "Sahara Dashboard Releasenotes"
msgid "Security Issues"
msgstr "Sicherheitsrelevante Probleme"
msgid "Start using reno for release notes management."
msgstr "Reno wird für die Verwaltung der Releasenotes verwendet."
msgid "Stein Series Release Notes"
msgstr "Stein Serie Releasenotes"
msgid ""
"The 'SAHARA_AUTO_IP_ALLOCATION_ENABLED' config parameter in Sahara was "
"deprecated and replaced by 'SAHARA_FLOATING_IP_DISABLED' which has a less "
"confusing name."
msgstr ""
"Der 'SAHARA_AUTO_IP_ALLOCATION_ENABLED' Konfigurationsparameter in Sahara "
"wurde abgelöst und durch den weniger verwirrenden Namen "
"'SAHARA_FLOATING_IP_DISABLED' ersetzt."
msgid ""
"The config parameter 'SAHARA_FLOATING_IP_DISABLED' replaces "
"'SAHARA_AUTO_IP_ALLOCATION_ENABLED'."
msgstr ""
"Der Konfigurationsparameter 'SAHARA_FLOATING_IP_DISABLED' ersetzt "
"'SAHARA_AUTO_IP_ALLOCATION_ENABLED'."
msgid ""
"There is now support for APIv2 in sahara-dashboard. Enable it by setting the "
"\"data-processing\" API version to \"2\" in the `OPENSTACK_API_VERSIONS` "
"dictionary found in Horizon's local_settings.py. Some Sahara features are "
"only exposed through APIv2."
msgstr ""
"Es gibt jetzt Unterstützung für APIv2 in sahara-dashboard. Aktivieren Sie "
"dies durch setzen von \"data-processing\" API Version auf \"2\" im "
"`OPENSTACK_API_VERSIONS` Wörterbuch in der Horizon-Datei local_settings.py. "
"Einige Sahara-Features können nur mittels der APIv2 verwendet werden."
msgid "Train Series Release Notes"
msgstr "Train Serie Releasenotes"

View File

@ -1,222 +0,0 @@
# Andi Chandler <andi@gowling.com>, 2016. #zanata
# Andi Chandler <andi@gowling.com>, 2017. #zanata
# Andi Chandler <andi@gowling.com>, 2018. #zanata
# Andi Chandler <andi@gowling.com>, 2019. #zanata
# Andi Chandler <andi@gowling.com>, 2020. #zanata
# Andi Chandler <andi@gowling.com>, 2022. #zanata
# Andi Chandler <andi@gowling.com>, 2023. #zanata
msgid ""
msgstr ""
"Project-Id-Version: sahara-dashboard\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-01-30 23:36+0000\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"PO-Revision-Date: 2023-01-26 10:29+0000\n"
"Last-Translator: Andi Chandler <andi@gowling.com>\n"
"Language-Team: English (United Kingdom)\n"
"Language: en_GB\n"
"X-Generator: Zanata 4.3.3\n"
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
msgid "10.0.0"
msgstr "10.0.0"
msgid "12.0.0"
msgstr "12.0.0"
msgid "12.0.0.0rc1"
msgstr "12.0.0.0rc1"
msgid "17.0.0"
msgstr "17.0.0"
msgid "4.1.2"
msgstr "4.1.2"
msgid "5.0.0"
msgstr "5.0.0"
msgid "5.0.0.0b2"
msgstr "5.0.0.0b2"
msgid "5.0.0.0b3"
msgstr "5.0.0.0b3"
msgid "7.0.2"
msgstr "7.0.2"
msgid "8.0.0"
msgstr "8.0.0"
msgid "8.0.2"
msgstr "8.0.2"
msgid "9.0.0"
msgstr "9.0.0"
msgid "9.0.0.0b3"
msgstr "9.0.0.0b3"
msgid "9.0.2"
msgstr "9.0.2"
msgid "Added integration with Designate for hostname resolution."
msgstr "Added integration with Designate for hostname resolution."
msgid "Bug Fixes"
msgstr "Bug Fixes"
msgid ""
"Coinciding with the official stability of Sahara APIv2, operators should "
"feel especially encouraged to set \"data-processing\" API version to \"2\" "
"in the `OPENSTACK_API_VERSIONS` dictionary found in Horizon's local_settings."
"py. As previously stated, some Sahara features are only exposed through "
"APIv2."
msgstr ""
"Coinciding with the official stability of Sahara APIv2, operators should "
"feel especially encouraged to set \"data-processing\" API version to \"2\" "
"in the `OPENSTACK_API_VERSIONS` dictionary found in Horizon's local_settings."
"py. As previously stated, some Sahara features are only exposed through "
"APIv2."
msgid "Current Series Release Notes"
msgstr "Current Series Release Notes"
msgid "Deprecation Notes"
msgstr "Deprecation Notes"
msgid ""
"Django 1.10 introduced a new var : DATA_UPLOAD_MAX_NUMBER_FIELDS which "
"prevent DOS on data received via GET and POST methods. Default values is set "
"to 2000 instead of upstream default of 1000 to keep the security feature and "
"to not cause issue with the Sahara Dashboard forms."
msgstr ""
"Django 1.10 introduced a new var : DATA_UPLOAD_MAX_NUMBER_FIELDS which "
"prevent DOS on data received via GET and POST methods. Default values is set "
"to 2000 instead of upstream default of 1000 to keep the security feature and "
"to not cause issue with the Sahara Dashboard forms."
msgid "Fixed issue with retrieving details for public clusters."
msgstr "Fixed issue with retrieving details for public clusters."
msgid ""
"Increasing DATA_UPLOAD_MAX_NUMBER_FIELDS django configuration to allow "
"creation of CDH node group templates."
msgstr ""
"Increasing DATA_UPLOAD_MAX_NUMBER_FIELDS django configuration to allow "
"creation of CDH node group templates."
msgid "Mitaka Series Release Notes"
msgstr "Mitaka Series Release Notes"
msgid "New Features"
msgstr "New Features"
msgid "Newton Series Release Notes"
msgstr "Newton Series Release Notes"
msgid "Ocata Series Release Notes"
msgstr "Ocata Series Release Notes"
msgid "Other Notes"
msgstr "Other Notes"
msgid "Pike Series Release Notes"
msgstr "Pike Series Release Notes"
msgid "Prelude"
msgstr "Prelude"
msgid ""
"Python 2.7 support has been dropped. Last release of sahara-dashboard to "
"support python 2.7 is OpenStack Train. The minimum version of Python now "
"supported by sahara-dashboard is Python 3.6."
msgstr ""
"Python 2.7 support has been dropped. Last release of Sahara-dashboard to "
"support Python 2.7 is OpenStack Train. The minimum version of Python now "
"supported by Sahara-dashboard is Python 3.6."
msgid ""
"Python 3.6 & 3.7 support has been dropped. The minimum version of Python now "
"supported is Python 3.8."
msgstr ""
"Python 3.6 & 3.7 support has been dropped. The minimum version of Python now "
"supported is Python 3.8."
msgid "Queens Series Release Notes"
msgstr "Queens Series Release Notes"
msgid "Rocky Series Release Notes"
msgstr "Rocky Series Release Notes"
msgid "Sahara Dashboard Release Notes"
msgstr "Sahara Dashboard Release Notes"
msgid "Security Issues"
msgstr "Security Issues"
msgid "Start using reno for release notes management."
msgstr "Start using reno for release notes management."
msgid "Stein Series Release Notes"
msgstr "Stein Series Release Notes"
msgid ""
"Support for the new boot from volume options introduced in the Stein release "
"of Sahara is added."
msgstr ""
"Support for the new boot from volume options introduced in the Stein release "
"of Sahara is added."
msgid ""
"The 'SAHARA_AUTO_IP_ALLOCATION_ENABLED' config parameter in Sahara was "
"deprecated and replaced by 'SAHARA_FLOATING_IP_DISABLED' which has a less "
"confusing name."
msgstr ""
"The 'SAHARA_AUTO_IP_ALLOCATION_ENABLED' config parameter in Sahara was "
"deprecated and replaced by 'SAHARA_FLOATING_IP_DISABLED' which has a less "
"confusing name."
msgid ""
"The config parameter 'SAHARA_FLOATING_IP_DISABLED' replaces "
"'SAHARA_AUTO_IP_ALLOCATION_ENABLED'."
msgstr ""
"The config parameter 'SAHARA_FLOATING_IP_DISABLED' replaces "
"'SAHARA_AUTO_IP_ALLOCATION_ENABLED'."
msgid ""
"There is now support for APIv2 in sahara-dashboard. Enable it by setting the "
"\"data-processing\" API version to \"2\" in the `OPENSTACK_API_VERSIONS` "
"dictionary found in Horizon's local_settings.py. Some Sahara features are "
"only exposed through APIv2."
msgstr ""
"There is now support for APIv2 in Sahara-Dashboard. Enable it by setting the "
"\"data-processing\" API version to \"2\" in the `OPENSTACK_API_VERSIONS` "
"dictionary found in Horizon's local_settings.py. Some Sahara features are "
"only exposed through APIv2."
msgid "Train Series Release Notes"
msgstr "Train Series Release Notes"
msgid "Upgrade Notes"
msgstr "Upgrade Notes"
msgid "Ussuri Series Release Notes"
msgstr "Ussuri Series Release Notes"
msgid "Victoria Series Release Notes"
msgstr "Victoria Series Release Notes"
msgid "Wallaby Series Release Notes"
msgstr "Wallaby Series Release Notes"
msgid "Xena Series Release Notes"
msgstr "Xena Series Release Notes"
msgid "Yoga Series Release Notes"
msgstr "Yoga Series Release Notes"
msgid "Zed Series Release Notes"
msgstr "Zed Series Release Notes"

View File

@ -1,55 +0,0 @@
# Gérald LONLAS <g.lonlas@gmail.com>, 2016. #zanata
msgid ""
msgstr ""
"Project-Id-Version: sahara-dashboard\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-10-26 16:02+0000\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"PO-Revision-Date: 2016-10-22 06:21+0000\n"
"Last-Translator: Gérald LONLAS <g.lonlas@gmail.com>\n"
"Language-Team: French\n"
"Language: fr\n"
"X-Generator: Zanata 4.3.3\n"
"Plural-Forms: nplurals=2; plural=(n > 1)\n"
msgid "5.0.0"
msgstr "5.0.0"
msgid "5.0.0.0b2"
msgstr "5.0.0.0b2"
msgid "5.0.0.0b3"
msgstr "5.0.0.0b3"
msgid "Added integration with Designate for hostname resolution."
msgstr "Ajout l'intégration de la résolution du hostname avec Designate."
msgid "Bug Fixes"
msgstr "Corrections de bugs"
msgid "Current Series Release Notes"
msgstr "Note de la release actuelle"
msgid "Fixed issue with retrieving details for public clusters."
msgstr ""
"Corrige un bug durant la récupération des détails des Clusters publics."
msgid "Mitaka Series Release Notes"
msgstr "Note de release pour Mitaka"
msgid "New Features"
msgstr "Nouvelles fonctionnalités"
msgid "Newton Series Release Notes"
msgstr "Note de release pour Newton"
msgid "Other Notes"
msgstr "Autres notes"
msgid "Sahara Dashboard Release Notes"
msgstr "Note de release pour le Tableau de bord Sahara"
msgid "Start using reno for release notes management."
msgstr "Commence à utiliser reno pour la gestion des notes de release"

View File

@ -1,161 +0,0 @@
# suhartono <cloudsuhartono@gmail.com>, 2016. #zanata
# suhartono <cloudsuhartono@gmail.com>, 2017. #zanata
# suhartono <cloudsuhartono@gmail.com>, 2018. #zanata
# suhartono <cloudsuhartono@gmail.com>, 2019. #zanata
msgid ""
msgstr ""
"Project-Id-Version: sahara-dashboard\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-10-26 16:02+0000\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"PO-Revision-Date: 2019-07-17 04:38+0000\n"
"Last-Translator: suhartono <cloudsuhartono@gmail.com>\n"
"Language-Team: Indonesian\n"
"Language: id\n"
"X-Generator: Zanata 4.3.3\n"
"Plural-Forms: nplurals=1; plural=0\n"
msgid "10.0.0"
msgstr "10.0.0"
msgid "4.1.2"
msgstr "4.1.2"
msgid "5.0.0"
msgstr "5.0.0"
msgid "5.0.0.0b2"
msgstr "5.0.0.0b2"
msgid "5.0.0.0b3"
msgstr "5.0.0.0b3"
msgid "7.0.2"
msgstr "7.0.2"
msgid "8.0.0"
msgstr "8.0.0"
msgid "9.0.0"
msgstr "9.0.0"
msgid "Added integration with Designate for hostname resolution."
msgstr "Ditambahkan integrasi dengan Designate untuk resolusi hostname"
msgid "Bug Fixes"
msgstr "Bug Fixes (perbaikan kerusakan)"
msgid ""
"Coinciding with the official stability of Sahara APIv2, operators should "
"feel especially encouraged to set \"data-processing\" API version to \"2\" "
"in the `OPENSTACK_API_VERSIONS` dictionary found in Horizon's local_settings."
"py. As previously stated, some Sahara features are only exposed through "
"APIv2."
msgstr ""
"Bertepatan dengan stabilitas resmi Sahara APIv2, operator harus merasa "
"sangat dianjurkan untuk mengatur versi API \"data-processing\" menjadi "
"\"2\" dalam kamus `OPENSTACK_API_VERSIONS` yang ditemukan di kamus Horizon "
"local_settings.py. Seperti yang dinyatakan sebelumnya, beberapa fitur Sahara "
"hanya diekspos melalui APIv2."
msgid "Current Series Release Notes"
msgstr "Current Series Release Notes (catatan rilis Curent Series)"
msgid "Deprecation Notes"
msgstr "Catatan Deprecation"
msgid ""
"Django 1.10 introduced a new var : DATA_UPLOAD_MAX_NUMBER_FIELDS which "
"prevent DOS on data received via GET and POST methods. Default values is set "
"to 2000 instead of upstream default of 1000 to keep the security feature and "
"to not cause issue with the Sahara Dashboard forms."
msgstr ""
"Django 1.10 memperkenalkan sebuah var baru: DATA_UPLOAD_MAX_NUMBER_FIELDS "
"yang mencegah DOS pada data yang diterima melalui metode GET dan POST. Nilai "
"default diatur ke 2000, bukan default hulu 1000 untuk menjaga fitur keamanan "
"dan tidak menimbulkan masalah dengan Dashboard Sahara form."
msgid "Fixed issue with retrieving details for public clusters."
msgstr "Isu tetap mengenai pengambilan rincian untuk kelompok masyarakat."
msgid ""
"Increasing DATA_UPLOAD_MAX_NUMBER_FIELDS django configuration to allow "
"creation of CDH node group templates."
msgstr ""
"Meningkatkan konfigurasi django DATA_UPLOAD_MAX_NUMBER_FIELDS untuk "
"memungkinkan pembuatan templat grup node CDH."
msgid "Mitaka Series Release Notes"
msgstr "Mitaka Series Release Notes (Catatan rilis Mitaka Series)"
msgid "New Features"
msgstr "New Features (fitur baru)"
msgid "Newton Series Release Notes"
msgstr "Newton Series Release Notes (catatan rilis Newton Series"
msgid "Ocata Series Release Notes"
msgstr "Catatan rilis seri Ocata"
msgid "Other Notes"
msgstr "Other Notes (catatan lain)"
msgid "Pike Series Release Notes"
msgstr "Catatan Rilis Seri Pike"
msgid "Prelude"
msgstr "Prelude"
msgid "Queens Series Release Notes"
msgstr "Catatan Rilis Seri Queens"
msgid "Rocky Series Release Notes"
msgstr "Rocky Series Release Notes"
msgid "Sahara Dashboard Release Notes"
msgstr "Sahara Dashboard Release Notes (catatan rilis Sahara Dashboard)"
msgid "Security Issues"
msgstr "Security Issues"
msgid "Start using reno for release notes management."
msgstr "Mulai menggunakan reno untuk manajemen catatan rilis."
msgid "Stein Series Release Notes"
msgstr "Catatan Rilis Seri Stein"
msgid ""
"Support for the new boot from volume options introduced in the Stein release "
"of Sahara is added."
msgstr ""
"Dukungan untuk boot baru dari opsi volume yang diperkenalkan dalam rilis "
"Sahara dari Stein ditambahkan."
msgid ""
"The 'SAHARA_AUTO_IP_ALLOCATION_ENABLED' config parameter in Sahara was "
"deprecated and replaced by 'SAHARA_FLOATING_IP_DISABLED' which has a less "
"confusing name."
msgstr ""
"Parameter konfigurasi 'SAHARA_AUTO_IP_ALLOCATION_ENABLED' di Sahara sudah "
"tidak berlaku lagi dan diganti dengan 'SAHARA_FLOATING_IP_DISABLED' yang "
"memiliki nama yang tidak membingungkan."
msgid ""
"The config parameter 'SAHARA_FLOATING_IP_DISABLED' replaces "
"'SAHARA_AUTO_IP_ALLOCATION_ENABLED'."
msgstr ""
"Parameter konfigurasi 'SAHARA_FLOATING_IP_DISABLED' menggantikan "
"'SAHARA_AUTO_IP_ALLOCATION_ENABLED'."
msgid ""
"There is now support for APIv2 in sahara-dashboard. Enable it by setting the "
"\"data-processing\" API version to \"2\" in the `OPENSTACK_API_VERSIONS` "
"dictionary found in Horizon's local_settings.py. Some Sahara features are "
"only exposed through APIv2."
msgstr ""
"Sekarang ada dukungan untuk APIv2 di sahara-dashboard. Aktifkan dengan "
"menyetel versi API \"data-processing\" ke \"2\" dalam kamus "
"`OPENSTACK_API_VERSIONS` yang ditemukan di local_settings.py Horizon. "
"Beberapa fitur Sahara hanya diekspos melalui APIv2."

View File

@ -1,27 +0,0 @@
# Shu Muto <shu-mutou@rf.jp.nec.com>, 2016. #zanata
msgid ""
msgstr ""
"Project-Id-Version: Sahara Dashboard Release Notes\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-02-09 18:15+0000\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"PO-Revision-Date: 2016-06-16 05:52+0000\n"
"Last-Translator: Shu Muto <shu-mutou@rf.jp.nec.com>\n"
"Language-Team: Japanese\n"
"Language: ja\n"
"X-Generator: Zanata 3.9.6\n"
"Plural-Forms: nplurals=1; plural=0\n"
msgid "Current Series Release Notes"
msgstr "開発中バージョンのリリースノート"
msgid "Other Notes"
msgstr "その他の注意点"
msgid "Sahara Dashboard Release Notes"
msgstr "Sahara Dashboard リリースノート"
msgid "Start using reno for release notes management."
msgstr "リリースノートの管理に reno を使い始めました。"

View File

@ -1,105 +0,0 @@
# Ian Y. Choi <ianyrchoi@gmail.com>, 2016. #zanata
# Ian Y. Choi <ianyrchoi@gmail.com>, 2017. #zanata
# Sungjin Kang <gang.sungjin@gmail.com>, 2017. #zanata
# minwook-shin <minwook0106@gmail.com>, 2017. #zanata
# Hongjae Kim <neo415ha@gmail.com>, 2019. #zanata
msgid ""
msgstr ""
"Project-Id-Version: sahara-dashboard\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-10-26 16:02+0000\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"PO-Revision-Date: 2019-11-02 09:05+0000\n"
"Last-Translator: Hongjae Kim <neo415ha@gmail.com>\n"
"Language-Team: Korean (South Korea)\n"
"Language: ko_KR\n"
"X-Generator: Zanata 4.3.3\n"
"Plural-Forms: nplurals=1; plural=0\n"
msgid "5.0.0"
msgstr "5.0.0"
msgid "5.0.0.0b2"
msgstr "5.0.0.0b2"
msgid "5.0.0.0b3"
msgstr "5.0.0.0b3"
msgid "Added integration with Designate for hostname resolution."
msgstr "호스트 이름 변환을 위해 Designate와의 통합을 추가하였습니다."
msgid "Bug Fixes"
msgstr "버그 수정"
msgid ""
"Coinciding with the official stability of Sahara APIv2, operators should "
"feel especially encouraged to set \"data-processing\" API version to \"2\" "
"in the `OPENSTACK_API_VERSIONS` dictionary found in Horizon's local_settings."
"py. As previously stated, some Sahara features are only exposed through "
"APIv2."
msgstr ""
"사하라 APIv2의 공식 안정성과 일치하여 운영자들은 특히 Horizon의 "
"local_settings.py.에 수록된 \"OPENSTACK_API_VERSIONS\" 사전에서 \"데이터 처리"
"\" API 버전을 \"2\"로 설정하도록 권장해야 한다. 앞서 언급한 바와 같이 일부 사"
"하라 기능은 APIv2를 통해서만 노출된다."
msgid "Current Series Release Notes"
msgstr "현재 시리즈에 대한 릴리즈 노트"
msgid "Deprecation Notes"
msgstr "감가 상각"
msgid ""
"Django 1.10 introduced a new var : DATA_UPLOAD_MAX_NUMBER_FIELDS which "
"prevent DOS on data received via GET and POST methods. Default values is set "
"to 2000 instead of upstream default of 1000 to keep the security feature and "
"to not cause issue with the Sahara Dashboard forms."
msgstr ""
"Django 1.10에서 새로운 var: DATA_UPLOAD_를 도입함GET 및 POST 방법을 통해 수신"
"된 데이터에 대한 DOS를 방지하는 MAX_NUMBER_FIELDS. 보안 기능을 유지하고 사하"
"라 대시보드 양식에 문제를 일으키지 않으려면 기본값을 업스트림 기본값 1000이 "
"아닌 2000으로 설정하십시오."
msgid "Fixed issue with retrieving details for public clusters."
msgstr ""
"공용 클러스터에 대한 세부 사항을 가져오는 데 존재하던 이슈를 수정하였습니다."
msgid ""
"Increasing DATA_UPLOAD_MAX_NUMBER_FIELDS django configuration to allow "
"creation of CDH node group templates."
msgstr ""
"DATA_UPLOAD_ 증가CDH 노드 그룹 템플릿을 만들 수 있도록 MAX_NUMBER_FIELDS "
"django 구성."
msgid "Mitaka Series Release Notes"
msgstr "Mitaka 시리즈에 대한 릴리즈 노트"
msgid "New Features"
msgstr "새로운 기능"
msgid "Newton Series Release Notes"
msgstr "Newton 시리즈에 대한 릴리즈 노트"
msgid "Ocata Series Release Notes"
msgstr "Ocata 시리즈에 대한 릴리즈 노트"
msgid "Other Notes"
msgstr "기타 기능"
msgid "Pike Series Release Notes"
msgstr "Pike 시리즈에 대한 릴리즈 노트"
msgid "Sahara Dashboard Release Notes"
msgstr "Sahara 대시보드 릴리즈 노트"
msgid "Start using reno for release notes management."
msgstr "릴리즈 노트 관리를 위해 reno 사용을 시작합니다."
msgid ""
"Support for the new boot from volume options introduced in the Stein release "
"of Sahara is added."
msgstr ""
"Sahara의 Stein 릴리스에 도입된 볼륨 옵션에서 새로운 부팅에 대한 지원이 추가되"
"었다."

View File

@ -1,58 +0,0 @@
# sunanchen <KF.sunanchen@h3c.com>, 2016. #zanata
# Bin <liubin@glab.cn>, 2017. #zanata
msgid ""
msgstr ""
"Project-Id-Version: sahara-dashboard\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-10-26 16:02+0000\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"PO-Revision-Date: 2017-07-24 03:49+0000\n"
"Last-Translator: Bin <liubin@glab.cn>\n"
"Language-Team: Chinese (China)\n"
"Language: zh_CN\n"
"X-Generator: Zanata 4.3.3\n"
"Plural-Forms: nplurals=1; plural=0\n"
msgid "5.0.0"
msgstr "5.0.0版本"
msgid "5.0.0.0b2"
msgstr "5.0.0.0b2版本"
msgid "5.0.0.0b3"
msgstr "5.0.0.0b3版本"
msgid "Added integration with Designate for hostname resolution."
msgstr "给Designate增加域名以便于主机名解析"
msgid "Bug Fixes"
msgstr "修复漏洞"
msgid "Current Series Release Notes"
msgstr "当前版本发布说明"
msgid "Fixed issue with retrieving details for public clusters."
msgstr "修复了公共集群的回收问题"
msgid "Mitaka Series Release Notes"
msgstr "Mitaka版本发布说明"
msgid "New Features"
msgstr "新特征"
msgid "Newton Series Release Notes"
msgstr "Newton版本发布说明"
msgid "Ocata Series Release Notes"
msgstr "Ocata版本发布说明"
msgid "Other Notes"
msgstr "其他注意事项"
msgid "Sahara Dashboard Release Notes"
msgstr "Sahara Dashboard发布说明"
msgid "Start using reno for release notes management."
msgstr "开始使用reno来管理版本发布"

View File

@ -1,6 +0,0 @@
============================
Mitaka Series Release Notes
============================
.. release-notes::
:branch: origin/stable/mitaka

View File

@ -1,6 +0,0 @@
===================================
Newton Series Release Notes
===================================
.. release-notes::
:branch: origin/stable/newton

View File

@ -1,6 +0,0 @@
===================================
Ocata Series Release Notes
===================================
.. release-notes::
:branch: origin/stable/ocata

View File

@ -1,6 +0,0 @@
===================================
Pike Series Release Notes
===================================
.. release-notes::
:branch: stable/pike

View File

@ -1,6 +0,0 @@
===================================
Queens Series Release Notes
===================================
.. release-notes::
:branch: stable/queens

View File

@ -1,6 +0,0 @@
===================================
Rocky Series Release Notes
===================================
.. release-notes::
:branch: stable/rocky

View File

@ -1,6 +0,0 @@
===================================
Stein Series Release Notes
===================================
.. release-notes::
:branch: stable/stein

View File

@ -1,6 +0,0 @@
==========================
Train Series Release Notes
==========================
.. release-notes::
:branch: stable/train

View File

@ -1,5 +0,0 @@
==============================
Current Series Release Notes
==============================
.. release-notes::

View File

@ -1,6 +0,0 @@
===========================
Ussuri Series Release Notes
===========================
.. release-notes::
:branch: stable/ussuri

View File

@ -1,6 +0,0 @@
=============================
Victoria Series Release Notes
=============================
.. release-notes::
:branch: stable/victoria

View File

@ -1,6 +0,0 @@
============================
Wallaby Series Release Notes
============================
.. release-notes::
:branch: stable/wallaby

View File

@ -1,6 +0,0 @@
=========================
Xena Series Release Notes
=========================
.. release-notes::
:branch: stable/xena

View File

@ -1,6 +0,0 @@
=========================
Yoga Series Release Notes
=========================
.. release-notes::
:branch: stable/yoga

View File

@ -1,6 +0,0 @@
========================
Zed Series Release Notes
========================
.. release-notes::
:branch: stable/zed

View File

@ -1,21 +0,0 @@
# Requirements lower bounds listed here are our best effort to keep them up to
# date but we do not test them so no guarantee of having them all correct. If
# you find any incorrect lower bounds, let us know or propose a fix.
# The order of packages is significant, because pip processes them in the order
# of appearance. Changing the order has an impact on the overall integration
# process, which may cause wedges in the gate later.
pbr!=2.1.0,>=2.0.0 # Apache-2.0
keystoneauth1>=3.8.0 # Apache-2.0
oslo.log>=3.36.0 # Apache-2.0
oslo.serialization!=2.19.1,>=2.18.0 # Apache-2.0
python-designateclient>=2.7.0 # Apache-2.0
python-keystoneclient>=3.22.0 # Apache-2.0
python-manilaclient>=1.16.0 # Apache-2.0
python-neutronclient>=6.7.0 # Apache-2.0
python-novaclient>=9.1.0 # Apache-2.0
python-saharaclient>=2.2.0 # Apache-2.0
pytz>=2013.6 # MIT
horizon>=17.1.0 # Apache-2.0

View File

@ -1,4 +0,0 @@
---
devstack_base_dir: "/opt/stack"
sahara_cloud_admin: "devstack-admin"
sahara_cloud_demo: "devstack"

View File

@ -1,7 +0,0 @@
{
"plugin_labels": {
"hidden": {
"status": false
}
}
}

View File

@ -1,10 +0,0 @@
---
- name: copy the updated config snipped which enables the fake plugin
copy:
src: fake_config.json
dest: /tmp/sahara_fake_config.json
- name: change the config of the fake plugin
shell: |
openstack --os-cloud {{ sahara_cloud_admin }} --os-project-name demo \
dataprocessing plugin update fake /tmp/sahara_fake_config.json

View File

@ -1,5 +0,0 @@
---
devstack_base_dir: "/opt/stack"
sahara_cloud_image: "https://cloud-images.ubuntu.com/xenial/current/xenial-server-cloudimg-amd64-disk1.img"
# TODO: if the following option is changed, it should also be specified in tempest.conf
sahara_cloud_image_remote_path: "/tmp/xenial-server-cloudimg-amd64-disk1.img"

View File

@ -1,5 +0,0 @@
[image]
panel_type=legacy
[flavors]
panel_type=legacy

View File

@ -1,21 +0,0 @@
---
- name: download the ubuntu image file
get_url:
url: "{{ sahara_cloud_image }}"
dest: "{{ sahara_cloud_image_remote_path }}"
# enable legacy panels (two files in local/local_settings.d and a new setting file
# for the integration tests)
- name: setup the old behavior of tables for testing purposes
copy:
src: "{{ devstack_base_dir }}/horizon/openstack_dashboard/local/local_settings.d/{{ item }}.example"
dest: "{{ devstack_base_dir }}/horizon/openstack_dashboard/local/local_settings.d/{{ item }}"
remote_src: yes
with_items:
- '_20_integration_tests_scaffolds.py'
- '_2010_integration_tests_deprecated.py'
- name: setup the old behavior of panels for testing purposes
copy:
src: legacy_panels.conf
dest: "{{ devstack_base_dir }}/horizon/openstack_dashboard/test/integration_tests/local-horizon.conf"

View File

@ -1,18 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from sahara_dashboard.api import sahara
__all__ = [
"sahara"
]

View File

@ -1,34 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from designateclient.v2 import client as designate
from keystoneauth1.identity import generic
from keystoneauth1 import session as keystone_session
from openstack_dashboard.api import base
def client(request):
auth_url = base.url_for(request, 'identity')
token_kwargs = dict(
auth_url=auth_url,
token=request.user.token.id,
tenant_id=request.user.project_id,
tenant_name=request.user.project_name,
)
auth = generic.Token(**token_kwargs)
session = keystone_session.Session(auth=auth)
return designate.Client(session=session)
def get_domain_names(request):
return client(request).zones.list()

View File

@ -1,62 +0,0 @@
# Copyright 2012 United States Government as represented by the
# Administrator of the National Aeronautics and Space Administration.
# All Rights Reserved.
#
# Copyright 2012 OpenStack Foundation
# Copyright 2012 Nebula, Inc.
# Copyright (c) 2012 X.commerce, a business unit of eBay Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from django.conf import settings
from manilaclient.v1 import client as manila_client
from oslo_log import log as logging
from horizon import exceptions
from horizon.utils.memoized import memoized # noqa
from openstack_dashboard.api import base
LOG = logging.getLogger(__name__)
# API static values
SHARE_STATE_AVAILABLE = "available"
DEFAULT_QUOTA_NAME = 'default'
def manilaclient(request):
insecure = getattr(settings, 'OPENSTACK_SSL_NO_VERIFY', False)
cacert = getattr(settings, 'OPENSTACK_SSL_CACERT', None)
try:
manila_url = base.url_for(request, 'share')
except exceptions.ServiceCatalogException:
LOG.debug('no share service configured.')
return None
c = manila_client.Client(request.user.username,
input_auth_token=request.user.token.id,
project_id=request.user.tenant_id,
service_catalog_url=manila_url,
insecure=insecure,
cacert=cacert,
http_log_debug=settings.DEBUG)
c.client.auth_token = request.user.token.id
c.client.management_url = manila_url
return c
def share_list(request, search_opts=None):
return manilaclient(request).shares.list(search_opts=search_opts)
def share_get(request, share_id):
return manilaclient(request).shares.get(share_id)

View File

@ -1,778 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from django.conf import settings
from keystoneauth1 import identity
from keystoneauth1 import session
from saharaclient.api.base import APIException
from saharaclient.api.base import Page
from saharaclient import client as api_client
from horizon import exceptions
from horizon.utils import functions
from horizon.utils.memoized import memoized # noqa
from openstack_dashboard.api import base
from sahara_dashboard import utils as u
# "type" of Sahara service registered in keystone
SAHARA_SERVICE = 'data-processing'
try:
SAHARA_FLOATING_IP_DISABLED = getattr(
settings,
'SAHARA_FLOATING_IP_DISABLED')
except AttributeError:
SAHARA_FLOATING_IP_DISABLED = getattr(
settings,
'SAHARA_AUTO_IP_ALLOCATION_ENABLED',
False)
SAHARA_VERIFICATION_DISABLED = getattr(
settings,
'SAHARA_VERIFICATION_DISABLED',
False)
VERSIONS = base.APIVersionManager(
SAHARA_SERVICE,
preferred_version=getattr(settings,
'OPENSTACK_API_VERSIONS',
{}).get(SAHARA_SERVICE, 1.1))
VERSIONS.load_supported_version(1.1, {"client": api_client,
"version": 1.1})
VERSIONS.load_supported_version(2, {"client": api_client,
"version": 2})
SAHARA_PAGE_SIZE = 15
def get_page_size(request=None):
if request:
return functions.get_page_size(request)
else:
return SAHARA_PAGE_SIZE
def _get_marker(request):
return request.GET["marker"] if 'marker' in request.GET else None
def _update_pagination_params(marker, limit, request=None):
marker = _get_marker(request) if marker is None else marker
limit = get_page_size(request) if limit is None else limit
return marker, limit
def safe_call(func, *args, **kwargs):
"""Call a function ignoring Not Found error
This method is supposed to be used only for safe retrieving Sahara
objects. If the object is no longer available, then None should be
returned.
"""
try:
return func(*args, **kwargs)
except APIException as e:
if e.error_code == 404:
return None # Not found. Exiting with None
raise # Other errors are not expected here
@memoized
def client(request):
insecure = getattr(settings, 'OPENSTACK_SSL_NO_VERIFY', False)
cacert = getattr(settings, 'OPENSTACK_SSL_CACERT', None)
endpoint_type = getattr(settings, 'OPENSTACK_ENDPOINT_TYPE', 'publicURL')
auth = identity.Token(auth_url=request.user.endpoint,
token=request.user.token.id,
project_id=request.user.project_id)
verify = False
if cacert:
verify = cacert
elif not insecure:
verify = True
sess = session.Session(auth=auth, verify=verify)
return api_client.Client(VERSIONS.get_active_version()["version"],
service_type=SAHARA_SERVICE,
session=sess,
endpoint_type=endpoint_type)
def prepare_acl_update_dict(is_public=None, is_protected=None):
data = dict(is_public=is_public, is_protected=is_protected)
result = {}
for key in data:
if data[key] is not None:
result[key] = data[key]
return result
def image_list(request, search_opts=None):
return client(request).images.list(search_opts=search_opts)
def image_get(request, image_id):
return client(request).images.get(id=image_id)
def image_unregister(request, image_id):
client(request).images.unregister_image(image_id=image_id)
def image_update(request, image_id, user_name, desc):
client(request).images.update_image(image_id=image_id,
user_name=user_name,
desc=desc)
def image_tags_update(request, image_id, image_tags):
client(request).images.update_tags(image_id=image_id,
new_tags=image_tags)
def plugin_list(request, search_opts=None):
return client(request).plugins.list(search_opts=search_opts)
def plugin_get(request, plugin_name):
return client(request).plugins.get(plugin_name=plugin_name)
def plugin_get_version_details(request, plugin_name, hadoop_version):
return client(request).plugins.get_version_details(
plugin_name, hadoop_version)
def nodegroup_template_create(request, name, plugin_name, hadoop_version,
flavor_id, description=None,
volumes_per_node=None, volumes_size=None,
node_processes=None, node_configs=None,
floating_ip_pool=None, security_groups=None,
auto_security_group=False,
availability_zone=False,
volumes_availability_zone=False,
volume_type=None,
image_id=None,
is_proxy_gateway=False,
volume_local_to_instance=False,
use_autoconfig=None,
shares=None,
is_public=None,
is_protected=None,
volume_mount_prefix=None,
boot_from_volume=None,
boot_volume_type=None,
boot_volume_availability_zone=None,
boot_volume_local_to_instance=None):
payload = dict(
name=name,
plugin_name=plugin_name,
flavor_id=flavor_id,
description=description,
volumes_per_node=volumes_per_node,
volumes_size=volumes_size,
node_processes=node_processes,
node_configs=node_configs,
floating_ip_pool=floating_ip_pool,
security_groups=security_groups,
auto_security_group=auto_security_group,
availability_zone=availability_zone,
volumes_availability_zone=volumes_availability_zone,
volume_type=volume_type,
image_id=image_id,
is_proxy_gateway=is_proxy_gateway,
volume_local_to_instance=volume_local_to_instance,
use_autoconfig=use_autoconfig,
shares=shares,
is_public=is_public,
is_protected=is_protected,
volume_mount_prefix=volume_mount_prefix)
if VERSIONS.active == '2':
payload['plugin_version'] = hadoop_version
payload['boot_from_volume'] = boot_from_volume
payload['boot_volume_type'] = boot_volume_type
payload['boot_volume_availability_zone'] = (
boot_volume_availability_zone
)
payload['boot_volume_local_to_instance'] = (
boot_volume_local_to_instance
)
else:
payload['hadoop_version'] = hadoop_version
return client(request).node_group_templates.create(**payload)
def nodegroup_template_list(request, search_opts=None,
marker=None, limit=None):
marker, limit = _update_pagination_params(marker, limit, request)
return client(request).node_group_templates.list(
search_opts=search_opts, limit=limit, marker=marker)
def nodegroup_template_get(request, ngt_id):
return client(request).node_group_templates.get(ng_template_id=ngt_id)
def nodegroup_template_find(request, **kwargs):
if "hadoop_version" in kwargs and VERSIONS.active == '2':
kwargs["plugin_version"] = kwargs.pop("hadoop_version")
return client(request).node_group_templates.find(**kwargs)
def nodegroup_template_delete(request, ngt_id):
client(request).node_group_templates.delete(ng_template_id=ngt_id)
def nodegroup_template_update(request, ngt_id, name, plugin_name,
hadoop_version, flavor_id,
description=None, volumes_per_node=None,
volumes_size=None, node_processes=None,
node_configs=None, floating_ip_pool=None,
security_groups=None, auto_security_group=False,
availability_zone=None,
volumes_availability_zone=None,
volume_type=None,
is_proxy_gateway=False,
volume_local_to_instance=False,
use_autoconfig=None,
shares=None,
is_protected=None,
is_public=None,
image_id=None,
boot_from_volume=None,
boot_volume_type=None,
boot_volume_availability_zone=None,
boot_volume_local_to_instance=None):
payload = dict(
ng_template_id=ngt_id,
name=name,
plugin_name=plugin_name,
flavor_id=flavor_id,
description=description,
volumes_per_node=volumes_per_node,
volumes_size=volumes_size,
node_processes=node_processes,
node_configs=node_configs,
floating_ip_pool=floating_ip_pool,
security_groups=security_groups,
auto_security_group=auto_security_group,
availability_zone=availability_zone,
volumes_availability_zone=volumes_availability_zone,
volume_type=volume_type,
is_proxy_gateway=is_proxy_gateway,
volume_local_to_instance=volume_local_to_instance,
use_autoconfig=use_autoconfig,
shares=shares,
is_public=is_public,
is_protected=is_protected,
image_id=image_id)
if VERSIONS.active == '2':
payload['plugin_version'] = hadoop_version
payload['boot_from_volume'] = boot_from_volume
payload['boot_volume_type'] = boot_volume_type
payload['boot_volume_availability_zone'] = (
boot_volume_availability_zone
)
payload['boot_volume_local_to_instance'] = (
boot_volume_local_to_instance
)
else:
payload['hadoop_version'] = hadoop_version
return client(request).node_group_templates.update(**payload)
def nodegroup_update_acl_rules(request, nid,
is_public=None, is_protected=None):
return client(request).node_group_templates.update(
nid, **prepare_acl_update_dict(is_public, is_protected))
def nodegroup_template_export(request, object_id):
return client(request).node_group_templates.export(object_id)
def cluster_template_create(request, name, plugin_name, hadoop_version,
description=None, cluster_configs=None,
node_groups=None, anti_affinity=None,
net_id=None, use_autoconfig=None, shares=None,
is_public=None, is_protected=None,
domain_name=None):
payload = dict(
name=name,
plugin_name=plugin_name,
description=description,
cluster_configs=cluster_configs,
node_groups=node_groups,
anti_affinity=anti_affinity,
net_id=net_id,
use_autoconfig=use_autoconfig,
shares=shares,
is_public=is_public,
is_protected=is_protected,
domain_name=domain_name
)
if VERSIONS.active == '2':
payload['plugin_version'] = hadoop_version
else:
payload['hadoop_version'] = hadoop_version
return client(request).cluster_templates.create(**payload)
def cluster_template_list(request, search_opts=None, marker=None, limit=None):
marker, limit = _update_pagination_params(marker, limit, request)
return client(request).cluster_templates.list(
search_opts=search_opts,
limit=limit,
marker=marker)
def cluster_template_get(request, ct_id):
return client(request).cluster_templates.get(cluster_template_id=ct_id)
def cluster_template_delete(request, ct_id):
client(request).cluster_templates.delete(cluster_template_id=ct_id)
def cluster_template_update(request, ct_id, name, plugin_name,
hadoop_version, description=None,
cluster_configs=None, node_groups=None,
anti_affinity=None, net_id=None,
use_autoconfig=None, shares=None,
is_public=None, is_protected=None,
domain_name=None):
try:
payload = dict(
cluster_template_id=ct_id,
name=name,
plugin_name=plugin_name,
description=description,
cluster_configs=cluster_configs,
node_groups=node_groups,
anti_affinity=anti_affinity,
net_id=net_id,
use_autoconfig=use_autoconfig,
shares=shares,
is_public=is_public,
is_protected=is_protected,
domain_name=domain_name
)
if VERSIONS.active == '2':
payload['plugin_version'] = hadoop_version
else:
payload['hadoop_version'] = hadoop_version
template = client(request).cluster_templates.update(**payload)
except APIException as e:
raise exceptions.Conflict(e)
return template
def cluster_template_update_acl_rules(request, ct_id,
is_public=None, is_protected=None):
return client(request).cluster_templates.update(
ct_id, **prepare_acl_update_dict(is_public, is_protected))
def cluster_template_export(request, object_id):
return client(request).cluster_templates.export(object_id)
def cluster_create(request, name, plugin_name, hadoop_version,
cluster_template_id=None, default_image_id=None,
is_transient=None, description=None, cluster_configs=None,
node_groups=None, user_keypair_id=None, anti_affinity=None,
net_id=None, count=None, use_autoconfig=None,
is_public=None, is_protected=None):
payload = dict(
name=name,
plugin_name=plugin_name,
cluster_template_id=cluster_template_id,
default_image_id=default_image_id,
is_transient=is_transient,
description=description,
cluster_configs=cluster_configs,
node_groups=node_groups,
user_keypair_id=user_keypair_id,
anti_affinity=anti_affinity,
net_id=net_id,
count=count,
use_autoconfig=use_autoconfig,
is_public=is_public,
is_protected=is_protected)
if VERSIONS.active == '2':
payload['plugin_version'] = hadoop_version
else:
payload['hadoop_version'] = hadoop_version
return client(request).clusters.create(**payload)
def cluster_scale(request, cluster_id, scale_object):
return client(request).clusters.scale(
cluster_id=cluster_id,
scale_object=scale_object)
def cluster_list(request, search_opts=None, marker=None, limit=None):
marker, limit = _update_pagination_params(marker, limit, request)
return client(request).clusters.list(
search_opts=search_opts, limit=limit, marker=marker)
def _cluster_list(request):
return client(request).clusters.list()
def cluster_get(request, cluster_id, show_progress=False):
return client(request).clusters.get(
cluster_id=cluster_id,
show_progress=show_progress)
def cluster_delete(request, cluster_id):
client(request).clusters.delete(cluster_id=cluster_id)
def cluster_force_delete(request, cluster_id):
client(request).clusters.force_delete(cluster_id=cluster_id)
def cluster_update(request, cluster_id, name=None, description=None,
is_public=None, is_protected=None, shares=None):
return client(request).clusters.update(cluster_id,
name=name,
description=description,
is_public=is_public,
is_protected=is_protected,
shares=shares)
def cluster_update_shares(request, cl_id, shares):
return client(request).clusters.update(cl_id, shares)
def cluster_update_acl_rules(request, cl_id, is_public=None,
is_protected=None):
return client(request).clusters.update(
cl_id, **prepare_acl_update_dict(is_public, is_protected))
def data_source_create(request, name, description, ds_type, url,
credential_user=None, credential_pass=None,
is_public=None, is_protected=None,
s3_credentials=None):
return client(request).data_sources.create(
name=name,
description=description,
data_source_type=ds_type,
url=url,
credential_user=credential_user or None,
credential_pass=credential_pass or None,
is_public=is_public,
is_protected=is_protected,
s3_credentials=s3_credentials)
def data_source_list(request, search_opts=None, limit=None, marker=None):
marker, limit = _update_pagination_params(marker, limit, request)
return client(request).data_sources.list(
search_opts=search_opts,
limit=limit,
marker=marker)
def data_source_get(request, ds_id):
return client(request).data_sources.get(data_source_id=ds_id)
def data_source_delete(request, ds_id):
client(request).data_sources.delete(data_source_id=ds_id)
def data_source_update(request, ds_id, data):
return client(request).data_sources.update(ds_id, data)
def job_binary_create(request, name, url, description, extra,
is_public=None, is_protected=None):
return client(request).job_binaries.create(
name=name,
url=url,
description=description,
extra=extra,
is_public=is_public,
is_protected=is_protected,
)
def job_binary_list(request, search_opts=None, marker=None, limit=None):
marker, limit = _update_pagination_params(marker, limit, request)
return client(request).job_binaries.list(
search_opts=search_opts,
limit=limit,
marker=marker)
def job_binary_get(request, jb_id):
return client(request).job_binaries.get(job_binary_id=jb_id)
def job_binary_delete(request, jb_id):
client(request).job_binaries.delete(job_binary_id=jb_id)
def job_binary_get_file(request, jb_id):
return client(request).job_binaries.get_file(job_binary_id=jb_id)
def job_binary_update(request, jb_id, data):
return client(request).job_binaries.update(jb_id, data)
def job_binary_internal_create(request, name, data):
return client(request).job_binary_internals.create(
name=name,
data=data)
def job_binary_internal_list(request, search_opts=None,
marker=None, limit=None):
marker, limit = _update_pagination_params(marker, limit, request)
return client(request).job_binary_internals.list(
search_opts=search_opts,
limit=limit,
marker=marker)
def job_binary_internal_get(request, jbi_id):
# The argument name looks wrong. This should be changed in the sahara
# client first and then updated here
return client(request).job_binary_internals.get(job_binary_id=jbi_id)
def job_binary_internal_delete(request, jbi_id):
# The argument name looks wrong. This should be changed in the sahara
# client first and then updated here
client(request).job_binary_internals.delete(job_binary_id=jbi_id)
def job_create(request, name, j_type, mains, libs, description, interface,
is_public=None, is_protected=None):
sahara = client(request)
if VERSIONS.active == '2':
manager = 'job_templates'
else:
manager = 'jobs'
return getattr(sahara, manager).create(
name=name,
type=j_type,
mains=mains,
libs=libs,
description=description,
interface=interface,
is_public=is_public,
is_protected=is_protected)
def job_update(request, job_id, is_public=None, is_protected=None):
sahara = client(request)
if VERSIONS.active == '2':
manager = 'job_templates'
else:
manager = 'jobs'
return getattr(sahara, manager).update(
job_id=job_id, **prepare_acl_update_dict(is_public, is_protected))
def job_list(request, search_opts=None, marker=None, limit=None):
marker, limit = _update_pagination_params(marker, limit, request)
sahara = client(request)
if VERSIONS.active == '2':
manager = 'job_templates'
else:
manager = 'jobs'
return getattr(sahara, manager).list(
search_opts=search_opts,
limit=limit,
marker=marker)
def _job_list(request):
sahara = client(request)
if VERSIONS.active == '2':
manager = 'job_templates'
else:
manager = 'jobs'
return getattr(sahara, manager).list()
def job_get(request, job_id):
sahara = client(request)
if VERSIONS.active == '2':
manager = 'job_templates'
else:
manager = 'jobs'
return getattr(sahara, manager).get(job_id=job_id)
def job_delete(request, job_id):
sahara = client(request)
if VERSIONS.active == '2':
manager = 'job_templates'
else:
manager = 'jobs'
getattr(sahara, manager).delete(job_id=job_id)
def job_get_configs(request, job_type):
sahara = client(request)
if VERSIONS.active == '2':
manager = 'job_templates'
else:
manager = 'jobs'
return getattr(sahara, manager).get_configs(job_type=job_type)
def job_execution_create(request, job_id, cluster_id,
input_id, output_id, configs,
interface, is_public=None, is_protected=None):
if input_id in [None, "", "None"]:
input_id = None
if output_id in [None, "", "None"]:
output_id = None
sahara = client(request)
if VERSIONS.active == '2':
manager = 'jobs'
else:
manager = 'job_executions'
return getattr(sahara, manager).create(
job_id,
cluster_id=cluster_id,
input_id=input_id,
output_id=output_id,
configs=configs,
interface=interface,
is_public=is_public,
is_protected=is_protected,
)
def job_execution_update(request, jbe_id, is_public=None, is_protected=None):
sahara = client(request)
if VERSIONS.active == '2':
manager = 'jobs'
else:
manager = 'job_executions'
return getattr(sahara, manager).update(jbe_id,
**prepare_acl_update_dict(
is_public, is_protected))
def _resolve_job_execution_names(job_execution, cluster=None,
job=None):
job_execution.cluster_name = None
if cluster:
job_execution.cluster_name = cluster.name
job_execution.job_name = None
if job:
job_execution.job_name = job.name
return job_execution
def job_execution_list(request, search_opts=None, marker=None, limit=None):
marker, limit = _update_pagination_params(marker, limit, request)
sahara = client(request)
if VERSIONS.active == '2':
manager = 'jobs'
else:
manager = 'job_executions'
job_execution_list = getattr(sahara, manager).list(
search_opts=search_opts, limit=limit,
marker=marker)
new_request = u.delete_pagination_params_from_request(
request, save_limit=False)
job_dict = {j.id: j for j in _job_list(new_request)}
cluster_dict = {c.id: c for c in _cluster_list(new_request)}
def _find_jt_id(jex_obj):
try:
return jex_obj.job_template_id # typical APIv2
except AttributeError:
return jex_obj.job_id # APIv1.1, older APIv2
resolved_job_execution_list = [
_resolve_job_execution_names(
job_execution,
cluster_dict.get(job_execution.cluster_id),
job_dict.get(_find_jt_id(job_execution)))
for job_execution in job_execution_list
]
return Page(resolved_job_execution_list, job_execution_list.prev,
job_execution_list.next, job_execution_list.limit)
def job_execution_get(request, jex_id):
sahara = client(request)
if VERSIONS.active == '2':
je_manager = 'jobs'
jt_manager = 'job_templates'
else:
je_manager = 'job_executions'
jt_manager = 'jobs'
jex = getattr(sahara, je_manager).get(obj_id=jex_id)
cluster = safe_call(client(request).clusters.get, jex.cluster_id)
try:
jt_id = jex.job_template_id # typical APIv2
except AttributeError:
jt_id = jex.job_id # APIv1.1, older APIv2
job = safe_call(getattr(sahara, jt_manager).get, jt_id)
return _resolve_job_execution_names(jex, cluster, job)
def job_execution_delete(request, jex_id):
sahara = client(request)
if VERSIONS.active == '2':
manager = 'jobs'
else:
manager = 'job_executions'
getattr(sahara, manager).delete(obj_id=jex_id)
def job_types_list(request):
return client(request).job_types.list()
def verification_update(request, cluster_id, status):
return client(request).clusters.verification_update(cluster_id, status)
def plugin_update(request, plugin_name, values):
return client(request).plugins.update(plugin_name, values)

View File

@ -1,220 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from django.utils.translation import gettext_lazy as _
from oslo_serialization import jsonutils as json
from horizon import exceptions
from horizon import forms
from saharaclient.api import base as api_base
from sahara_dashboard.api import sahara as saharaclient
import sahara_dashboard.content.data_processing. \
utils.workflow_helpers as whelpers
from sahara_dashboard import utils
BASE_IMAGE_URL = "horizon:project:data_processing.clusters:register"
class ImportClusterTemplateFileForm(forms.SelfHandlingForm):
class Meta(object):
name = _("Import Cluster Template")
def __init__(self, *args, **kwargs):
self.next_view = kwargs.pop("next_view")
super(ImportClusterTemplateFileForm, self).__init__(
*args, **kwargs)
template_upload = forms.FileField(
label=_("Template File"),
required=True)
def handle(self, request, data):
kwargs = {"template_upload": data["template_upload"]}
request.method = "GET"
return self.next_view.as_view()(request, **kwargs)
class ImportClusterTemplateNameForm(forms.SelfHandlingForm):
class Meta(object):
name = _("Import Cluster Template")
template_upload = forms.CharField(
widget=forms.widgets.HiddenInput)
name = forms.CharField(label=_("Name"),
required=False,
help_text=_("Name must be provided "
"either here or in the template. If "
"provided in both places, this one "
"will be used."))
def __init__(self, *args, **kwargs):
try:
request = args[0]
template_string = ""
self.next_view = kwargs.pop("next_view")
if "template_upload" in kwargs:
template_upload = kwargs.pop("template_upload")
super(ImportClusterTemplateNameForm, self).__init__(
*args, **kwargs)
template_string = template_upload.read()
self.fields["template_upload"].initial = template_string
else:
super(ImportClusterTemplateNameForm, self).__init__(
*args, **kwargs)
except (ValueError, KeyError):
raise exceptions.BadRequest(_("Could not parse template"))
except Exception:
exceptions.handle(request)
def handle(self, request, data):
template = data["template_upload"]
if data["name"]:
template = json.loads(template)
template["cluster_template"]["name"] = data["name"]
template = json.dumps(template)
kwargs = {"template_upload": template}
request.method = "GET"
return self.next_view.as_view()(request, **kwargs)
class ImportClusterTemplateNodegroupsForm(forms.SelfHandlingForm):
class Meta(object):
name = _("Import Cluster Template")
template_upload = forms.CharField(
widget=forms.widgets.HiddenInput)
hidden_nodegroups_field = forms.CharField(
required=False,
widget=forms.HiddenInput(attrs={"class": "hidden_nodegroups_field"}))
forms_ids = forms.CharField(
required=False,
widget=forms.HiddenInput())
def __init__(self, *args, **kwargs):
try:
request = args[0]
template_string = ""
if "template_upload" in kwargs:
template_string = kwargs.pop("template_upload")
super(ImportClusterTemplateNodegroupsForm, self).__init__(
*args, **kwargs)
self.fields["template_upload"].initial = template_string
else:
super(ImportClusterTemplateNodegroupsForm, self).__init__(
*args, **kwargs)
template_string = self.data["template_upload"]
template_json = json.loads(template_string)
template_json = template_json["cluster_template"]
req = request.GET.copy()
req.update(request.POST)
plugin = template_json["plugin_name"]
version = (template_json.get("hadoop_version", None) or
template_json["plugin_version"])
if not plugin or not version:
self.templates = saharaclient.nodegroup_template_find(request)
else:
self.templates = saharaclient.nodegroup_template_find(
request, plugin_name=plugin, hadoop_version=version)
deletable = req.get("deletable", dict())
if "forms_ids" in req:
self.groups = []
for id in json.loads(req["forms_ids"]):
group_name = "group_name_" + str(id)
template_id = "template_id_" + str(id)
count = "count_" + str(id)
serialized = "serialized_" + str(id)
self.groups.append({"name": req[group_name],
"template_id": req[template_id],
"count": req[count],
"id": id,
"deletable": deletable.get(
req[group_name], "true"),
"serialized": req[serialized]})
whelpers.build_node_group_fields(self,
group_name,
template_id,
count,
serialized)
except (ValueError, KeyError):
raise exceptions.BadRequest(_("Could not parse template"))
except Exception:
exceptions.handle(request)
def handle(self, request, data):
try:
template = data["template_upload"]
template = json.loads(template)
template = template["cluster_template"]
if "name" not in template.keys():
return False
if "neutron_management_network" in template:
template["net_id"] = (
template.pop("neutron_management_network"))
# default_image_id is not supported by the client now
if "default_image_id" in template:
template.pop("default_image_id")
node_groups = []
ids = json.loads(data['forms_ids'])
for id in ids:
name = data['group_name_' + str(id)]
template_id = data['template_id_' + str(id)]
count = data['count_' + str(id)]
raw_ng = data.get("serialized_" + str(id))
if raw_ng and raw_ng != 'null':
ng = json.loads(utils.deserialize(str(raw_ng)))
else:
ng = dict()
ng["name"] = name
ng["count"] = count
if template_id and template_id != u'None':
ng["node_group_template_id"] = template_id
node_groups.append(ng)
template["node_groups"] = node_groups
saharaclient.cluster_template_create(request, **template)
return True
except api_base.APIException as e:
self.error_description = str(e)
return False
except Exception as e:
if isinstance(e, TypeError):
raise exceptions.BadRequest(
_("Template JSON contained invalid key"))
else:
raise exceptions.BadRequest(_("Could not parse template"))

View File

@ -1,208 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from django import http as http_response
from django.template import defaultfilters as filters
from django import urls
from django.utils import http
from django.utils.translation import gettext_lazy as _
from django.utils.translation import ngettext_lazy
from horizon import tables
from horizon.tabs import base as tabs_base
from oslo_serialization import jsonutils as json
from sahara_dashboard.api import sahara as saharaclient
from sahara_dashboard.content.data_processing \
import tables as sahara_table
from sahara_dashboard.content.data_processing.utils \
import acl as acl_utils
class ClusterTemplatesFilterAction(tables.FilterAction):
filter_type = "server"
filter_choices = (('name', _("Name"), True),
('plugin_name', _("Plugin"), True),
('hadoop_version', _("Version"), True),
('description', _("Description")))
class CreateCluster(tables.LinkAction):
name = "create cluster"
verbose_name = _("Launch Cluster")
url = "horizon:project:data_processing.clusters:configure-cluster"
classes = ("ajax-modal",)
icon = "plus"
def get_link_url(self, datum):
base_url = urls.reverse(self.url)
if saharaclient.VERSIONS.active == '2':
version_attr = "plugin_version"
else:
version_attr = "hadoop_version"
params = http.urlencode({"hadoop_version":
getattr(datum, version_attr),
"plugin_name": datum.plugin_name,
"cluster_template_id": datum.id})
return "?".join([base_url, params])
class CopyTemplate(tables.LinkAction):
name = "copy"
verbose_name = _("Copy Template")
url = "horizon:project:data_processing.clusters:ct-copy"
classes = ("ajax-modal", )
class ExportTemplate(tables.Action):
name = "export"
verbose_name = _("Export Template")
classes = ("ajax-modal", )
def single(self, data_table, request, object_id):
content = json.dumps(saharaclient.cluster_template_export(
request, object_id)._info)
response = http_response.HttpResponse(
content, content_type="application/json")
filename = '%s-cluster-template.json' % object_id
disposition = 'attachment; filename="%s"' % filename
response['Content-Disposition'] = disposition.encode('utf-8')
response['Content-Length'] = str(len(response.content))
return response
class EditTemplate(tables.LinkAction):
name = "edit"
verbose_name = _("Edit Template")
url = "horizon:project:data_processing.clusters:ct-edit"
classes = ("ajax-modal", )
class DeleteTemplate(tables.DeleteAction):
@staticmethod
def action_present(count):
return ngettext_lazy(
u"Delete Template",
u"Delete Templates",
count
)
@staticmethod
def action_past(count):
return ngettext_lazy(
u"Deleted Template",
u"Deleted Templates",
count
)
def delete(self, request, template_id):
saharaclient.cluster_template_delete(request, template_id)
class CreateClusterTemplate(tables.LinkAction):
name = "create"
verbose_name = _("Create Template")
url = ("horizon:project:data_processing.clusters:"
"create-cluster-template")
classes = ("ajax-modal", "create-clustertemplate-btn")
icon = "plus"
class ImportClusterTemplate(tables.LinkAction):
name = "import"
verbose_name = _("Import Template")
url = ("horizon:project:data_processing.clusters:"
"import-cluster-template-file")
classes = ("ajax-modal",)
icon = "plus"
class ConfigureClusterTemplate(tables.LinkAction):
name = "configure"
verbose_name = _("Configure Cluster Template")
url = ("horizon:project:data_processing.clusters:"
"configure-cluster-template")
classes = ("ajax-modal", "configure-clustertemplate-btn")
icon = "plus"
attrs = {"style": "display: none"}
def render_node_groups(cluster_template):
node_groups = [node_group['name'] + ': ' + str(node_group['count'])
for node_group in cluster_template.node_groups]
return node_groups
class MakePublic(acl_utils.MakePublic):
def change_rule_method(self, request, datum_id, **update_kwargs):
saharaclient.cluster_template_update_acl_rules(
request, datum_id, **update_kwargs)
class MakePrivate(acl_utils.MakePrivate):
def change_rule_method(self, request, datum_id, **update_kwargs):
saharaclient.cluster_template_update_acl_rules(
request, datum_id, **update_kwargs)
class MakeProtected(acl_utils.MakeProtected):
def change_rule_method(self, request, datum_id, **update_kwargs):
saharaclient.cluster_template_update_acl_rules(
request, datum_id, **update_kwargs)
class MakeUnProtected(acl_utils.MakeUnProtected):
def change_rule_method(self, request, datum_id, **update_kwargs):
saharaclient.cluster_template_update_acl_rules(
request, datum_id, **update_kwargs)
class ClusterTemplatesTable(sahara_table.SaharaPaginateTabbedTable):
tab_name = 'cluster_tabs%sclusters_templates_tab' % tabs_base.SEPARATOR
name = tables.Column("name",
verbose_name=_("Name"),
link=("horizon:project:data_processing."
"clusters:ct-details"))
plugin_name = tables.Column("plugin_name",
verbose_name=_("Plugin"))
if saharaclient.VERSIONS.active == '2':
version_attr = "plugin_version"
else:
version_attr = "hadoop_version"
hadoop_version = tables.Column(version_attr,
verbose_name=_("Version"))
node_groups = tables.Column(render_node_groups,
verbose_name=_("Node Groups"),
wrap_list=True,
filters=(filters.unordered_list,))
description = tables.Column("description",
verbose_name=_("Description"))
class Meta(object):
name = "cluster_templates"
verbose_name = _("Cluster Templates")
table_actions = (CreateClusterTemplate,
ImportClusterTemplate,
ConfigureClusterTemplate,
DeleteTemplate,
ClusterTemplatesFilterAction,)
table_actions_menu = (MakePublic, MakePrivate,
MakeProtected, MakeUnProtected)
row_actions = (CreateCluster,
EditTemplate,
CopyTemplate,
ExportTemplate,
DeleteTemplate, MakePublic, MakePrivate,
MakeProtected, MakeUnProtected)

View File

@ -1,118 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from oslo_log import log as logging
from django.utils.translation import gettext_lazy as _
from horizon import exceptions
from horizon import tabs
from openstack_dashboard.api import nova
from sahara_dashboard.api import sahara as saharaclient
from sahara_dashboard.content. \
data_processing.utils import workflow_helpers as helpers
from sahara_dashboard.content.data_processing.clusters.cluster_templates \
import tables as cluster_template_tables
from sahara_dashboard.content.data_processing \
import tabs as sahara_tabs
LOG = logging.getLogger(__name__)
class ClusterTemplatesTab(sahara_tabs.SaharaTableTab):
table_classes = (cluster_template_tables.ClusterTemplatesTable, )
name = _("Cluster Templates")
slug = "clusters_templates_tab"
template_name = "horizon/common/_detail_table.html"
def get_cluster_templates_data(self):
try:
table = self._tables['cluster_templates']
search_opts = {}
filter = self.get_server_filter_info(table.request, table)
if filter['value'] and filter['field']:
search_opts = {filter['field']: filter['value']}
cluster_templates = saharaclient.cluster_template_list(
self.request, search_opts)
except Exception:
cluster_templates = []
exceptions.handle(self.request,
_("Unable to fetch cluster template list"))
return cluster_templates
class GeneralTab(tabs.Tab):
name = _("General Info")
slug = "cluster_template_details_tab"
template_name = "cluster_templates/_details.html"
def get_context_data(self, request):
template_id = self.tab_group.kwargs['template_id']
try:
template = saharaclient.cluster_template_get(request, template_id)
except Exception as e:
template = {}
LOG.error("Unable to fetch cluster template details: %s" % str(e))
if saharaclient.VERSIONS.active == '2':
template.hadoop_version = template.plugin_version
return {"template": template}
class ClusterTemplateConfigsDetails(tabs.Tab):
name = _("Configuration Details")
slug = "cluster_template_configs_details_tab"
template_name = (
"cluster_templates/_cluster_template_configs_details.html")
def get_context_data(self, request):
template_id = self.tab_group.kwargs['template_id']
try:
template = saharaclient.cluster_template_get(request, template_id)
except Exception as e:
template = {}
LOG.error("Unable to fetch cluster template details: %s" % str(e))
return {"template": template}
class NodeGroupsTab(tabs.Tab):
name = _("Node Groups")
slug = "cluster_template_nodegroups_tab"
template_name = "cluster_templates/_nodegroups_details.html"
def get_context_data(self, request):
template_id = self.tab_group.kwargs['template_id']
try:
template = saharaclient.cluster_template_get(request, template_id)
for ng in template.node_groups:
if not ng["flavor_id"]:
continue
ng["flavor_name"] = (
nova.flavor_get(request, ng["flavor_id"]).name)
ng["node_group_template"] = saharaclient.safe_call(
saharaclient.nodegroup_template_get,
request, ng.get("node_group_template_id", None))
ng["security_groups_full"] = helpers.get_security_groups(
request, ng.get("security_groups"))
except Exception:
template = {}
exceptions.handle(request,
_("Unable to fetch node group details."))
return {"template": template}
class ClusterTemplateDetailsTabs(tabs.TabGroup):
slug = "cluster_template_details"
tabs = (GeneralTab, ClusterTemplateConfigsDetails, NodeGroupsTab, )
sticky = True

View File

@ -1,159 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import copy
from django.urls import reverse
from oslo_serialization import jsonutils
from openstack_dashboard import api as dash_api
from sahara_dashboard import api
from sahara_dashboard.test import helpers as test
from sahara_dashboard import utils
INDEX_URL = reverse('horizon:project:data_processing.clusters:'
'cluster-templates-tab')
DETAILS_URL = reverse(
'horizon:project:data_processing.clusters:ct-details', args=['id'])
class DataProcessingClusterTemplateTests(test.TestCase):
@test.create_mocks({api.sahara: ('cluster_template_list',
'image_list',
'cluster_list',
'nodegroup_template_list')})
def test_index(self):
self.mock_cluster_template_list.return_value = \
self.cluster_templates.list()
res = self.client.get(INDEX_URL)
self.mock_cluster_template_list.assert_called_once_with(
test.IsHttpRequest(), {})
self.assertTemplateUsed(res, 'clusters/index.html')
self.assertContains(res, 'Cluster Templates')
self.assertContains(res, 'Name')
@test.create_mocks({api.sahara: ('cluster_template_get',
'nodegroup_template_get'),
dash_api.nova: ('flavor_get',)})
def test_details(self):
flavor = self.flavors.first()
ct = self.cluster_templates.first()
self.mock_flavor_get.return_value = flavor
self.mock_cluster_template_get.return_value = ct
res = self.client.get(DETAILS_URL)
self.assertTemplateUsed(res, 'horizon/common/_detail.html')
@test.create_mocks({api.sahara: ('client',
'cluster_template_get',
'plugin_get_version_details',
'nodegroup_template_find')})
def test_copy(self):
ct = self.cluster_templates.first()
ngts = self.nodegroup_templates.list()
configs = self.plugins_configs.first()
self.mock_cluster_template_get.return_value = ct
self.mock_plugin_get_version_details.return_value = configs
self.mock_nodegroup_template_find.return_value = ngts
url = reverse('horizon:project:data_processing.clusters:ct-copy',
args=[ct.id])
res = self.client.get(url)
self.mock_cluster_template_get.assert_called_once_with(
test.IsHttpRequest(), ct.id)
workflow = res.context['workflow']
step = workflow.get_step("generalconfigaction")
self.assertEqual(step.action['cluster_template_name'].field.initial,
ct.name + "-copy")
@test.create_mocks({api.sahara: ('cluster_template_list',
'cluster_template_delete')})
def test_delete(self):
ct = self.cluster_templates.first()
self.mock_cluster_template_list.return_value = \
self.cluster_templates.list()
self.mock_cluster_template_delete.return_value = None
form_data = {'action': 'cluster_templates__delete__%s' % ct.id}
res = self.client.post(INDEX_URL, form_data)
self.mock_cluster_template_list.assert_called_once_with(
test.IsHttpRequest(), {})
self.mock_cluster_template_delete.assert_called_once_with(
test.IsHttpRequest(), ct.id)
self.assertNoFormErrors(res)
self.assertRedirectsNoFollow(res, INDEX_URL)
self.assertMessageCount(success=1)
@test.create_mocks({api.sahara: ('client',
'cluster_template_get',
'cluster_template_update',
'plugin_get_version_details',
'nodegroup_template_find')})
def test_update(self):
ct = self.cluster_templates.first()
ngts = self.nodegroup_templates.list()
configs = self.plugins_configs.first()
new_name = "UpdatedName"
new_ct = copy.copy(ct)
new_ct.name = new_name
self.mock_cluster_template_get.return_value = ct
self.mock_plugin_get_version_details.return_value = configs
self.mock_nodegroup_template_find.return_value = ngts
self.mock_cluster_template_update.return_value = new_ct
url = reverse('horizon:project:data_processing.clusters:ct-edit',
args=[ct.id])
def serialize(obj):
return utils.serialize(jsonutils.dump_as_bytes(obj))
res = self.client.post(
url,
{'ct_id': ct.id,
'cluster_template_name': new_name,
'plugin_name': ct.plugin_name,
'hadoop_version': ct.hadoop_version,
'description': ct.description,
'hidden_configure_field': "",
'template_id_0': ct.node_groups[0]['node_group_template_id'],
'group_name_0': ct.node_groups[0]['name'],
'count_0': 1,
'serialized_0': serialize(ct.node_groups[0]),
'template_id_1': ct.node_groups[1]['node_group_template_id'],
'group_name_1': ct.node_groups[1]['name'],
'count_1': 2,
'serialized_1': serialize(ct.node_groups[1]),
'forms_ids': "[0,1]",
})
self.assertNoFormErrors(res)
self.assertRedirectsNoFollow(res, INDEX_URL)
self.assertMessageCount(success=1)
self.mock_cluster_template_update.assert_called_once_with(
request=test.IsHttpRequest(),
ct_id=ct.id,
name=new_name,
plugin_name=ct.plugin_name,
hadoop_version=ct.hadoop_version,
description=ct.description,
cluster_configs=ct.cluster_configs,
node_groups=ct.node_groups,
anti_affinity=ct.anti_affinity,
use_autoconfig=False,
shares=ct.shares,
is_public=False,
is_protected=False,
domain_name=ct.domain_name
)

View File

@ -1,181 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from django.urls import reverse
from django.urls import reverse_lazy
from django.utils.translation import gettext_lazy as _
from horizon import exceptions
from horizon import forms
from horizon import tabs
from horizon.utils import memoized
from horizon import workflows
from sahara_dashboard.api import sahara as saharaclient
import sahara_dashboard.content.data_processing.clusters. \
cluster_templates.tables as ct_tables
import sahara_dashboard.content.data_processing.clusters. \
cluster_templates.tabs as _tabs
import sahara_dashboard.content.data_processing.clusters. \
cluster_templates.workflows.copy as copy_flow
import sahara_dashboard.content.data_processing.clusters. \
cluster_templates.workflows.create as create_flow
import sahara_dashboard.content.data_processing.clusters. \
cluster_templates.workflows.edit as edit_flow
import sahara_dashboard.content.data_processing.clusters. \
cluster_templates.forms.import_forms as import_forms
class ClusterTemplateDetailsView(tabs.TabView):
tab_group_class = _tabs.ClusterTemplateDetailsTabs
template_name = 'horizon/common/_detail.html'
page_title = "{{ template.name|default:template.id }}"
@memoized.memoized_method
def get_object(self):
ct_id = self.kwargs["template_id"]
try:
return saharaclient.cluster_template_get(self.request, ct_id)
except Exception:
msg = _('Unable to retrieve details for '
'cluster template "%s".') % ct_id
redirect = self.get_redirect_url()
exceptions.handle(self.request, msg, redirect=redirect)
def get_context_data(self, **kwargs):
context = super(ClusterTemplateDetailsView, self)\
.get_context_data(**kwargs)
cluster_template = self.get_object()
context['template'] = cluster_template
context['url'] = self.get_redirect_url()
context['actions'] = self._get_actions(cluster_template)
return context
def _get_actions(self, cluster_template):
table = ct_tables.ClusterTemplatesTable(self.request)
return table.render_row_actions(cluster_template)
@staticmethod
def get_redirect_url():
return reverse("horizon:project:data_processing."
"clusters:index")
class CreateClusterTemplateView(workflows.WorkflowView):
workflow_class = create_flow.CreateClusterTemplate
success_url = ("horizon:project:data_processing.clusters"
":create-cluster-template")
classes = ("ajax-modal",)
template_name = "cluster_templates/create.html"
page_title = _("Create Cluster Template")
class ConfigureClusterTemplateView(workflows.WorkflowView):
workflow_class = create_flow.ConfigureClusterTemplate
success_url = ("horizon:project:data_processing.clusters"
":index")
template_name = "cluster_templates/configure.html"
page_title = _("Configure Cluster Template")
class CopyClusterTemplateView(workflows.WorkflowView):
workflow_class = copy_flow.CopyClusterTemplate
success_url = ("horizon:project:data_processing.clusters"
":index")
template_name = "cluster_templates/configure.html"
page_title = _("Copy Cluster Template")
def get_context_data(self, **kwargs):
context = super(CopyClusterTemplateView, self)\
.get_context_data(**kwargs)
context["template_id"] = kwargs["template_id"]
return context
def get_object(self, *args, **kwargs):
if not hasattr(self, "_object"):
template_id = self.kwargs['template_id']
try:
template = saharaclient.cluster_template_get(self.request,
template_id)
except Exception:
template = {}
exceptions.handle(self.request,
_("Unable to fetch cluster template."))
self._object = template
return self._object
def get_initial(self):
initial = super(CopyClusterTemplateView, self).get_initial()
initial['template_id'] = self.kwargs['template_id']
return initial
class EditClusterTemplateView(CopyClusterTemplateView):
workflow_class = edit_flow.EditClusterTemplate
success_url = ("horizon:project:data_processing.clusters"
":index")
template_name = "cluster_templates/configure.html"
class ImportClusterTemplateFileView(forms.ModalFormView):
template_name = "cluster_templates/import.html"
form_class = import_forms.ImportClusterTemplateFileForm
submit_label = _("Next")
submit_url = reverse_lazy("horizon:project:data_processing."
"clusters:import-cluster-template-file")
success_url = reverse_lazy("horizon:project:data_processing."
"clusters:import-cluster-template-name")
page_title = _("Import Cluster Template")
def get_form_kwargs(self):
kwargs = super(ImportClusterTemplateFileView, self).get_form_kwargs()
kwargs['next_view'] = ImportClusterTemplateNameView
return kwargs
class ImportClusterTemplateNameView(forms.ModalFormView):
template_name = "cluster_templates/import.html"
form_class = import_forms.ImportClusterTemplateNameForm
submit_label = _("Next")
submit_url = reverse_lazy("horizon:project:data_processing."
"clusters:import-cluster-template-name")
success_url = reverse_lazy("horizon:project:data_processing."
"clusters:import-cluster-template-nodegroups")
page_title = _("Import Cluster Template")
def get_form_kwargs(self):
kwargs = super(ImportClusterTemplateNameView, self).get_form_kwargs()
kwargs['next_view'] = ImportClusterTemplateNodegroupsView
if 'template_upload' in self.kwargs:
kwargs['template_upload'] = self.kwargs['template_upload']
return kwargs
class ImportClusterTemplateNodegroupsView(forms.ModalFormView):
template_name = "cluster_templates/import_nodegroups.html"
# template_name = "some_random_stuff.html"
form_class = import_forms.ImportClusterTemplateNodegroupsForm
submit_label = _("Import")
submit_url = reverse_lazy("horizon:project:data_processing."
"clusters:import-cluster-template-nodegroups")
success_url = reverse_lazy("horizon:project:data_processing."
"clusters:index")
page_title = _("Import Cluster Template")
def get_form_kwargs(self):
kwargs = super(ImportClusterTemplateNodegroupsView,
self).get_form_kwargs()
if 'template_upload' in self.kwargs:
kwargs['template_upload'] = self.kwargs['template_upload']
return kwargs

View File

@ -1,137 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import json
from django.utils.translation import gettext_lazy as _
from horizon import exceptions
from sahara_dashboard.api import sahara as saharaclient
import sahara_dashboard.content.data_processing.clusters. \
cluster_templates.workflows.create as create_flow
import sahara_dashboard.content.data_processing.utils. \
workflow_helpers as wf_helpers
from sahara_dashboard import utils
class CopyClusterTemplate(create_flow.ConfigureClusterTemplate):
success_message = _("Cluster Template copy %s created")
entry_point = "generalconfigaction"
def __init__(self, request, context_seed, entry_point, *args, **kwargs):
self.cluster_template_id = context_seed["template_id"]
try:
self.template = saharaclient.cluster_template_get(
request,
self.cluster_template_id)
self._set_configs_to_copy(self.template.cluster_configs)
if saharaclient.VERSIONS.active == '2':
version_attr = 'plugin_version'
else:
version_attr = 'hadoop_version'
hadoop_version = getattr(self.template, version_attr)
request.GET = request.GET.copy()
request.GET.update({"plugin_name": self.template.plugin_name,
version_attr: hadoop_version,
"aa_groups": self.template.anti_affinity})
super(CopyClusterTemplate, self).__init__(request, context_seed,
entry_point, *args,
**kwargs)
# Initialize node groups.
# TODO(rdopieralski) The same (or very similar) code appears
# multiple times in this dashboard. It should be refactored to
# a function.
for step in self.steps:
if isinstance(step, create_flow.ConfigureNodegroups):
ng_action = step.action
template_ngs = self.template.node_groups
if 'forms_ids' in request.POST:
continue
ng_action.groups = []
for i, templ_ng in enumerate(template_ngs):
group_name = "group_name_%d" % i
template_id = "template_id_%d" % i
count = "count_%d" % i
serialized = "serialized_%d" % i
# save the original node group with all its fields in
# case the template id is missing
serialized_val = utils.serialize(
json.dumps(wf_helpers.clean_node_group(templ_ng)))
ng = {
"name": templ_ng["name"],
"count": templ_ng["count"],
"id": i,
"deletable": "true",
"serialized": serialized_val
}
if "node_group_template_id" in templ_ng:
ng["template_id"] = templ_ng[
"node_group_template_id"]
ng_action.groups.append(ng)
wf_helpers.build_node_group_fields(
ng_action, group_name, template_id, count,
serialized)
elif isinstance(step, create_flow.GeneralConfig):
fields = step.action.fields
fields["cluster_template_name"].initial = (
self.template.name + "-copy")
fields['use_autoconfig'].initial = (
self.template.use_autoconfig)
fields["description"].initial = self.template.description
elif isinstance(step, create_flow.SelectClusterShares):
fields = step.action.fields
fields["shares"].initial = (
self._get_share_defaults(fields["shares"].choices)
)
fields['is_public'].initial = (
self.template.is_public)
fields['is_protected'].initial = (
self.template.is_protected)
elif isinstance(step, create_flow.SelectDnsDomains):
fields = step.action.fields
fields["domain_name"].initial = self.template.domain_name
except Exception:
exceptions.handle(request,
_("Unable to fetch template to copy."))
def _get_share_defaults(self, choices):
values = dict()
for i, choice in enumerate(choices):
share_id = choice[0]
s = [s for s in self.template.shares if s['id'] == share_id]
if len(s) > 0:
path = s[0]["path"] if "path" in s[0] else ""
values["share_id_{0}".format(i)] = {
"id": s[0]["id"],
"path": path,
"access_level": s[0]["access_level"]
}
else:
values["share_id_{0}".format(i)] = {
"id": None,
"path": None,
"access_level": None
}
return values

View File

@ -1,433 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import json
from django import urls
from django.utils.translation import gettext_lazy as _
from saharaclient.api import base as api_base
from horizon import exceptions
from horizon import forms
from horizon import workflows
from sahara_dashboard.api import designate as designateclient
from sahara_dashboard.api import manila as manilaclient
from sahara_dashboard.api import sahara as saharaclient
from sahara_dashboard.content.data_processing.utils import helpers
from sahara_dashboard.content.data_processing. \
utils import anti_affinity as aa
from sahara_dashboard.content.data_processing.utils \
import acl as acl_utils
import sahara_dashboard.content.data_processing. \
utils.workflow_helpers as whelpers
from sahara_dashboard import utils
class SelectPluginAction(workflows.Action,
whelpers.PluginAndVersionMixin):
hidden_create_field = forms.CharField(
required=False,
widget=forms.HiddenInput(attrs={"class": "hidden_create_field"}))
def __init__(self, request, *args, **kwargs):
super(SelectPluginAction, self).__init__(request, *args, **kwargs)
sahara = saharaclient.client(request)
self._generate_plugin_version_fields(sahara)
class Meta(object):
name = _("Select plugin and hadoop version for cluster template")
help_text_template = ("cluster_templates/"
"_create_general_help.html")
class SelectPlugin(workflows.Step):
action_class = SelectPluginAction
class CreateClusterTemplate(workflows.Workflow):
slug = "create_cluster_template"
name = _("Create Cluster Template")
finalize_button_name = _("Next")
success_message = _("Created")
failure_message = _("Could not create")
success_url = "horizon:project:data_processing.clusters:clusters-tab"
default_steps = (SelectPlugin, )
def get_success_url(self):
url = urls.reverse(self.success_url)
return url
class GeneralConfigAction(workflows.Action):
hidden_configure_field = forms.CharField(
required=False,
widget=forms.HiddenInput(attrs={"class": "hidden_configure_field"}))
hidden_to_delete_field = forms.CharField(
required=False,
widget=forms.HiddenInput(attrs={"class": "hidden_to_delete_field"}))
cluster_template_name = forms.CharField(label=_("Template Name"))
description = forms.CharField(label=_("Description"),
required=False,
widget=forms.Textarea(attrs={'rows': 4}))
use_autoconfig = forms.BooleanField(
label=_("Auto-configure"),
help_text=_("If selected, instances of a cluster will be "
"automatically configured during creation. Otherwise you "
"should manually specify configuration values"),
required=False,
widget=forms.CheckboxInput(),
initial=True,
)
is_public = acl_utils.get_is_public_form(_("cluster template"))
is_protected = acl_utils.get_is_protected_form(_("cluster template"))
anti_affinity = aa.anti_affinity_field()
def __init__(self, request, *args, **kwargs):
super(GeneralConfigAction, self).__init__(request, *args, **kwargs)
plugin, hadoop_version = whelpers.\
get_plugin_and_hadoop_version(request)
self.fields["plugin_name"] = forms.CharField(
widget=forms.HiddenInput(),
initial=plugin
)
self.fields["hadoop_version"] = forms.CharField(
widget=forms.HiddenInput(),
initial=hadoop_version
)
populate_anti_affinity_choices = aa.populate_anti_affinity_choices
def get_help_text(self):
extra = dict()
plugin_name, hadoop_version = whelpers\
.get_plugin_and_hadoop_version(self.request)
extra["plugin_name"] = plugin_name
extra["hadoop_version"] = hadoop_version
plugin = saharaclient.plugin_get_version_details(
self.request, plugin_name, hadoop_version)
extra["deprecated"] = whelpers.is_version_of_plugin_deprecated(
plugin, hadoop_version)
return super(GeneralConfigAction, self).get_help_text(extra)
def clean(self):
cleaned_data = super(GeneralConfigAction, self).clean()
if cleaned_data.get("hidden_configure_field", None) \
== "create_nodegroup":
self._errors = dict()
return cleaned_data
class Meta(object):
name = _("Details")
help_text_template = ("cluster_templates/_configure_general_help.html")
class GeneralConfig(workflows.Step):
action_class = GeneralConfigAction
contributes = ("hidden_configure_field", )
def contribute(self, data, context):
for k, v in data.items():
context["general_" + k] = v
post = self.workflow.request.POST
context['anti_affinity_info'] = post.getlist("anti_affinity")
return context
class ConfigureNodegroupsAction(workflows.Action):
hidden_nodegroups_field = forms.CharField(
required=False,
widget=forms.HiddenInput(attrs={"class": "hidden_nodegroups_field"}))
forms_ids = forms.CharField(
required=False,
widget=forms.HiddenInput())
def __init__(self, request, *args, **kwargs):
super(ConfigureNodegroupsAction, self). \
__init__(request, *args, **kwargs)
# when we copy or edit a cluster template then
# request contains valuable info in both GET and POST methods
req = request.GET.copy()
req.update(request.POST)
plugin = req.get("plugin_name")
version = req.get("hadoop_version", None) or req["plugin_version"]
if plugin and not version:
version_name = plugin + "_version"
version = req.get(version_name)
if not plugin or not version:
self.templates = saharaclient.nodegroup_template_find(request)
else:
self.templates = saharaclient.nodegroup_template_find(
request, plugin_name=plugin, hadoop_version=version)
deletable = req.get("deletable", dict())
if 'forms_ids' in req:
self.groups = []
for id in json.loads(req['forms_ids']):
group_name = "group_name_" + str(id)
template_id = "template_id_" + str(id)
count = "count_" + str(id)
serialized = "serialized_" + str(id)
self.groups.append({"name": req[group_name],
"template_id": req[template_id],
"count": req[count],
"id": id,
"deletable": deletable.get(
req[group_name], "true"),
"serialized": req[serialized]})
whelpers.build_node_group_fields(self,
group_name,
template_id,
count,
serialized)
def clean(self):
cleaned_data = super(ConfigureNodegroupsAction, self).clean()
if cleaned_data.get("hidden_nodegroups_field", None) \
== "create_nodegroup":
self._errors = dict()
return cleaned_data
class Meta(object):
name = _("Node Groups")
class ConfigureNodegroups(workflows.Step):
action_class = ConfigureNodegroupsAction
contributes = ("hidden_nodegroups_field", )
template_name = ("cluster_templates/cluster_node_groups_template.html")
def contribute(self, data, context):
for k, v in data.items():
context["ng_" + k] = v
return context
class SelectClusterSharesAction(workflows.Action):
def __init__(self, request, *args, **kwargs):
super(SelectClusterSharesAction, self).__init__(
request, *args, **kwargs)
possible_shares = self.get_possible_shares(request)
self.fields["shares"] = whelpers.MultipleShareChoiceField(
label=_("Select Shares"),
widget=whelpers.ShareWidget(choices=possible_shares),
required=False,
choices=possible_shares
)
def get_possible_shares(self, request):
try:
shares = manilaclient.share_list(request)
choices = [(s.id, s.name) for s in shares]
except Exception:
exceptions.handle(request, _("Failed to get list of shares"))
choices = []
return choices
def clean(self):
cleaned_data = super(SelectClusterSharesAction, self).clean()
self._errors = dict()
return cleaned_data
class Meta(object):
name = _("Shares")
help_text = _("Select the manila shares for this cluster")
class SelectClusterShares(workflows.Step):
action_class = SelectClusterSharesAction
def contribute(self, data, context):
post = self.workflow.request.POST
shares_details = []
for index in range(0, len(self.action.fields['shares'].choices) * 3):
if index % 3 == 0:
share = post.get("shares_{0}".format(index))
if share:
path = post.get("shares_{0}".format(index + 1))
permissions = post.get("shares_{0}".format(index + 2))
shares_details.append({
"id": share,
"path": path,
"access_level": permissions
})
context['ct_shares'] = shares_details
return context
class SelectDnsDomainsAction(workflows.Action):
domain_name = forms.DynamicChoiceField(
label=_("Domain Name"),
required=False
)
def __init__(self, request, *args, **kwargs):
super(SelectDnsDomainsAction, self).__init__(request, *args, **kwargs)
def _get_domain_choices(self, request):
domains = designateclient.get_domain_names(request)
choices = [(None, _('No domain is specified'))]
choices.extend(
[(domain.get('name'), domain.get('name')) for domain in domains])
return choices
def populate_domain_name_choices(self, request, context):
return self._get_domain_choices(request)
class Meta(object):
name = _("DNS Domain Names")
help_text_template = (
"cluster_templates/_config_domain_names_help.html")
class SelectDnsDomains(workflows.Step):
action_class = SelectDnsDomainsAction
def contribute(self, data, context):
for k, v in data.items():
context["dns_" + k] = v
return context
class ConfigureClusterTemplate(whelpers.ServiceParametersWorkflow,
whelpers.StatusFormatMixin):
slug = "configure_cluster_template"
name = _("Create Cluster Template")
finalize_button_name = _("Create")
success_message = _("Created Cluster Template %s")
name_property = "general_cluster_template_name"
success_url = ("horizon:project:data_processing.clusters:"
"cluster-templates-tab")
default_steps = (GeneralConfig,
ConfigureNodegroups)
def __init__(self, request, context_seed, entry_point, *args, **kwargs):
ConfigureClusterTemplate._cls_registry = []
hlps = helpers.Helpers(request)
plugin, hadoop_version = whelpers.\
get_plugin_and_hadoop_version(request)
general_parameters = hlps.get_cluster_general_configs(
plugin,
hadoop_version)
service_parameters = hlps.get_targeted_cluster_configs(
plugin,
hadoop_version)
if saharaclient.base.is_service_enabled(request, 'share'):
ConfigureClusterTemplate._register_step(self, SelectClusterShares)
if saharaclient.base.is_service_enabled(request, 'dns'):
ConfigureClusterTemplate._register_step(self, SelectDnsDomains)
self._populate_tabs(general_parameters, service_parameters)
super(ConfigureClusterTemplate, self).__init__(request,
context_seed,
entry_point,
*args, **kwargs)
def is_valid(self):
steps_valid = True
for step in self.steps:
if not step.action.is_valid():
steps_valid = False
step.has_errors = True
errors_fields = list(step.action.errors.keys())
step.action.errors_fields = errors_fields
if not steps_valid:
return steps_valid
return self.validate(self.context)
def handle(self, request, context):
try:
node_groups = []
configs_dict = whelpers.parse_configs_from_context(context,
self.defaults)
ids = json.loads(context['ng_forms_ids'])
for id in ids:
name = context['ng_group_name_' + str(id)]
template_id = context['ng_template_id_' + str(id)]
count = context['ng_count_' + str(id)]
raw_ng = context.get("ng_serialized_" + str(id))
if raw_ng and raw_ng != 'null':
ng = json.loads(utils.deserialize(str(raw_ng)))
else:
ng = dict()
ng["name"] = name
ng["count"] = count
if template_id and template_id != u'None':
ng["node_group_template_id"] = template_id
node_groups.append(ng)
plugin, hadoop_version = whelpers.\
get_plugin_and_hadoop_version(request)
ct_shares = []
if "ct_shares" in context:
ct_shares = context["ct_shares"]
domain = context.get('dns_domain_name', None)
if domain == 'None':
domain = None
# TODO(nkonovalov): Fix client to support default_image_id
saharaclient.cluster_template_create(
request,
context["general_cluster_template_name"],
plugin,
hadoop_version,
context["general_description"],
configs_dict,
node_groups,
context["anti_affinity_info"],
use_autoconfig=context['general_use_autoconfig'],
shares=ct_shares,
is_public=context['general_is_public'],
is_protected=context['general_is_protected'],
domain_name=domain
)
hlps = helpers.Helpers(request)
if hlps.is_from_guide():
request.session["guide_cluster_template_name"] = (
context["general_cluster_template_name"])
self.success_url = (
"horizon:project:data_processing.clusters:cluster_guide")
return True
except api_base.APIException as e:
self.error_description = str(e)
return False
except Exception:
exceptions.handle(request,
_("Cluster template creation failed"))
return False

View File

@ -1,115 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import json
from django.utils.translation import gettext_lazy as _
from horizon import exceptions
from horizon import forms
from sahara_dashboard.api import sahara as saharaclient
import sahara_dashboard.content.data_processing.clusters. \
cluster_templates.workflows.create as create_flow
import sahara_dashboard.content.data_processing.clusters. \
cluster_templates.workflows.copy as copy_flow
import sahara_dashboard.content.data_processing. \
utils.workflow_helpers as whelpers
from sahara_dashboard import utils
class EditClusterTemplate(copy_flow.CopyClusterTemplate):
success_message = _("Cluster Template %s updated")
entry_point = "generalconfigaction"
finalize_button_name = _("Update")
name = _("Edit Cluster Template")
def __init__(self, request, context_seed, entry_point, *args, **kwargs):
try:
super(EditClusterTemplate, self).__init__(request, context_seed,
entry_point, *args,
**kwargs)
for step in self.steps:
if isinstance(step, create_flow.GeneralConfig):
fields = step.action.fields
fields["cluster_template_name"].initial = (
self.template.name)
fields["cluster_template_id"] = forms.CharField(
widget=forms.HiddenInput(),
initial=self.cluster_template_id)
elif isinstance(step, create_flow.SelectDnsDomains):
fields = step.action.fields
fields["domain_name"].initial = self.template.domain_name
except Exception:
exceptions.handle(request,
_("Unable to fetch template to edit."))
def handle(self, request, context):
try:
node_groups = []
configs_dict = whelpers.parse_configs_from_context(context,
self.defaults)
ids = json.loads(context['ng_forms_ids'])
for id in ids:
name = context['ng_group_name_' + str(id)]
template_id = context['ng_template_id_' + str(id)]
count = context['ng_count_' + str(id)]
raw_ng = context.get("ng_serialized_" + str(id))
if raw_ng and raw_ng != 'null':
ng = json.loads(utils.deserialize(str(raw_ng)))
else:
ng = dict()
ng["name"] = name
ng["count"] = count
if template_id and template_id != u'None':
ng["node_group_template_id"] = template_id
node_groups.append(ng)
plugin, hadoop_version = whelpers. \
get_plugin_and_hadoop_version(request)
ct_shares = []
if "ct_shares" in context:
ct_shares = context["ct_shares"]
domain = context.get('dns_domain_name', None)
if domain == 'None':
domain = None
saharaclient.cluster_template_update(
request=request,
ct_id=self.cluster_template_id,
name=context["general_cluster_template_name"],
plugin_name=plugin,
hadoop_version=hadoop_version,
description=context["general_description"],
cluster_configs=configs_dict,
node_groups=node_groups,
anti_affinity=context["anti_affinity_info"],
use_autoconfig=context['general_use_autoconfig'],
shares=ct_shares,
is_public=context['general_is_public'],
is_protected=context['general_is_protected'],
domain_name=domain
)
return True
except exceptions.Conflict as e:
self.error_description = str(e)
return False
except Exception:
exceptions.handle(request,
_("Cluster template update failed"))
return False

View File

@ -1,314 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from django.http import Http404 # noqa
from django.template.loader import render_to_string
from django.utils.translation import gettext_lazy as _
from django.utils.translation import ngettext_lazy
from saharaclient.api import base as api_base
from horizon import messages
from horizon import tables
from horizon.tables import base as tables_base
from horizon.tabs import base as tabs_base
from sahara_dashboard.api import sahara as saharaclient
from sahara_dashboard.content.data_processing \
import tables as sahara_table
from sahara_dashboard.content.data_processing.utils \
import acl as acl_utils
from sahara_dashboard.content.data_processing.utils import helpers
SAHARA_VERIFICATION_DISABLED = saharaclient.SAHARA_VERIFICATION_DISABLED
class ClustersFilterAction(tables.FilterAction):
filter_type = "server"
filter_choices = (('name', _("Name"), True),
('status', _("Status"), True))
class ClusterGuide(tables.LinkAction):
name = "cluster_guide"
verbose_name = _("Cluster Creation Guide")
url = "horizon:project:data_processing.clusters:cluster_guide"
class CreateCluster(tables.LinkAction):
name = "create"
verbose_name = _("Launch Cluster")
url = "horizon:project:data_processing.clusters:create-cluster"
classes = ("ajax-modal",)
icon = "plus"
class ScaleCluster(tables.LinkAction):
name = "scale"
verbose_name = _("Scale Cluster")
url = "horizon:project:data_processing.clusters:scale"
classes = ("ajax-modal", "btn-edit")
def allowed(self, request, cluster=None):
return cluster.status == "Active"
class DeleteCluster(tables.DeleteAction):
@staticmethod
def action_present(count):
return ngettext_lazy(
u"Delete Cluster",
u"Delete Clusters",
count
)
@staticmethod
def action_past(count):
return ngettext_lazy(
u"Deleted Cluster",
u"Deleted Clusters",
count
)
def delete(self, request, obj_id):
saharaclient.cluster_delete(request, obj_id)
class CheckClusterAction(tables.BatchAction):
name = 'check_cluster'
@staticmethod
def action_present(count):
return ngettext_lazy(
u"Start Verification",
u"Start Verifications",
count
)
@staticmethod
def action_past(count):
return ngettext_lazy(
u"Started Verification",
u"Started Verifications",
count
)
def action(self, request, datum_id):
saharaclient.verification_update(request, datum_id, status='START')
class ForceDeleteCluster(tables.DeleteAction):
name = "force_delete"
@staticmethod
def action_present(count):
return ngettext_lazy(
u"Force Delete Cluster",
u"Force Delete Clusters",
count
)
@staticmethod
def action_past(count):
return ngettext_lazy(
u"Force Deleted Cluster",
u"Force Deleted Clusters",
count
)
def delete(self, request, obj_id):
saharaclient.cluster_force_delete(request, obj_id)
class UpdateClusterShares(tables.LinkAction):
name = "update_shares"
verbose_name = _("Update Shares")
url = "horizon:project:data_processing.clusters:update-shares"
classes = ("ajax-modal", "btn-edit")
def allowed(self, request, cluster=None):
return cluster.status == "Active"
class UpdateRow(tables.Row):
ajax = True
def get_data(self, request, instance_id):
try:
return saharaclient.cluster_get(request, instance_id)
except api_base.APIException as e:
if e.error_code == 404:
raise Http404
else:
messages.error(request,
_("Unable to update row"))
def get_instances_count(cluster):
return sum([len(ng["instances"])
for ng in cluster.node_groups])
class RichErrorCell(tables_base.Cell):
@property
def status(self):
# The error cell values becomes quite complex and cannot be handled
# correctly with STATUS_CHOICES. Handling that explicitly.
status = self.datum.status.lower()
health = get_health_status_info(self.datum).lower()
# error status always terminal
if status == "error":
return False
if health == 'checking' or health == 'unknown':
return None
if status == "active":
return True
return None
def get_rich_status_info(cluster):
return {
"status": cluster.status,
"status_description": cluster.status_description
}
def rich_status_filter(status_dict):
if status_dict['status'].lower() not in ['error', 'active']:
return render_to_string("clusters/_in_progress.html", status_dict)
# Render the status "as is" if no description is provided.
if status_dict["status_description"]:
# Error is rendered with a template containing an error description.
return render_to_string("clusters/_rich_status.html", status_dict)
return status_dict["status"]
class ConfigureCluster(tables.LinkAction):
name = "configure"
verbose_name = _("Configure Cluster")
url = "horizon:project:data_processing.clusters:configure-cluster"
classes = ("ajax-modal", "configure-cluster-btn")
icon = "plus"
attrs = {"style": "display: none"}
class MakePublic(acl_utils.MakePublic):
def change_rule_method(self, request, datum_id, **update_kwargs):
saharaclient.cluster_update_acl_rules(
request, datum_id, **update_kwargs)
class MakePrivate(acl_utils.MakePrivate):
def change_rule_method(self, request, datum_id, **update_kwargs):
saharaclient.cluster_update_acl_rules(
request, datum_id, **update_kwargs)
class MakeProtected(acl_utils.MakeProtected):
def change_rule_method(self, request, datum_id, **update_kwargs):
saharaclient.cluster_update_acl_rules(
request, datum_id, **update_kwargs)
class MakeUnProtected(acl_utils.MakeUnProtected):
def change_rule_method(self, request, datum_id, **update_kwargs):
saharaclient.cluster_update_acl_rules(
request, datum_id, **update_kwargs)
def get_health_status_info(cluster):
try:
return cluster.verification['status']
except (AttributeError, KeyError):
return 'UNKNOWN'
def get_health_filter(health):
if health == 'CHECKING':
return render_to_string("clusters/_in_progress.html", {
'status': _("Checking")})
mapper = {'GREEN': 'success', 'YELLOW': 'warning',
'RED': 'danger', 'CHECKING': 'info'}
label = mapper.get(health, 'default')
return render_to_string('clusters/_health_status.html',
{'status': health, 'label': label})
class ClustersTable(sahara_table.SaharaPaginateTabbedTable):
tab_name = 'cluster_tabs%sclusters_tab' % tabs_base.SEPARATOR
class UptimeColumn(tables.Column):
def get_data(self, cluster):
return helpers.Helpers(None).get_duration(cluster.created_at)
name = tables.Column("name",
verbose_name=_("Name"),
link=("horizon:project:data_processing."
"clusters:cluster-details"))
plugin = tables.Column("plugin_name",
verbose_name=_("Plugin"))
if saharaclient.VERSIONS.active == '2':
version_attr = "plugin_version"
else:
version_attr = "hadoop_version"
version = tables.Column(version_attr,
verbose_name=_("Version"))
# Status field need the whole cluster object to build the rich status.
status = tables.Column(get_rich_status_info,
verbose_name=_("Status"),
filters=(rich_status_filter,))
if not SAHARA_VERIFICATION_DISABLED:
health = tables.Column(get_health_status_info,
verbose_name=_("Health"),
filters=(get_health_filter,))
instances_count = tables.Column(get_instances_count,
verbose_name=_("Instances Count"))
uptime = UptimeColumn("uptime",
verbose_name=_("Uptime"))
class Meta(object):
name = "clusters"
verbose_name = _("Clusters")
row_class = UpdateRow
cell_class = RichErrorCell
status_columns = ["status"]
if not SAHARA_VERIFICATION_DISABLED:
status_columns.append("health")
table_actions = (ClusterGuide,
CreateCluster,
ConfigureCluster,
DeleteCluster,
ClustersFilterAction)
if saharaclient.VERSIONS.active == '2':
table_actions = table_actions + (ForceDeleteCluster,)
table_actions_menu = (MakePublic, MakePrivate,
MakeProtected, MakeUnProtected)
if SAHARA_VERIFICATION_DISABLED:
row_actions = (ScaleCluster,
UpdateClusterShares,
DeleteCluster, MakePublic, MakePrivate,
MakeProtected, MakeUnProtected)
else:
row_actions = (ScaleCluster,
UpdateClusterShares,
DeleteCluster, MakePublic, MakePrivate,
MakeProtected, MakeUnProtected,
CheckClusterAction)
if saharaclient.VERSIONS.active == '2':
row_actions = row_actions + (ForceDeleteCluster,)

View File

@ -1,266 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from oslo_log import log as logging
from django.utils.translation import gettext_lazy as _
from sahara_dashboard.api import sahara as saharaclient
from horizon import exceptions
from horizon import tables
from horizon import tabs
from openstack_dashboard.api import glance
from openstack_dashboard.api import neutron
from openstack_dashboard.api import nova
from sahara_dashboard.content.data_processing.clusters.clusters \
import tables as cluster_tables
from sahara_dashboard.content.data_processing \
import tabs as sahara_tabs
from sahara_dashboard.content.data_processing.utils \
import workflow_helpers as helpers
LOG = logging.getLogger(__name__)
class ClustersTab(sahara_tabs.SaharaTableTab):
table_classes = (cluster_tables.ClustersTable, )
name = _("Clusters")
slug = "clusters_tab"
template_name = "horizon/common/_detail_table.html"
def get_clusters_data(self):
try:
table = self._tables['clusters']
search_opts = {}
filter = self.get_server_filter_info(table.request, table)
if filter['value'] and filter['field']:
search_opts = {filter['field']: filter['value']}
clusters = saharaclient.cluster_list(self.request, search_opts)
except Exception:
clusters = []
exceptions.handle(self.request,
_("Unable to fetch cluster list"))
return clusters
class GeneralTab(tabs.Tab):
name = _("General Info")
slug = "cluster_details_tab"
template_name = "clusters/_details.html"
def get_context_data(self, request):
cluster_id = self.tab_group.kwargs['cluster_id']
cluster_info = {}
try:
sahara = saharaclient.client(request)
cluster = sahara.clusters.get(cluster_id)
for info_key, info_val in cluster.info.items():
for key, val in info_val.items():
if str(val).startswith(('http://', 'https://')):
cluster.info[info_key][key] = build_link(val)
try:
base_image = glance.image_get(request,
cluster.default_image_id)
except Exception:
exceptions.handle(
request, _("Unable to fetch base image details"))
base_image = {}
if getattr(cluster, 'cluster_template_id', None):
cluster_template = saharaclient.safe_call(
sahara.cluster_templates.get,
cluster.cluster_template_id)
else:
cluster_template = None
try:
if getattr(cluster, 'neutron_management_network', None):
net_id = cluster.neutron_management_network
network = neutron.network_get(request, net_id)
net_name = network.name_or_id
else:
net_name = None
except Exception:
exceptions.handle(
request, _("Unable to fetch network details"))
net_name = None
cluster_info.update({"cluster": cluster,
"base_image": base_image,
"cluster_template": cluster_template,
"network": net_name})
if saharaclient.VERSIONS.active == '2':
cluster_info["cluster"].hadoop_version = (
cluster_info["cluster"].plugin_version
)
except Exception as e:
LOG.error("Unable to fetch cluster details: %s" % str(e))
return cluster_info
class ClusterConfigsDetails(tabs.Tab):
name = _("Configuration Details")
slug = "cluster_configs_details_tab"
template_name = (
"clusters/_cluster_configs_details.html")
def get_context_data(self, request):
cluster_id = self.tab_group.kwargs['cluster_id']
cluster = {}
try:
sahara = saharaclient.client(request)
cluster = sahara.clusters.get(cluster_id)
except Exception as e:
LOG.error("Unable to fetch cluster details: %s" % str(e))
return {'cluster': cluster}
def build_link(url):
return "<a href='" + url + "' target=\"_blank\">" + url + "</a>"
class NodeGroupsTab(tabs.Tab):
name = _("Node Groups")
slug = "cluster_nodegroups_tab"
template_name = "clusters/_nodegroups_details.html"
def get_context_data(self, request):
cluster_id = self.tab_group.kwargs['cluster_id']
try:
sahara = saharaclient.client(request)
cluster = sahara.clusters.get(cluster_id)
for ng in cluster.node_groups:
if ng["flavor_id"]:
ng["flavor_name"] = (
nova.flavor_get(request, ng["flavor_id"]).name)
if ng["floating_ip_pool"]:
ng["floating_ip_pool_name"] = (
self._get_floating_ip_pool_name(
request, ng["floating_ip_pool"]))
if ng.get("node_group_template_id", None):
ng["node_group_template"] = saharaclient.safe_call(
sahara.node_group_templates.get,
ng["node_group_template_id"])
ng["security_groups_full"] = helpers.get_security_groups(
request, ng["security_groups"])
except Exception:
cluster = {}
exceptions.handle(request,
_("Unable to get node group details."))
return {"cluster": cluster}
def _get_floating_ip_pool_name(self, request, pool_id):
pools = [pool for pool in neutron.floating_ip_pools_list(
request) if pool.id == pool_id]
return pools[0].name if pools else pool_id
class Instance(object):
def __init__(self, name=None, id=None, internal_ip=None,
management_ip=None):
self.name = name
self.id = id
self.internal_ip = internal_ip
self.management_ip = management_ip
class InstancesTable(tables.DataTable):
name = tables.Column("name",
link="horizon:project:instances:detail",
verbose_name=_("Name"))
internal_ip = tables.Column("internal_ip",
verbose_name=_("Internal IP"))
management_ip = tables.Column("management_ip",
verbose_name=_("Management IP"))
class Meta(object):
name = "cluster_instances"
verbose_name = _("Cluster Instances")
class InstancesTab(tabs.TableTab):
name = _("Instances")
slug = "cluster_instances_tab"
template_name = "clusters/_instances_details.html"
table_classes = (InstancesTable, )
def get_cluster_instances_data(self):
cluster_id = self.tab_group.kwargs['cluster_id']
try:
sahara = saharaclient.client(self.request)
cluster = sahara.clusters.get(cluster_id)
instances = []
for ng in cluster.node_groups:
for instance in ng["instances"]:
instances.append(Instance(
name=instance["instance_name"],
id=instance["instance_id"],
internal_ip=instance.get("internal_ip",
"Not assigned"),
management_ip=instance.get("management_ip",
"Not assigned")))
except Exception:
instances = []
exceptions.handle(self.request,
_("Unable to fetch instance details."))
return instances
class EventLogTab(tabs.Tab):
name = _("Cluster Events")
slug = "cluster_event_log"
template_name = "clusters/_event_log.html"
def get_context_data(self, request, **kwargs):
cluster_id = self.tab_group.kwargs['cluster_id']
kwargs["cluster_id"] = cluster_id
kwargs['data_update_url'] = request.get_full_path()
return kwargs
class HealthChecksTab(tabs.Tab):
name = _("Cluster health checks")
slug = 'cluster_health_checks'
template_name = "clusters/_health_checks_table.html"
def get_context_data(self, request, **kwargs):
cluster_id = self.tab_group.kwargs['cluster_id']
kwargs['cluster_id'] = cluster_id
kwargs['data_update_url'] = request.get_full_path()
return kwargs
class ClusterDetailsTabs(tabs.TabGroup):
slug = "cluster_details"
if saharaclient.SAHARA_VERIFICATION_DISABLED:
tabs = (GeneralTab, ClusterConfigsDetails, NodeGroupsTab, InstancesTab,
EventLogTab)
else:
tabs = (GeneralTab, ClusterConfigsDetails, NodeGroupsTab, InstancesTab,
EventLogTab, HealthChecksTab)
sticky = True

View File

@ -1,146 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from django.urls import reverse
from oslo_serialization import jsonutils
from openstack_dashboard import api as os_api
from sahara_dashboard import api
from sahara_dashboard.test import helpers as test
from sahara_dashboard.test.helpers import IsHttpRequest
INDEX_URL = reverse('horizon:project:data_processing.clusters:clusters-tab')
DETAILS_URL = reverse(
'horizon:project:data_processing.clusters:details', args=['id'])
class DataProcessingClusterTests(test.TestCase):
@test.create_mocks({api.sahara: ('cluster_template_list',
'image_list',
'cluster_list',
'nodegroup_template_list')})
def test_index(self):
self.mock_cluster_list.return_value = self.clusters.list()
res = self.client.get(INDEX_URL)
self.mock_cluster_list.assert_called_once_with(IsHttpRequest(), {})
self.assertTemplateUsed(res, 'clusters/index.html')
self.assertContains(res, 'Clusters')
self.assertContains(res, 'Name')
@test.create_mocks({api.sahara: ('cluster_template_list', 'image_list',
'plugin_get_version_details'),
os_api.neutron: ('network_list',),
os_api.nova: ('keypair_list',)})
def test_launch_cluster_get_nodata(self):
self.mock_cluster_template_list.return_value = []
self.mock_image_list.return_value = []
url = reverse(
'horizon:project:data_processing.clusters:configure-cluster')
res = self.client.get("%s?plugin_name=shoes&hadoop_version=1.1" % url)
self.mock_cluster_template_list.assert_called_once_with(
IsHttpRequest())
self.mock_image_list.assert_called_once_with(IsHttpRequest())
self.assertContains(res, "No Images Available")
self.assertContains(res, "No Templates Available")
@test.create_mocks({api.sahara: ('cluster_get',)})
def test_event_log_tab(self):
cluster = self.clusters.list()[-1]
self.mock_cluster_get.return_value = cluster
url = reverse(
'horizon:project:data_processing.clusters:events', args=["cl2"])
res = self.client.get(url)
data = jsonutils.loads(res.content)
self.mock_cluster_get.assert_called_once_with(
IsHttpRequest(), "cl2", show_progress=True)
self.assertIn("provision_steps", data)
self.assertEqual(data["need_update"], False)
step_0 = data["provision_steps"][0]
self.assertEqual(2, step_0["completed"])
self.assertEqual(2, len(step_0["events"]))
for evt in step_0["events"]:
self.assertEqual(True, evt["successful"])
step_1 = data["provision_steps"][1]
self.assertEqual(3, step_1["completed"])
self.assertEqual(0, len(step_1["events"]))
@test.create_mocks({api.sahara: ('cluster_get', )})
def test_health_checks_tab_sc1(self):
cluster = self.clusters.list()[-1]
self.mock_cluster_get.return_value = cluster
url = reverse(
'horizon:project:data_processing.clusters:verifications',
args=["cl2"])
res = self.client.get(url)
data = jsonutils.loads(res.content)
self.mock_cluster_get.assert_called_once_with(
IsHttpRequest(), "cl2")
self.assertFalse(data['need_update'])
check0 = data['checks'][0]
check1 = data['checks'][1]
self.assertEqual('success', check0['label'])
self.assertEqual('danger', check1['label'])
self.assertEqual('GREEN', check0['status'])
self.assertEqual('RED', check1['status'])
self.assertEqual('0:07:40', check0['duration'])
@test.create_mocks({api.sahara: ('cluster_get', )})
def test_health_checks_tab_sc2(self):
cluster = self.clusters.list()[0]
cl1_id = 'ec9a0d28-5cfb-4028-a0b5-40afe23f1533'
self.mock_cluster_get.return_value = cluster
url = reverse(
'horizon:project:data_processing.clusters:verifications',
args=[cl1_id])
res = self.client.get(url)
data = jsonutils.loads(res.content)
self.mock_cluster_get.assert_called_once_with(
IsHttpRequest(), cl1_id)
self.assertTrue(data['need_update'])
check0 = data['checks'][0]
check1 = data['checks'][1]
self.assertEqual('info', check0['label'])
self.assertEqual('danger', check1['label'])
self.assertEqual('CHECKING', check0['status'])
self.assertEqual('RED', check1['status'])
self.assertEqual('Houston, we have a problem', check1['description'])
@test.create_mocks({api.sahara: ('cluster_list',
'cluster_delete')})
def test_delete(self):
cluster = self.clusters.first()
self.mock_cluster_list.return_value = self.clusters.list()
self.mock_cluster_delete.return_value = None
form_data = {'action': 'clusters__delete__%s' % cluster.id}
res = self.client.post(INDEX_URL, form_data)
self.mock_cluster_list.assert_called_once_with(
IsHttpRequest(), {})
self.mock_cluster_delete.assert_called_once_with(
IsHttpRequest(), cluster.id)
self.assertNoFormErrors(res)
self.assertRedirectsNoFollow(res, INDEX_URL)
self.assertMessageCount(success=1)

View File

@ -1,281 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import json
from django.http import HttpResponse
from django.urls import reverse
from django.utils.translation import gettext as _
from django.views.generic import base as django_base
from oslo_utils import timeutils
from saharaclient.api.base import APIException
from horizon import exceptions
from horizon import tabs
from horizon.utils import memoized
from horizon import workflows
from sahara_dashboard.api import sahara as saharaclient
import sahara_dashboard.content.data_processing.clusters.clusters. \
tables as c_tables
import sahara_dashboard.content.data_processing.clusters.clusters. \
tabs as _tabs
import sahara_dashboard.content.data_processing.clusters.clusters. \
workflows.create as create_flow
import sahara_dashboard.content.data_processing.clusters.clusters. \
workflows.scale as scale_flow
import sahara_dashboard.content.data_processing.clusters.clusters. \
workflows.update as update_flow
import sahara_dashboard.content.data_processing.utils.helpers as helpers
class ClusterDetailsView(tabs.TabView):
tab_group_class = _tabs.ClusterDetailsTabs
template_name = 'horizon/common/_detail.html'
page_title = "{{ cluster.name|default:cluster.id }}"
@memoized.memoized_method
def get_object(self):
cl_id = self.kwargs["cluster_id"]
try:
return saharaclient.cluster_get(self.request, cl_id)
except Exception:
msg = _('Unable to retrieve details for cluster "%s".') % cl_id
redirect = self.get_redirect_url()
exceptions.handle(self.request, msg, redirect=redirect)
def get_context_data(self, **kwargs):
context = super(ClusterDetailsView, self).get_context_data(**kwargs)
cluster = self.get_object()
context['cluster'] = cluster
context['url'] = self.get_redirect_url()
context['actions'] = self._get_actions(cluster)
return context
def _get_actions(self, cluster):
table = c_tables.ClustersTable(self.request)
return table.render_row_actions(cluster)
@staticmethod
def get_redirect_url():
return reverse("horizon:project:data_processing.clusters:index")
class ClusterEventsView(django_base.View):
@staticmethod
def _created_at_key(obj):
return timeutils.parse_isotime(obj["created_at"])
def get(self, request, *args, **kwargs):
cluster_id = kwargs.get("cluster_id")
time_helpers = helpers.Helpers(request)
try:
cluster = saharaclient.cluster_get(request, cluster_id,
show_progress=True)
node_group_mapping = {}
for node_group in cluster.node_groups:
node_group_mapping[node_group["id"]] = node_group["name"]
provision_steps = cluster.provision_progress
# Sort by create time
provision_steps = sorted(provision_steps,
key=ClusterEventsView._created_at_key,
reverse=True)
for step in provision_steps:
# Sort events of the steps also
step["events"] = sorted(step["events"],
key=ClusterEventsView._created_at_key,
reverse=True)
successful_events_count = 0
for event in step["events"]:
if event["node_group_id"]:
event["node_group_name"] = node_group_mapping[
event["node_group_id"]]
event_result = _("Unknown")
if event["successful"] is True:
successful_events_count += 1
event_result = _("Completed Successfully")
elif event["successful"] is False:
event_result = _("Failed")
event["result"] = event_result
if not event["event_info"]:
event["event_info"] = _("No info available")
step["duration"] = time_helpers.get_duration(
step["created_at"],
step["updated_at"])
step['started_at'] = time_helpers.to_time_zone(
step["created_at"], localize=True)
result = _("In progress")
step["completed"] = successful_events_count
if step["successful"] is True:
step["completed"] = step["total"]
result = _("Completed Successfully")
elif step["successful"] is False:
result = _("Failed")
step["result"] = result
status = cluster.status.lower()
need_update = status not in ("active", "error")
except APIException:
# Cluster is not available. Returning empty event log.
need_update = False
provision_steps = []
context = {"provision_steps": provision_steps,
"need_update": need_update}
return HttpResponse(json.dumps(context),
content_type='application/json')
class ClusterHealthChecksView(django_base.View):
_date_format = "%Y-%m-%dT%H:%M:%S"
_status_in_progress = 'CHECKING'
def _get_checks(self, cluster):
try:
return cluster.verification['checks']
except (AttributeError, KeyError):
return []
def get(self, request, *args, **kwargs):
time_helpers = helpers.Helpers(request)
cluster_id = kwargs.get("cluster_id")
need_update, not_done_count, checks = False, 0, []
mapping_to_label_type = {'red': 'danger', 'yellow': 'warning',
'green': 'success', 'checking': 'info'}
try:
cluster = saharaclient.cluster_get(request, cluster_id)
for check in self._get_checks(cluster):
check['label'] = mapping_to_label_type.get(
check['status'].lower())
if not check['description']:
check['description'] = _("No description")
if check['status'] == self._status_in_progress:
not_done_count += 1
check['duration'] = time_helpers.get_duration(
check['created_at'], check['updated_at'])
checks.append(check)
except APIException:
need_update = False
checks = []
if not_done_count > 0:
need_update = True
context = {"checks": checks,
"need_update": need_update}
return HttpResponse(json.dumps(context),
content_type='application/json')
class CreateClusterView(workflows.WorkflowView):
workflow_class = create_flow.CreateCluster
success_url = \
"horizon:project:data_processing.clusters:create-cluster"
classes = ("ajax-modal",)
template_name = "clusters/create.html"
page_title = _("Launch Cluster")
class ConfigureClusterView(workflows.WorkflowView):
workflow_class = create_flow.ConfigureCluster
success_url = "horizon:project:data_processing.clusters:index"
template_name = "clusters/configure.html"
page_title = _("Configure Cluster")
def get_initial(self):
initial = super(ConfigureClusterView, self).get_initial()
initial.update(self.kwargs)
return initial
class ScaleClusterView(workflows.WorkflowView):
workflow_class = scale_flow.ScaleCluster
success_url = "horizon:project:data_processing.clusters:index"
classes = ("ajax-modal",)
template_name = "clusters/scale.html"
page_title = _("Scale Cluster")
def get_context_data(self, **kwargs):
context = super(ScaleClusterView, self)\
.get_context_data(**kwargs)
context["cluster_id"] = kwargs["cluster_id"]
return context
def get_object(self, *args, **kwargs):
if not hasattr(self, "_object"):
template_id = self.kwargs['cluster_id']
try:
template = saharaclient.cluster_template_get(self.request,
template_id)
except Exception:
template = None
exceptions.handle(self.request,
_("Unable to fetch cluster template."))
self._object = template
return self._object
def get_initial(self):
initial = super(ScaleClusterView, self).get_initial()
initial.update({'cluster_id': self.kwargs['cluster_id']})
return initial
class UpdateClusterSharesView(workflows.WorkflowView):
workflow_class = update_flow.UpdateShares
success_url = "horizon:project:data_processing.clusters"
classes = ("ajax-modal",)
template_name = "clusters/update.html"
page_title = _("Update Cluster Shares")
def get_context_data(self, **kwargs):
context = super(UpdateClusterSharesView, self)\
.get_context_data(**kwargs)
context["cluster_id"] = kwargs["cluster_id"]
return context
def get_object(self, *args, **kwargs):
if not hasattr(self, "_object"):
cluster_id = self.kwargs['cluster_id']
try:
cluster = saharaclient.cluster_get(self.request, cluster_id)
except Exception:
cluster = None
exceptions.handle(self.request,
_("Unable to fetch cluster."))
self._object = cluster
return self._object
def get_initial(self):
initial = super(UpdateClusterSharesView, self).get_initial()
initial.update({
'cluster_id': self.kwargs['cluster_id'],
'cluster': self.get_object()})
return initial

View File

@ -1,246 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from horizon import exceptions
from horizon import forms
from horizon import workflows
from django.utils.translation import gettext_lazy as _
from saharaclient.api import base as api_base
from openstack_dashboard.api import nova
from sahara_dashboard.content.data_processing.utils \
import acl as acl_utils
from sahara_dashboard.content.data_processing.utils import neutron_support
import sahara_dashboard.content.data_processing.utils. \
workflow_helpers as whelpers
from sahara_dashboard.api import sahara as saharaclient
import sahara_dashboard.content.data_processing.clusters. \
cluster_templates.workflows.create as t_flows
KEYPAIR_IMPORT_URL = "horizon:project:key_pairs:import"
BASE_IMAGE_URL = "horizon:project:data_processing.clusters:register"
TEMPLATE_UPLOAD_URL = (
"horizon:project:data_processing.clusters:upload_file")
class SelectPluginAction(t_flows.SelectPluginAction):
class Meta(object):
name = _("Select plugin and hadoop version for cluster")
help_text_template = "clusters/_create_general_help.html"
class SelectPlugin(t_flows.SelectPlugin):
action_class = SelectPluginAction
class CreateCluster(t_flows.CreateClusterTemplate):
slug = "create_cluster"
name = _("Launch Cluster")
success_url = "horizon:project:data_processing.clusters:clusters-tab"
default_steps = (SelectPlugin,)
class GeneralConfigAction(workflows.Action):
populate_neutron_management_network_choices = \
neutron_support.populate_neutron_management_network_choices
hidden_configure_field = forms.CharField(
required=False,
widget=forms.HiddenInput(attrs={"class": "hidden_configure_field"}))
hidden_to_delete_field = forms.CharField(
required=False,
widget=forms.HiddenInput(attrs={"class": "hidden_to_delete_field"}))
cluster_name = forms.CharField(label=_("Cluster Name"))
description = forms.CharField(label=_("Description"),
required=False,
widget=forms.Textarea(attrs={'rows': 4}))
cluster_template = forms.DynamicChoiceField(
label=_("Cluster Template"),
initial=(None, "None"),
add_item_link=TEMPLATE_UPLOAD_URL)
cluster_count = forms.IntegerField(min_value=1,
label=_("Cluster Count"),
initial=1,
help_text=(
_("Number of clusters to launch.")))
image = forms.DynamicChoiceField(label=_("Base Image"),
add_item_link=BASE_IMAGE_URL)
keypair = forms.DynamicChoiceField(
label=_("Keypair"),
required=False,
help_text=_("Which keypair to use for authentication."),
add_item_link=KEYPAIR_IMPORT_URL)
def __init__(self, request, *args, **kwargs):
super(GeneralConfigAction, self).__init__(request, *args, **kwargs)
plugin, hadoop_version = whelpers.\
get_plugin_and_hadoop_version(request)
if saharaclient.base.is_service_enabled(request, 'network'):
self.fields["neutron_management_network"] = forms.ChoiceField(
label=_("Neutron Management Network"),
choices=self.populate_neutron_management_network_choices(
request, {})
)
self.fields['is_public'] = acl_utils.get_is_public_form(
_("cluster"))
self.fields['is_protected'] = acl_utils.get_is_protected_form(
_("cluster"))
self.fields["plugin_name"] = forms.CharField(
widget=forms.HiddenInput(),
initial=plugin
)
self.fields["hadoop_version"] = forms.CharField(
widget=forms.HiddenInput(),
initial=hadoop_version
)
def populate_image_choices(self, request, context):
return whelpers.populate_image_choices(self, request, context)
def populate_keypair_choices(self, request, context):
try:
keypairs = nova.keypair_list(request)
except Exception:
keypairs = []
exceptions.handle(request,
_("Unable to fetch keypair choices."))
keypair_list = [(kp.name, kp.name) for kp in keypairs]
keypair_list.insert(0, ("", _("No keypair")))
return keypair_list
def populate_cluster_template_choices(self, request, context):
templates = saharaclient.cluster_template_list(request)
plugin, hadoop_version = whelpers.\
get_plugin_and_hadoop_version(request)
choices = []
for template in templates:
version = (
getattr(template, "hadoop_version", None) or
template.plugin_version
)
if version == hadoop_version and template.plugin_name == plugin:
choices.append((template.id, template.name))
if not choices:
choices.append(("", _("No Templates Available")))
# cluster_template_id comes from cluster templates table, when
# Create Cluster from template is clicked there
selected_template_name = None
req = request.GET or request.POST
if req.get("cluster_template_name"):
selected_template_name = (
req.get("cluster_template_name"))
if selected_template_name:
for template in templates:
if template.name == selected_template_name:
selected_template_id = template.id
break
else:
selected_template_id = (
req.get("cluster_template_id", None))
for template in templates:
if template.id == selected_template_id:
self.fields['cluster_template'].initial = template.id
return choices
def get_help_text(self):
extra = dict()
plugin, hadoop_version = whelpers.\
get_plugin_and_hadoop_version(self.request)
extra["plugin_name"] = plugin
extra["hadoop_version"] = hadoop_version
return super(GeneralConfigAction, self).get_help_text(extra)
def clean(self):
cleaned_data = super(GeneralConfigAction, self).clean()
if cleaned_data.get("hidden_configure_field", None) \
== "create_nodegroup":
self._errors = dict()
return cleaned_data
class Meta(object):
name = _("Configure Cluster")
help_text_template = "clusters/_configure_general_help.html"
class GeneralConfig(workflows.Step):
action_class = GeneralConfigAction
contributes = ("hidden_configure_field", )
def contribute(self, data, context):
for k, v in data.items():
context["general_" + k] = v
return context
class ConfigureCluster(whelpers.StatusFormatMixin, workflows.Workflow):
slug = "configure_cluster"
name = _("Launch Cluster")
finalize_button_name = _("Launch")
success_message = _("Launched Cluster %s")
name_property = "general_cluster_name"
success_url = "horizon:project:data_processing.clusters:clusters-tab"
default_steps = (GeneralConfig, )
def handle(self, request, context):
try:
# TODO(nkonovalov) Implement AJAX Node Groups.
node_groups = None
plugin, hadoop_version = whelpers.\
get_plugin_and_hadoop_version(request)
cluster_template_id = context["general_cluster_template"] or None
user_keypair = context["general_keypair"] or None
image_id = context["general_image"] or None
saharaclient.cluster_create(
request,
context["general_cluster_name"],
plugin, hadoop_version,
cluster_template_id=cluster_template_id,
default_image_id=image_id,
description=context["general_description"],
node_groups=node_groups,
user_keypair_id=user_keypair,
count=context['general_cluster_count'],
net_id=context.get("general_neutron_management_network", None),
is_public=context['general_is_public'],
is_protected=context['general_is_protected']
)
return True
except api_base.APIException as e:
self.error_description = str(e)
return False
except Exception:
exceptions.handle(request,
_('Unable to create the cluster'))
return False

View File

@ -1,172 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import json
from django.utils.translation import gettext_lazy as _
from saharaclient.api import base as api_base
from horizon import exceptions
from sahara_dashboard.api import sahara as saharaclient
import sahara_dashboard.content.data_processing.clusters. \
cluster_templates.workflows.create as clt_create_flow
import sahara_dashboard.content.data_processing.clusters. \
clusters.workflows.create as cl_create_flow
from sahara_dashboard.content.data_processing.utils import workflow_helpers
from sahara_dashboard import utils
class NodeGroupsStep(clt_create_flow.ConfigureNodegroups):
pass
class ScaleCluster(cl_create_flow.ConfigureCluster,
workflow_helpers.StatusFormatMixin):
slug = "scale_cluster"
name = _("Scale Cluster")
finalize_button_name = _("Scale")
success_url = "horizon:project:data_processing.clusters:index"
default_steps = (NodeGroupsStep, )
def __init__(self, request, context_seed, entry_point, *args, **kwargs):
ScaleCluster._cls_registry = []
self.success_message = _("Scaled cluster successfully started.")
cluster_id = context_seed["cluster_id"]
try:
cluster = saharaclient.cluster_get(request, cluster_id)
plugin = cluster.plugin_name
if saharaclient.VERSIONS.active == '2':
version_attr = 'plugin_version'
else:
version_attr = 'hadoop_version'
hadoop_version = getattr(cluster, version_attr)
# Initialize deletable node groups.
deletable = dict()
for group in cluster.node_groups:
deletable[group["name"]] = "false"
request.GET = request.GET.copy()
request.GET.update({
"cluster_id": cluster_id,
"plugin_name": plugin,
version_attr: hadoop_version,
"deletable": deletable
})
super(ScaleCluster, self).__init__(request, context_seed,
entry_point, *args,
**kwargs)
# Initialize node groups.
for step in self.steps:
if not isinstance(step, clt_create_flow.ConfigureNodegroups):
continue
ng_action = step.action
template_ngs = cluster.node_groups
if 'forms_ids' in request.POST:
continue
ng_action.groups = []
for i, templ_ng in enumerate(template_ngs):
group_name = "group_name_%d" % i
template_id = "template_id_%d" % i
count = "count_%d" % i
serialized = "serialized_%d" % i
serialized_val = utils.serialize(json.dumps(
workflow_helpers.clean_node_group(templ_ng)))
ng_action.groups.append({
"name": templ_ng["name"],
"template_id": templ_ng["node_group_template_id"],
"count": templ_ng["count"],
"id": i,
"deletable": "false",
"serialized": serialized_val
})
workflow_helpers.build_node_group_fields(ng_action,
group_name,
template_id,
count,
serialized)
except Exception:
exceptions.handle(request,
_("Unable to fetch cluster to scale"))
def format_status_message(self, message):
# Scaling form requires special handling because it has no Cluster name
# in it's context
error_description = getattr(self, 'error_description', None)
if error_description:
return error_description
else:
return self.success_message
def handle(self, request, context):
cluster_id = request.GET["cluster_id"]
try:
cluster = saharaclient.cluster_get(request, cluster_id)
existing_node_groups = set([])
for ng in cluster.node_groups:
existing_node_groups.add(ng["name"])
scale_object = dict()
ids = json.loads(context["ng_forms_ids"])
for _id in ids:
name = context["ng_group_name_%s" % _id]
template_id = context["ng_template_id_%s" % _id]
count = context["ng_count_%s" % _id]
if name not in existing_node_groups:
if "add_node_groups" not in scale_object:
scale_object["add_node_groups"] = []
scale_object["add_node_groups"].append(
{"name": name,
"node_group_template_id": template_id,
"count": int(count)})
else:
old_count = None
for ng in cluster.node_groups:
if name == ng["name"]:
old_count = ng["count"]
break
if old_count != count:
if "resize_node_groups" not in scale_object:
scale_object["resize_node_groups"] = []
scale_object["resize_node_groups"].append(
{"name": name,
"count": int(count)}
)
except Exception:
scale_object = {}
exceptions.handle(request,
_("Unable to fetch cluster to scale."))
try:
saharaclient.cluster_scale(request, cluster_id, scale_object)
return True
except api_base.APIException as e:
self.error_description = str(e)
return False
except Exception:
exceptions.handle(request,
_("Scale cluster operation failed"))
return False

View File

@ -1,132 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from django.utils.translation import gettext_lazy as _
from saharaclient.api import base as api_base
from horizon import exceptions
from horizon import forms
from horizon import workflows
from sahara_dashboard.api import manila as manilaclient
from sahara_dashboard.api import sahara as saharaclient
import sahara_dashboard.content.data_processing. \
utils.workflow_helpers as whelpers
class SelectSharesAction(workflows.Action):
def __init__(self, request, *args, **kwargs):
super(SelectSharesAction, self).__init__(
request, *args, **kwargs)
possible_shares = self.get_possible_shares(request)
cluster_id = [x["cluster_id"] for x in args if "cluster_id" in x][0]
self.fields["cluster_id"] = forms.CharField(
widget=forms.HiddenInput(),
initial=cluster_id)
self.fields["shares"] = whelpers.MultipleShareChoiceField(
label=_("Select Shares"),
widget=whelpers.ShareWidget(choices=possible_shares),
required=False,
choices=possible_shares,
)
cluster = [x["cluster"] for x in args if "cluster" in x][0]
self.fields["shares"].initial = (
self._get_share_defaults(cluster.shares,
self.fields["shares"]))
def _get_share_defaults(self, cluster_shares, share_field):
values = dict()
choices = share_field.choices
for i, choice in enumerate(choices):
share_id = choice[0]
s = [s for s in cluster_shares if s['id'] == share_id]
if len(s) > 0:
path = s[0]["path"] if "path" in s[0] else ""
values["share_id_{0}".format(i)] = {
"id": s[0]["id"],
"path": path,
"access_level": s[0]["access_level"]
}
else:
values["share_id_{0}".format(i)] = {
"id": None,
"path": None,
"access_level": None
}
return values
def get_possible_shares(self, request):
try:
shares = manilaclient.share_list(request)
choices = [(s.id, s.name) for s in shares]
except Exception:
exceptions.handle(request, _("Failed to get list of shares"))
choices = []
return choices
def clean(self):
cleaned_data = super(SelectSharesAction, self).clean()
self._errors = dict()
return cleaned_data
class Meta(object):
name = _("Shares")
help_text = _("Select the manila shares for this cluster")
class SelectShares(workflows.Step):
action_class = SelectSharesAction
depends_on = ("cluster_id", "cluster")
def contribute(self, data, context):
post = self.workflow.request.POST
shares_details = []
for index in range(0, len(self.action.fields['shares'].choices) * 3):
if index % 3 == 0:
share = post.get("shares_{0}".format(index))
if share:
path = post.get("shares_{0}".format(index + 1))
permissions = post.get("shares_{0}".format(index + 2))
shares_details.append({
"id": share,
"path": path,
"access_level": permissions
})
context['cluster_shares'] = shares_details
return context
class UpdateShares(workflows.Workflow):
slug = "update_cluster_shares"
name = _("Update Cluster Shares")
success_message = _("Updated")
failure_message = _("Could not update cluster shares")
success_url = "horizon:project:data_processing.clusters:index"
default_steps = (SelectShares,)
def handle(self, request, context):
try:
saharaclient.cluster_update_shares(
request, context["cluster_id"], context["cluster_shares"])
return True
except api_base.APIException as e:
self.error_description = str(e)
return False
except Exception:
exceptions.handle(request,
_("Cluster share update failed."))
return False

View File

@ -1,118 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import json
from django.utils.translation import gettext_lazy as _
from horizon import exceptions
from horizon import forms
from horizon import messages
from openstack_dashboard.api import glance
from sahara_dashboard.api import sahara as saharaclient
class ImageForm(forms.SelfHandlingForm):
image_id = forms.CharField(widget=forms.HiddenInput())
tags_list = forms.CharField(widget=forms.HiddenInput())
user_name = forms.CharField(max_length=80, label=_("User Name"))
description = forms.CharField(max_length=80,
label=_("Description"),
required=False,
widget=forms.Textarea(attrs={'rows': 4}))
def handle(self, request, data):
try:
image_id = data['image_id']
user_name = data['user_name']
desc = data['description']
saharaclient.image_update(request, image_id, user_name, desc)
image_tags = json.loads(data["tags_list"])
saharaclient.image_tags_update(request, image_id, image_tags)
updated_image = saharaclient.image_get(request, image_id)
messages.success(request,
_("Successfully updated image."))
return updated_image
except Exception:
exceptions.handle(request,
_("Failed to update image."))
return False
class EditTagsForm(ImageForm):
image_id = forms.CharField(widget=forms.HiddenInput())
class RegisterImageForm(ImageForm):
image_id = forms.ChoiceField(label=_("Image"))
def __init__(self, request, *args, **kwargs):
super(RegisterImageForm, self).__init__(request, *args, **kwargs)
self._populate_image_id_choices()
def _populate_image_id_choices(self):
images = self._get_available_images(self.request)
choices = [(image.id, image.name)
for image in images
if image.to_dict()['properties'].get(
"image_type") != "snapshot"]
if choices:
choices.insert(0, ("", _("Select Image")))
else:
choices.insert(0, ("", _("No images available.")))
self.fields['image_id'].choices = choices
def _get_images(self, request, filter):
try:
images, _more, _prev = (
glance.image_list_detailed(request, filters=filter))
except Exception:
images = []
exceptions.handle(request,
_("Unable to retrieve images with filter %s.") %
filter)
return images
def _get_public_images(self, request):
filter = {"is_public": True,
"status": "active"}
return self._get_images(request, filter)
def _get_tenant_images(self, request):
filter = {"owner": request.user.tenant_id,
"status": "active"}
return self._get_images(request, filter)
def _get_available_images(self, request):
images = self._get_tenant_images(request)
if request.user.is_superuser:
images += self._get_public_images(request)
final_images = []
try:
image_ids = set(img.id for img in saharaclient.image_list(request))
except Exception:
image_ids = set()
exceptions.handle(request,
_("Unable to fetch available images."))
for image in images:
if (image not in final_images and
image.id not in image_ids and
image.container_format not in ('aki', 'ari')):
final_images.append(image)
return final_images

View File

@ -1,80 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from django import template
from django.utils.translation import gettext_lazy as _
from django.utils.translation import ngettext_lazy
from horizon import tables
from sahara_dashboard.api import sahara as saharaclient
class EditTagsAction(tables.LinkAction):
name = "edit_tags"
verbose_name = _("Edit Tags")
url = "horizon:project:data_processing.clusters:edit_tags"
classes = ("ajax-modal",)
def tags_to_string(image):
template_name = 'image_registry/_list_tags.html'
context = {"image": image}
return template.loader.render_to_string(template_name, context)
class RegisterImage(tables.LinkAction):
name = "register"
verbose_name = _("Register Image")
url = "horizon:project:data_processing.clusters:register"
classes = ("ajax-modal",)
icon = "plus"
class UnregisterImages(tables.DeleteAction):
name = "unregister"
@staticmethod
def action_present(count):
return ngettext_lazy(
u"Unregister Image",
u"Unregister Images",
count
)
@staticmethod
def action_past(count):
return ngettext_lazy(
u"Unregistered Image",
u"Unregistered Images",
count
)
def delete(self, request, obj_id):
saharaclient.image_unregister(request, obj_id)
class ImageRegistryTable(tables.DataTable):
name = tables.Column("name",
verbose_name=_("Image"),
link=("horizon:project:"
"images:images:detail"))
tags = tables.Column(tags_to_string,
verbose_name=_("Tags"))
user = tables.Column("username", verbose_name=_("User"))
class Meta(object):
name = "image_registry"
verbose_name = _("Image Registry")
table_actions = (RegisterImage, UnregisterImages,)
row_actions = (EditTagsAction, UnregisterImages,)

View File

@ -1,37 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from django.utils.translation import gettext_lazy as _
from horizon import exceptions
from horizon import tabs
from sahara_dashboard.api import sahara as saharaclient
from sahara_dashboard.content.data_processing.clusters.image_registry \
import tables as image_registry_tables
class ImageRegistryTab(tabs.TableTab):
table_classes = (image_registry_tables.ImageRegistryTable, )
name = _("Image Registry")
slug = "image_registry_tab"
template_name = "horizon/common/_detail_table.html"
def get_image_registry_data(self):
try:
images = saharaclient.image_list(self.request)
except Exception:
images = []
msg = _('Unable to retrieve image list')
exceptions.handle(self.request, msg)
return images

View File

@ -1,129 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from django.urls import reverse
from openstack_dashboard import api as dash_api
from sahara_dashboard import api
from sahara_dashboard.test import helpers as test
from sahara_dashboard.test.helpers import IsHttpRequest
INDEX_URL = reverse(
'horizon:project:data_processing.clusters:image-registry-tab')
REGISTER_URL = reverse(
'horizon:project:data_processing.clusters:register')
SUCCESS_URL = reverse(
'horizon:project:data_processing.clusters:index')
class DataProcessingImageRegistryTests(test.TestCase):
@test.create_mocks({api.sahara: ('cluster_template_list',
'image_list',
'cluster_list',
'nodegroup_template_list')})
def test_index(self):
self.mock_image_list.return_value = self.images.list()
res = self.client.get(INDEX_URL)
self.mock_image_list.assert_called_once_with(
IsHttpRequest())
self.assertTemplateUsed(res, 'clusters/index.html')
self.assertContains(res, 'Image Registry')
self.assertContains(res, 'Image')
self.assertContains(res, 'Tags')
@test.create_mocks({api.sahara: ('image_get',
'image_update',
'image_tags_update',
'image_list'),
dash_api.glance: ('image_list_detailed',)})
def test_register(self):
image = self.images.first()
image_id = image.id
test_username = 'myusername'
test_description = 'mydescription'
self.mock_image_get.return_value = image
self.mock_image_list_detailed.return_value = (
self.images.list(), False, False)
self.mock_image_update.return_value = True
self.mock_image_tags_update.return_value = True
self.mock_image_list.return_value = []
res = self.client.post(
REGISTER_URL,
{'image_id': image_id,
'user_name': test_username,
'description': test_description,
'tags_list': '{}'})
self.mock_image_list_detailed.assert_called_once_with(
IsHttpRequest(),
filters={'owner': self.user.id,
'status': 'active'})
self.mock_image_update.assert_called_once_with(
IsHttpRequest(), image_id, test_username, test_description)
self.mock_image_tags_update.assert_called_once_with(
IsHttpRequest(), image_id, {})
self.mock_image_list.assert_called_once_with(
IsHttpRequest())
self.assertNoFormErrors(res)
self.assertRedirectsNoFollow(res, SUCCESS_URL)
self.assertMessageCount(success=1)
@test.create_mocks({api.sahara: ('image_list',
'image_unregister')})
def test_unregister(self):
image = self.images.first()
self.mock_image_list.return_value = self.images.list()
self.mock_image_unregister.return_value = None
form_data = {'action': 'image_registry__unregister__%s' % image.id}
res = self.client.post(INDEX_URL, form_data)
self.mock_image_list.assert_called_once_with(
IsHttpRequest())
self.mock_image_unregister.assert_called_once_with(
IsHttpRequest(), image.id)
self.assertNoFormErrors(res)
self.assertRedirectsNoFollow(res, INDEX_URL)
self.assertMessageCount(success=1)
@test.create_mocks({api.sahara: ('image_get',
'image_update',
'image_tags_update')})
def test_edit_tags(self):
image = self.registered_images.first()
self.mock_image_get.return_value = image
self.mock_image_update.return_value = True
self.mock_image_tags_update.return_value = True
edit_tags_url = reverse(
'horizon:project:data_processing.clusters:edit_tags',
args=[image.id])
res = self.client.post(
edit_tags_url,
{'image_id': image.id,
'user_name': image.username,
'description': image.description,
'tags_list': '{"0": "mytag"}'})
self.mock_image_update.assert_called_once_with(
IsHttpRequest(), image.id, image.username,
image.description)
self.mock_image_tags_update.assert_called_once_with(
IsHttpRequest(), image.id, {"0": "mytag"})
self.assertNoFormErrors(res)
self.assertRedirectsNoFollow(res, SUCCESS_URL)
self.assertMessageCount(success=1)

View File

@ -1,109 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from collections import OrderedDict
import json
from django.urls import reverse_lazy
from django.utils.translation import gettext_lazy as _
from horizon import exceptions
from horizon import forms
from horizon.utils import memoized
from sahara_dashboard.api import sahara as saharaclient
from sahara_dashboard.content. \
data_processing.clusters.image_registry.forms import EditTagsForm
from sahara_dashboard.content. \
data_processing.clusters.image_registry.forms import RegisterImageForm
from sahara_dashboard import utils
def update_context_with_plugin_tags(request, context):
try:
plugins = saharaclient.plugin_list(request)
except Exception:
plugins = []
msg = _("Unable to process plugin tags")
exceptions.handle(request, msg)
plugins_object = dict()
for plugin in plugins:
plugins_object[plugin.name] = OrderedDict()
for version in sorted(plugin.versions, reverse=True,
key=utils.smart_sort_helper):
try:
details = saharaclient. \
plugin_get_version_details(request,
plugin.name,
version)
plugins_object[plugin.name][version] = (
details.required_image_tags)
except Exception:
msg = _("Unable to process plugin tags")
exceptions.handle(request, msg)
context["plugins"] = plugins_object
class EditTagsView(forms.ModalFormView):
form_class = EditTagsForm
template_name = 'image_registry/edit_tags.html'
success_url = reverse_lazy(
'horizon:project:data_processing.clusters:index')
page_title = _("Edit Image Tags")
def get_context_data(self, **kwargs):
context = super(EditTagsView, self).get_context_data(**kwargs)
context['image'] = self.get_object()
update_context_with_plugin_tags(self.request, context)
return context
@memoized.memoized_method
def get_object(self):
try:
image = saharaclient.image_get(self.request,
self.kwargs["image_id"])
except Exception:
image = None
msg = _("Unable to fetch the image details")
exceptions.handle(self.request, msg)
return image
def get_initial(self):
image = self.get_object()
return {"image_id": image.id,
"tags_list": json.dumps(image.tags),
"user_name": image.username,
"description": image.description}
class RegisterImageView(forms.ModalFormView):
form_class = RegisterImageForm
template_name = 'image_registry/register_image.html'
success_url = reverse_lazy(
'horizon:project:data_processing.clusters:index')
page_title = _("Register Image")
def get_context_data(self, **kwargs):
context = super(RegisterImageView, self).get_context_data(**kwargs)
context['action_url'] = ('horizon:project'
':data_processing.clusters:register')
update_context_with_plugin_tags(self.request, context)
return context
def get_initial(self):
# need this initialization to allow registration
# of images without tags
return {"tags_list": json.dumps([])}

View File

@ -1,165 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from django.utils.translation import gettext_lazy as _
from horizon import exceptions
from horizon import forms
from openstack_dashboard.api import neutron
from openstack_dashboard.dashboards.project.instances \
import utils as nova_utils
from oslo_serialization import jsonutils as json
from saharaclient.api import base as api_base
from sahara_dashboard.api import sahara as saharaclient
BASE_IMAGE_URL = "horizon:project:data_processing.clusters:register"
class ImportNodegroupTemplateFileForm(forms.SelfHandlingForm):
class Meta(object):
name = _("Import Node Group Template")
def __init__(self, *args, **kwargs):
self.next_view = kwargs.pop('next_view')
super(ImportNodegroupTemplateFileForm, self).__init__(
*args, **kwargs)
template_upload = forms.FileField(
label=_('Template File'),
required=True)
def handle(self, request, data):
kwargs = {'template_upload': data['template_upload']}
request.method = 'GET'
return self.next_view.as_view()(request, **kwargs)
class ImportNodegroupTemplateDetailsForm(forms.SelfHandlingForm):
class Meta(object):
name = _("Import Node Group Template")
template = forms.CharField(
widget=forms.widgets.HiddenInput)
name = forms.CharField(label=_("Name"),
required=False,
help_text=_("Name must be provided "
"either here or in the template. If "
"provided in both places, this one "
"will be used."))
security_groups = forms.MultipleChoiceField(
label=_("Security Groups"),
widget=forms.CheckboxSelectMultiple(),
help_text=_("Launch instances in these security groups. "
"Auto security group will be determined by the "
"value present in the imported template."),
required=False)
floating_ip_pool = forms.ChoiceField(
label=_("Floating IP Pool"),
required=False)
flavor = forms.ChoiceField(label=_("OpenStack Flavor"))
image_id = forms.DynamicChoiceField(label=_("Base Image"),
add_item_link=BASE_IMAGE_URL)
def _populate_image_choices(self, request, plugin, hadoop_version):
all_images = saharaclient.image_list(request)
details = saharaclient.plugin_get_version_details(request,
plugin,
hadoop_version)
return [(image.id, image.name) for image in all_images
if (set(details.required_image_tags).
issubset(set(image.tags)))]
def __init__(self, *args, **kwargs):
try:
request = args[0]
template_string = ""
if "template_upload" in kwargs:
template_upload = kwargs.pop('template_upload')
super(ImportNodegroupTemplateDetailsForm, self).__init__(
*args, **kwargs)
template_string = template_upload.read()
self.fields["template"].initial = template_string
else:
super(ImportNodegroupTemplateDetailsForm, self).__init__(
*args, **kwargs)
template_string = self.data["template"]
template_json = json.loads(template_string)
template_json = template_json["node_group_template"]
security_group_list = neutron.security_group_list(request)
security_group_choices = \
[(sg.id, sg.name) for sg in security_group_list]
self.fields["security_groups"].choices = security_group_choices
pools = neutron.floating_ip_pools_list(request)
pool_choices = [(pool.id, pool.name) for pool in pools]
pool_choices.insert(0, (None, "Do not assign floating IPs"))
self.fields["floating_ip_pool"].choices = pool_choices
flavors = nova_utils.flavor_list(request)
if flavors:
self.fields["flavor"].choices = nova_utils.sort_flavor_list(
request, flavors)
else:
self.fields["flavor"].choices = []
version = (template_json.get("hadoop_version", None) or
template_json["plugin_version"])
self.fields["image_id"].choices = \
self._populate_image_choices(request,
template_json["plugin_name"],
version)
except (ValueError, KeyError):
raise exceptions.BadRequest(_("Could not parse template"))
except Exception:
exceptions.handle(request)
def handle(self, request, data):
try:
template = data["template"]
template = json.loads(template)
template = template["node_group_template"]
if not data["name"] and "name" not in template.keys():
return False
if data["name"]:
template["name"] = data["name"]
template["security_groups"] = data["security_groups"]
template["floating_ip_pool"] = data["floating_ip_pool"]
template["flavor_id"] = data["flavor"]
template["image_id"] = data["image_id"]
saharaclient.nodegroup_template_create(request, **template)
return True
except api_base.APIException as e:
self.error_description = str(e)
return False
except Exception as e:
if isinstance(e, TypeError):
raise exceptions.BadRequest(
_("Template JSON contained invalid key"))
else:
raise exceptions.BadRequest(_("Could not parse template"))

Some files were not shown because too many files have changed in this diff Show More