Drop Heat related code from horizon
Orchestration tab in the admin info panel needs a discussion. It seems not to be covered by heat-dashboard yet. blueprint heat-dashboard-split-out Change-Id: I56e6edb1f2ac72e2f42d0e9f3291308e67f24cad
This commit is contained in:
parent
dee95a0dfd
commit
eac3e7032a
@ -30,8 +30,3 @@ As an administrative user, you can view information for OpenStack services.
|
|||||||
* :guilabel:`Network Agents`:
|
* :guilabel:`Network Agents`:
|
||||||
Displays the network agents active within the cluster, such as L3 and
|
Displays the network agents active within the cluster, such as L3 and
|
||||||
DHCP agents, and the status of each agent.
|
DHCP agents, and the status of each agent.
|
||||||
|
|
||||||
* :guilabel:`Orchestration Services`:
|
|
||||||
Displays information specific to the Orchestration service. Name,
|
|
||||||
engine id, host and topic are listed for each service, as well as its
|
|
||||||
activation status.
|
|
||||||
|
@ -116,7 +116,6 @@ You can also override existing methods with your own versions::
|
|||||||
|
|
||||||
NO = lambda *x: False
|
NO = lambda *x: False
|
||||||
|
|
||||||
tabs.HeatServiceTab.allowed = NO
|
|
||||||
tables.AssociateIP.allowed = NO
|
tables.AssociateIP.allowed = NO
|
||||||
tables.SimpleAssociateIP.allowed = NO
|
tables.SimpleAssociateIP.allowed = NO
|
||||||
tables.SimpleDisassociateIP.allowed = NO
|
tables.SimpleDisassociateIP.allowed = NO
|
||||||
|
@ -782,7 +782,6 @@ Default:
|
|||||||
'compute': 'nova_policy.json',
|
'compute': 'nova_policy.json',
|
||||||
'volume': 'cinder_policy.json',
|
'volume': 'cinder_policy.json',
|
||||||
'image': 'glance_policy.json',
|
'image': 'glance_policy.json',
|
||||||
'orchestration': 'heat_policy.json',
|
|
||||||
'network': 'neutron_policy.json',
|
'network': 'neutron_policy.json',
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1103,29 +1102,6 @@ Default:
|
|||||||
Used to customize features related to the image service, such as the list of
|
Used to customize features related to the image service, such as the list of
|
||||||
supported image formats.
|
supported image formats.
|
||||||
|
|
||||||
Heat
|
|
||||||
----
|
|
||||||
|
|
||||||
OPENSTACK_HEAT_STACK
|
|
||||||
~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
.. versionadded:: 9.0.0(Mitaka)
|
|
||||||
|
|
||||||
Default:
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
{
|
|
||||||
'enable_user_pass': True
|
|
||||||
}
|
|
||||||
|
|
||||||
A dictionary of settings to use with heat stacks. Currently, the only setting
|
|
||||||
available is "enable_user_pass", which can be used to disable the password
|
|
||||||
field while launching the stack. Currently HEAT API needs user password to
|
|
||||||
perform all the heat operations because in HEAT API trusts is not enabled by
|
|
||||||
default. So, this setting can be set as "False" in-case HEAT uses trusts by
|
|
||||||
default otherwise it needs to be set as "True".
|
|
||||||
|
|
||||||
Keystone
|
Keystone
|
||||||
--------
|
--------
|
||||||
|
|
||||||
|
@ -83,7 +83,7 @@ To start the Horizon development server use the command below
|
|||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
The default port for runserver is 8000 which is already consumed by
|
The default port for runserver is 8000 which might be already consumed by
|
||||||
heat-api-cfn in DevStack. If running in DevStack
|
heat-api-cfn in DevStack. If running in DevStack
|
||||||
``tox -e runserver -- localhost:9000`` will start the test server at
|
``tox -e runserver -- localhost:9000`` will start the test server at
|
||||||
``http://localhost:9000``
|
``http://localhost:9000``
|
||||||
|
@ -69,9 +69,6 @@ see https://docs.openstack.org/devstack/latest/
|
|||||||
SWIFT_REPLICAS=1
|
SWIFT_REPLICAS=1
|
||||||
SWIFT_DATA_DIR=$DEST/data/swift
|
SWIFT_DATA_DIR=$DEST/data/swift
|
||||||
|
|
||||||
# Enable Heat
|
|
||||||
enable_plugin heat https://git.openstack.org/openstack/heat
|
|
||||||
|
|
||||||
# Enable Neutron
|
# Enable Neutron
|
||||||
enable_plugin neutron https://git.openstack.org/openstack/neutron
|
enable_plugin neutron https://git.openstack.org/openstack/neutron
|
||||||
|
|
||||||
|
@ -22,7 +22,6 @@ System Requirements
|
|||||||
|
|
||||||
* `cinder <https://docs.openstack.org/cinder/latest/>`_: Block Storage
|
* `cinder <https://docs.openstack.org/cinder/latest/>`_: Block Storage
|
||||||
* `glance <https://docs.openstack.org/glance/latest/>`_: Image Management
|
* `glance <https://docs.openstack.org/glance/latest/>`_: Image Management
|
||||||
* `heat <https://docs.openstack.org/heat/latest/>`_: Orchestration
|
|
||||||
* `neutron <https://docs.openstack.org/neutron/latest/>`_: Networking
|
* `neutron <https://docs.openstack.org/neutron/latest/>`_: Networking
|
||||||
* `nova <https://docs.openstack.org/nova/latest/>`_: Compute
|
* `nova <https://docs.openstack.org/nova/latest/>`_: Compute
|
||||||
* `swift <https://docs.openstack.org/swift/latest/>`_: Object Storage
|
* `swift <https://docs.openstack.org/swift/latest/>`_: Object Storage
|
||||||
|
@ -18,7 +18,6 @@ sizes of server instances.
|
|||||||
manage-containers.rst
|
manage-containers.rst
|
||||||
manage-volumes.rst
|
manage-volumes.rst
|
||||||
manage-shares.rst
|
manage-shares.rst
|
||||||
stacks.rst
|
|
||||||
databases.rst
|
databases.rst
|
||||||
manage-lbaasv2.rst
|
manage-lbaasv2.rst
|
||||||
browser_support
|
browser_support
|
||||||
|
@ -50,11 +50,6 @@ The dashboard is generally installed on the controller node.
|
|||||||
(:ref:`dashboard-admin-tab`) and :guilabel:`Identity` tab
|
(:ref:`dashboard-admin-tab`) and :guilabel:`Identity` tab
|
||||||
(:ref:`dashboard-identity-tab`) are displayed.
|
(:ref:`dashboard-identity-tab`) are displayed.
|
||||||
|
|
||||||
.. note::
|
|
||||||
|
|
||||||
Some tabs, such as :guilabel:`Orchestration` and :guilabel:`Firewalls`,
|
|
||||||
only appear on the dashboard if they are properly configured.
|
|
||||||
|
|
||||||
.. _dashboard-project-tab:
|
.. _dashboard-project-tab:
|
||||||
|
|
||||||
OpenStack dashboard — Project tab
|
OpenStack dashboard — Project tab
|
||||||
@ -143,15 +138,6 @@ Network tab
|
|||||||
|
|
||||||
* :guilabel:`Firewall Rules`: Add and manage firewall rules.
|
* :guilabel:`Firewall Rules`: Add and manage firewall rules.
|
||||||
|
|
||||||
Orchestration tab
|
|
||||||
-----------------
|
|
||||||
|
|
||||||
* :guilabel:`Stacks`: Use the REST API to orchestrate multiple composite
|
|
||||||
cloud applications.
|
|
||||||
|
|
||||||
* :guilabel:`Resource Types`: Show a list of all the supported resource
|
|
||||||
types for HOT templates.
|
|
||||||
|
|
||||||
Object Store tab
|
Object Store tab
|
||||||
----------------
|
----------------
|
||||||
|
|
||||||
@ -234,9 +220,6 @@ System tab
|
|||||||
|
|
||||||
* :guilabel:`Network Agents`: View the network agents.
|
* :guilabel:`Network Agents`: View the network agents.
|
||||||
|
|
||||||
* :guilabel:`Orchestration Services`: View a list of all Orchestration
|
|
||||||
services.
|
|
||||||
|
|
||||||
* :guilabel:`Shares`: Use the following tabs to complete these tasks:
|
* :guilabel:`Shares`: Use the following tabs to complete these tasks:
|
||||||
|
|
||||||
* :guilabel:`Shares`: View, create, manage, and delete shares.
|
* :guilabel:`Shares`: View, create, manage, and delete shares.
|
||||||
|
@ -1,149 +0,0 @@
|
|||||||
========================
|
|
||||||
Launch and manage stacks
|
|
||||||
========================
|
|
||||||
|
|
||||||
OpenStack Orchestration is a service that you can use to
|
|
||||||
orchestrate multiple composite cloud applications. This
|
|
||||||
service supports the use of both the Amazon Web Services (AWS)
|
|
||||||
CloudFormation template format through both a Query API that
|
|
||||||
is compatible with CloudFormation and the native OpenStack
|
|
||||||
Heat Orchestration Template (HOT) format through a REST API.
|
|
||||||
|
|
||||||
These flexible template languages enable application
|
|
||||||
developers to describe and automate the deployment of
|
|
||||||
infrastructure, services, and applications. The templates
|
|
||||||
enable creation of most OpenStack resource types, such as
|
|
||||||
instances, floating IP addresses, volumes, security groups,
|
|
||||||
and users. Once created, the resources are referred to as
|
|
||||||
stacks.
|
|
||||||
|
|
||||||
The template languages are described in the `Template Guide
|
|
||||||
<https://docs.openstack.org/heat/latest/template_guide/>`_.
|
|
||||||
|
|
||||||
Launch a stack
|
|
||||||
~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
#. Log in to the dashboard.
|
|
||||||
#. Select the appropriate project from the drop down menu at the top left.
|
|
||||||
#. On the :guilabel:`Project` tab, open the :guilabel:`Orchestration` tab and
|
|
||||||
click :guilabel:`Stacks` category.
|
|
||||||
#. Click :guilabel:`Launch Stack`.
|
|
||||||
#. In the :guilabel:`Select Template` dialog box, specify the
|
|
||||||
following values:
|
|
||||||
|
|
||||||
+---------------------------------------+-------------------------------+
|
|
||||||
| :guilabel:`Template Source` | Choose the source of the |
|
|
||||||
| | template from the list. |
|
|
||||||
+---------------------------------------+-------------------------------+
|
|
||||||
| :guilabel:`Template URL/File/Data` | Depending on the source that |
|
|
||||||
| | you select, enter the URL, |
|
|
||||||
| | browse to the file location, |
|
|
||||||
| | or directly include the |
|
|
||||||
| | template. |
|
|
||||||
+---------------------------------------+-------------------------------+
|
|
||||||
| :guilabel:`Environment Source` | Choose the source of the |
|
|
||||||
| | environment from the list. |
|
|
||||||
| | The environment files contain |
|
|
||||||
| | additional settings for the |
|
|
||||||
| | stack. |
|
|
||||||
+---------------------------------------+-------------------------------+
|
|
||||||
| :guilabel:`Environment File/Data` | Depending on the source that |
|
|
||||||
| | you select, browse to the |
|
|
||||||
| | file location, directly |
|
|
||||||
| | include the environment |
|
|
||||||
+---------------------------------------+-------------------------------+
|
|
||||||
|
|
||||||
#. Click :guilabel:`Next`.
|
|
||||||
#. In the :guilabel:`Launch Stack` dialog box, specify the
|
|
||||||
following values:
|
|
||||||
|
|
||||||
+---------------------------------+---------------------------------+
|
|
||||||
| :guilabel:`Stack Name` | Enter a name to identify |
|
|
||||||
| | the stack. |
|
|
||||||
+---------------------------------+---------------------------------+
|
|
||||||
| :guilabel:`Creation Timeout` | Specify the number of minutes |
|
|
||||||
| :guilabel:`(minutes)` | that can elapse before the |
|
|
||||||
| | launch of the stack times out. |
|
|
||||||
+---------------------------------+---------------------------------+
|
|
||||||
| :guilabel:`Rollback On Failure` | Select this check box if you |
|
|
||||||
| | want the service to roll back |
|
|
||||||
| | changes if the stack fails to |
|
|
||||||
| | launch. |
|
|
||||||
+---------------------------------+---------------------------------+
|
|
||||||
| :guilabel:`Password for user` | Specify the password that |
|
|
||||||
| :guilabel:`"demo"` | the default user uses when the |
|
|
||||||
| | stack is created. |
|
|
||||||
+---------------------------------+---------------------------------+
|
|
||||||
| :guilabel:`DBUsername` | Specify the name of the |
|
|
||||||
| | database user. |
|
|
||||||
+---------------------------------+---------------------------------+
|
|
||||||
| :guilabel:`LinuxDistribution` | Specify the Linux distribution |
|
|
||||||
| | that is used in the stack. |
|
|
||||||
+---------------------------------+---------------------------------+
|
|
||||||
| :guilabel:`DBRootPassword` | Specify the root password for |
|
|
||||||
| | the database. |
|
|
||||||
+---------------------------------+---------------------------------+
|
|
||||||
| :guilabel:`KeyName` | Specify the name of the key pair|
|
|
||||||
| | to use to log in to the stack. |
|
|
||||||
+---------------------------------+---------------------------------+
|
|
||||||
| :guilabel:`DBName` | Specify the name of the |
|
|
||||||
| | database. |
|
|
||||||
+---------------------------------+---------------------------------+
|
|
||||||
| :guilabel:`DBPassword` | Specify the password of the |
|
|
||||||
| | database. |
|
|
||||||
+---------------------------------+---------------------------------+
|
|
||||||
| :guilabel:`InstanceType` | Specify the flavor for the |
|
|
||||||
| | instance. |
|
|
||||||
+---------------------------------+---------------------------------+
|
|
||||||
|
|
||||||
#. Click :guilabel:`Launch` to create a stack. The :guilabel:`Stacks`
|
|
||||||
tab shows the stack.
|
|
||||||
|
|
||||||
After the stack is created, click on the stack name to see the
|
|
||||||
following details:
|
|
||||||
|
|
||||||
Topology
|
|
||||||
The topology of the stack.
|
|
||||||
|
|
||||||
Overview
|
|
||||||
The parameters and details of the stack.
|
|
||||||
|
|
||||||
Resources
|
|
||||||
The resources used by the stack.
|
|
||||||
|
|
||||||
Events
|
|
||||||
The events related to the stack.
|
|
||||||
|
|
||||||
Template
|
|
||||||
The template for the stack.
|
|
||||||
|
|
||||||
Manage a stack
|
|
||||||
~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
#. Log in to the dashboard.
|
|
||||||
#. Select the appropriate project from the drop down menu at the top left.
|
|
||||||
#. On the :guilabel:`Project` tab, open the :guilabel:`Orchestration` tab and
|
|
||||||
click :guilabel:`Stacks` category.
|
|
||||||
#. Select the stack that you want to update.
|
|
||||||
#. Click :guilabel:`Change Stack Template`.
|
|
||||||
#. In the :guilabel:`Select Template` dialog box, select the
|
|
||||||
new template source or environment source.
|
|
||||||
#. Click :guilabel:`Next`.
|
|
||||||
|
|
||||||
The :guilabel:`Update Stack Parameters` window appears.
|
|
||||||
#. Enter new values for any parameters that you want to update.
|
|
||||||
#. Click :guilabel:`Update`.
|
|
||||||
|
|
||||||
Delete a stack
|
|
||||||
~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
When you delete a stack, you cannot undo this action.
|
|
||||||
|
|
||||||
#. Log in to the dashboard.
|
|
||||||
#. Select the appropriate project from the drop down menu at the top left.
|
|
||||||
#. On the :guilabel:`Project` tab, open the :guilabel:`Orchestration` tab and
|
|
||||||
click :guilabel:`Stacks` category.
|
|
||||||
#. Select the stack that you want to delete.
|
|
||||||
#. Click :guilabel:`Delete Stack`.
|
|
||||||
#. In the confirmation dialog box, click :guilabel:`Delete Stack`
|
|
||||||
to confirm the deletion.
|
|
@ -34,7 +34,6 @@ Keystone/Nova/Glance/Swift et. al.
|
|||||||
from openstack_dashboard.api import base
|
from openstack_dashboard.api import base
|
||||||
from openstack_dashboard.api import cinder
|
from openstack_dashboard.api import cinder
|
||||||
from openstack_dashboard.api import glance
|
from openstack_dashboard.api import glance
|
||||||
from openstack_dashboard.api import heat
|
|
||||||
from openstack_dashboard.api import keystone
|
from openstack_dashboard.api import keystone
|
||||||
from openstack_dashboard.api import network
|
from openstack_dashboard.api import network
|
||||||
from openstack_dashboard.api import neutron
|
from openstack_dashboard.api import neutron
|
||||||
@ -46,7 +45,6 @@ __all__ = [
|
|||||||
"base",
|
"base",
|
||||||
"cinder",
|
"cinder",
|
||||||
"glance",
|
"glance",
|
||||||
"heat",
|
|
||||||
"keystone",
|
"keystone",
|
||||||
"network",
|
"network",
|
||||||
"neutron",
|
"neutron",
|
||||||
|
@ -1,265 +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 contextlib
|
|
||||||
|
|
||||||
from django.conf import settings
|
|
||||||
from heatclient import client as heat_client
|
|
||||||
from heatclient.common import template_format
|
|
||||||
from heatclient.common import template_utils
|
|
||||||
from heatclient.common import utils as heat_utils
|
|
||||||
from oslo_serialization import jsonutils
|
|
||||||
import six
|
|
||||||
from six.moves.urllib import request
|
|
||||||
|
|
||||||
from horizon import exceptions
|
|
||||||
from horizon.utils import functions as utils
|
|
||||||
from horizon.utils.memoized import memoized
|
|
||||||
from openstack_dashboard.api import base
|
|
||||||
from openstack_dashboard.contrib.developer.profiler import api as profiler
|
|
||||||
|
|
||||||
|
|
||||||
def format_parameters(params):
|
|
||||||
parameters = {}
|
|
||||||
for count, p in enumerate(params, 1):
|
|
||||||
parameters['Parameters.member.%d.ParameterKey' % count] = p
|
|
||||||
parameters['Parameters.member.%d.ParameterValue' % count] = params[p]
|
|
||||||
return parameters
|
|
||||||
|
|
||||||
|
|
||||||
@memoized
|
|
||||||
def heatclient(request, password=None):
|
|
||||||
api_version = "1"
|
|
||||||
insecure = getattr(settings, 'OPENSTACK_SSL_NO_VERIFY', False)
|
|
||||||
cacert = getattr(settings, 'OPENSTACK_SSL_CACERT', None)
|
|
||||||
endpoint = base.url_for(request, 'orchestration')
|
|
||||||
kwargs = {
|
|
||||||
'token': request.user.token.id,
|
|
||||||
'insecure': insecure,
|
|
||||||
'ca_file': cacert,
|
|
||||||
'username': request.user.username,
|
|
||||||
'password': password
|
|
||||||
# 'timeout': args.timeout,
|
|
||||||
# 'ca_file': args.ca_file,
|
|
||||||
# 'cert_file': args.cert_file,
|
|
||||||
# 'key_file': args.key_file,
|
|
||||||
}
|
|
||||||
client = heat_client.Client(api_version, endpoint, **kwargs)
|
|
||||||
client.format_parameters = format_parameters
|
|
||||||
return client
|
|
||||||
|
|
||||||
|
|
||||||
@profiler.trace
|
|
||||||
def stacks_list(request, marker=None, sort_dir='desc', sort_key='created_at',
|
|
||||||
paginate=False, filters=None):
|
|
||||||
limit = getattr(settings, 'API_RESULT_LIMIT', 1000)
|
|
||||||
page_size = utils.get_page_size(request)
|
|
||||||
|
|
||||||
if paginate:
|
|
||||||
request_size = page_size + 1
|
|
||||||
else:
|
|
||||||
request_size = limit
|
|
||||||
|
|
||||||
kwargs = {'sort_dir': sort_dir, 'sort_key': sort_key}
|
|
||||||
if marker:
|
|
||||||
kwargs['marker'] = marker
|
|
||||||
|
|
||||||
if filters:
|
|
||||||
kwargs.update(filters)
|
|
||||||
if 'status' in kwargs:
|
|
||||||
kwargs['status'] = kwargs['status'].replace(' ', '_').upper()
|
|
||||||
|
|
||||||
stacks_iter = heatclient(request).stacks.list(limit=request_size,
|
|
||||||
**kwargs)
|
|
||||||
|
|
||||||
has_prev_data = False
|
|
||||||
has_more_data = False
|
|
||||||
stacks = list(stacks_iter)
|
|
||||||
|
|
||||||
if paginate:
|
|
||||||
if len(stacks) > page_size:
|
|
||||||
stacks.pop()
|
|
||||||
has_more_data = True
|
|
||||||
if marker is not None:
|
|
||||||
has_prev_data = True
|
|
||||||
elif sort_dir == 'asc' and marker is not None:
|
|
||||||
has_more_data = True
|
|
||||||
elif marker is not None:
|
|
||||||
has_prev_data = True
|
|
||||||
return (stacks, has_more_data, has_prev_data)
|
|
||||||
|
|
||||||
|
|
||||||
def _ignore_if(key, value):
|
|
||||||
if key != 'get_file' and key != 'type':
|
|
||||||
return True
|
|
||||||
if not isinstance(value, six.string_types):
|
|
||||||
return True
|
|
||||||
if (key == 'type' and
|
|
||||||
not value.endswith(('.yaml', '.template'))):
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
@profiler.trace
|
|
||||||
def get_template_files(template_data=None, template_url=None):
|
|
||||||
if template_data:
|
|
||||||
tpl = template_data
|
|
||||||
elif template_url:
|
|
||||||
with contextlib.closing(request.urlopen(template_url)) as u:
|
|
||||||
tpl = u.read()
|
|
||||||
else:
|
|
||||||
return {}, None
|
|
||||||
if not tpl:
|
|
||||||
return {}, None
|
|
||||||
if isinstance(tpl, six.binary_type):
|
|
||||||
tpl = tpl.decode('utf-8')
|
|
||||||
template = template_format.parse(tpl)
|
|
||||||
files = {}
|
|
||||||
_get_file_contents(template, files)
|
|
||||||
return files, template
|
|
||||||
|
|
||||||
|
|
||||||
def _get_file_contents(from_data, files):
|
|
||||||
if not isinstance(from_data, (dict, list)):
|
|
||||||
return
|
|
||||||
if isinstance(from_data, dict):
|
|
||||||
recurse_data = from_data.values()
|
|
||||||
for key, value in from_data.items():
|
|
||||||
if _ignore_if(key, value):
|
|
||||||
continue
|
|
||||||
if not value.startswith(('http://', 'https://')):
|
|
||||||
raise exceptions.GetFileError(value, 'get_file')
|
|
||||||
if value not in files:
|
|
||||||
file_content = heat_utils.read_url_content(value)
|
|
||||||
if template_utils.is_template(file_content):
|
|
||||||
template = get_template_files(template_url=value)[1]
|
|
||||||
file_content = jsonutils.dumps(template)
|
|
||||||
files[value] = file_content
|
|
||||||
else:
|
|
||||||
recurse_data = from_data
|
|
||||||
for value in recurse_data:
|
|
||||||
_get_file_contents(value, files)
|
|
||||||
|
|
||||||
|
|
||||||
@profiler.trace
|
|
||||||
def stack_delete(request, stack_id):
|
|
||||||
return heatclient(request).stacks.delete(stack_id)
|
|
||||||
|
|
||||||
|
|
||||||
@profiler.trace
|
|
||||||
def stack_get(request, stack_id):
|
|
||||||
return heatclient(request).stacks.get(stack_id)
|
|
||||||
|
|
||||||
|
|
||||||
@profiler.trace
|
|
||||||
def template_get(request, stack_id):
|
|
||||||
return heatclient(request).stacks.template(stack_id)
|
|
||||||
|
|
||||||
|
|
||||||
@profiler.trace
|
|
||||||
def stack_create(request, password=None, **kwargs):
|
|
||||||
return heatclient(request, password).stacks.create(**kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
@profiler.trace
|
|
||||||
def stack_preview(request, password=None, **kwargs):
|
|
||||||
return heatclient(request, password).stacks.preview(**kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
@profiler.trace
|
|
||||||
def stack_update(request, stack_id, password=None, **kwargs):
|
|
||||||
return heatclient(request, password).stacks.update(stack_id, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
@profiler.trace
|
|
||||||
def snapshot_create(request, stack_id):
|
|
||||||
return heatclient(request).stacks.snapshot(stack_id)
|
|
||||||
|
|
||||||
|
|
||||||
@profiler.trace
|
|
||||||
def snapshot_list(request, stack_id):
|
|
||||||
return heatclient(request).stacks.snapshot_list(stack_id)
|
|
||||||
|
|
||||||
|
|
||||||
@profiler.trace
|
|
||||||
def snapshot_show(request, stack_id, snapshot_id):
|
|
||||||
return heatclient(request).stacks.snapshot_show(stack_id, snapshot_id)
|
|
||||||
|
|
||||||
|
|
||||||
@profiler.trace
|
|
||||||
def snapshot_delete(request, stack_id, snapshot_id):
|
|
||||||
return heatclient(request).stacks.snapshot_delete(stack_id, snapshot_id)
|
|
||||||
|
|
||||||
|
|
||||||
@profiler.trace
|
|
||||||
def events_list(request, stack_name):
|
|
||||||
return heatclient(request).events.list(stack_name)
|
|
||||||
|
|
||||||
|
|
||||||
@profiler.trace
|
|
||||||
def resources_list(request, stack_name):
|
|
||||||
return heatclient(request).resources.list(stack_name)
|
|
||||||
|
|
||||||
|
|
||||||
@profiler.trace
|
|
||||||
def resource_get(request, stack_id, resource_name):
|
|
||||||
return heatclient(request).resources.get(stack_id, resource_name)
|
|
||||||
|
|
||||||
|
|
||||||
@profiler.trace
|
|
||||||
def resource_metadata_get(request, stack_id, resource_name):
|
|
||||||
return heatclient(request).resources.metadata(stack_id, resource_name)
|
|
||||||
|
|
||||||
|
|
||||||
@profiler.trace
|
|
||||||
def template_validate(request, **kwargs):
|
|
||||||
return heatclient(request).stacks.validate(**kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
@profiler.trace
|
|
||||||
def action_check(request, stack_id):
|
|
||||||
return heatclient(request).actions.check(stack_id)
|
|
||||||
|
|
||||||
|
|
||||||
@profiler.trace
|
|
||||||
def action_suspend(request, stack_id):
|
|
||||||
return heatclient(request).actions.suspend(stack_id)
|
|
||||||
|
|
||||||
|
|
||||||
@profiler.trace
|
|
||||||
def action_resume(request, stack_id):
|
|
||||||
return heatclient(request).actions.resume(stack_id)
|
|
||||||
|
|
||||||
|
|
||||||
@profiler.trace
|
|
||||||
def resource_types_list(request, filters=None):
|
|
||||||
return heatclient(request).resource_types.list(filters=filters)
|
|
||||||
|
|
||||||
|
|
||||||
@profiler.trace
|
|
||||||
def resource_type_get(request, resource_type):
|
|
||||||
return heatclient(request).resource_types.get(resource_type)
|
|
||||||
|
|
||||||
|
|
||||||
@profiler.trace
|
|
||||||
def service_list(request):
|
|
||||||
return heatclient(request).services.list()
|
|
||||||
|
|
||||||
|
|
||||||
@profiler.trace
|
|
||||||
def template_version_list(request):
|
|
||||||
return heatclient(request).template_versions.list()
|
|
||||||
|
|
||||||
|
|
||||||
@profiler.trace
|
|
||||||
def template_function_list(request, template_version):
|
|
||||||
return heatclient(request).template_versions.get(template_version)
|
|
@ -24,7 +24,6 @@ in https://wiki.openstack.org/wiki/APIChangeGuidelines.
|
|||||||
from openstack_dashboard.api.rest import cinder
|
from openstack_dashboard.api.rest import cinder
|
||||||
from openstack_dashboard.api.rest import config
|
from openstack_dashboard.api.rest import config
|
||||||
from openstack_dashboard.api.rest import glance
|
from openstack_dashboard.api.rest import glance
|
||||||
from openstack_dashboard.api.rest import heat
|
|
||||||
from openstack_dashboard.api.rest import keystone
|
from openstack_dashboard.api.rest import keystone
|
||||||
from openstack_dashboard.api.rest import network
|
from openstack_dashboard.api.rest import network
|
||||||
from openstack_dashboard.api.rest import neutron
|
from openstack_dashboard.api.rest import neutron
|
||||||
@ -37,7 +36,6 @@ __all__ = [
|
|||||||
'cinder',
|
'cinder',
|
||||||
'config',
|
'config',
|
||||||
'glance',
|
'glance',
|
||||||
'heat',
|
|
||||||
'keystone',
|
'keystone',
|
||||||
'network',
|
'network',
|
||||||
'neutron',
|
'neutron',
|
||||||
|
@ -1,51 +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.
|
|
||||||
"""API for the heat service."""
|
|
||||||
|
|
||||||
from django.views import generic
|
|
||||||
|
|
||||||
from openstack_dashboard import api
|
|
||||||
from openstack_dashboard.api.rest import urls
|
|
||||||
from openstack_dashboard.api.rest import utils as rest_utils
|
|
||||||
|
|
||||||
|
|
||||||
@urls.register
|
|
||||||
class Validate(generic.View):
|
|
||||||
"""API for validating a template"""
|
|
||||||
url_regex = r'heat/validate/$'
|
|
||||||
|
|
||||||
@rest_utils.ajax(data_required=True)
|
|
||||||
def post(self, request):
|
|
||||||
"""Validate a template
|
|
||||||
|
|
||||||
The following parameters may be passed in the POST
|
|
||||||
application/json object. The parameters are:
|
|
||||||
request:
|
|
||||||
|
|
||||||
:param template_url: The template to validate
|
|
||||||
"""
|
|
||||||
return api.heat.template_validate(request, **(request.DATA))
|
|
||||||
|
|
||||||
|
|
||||||
@urls.register
|
|
||||||
class Services(generic.View):
|
|
||||||
"""API for heat services."""
|
|
||||||
url_regex = r'heat/services/$'
|
|
||||||
|
|
||||||
@rest_utils.ajax()
|
|
||||||
def get(self, request):
|
|
||||||
"""Get a list of heat services."""
|
|
||||||
if api.base.is_service_enabled(request, 'orchestration'):
|
|
||||||
result = api.heat.service_list(request)
|
|
||||||
return {'items': [u.to_dict() for u in result]}
|
|
||||||
else:
|
|
||||||
raise rest_utils.AjaxError(501, '')
|
|
@ -1,92 +0,0 @@
|
|||||||
{
|
|
||||||
"context_is_admin": "role:admin",
|
|
||||||
"deny_stack_user": "not role:heat_stack_user",
|
|
||||||
"deny_everybody": "!",
|
|
||||||
|
|
||||||
"cloudformation:ListStacks": "rule:deny_stack_user",
|
|
||||||
"cloudformation:CreateStack": "rule:deny_stack_user",
|
|
||||||
"cloudformation:DescribeStacks": "rule:deny_stack_user",
|
|
||||||
"cloudformation:DeleteStack": "rule:deny_stack_user",
|
|
||||||
"cloudformation:UpdateStack": "rule:deny_stack_user",
|
|
||||||
"cloudformation:CancelUpdateStack": "rule:deny_stack_user",
|
|
||||||
"cloudformation:DescribeStackEvents": "rule:deny_stack_user",
|
|
||||||
"cloudformation:ValidateTemplate": "rule:deny_stack_user",
|
|
||||||
"cloudformation:GetTemplate": "rule:deny_stack_user",
|
|
||||||
"cloudformation:EstimateTemplateCost": "rule:deny_stack_user",
|
|
||||||
"cloudformation:DescribeStackResource": "",
|
|
||||||
"cloudformation:DescribeStackResources": "rule:deny_stack_user",
|
|
||||||
"cloudformation:ListStackResources": "rule:deny_stack_user",
|
|
||||||
|
|
||||||
"cloudwatch:DeleteAlarms": "rule:deny_stack_user",
|
|
||||||
"cloudwatch:DescribeAlarmHistory": "rule:deny_stack_user",
|
|
||||||
"cloudwatch:DescribeAlarms": "rule:deny_stack_user",
|
|
||||||
"cloudwatch:DescribeAlarmsForMetric": "rule:deny_stack_user",
|
|
||||||
"cloudwatch:DisableAlarmActions": "rule:deny_stack_user",
|
|
||||||
"cloudwatch:EnableAlarmActions": "rule:deny_stack_user",
|
|
||||||
"cloudwatch:GetMetricStatistics": "rule:deny_stack_user",
|
|
||||||
"cloudwatch:ListMetrics": "rule:deny_stack_user",
|
|
||||||
"cloudwatch:PutMetricAlarm": "rule:deny_stack_user",
|
|
||||||
"cloudwatch:PutMetricData": "",
|
|
||||||
"cloudwatch:SetAlarmState": "rule:deny_stack_user",
|
|
||||||
|
|
||||||
"actions:action": "rule:deny_stack_user",
|
|
||||||
"build_info:build_info": "rule:deny_stack_user",
|
|
||||||
"events:index": "rule:deny_stack_user",
|
|
||||||
"events:show": "rule:deny_stack_user",
|
|
||||||
"resource:index": "rule:deny_stack_user",
|
|
||||||
"resource:metadata": "",
|
|
||||||
"resource:signal": "",
|
|
||||||
"resource:mark_unhealthy": "rule:deny_stack_user",
|
|
||||||
"resource:show": "rule:deny_stack_user",
|
|
||||||
"stacks:abandon": "rule:deny_stack_user",
|
|
||||||
"stacks:create": "rule:deny_stack_user",
|
|
||||||
"stacks:delete": "rule:deny_stack_user",
|
|
||||||
"stacks:detail": "rule:deny_stack_user",
|
|
||||||
"stacks:export": "rule:deny_stack_user",
|
|
||||||
"stacks:generate_template": "rule:deny_stack_user",
|
|
||||||
"stacks:global_index": "rule:deny_everybody",
|
|
||||||
"stacks:index": "rule:deny_stack_user",
|
|
||||||
"stacks:list_resource_types": "rule:deny_stack_user",
|
|
||||||
"stacks:list_template_versions": "rule:deny_stack_user",
|
|
||||||
"stacks:list_template_functions": "rule:deny_stack_user",
|
|
||||||
"stacks:lookup": "",
|
|
||||||
"stacks:preview": "rule:deny_stack_user",
|
|
||||||
"stacks:resource_schema": "rule:deny_stack_user",
|
|
||||||
"stacks:show": "rule:deny_stack_user",
|
|
||||||
"stacks:template": "rule:deny_stack_user",
|
|
||||||
"stacks:environment": "rule:deny_stack_user",
|
|
||||||
"stacks:update": "rule:deny_stack_user",
|
|
||||||
"stacks:update_patch": "rule:deny_stack_user",
|
|
||||||
"stacks:preview_update": "rule:deny_stack_user",
|
|
||||||
"stacks:preview_update_patch": "rule:deny_stack_user",
|
|
||||||
"stacks:validate_template": "rule:deny_stack_user",
|
|
||||||
"stacks:snapshot": "rule:deny_stack_user",
|
|
||||||
"stacks:show_snapshot": "rule:deny_stack_user",
|
|
||||||
"stacks:delete_snapshot": "rule:deny_stack_user",
|
|
||||||
"stacks:list_snapshots": "rule:deny_stack_user",
|
|
||||||
"stacks:restore_snapshot": "rule:deny_stack_user",
|
|
||||||
"stacks:list_outputs": "rule:deny_stack_user",
|
|
||||||
"stacks:show_output": "rule:deny_stack_user",
|
|
||||||
|
|
||||||
"software_configs:global_index": "rule:deny_everybody",
|
|
||||||
"software_configs:index": "rule:deny_stack_user",
|
|
||||||
"software_configs:create": "rule:deny_stack_user",
|
|
||||||
"software_configs:show": "rule:deny_stack_user",
|
|
||||||
"software_configs:delete": "rule:deny_stack_user",
|
|
||||||
"software_deployments:index": "rule:deny_stack_user",
|
|
||||||
"software_deployments:create": "rule:deny_stack_user",
|
|
||||||
"software_deployments:show": "rule:deny_stack_user",
|
|
||||||
"software_deployments:update": "rule:deny_stack_user",
|
|
||||||
"software_deployments:delete": "rule:deny_stack_user",
|
|
||||||
"software_deployments:metadata": "",
|
|
||||||
|
|
||||||
"service:index": "rule:context_is_admin",
|
|
||||||
|
|
||||||
"resource_types:OS::Nova::Flavor": "rule:context_is_admin",
|
|
||||||
"resource_types:OS::Cinder::EncryptedVolumeType": "rule:context_is_admin",
|
|
||||||
"resource_types:OS::Cinder::VolumeType": "rule:context_is_admin",
|
|
||||||
"resource_types:OS::Manila::ShareType": "rule:context_is_admin",
|
|
||||||
"resource_types:OS::Neutron::QoSPolicy": "rule:context_is_admin",
|
|
||||||
"resource_types:OS::Neutron::QoSBandwidthLimitRule": "rule:context_is_admin",
|
|
||||||
"resource_types:OS::Nova::HostAggregate": "rule:context_is_admin"
|
|
||||||
}
|
|
@ -28,8 +28,7 @@ class Admin(horizon.Dashboard):
|
|||||||
('image', 'context_is_admin'),
|
('image', 'context_is_admin'),
|
||||||
('volume', 'context_is_admin'),
|
('volume', 'context_is_admin'),
|
||||||
('compute', 'context_is_admin'),
|
('compute', 'context_is_admin'),
|
||||||
('network', 'context_is_admin'),
|
('network', 'context_is_admin'),)
|
||||||
('orchestration', 'context_is_admin'),)
|
|
||||||
else:
|
else:
|
||||||
permissions = (tuple(utils.get_admin_permissions()),)
|
permissions = (tuple(utils.get_admin_permissions()),)
|
||||||
|
|
||||||
|
@ -26,5 +26,4 @@ class Info(horizon.Panel):
|
|||||||
slug = 'info'
|
slug = 'info'
|
||||||
policy_rules = (("compute", "context_is_admin"),
|
policy_rules = (("compute", "context_is_admin"),
|
||||||
("volume", "context_is_admin"),
|
("volume", "context_is_admin"),
|
||||||
("network", "context_is_admin"),
|
("network", "context_is_admin"),)
|
||||||
("orchestration", "context_is_admin"),)
|
|
||||||
|
@ -236,45 +236,3 @@ class NetworkAgentsTable(tables.DataTable):
|
|||||||
table_actions = (NetworkAgentsFilterAction, )
|
table_actions = (NetworkAgentsFilterAction, )
|
||||||
row_actions = (NetworkL3AgentRoutersLinkAction, )
|
row_actions = (NetworkL3AgentRoutersLinkAction, )
|
||||||
multi_select = False
|
multi_select = False
|
||||||
|
|
||||||
|
|
||||||
class HeatServiceFilterAction(tables.FilterAction):
|
|
||||||
filter_field = 'type'
|
|
||||||
|
|
||||||
def filter(self, table, services, filter_string):
|
|
||||||
q = filter_string.lower()
|
|
||||||
|
|
||||||
def comp(service):
|
|
||||||
attr = getattr(service, self.filter_field, '')
|
|
||||||
if attr is not None and q in attr.lower():
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
return filter(comp, services)
|
|
||||||
|
|
||||||
|
|
||||||
class HeatServiceTable(tables.DataTable):
|
|
||||||
hostname = tables.Column('hostname', verbose_name=_('Hostname'))
|
|
||||||
binary = tables.Column("binary", verbose_name=_('Name'))
|
|
||||||
engine_id = tables.Column('engine_id', verbose_name=_('Engine Id'))
|
|
||||||
host = tables.Column('host', verbose_name=_('Host'))
|
|
||||||
topic = tables.Column('topic', verbose_name=_('Topic'))
|
|
||||||
# For consistent with other tables in system info, set column name to
|
|
||||||
# 'state'
|
|
||||||
state = tables.Column('status', verbose_name=_('State'),
|
|
||||||
display_choices=SERVICE_STATE_DISPLAY_CHOICES)
|
|
||||||
updated_at = tables.Column('updated_at',
|
|
||||||
verbose_name=pgettext_lazy(
|
|
||||||
'Time since the last update',
|
|
||||||
u'Last Updated'),
|
|
||||||
filters=(utils_filters.parse_isotime,
|
|
||||||
filters.timesince))
|
|
||||||
|
|
||||||
def get_object_id(self, obj):
|
|
||||||
return "%s" % obj.engine_id
|
|
||||||
|
|
||||||
class Meta(object):
|
|
||||||
name = "heat_services"
|
|
||||||
verbose_name = _("Orchestration Services")
|
|
||||||
table_actions = (HeatServiceFilterAction,)
|
|
||||||
multi_select = False
|
|
||||||
|
@ -18,7 +18,6 @@ from horizon import exceptions
|
|||||||
from horizon import tabs
|
from horizon import tabs
|
||||||
from openstack_dashboard.api import base
|
from openstack_dashboard.api import base
|
||||||
from openstack_dashboard.api import cinder
|
from openstack_dashboard.api import cinder
|
||||||
from openstack_dashboard.api import heat
|
|
||||||
from openstack_dashboard.api import neutron
|
from openstack_dashboard.api import neutron
|
||||||
from openstack_dashboard.api import nova
|
from openstack_dashboard.api import nova
|
||||||
from openstack_dashboard.dashboards.admin.info import constants
|
from openstack_dashboard.dashboards.admin.info import constants
|
||||||
@ -118,32 +117,8 @@ class NetworkAgentsTab(tabs.TableTab):
|
|||||||
return agents
|
return agents
|
||||||
|
|
||||||
|
|
||||||
class HeatServiceTab(tabs.TableTab):
|
|
||||||
table_classes = (tables.HeatServiceTable,)
|
|
||||||
name = tables.HeatServiceTable.Meta.verbose_name
|
|
||||||
slug = tables.HeatServiceTable.Meta.name
|
|
||||||
template_name = constants.INFO_DETAIL_TEMPLATE_NAME
|
|
||||||
|
|
||||||
def allowed(self, request):
|
|
||||||
try:
|
|
||||||
return base.is_service_enabled(request, 'orchestration')
|
|
||||||
except Exception:
|
|
||||||
exceptions.handle(request, _('Orchestration service is disabled.'))
|
|
||||||
return False
|
|
||||||
|
|
||||||
def get_heat_services_data(self):
|
|
||||||
try:
|
|
||||||
services = heat.service_list(self.tab_group.request)
|
|
||||||
except Exception:
|
|
||||||
msg = _('Unable to get Orchestration service list.')
|
|
||||||
exceptions.check_message(["Connection", "refused"], msg)
|
|
||||||
exceptions.handle(self.request, msg)
|
|
||||||
services = []
|
|
||||||
return services
|
|
||||||
|
|
||||||
|
|
||||||
class SystemInfoTabs(tabs.TabGroup):
|
class SystemInfoTabs(tabs.TabGroup):
|
||||||
slug = "system_info"
|
slug = "system_info"
|
||||||
tabs = (ServicesTab, NovaServicesTab, CinderServicesTab,
|
tabs = (ServicesTab, NovaServicesTab, CinderServicesTab,
|
||||||
NetworkAgentsTab, HeatServiceTab)
|
NetworkAgentsTab)
|
||||||
sticky = True
|
sticky = True
|
||||||
|
@ -29,7 +29,7 @@ class SystemInfoViewTests(test.BaseAdminViewTests):
|
|||||||
api.nova: ('service_list',),
|
api.nova: ('service_list',),
|
||||||
api.neutron: ('agent_list', 'is_extension_supported'),
|
api.neutron: ('agent_list', 'is_extension_supported'),
|
||||||
api.cinder: ('service_list',),
|
api.cinder: ('service_list',),
|
||||||
api.heat: ('service_list',)})
|
})
|
||||||
def _test_base_index(self):
|
def _test_base_index(self):
|
||||||
api.base.is_service_enabled(IsA(http.HttpRequest), IgnoreArg()) \
|
api.base.is_service_enabled(IsA(http.HttpRequest), IgnoreArg()) \
|
||||||
.MultipleTimes().AndReturn(True)
|
.MultipleTimes().AndReturn(True)
|
||||||
@ -49,10 +49,6 @@ class SystemInfoViewTests(test.BaseAdminViewTests):
|
|||||||
api.cinder.service_list(IsA(http.HttpRequest)).\
|
api.cinder.service_list(IsA(http.HttpRequest)).\
|
||||||
AndReturn(cinder_services)
|
AndReturn(cinder_services)
|
||||||
|
|
||||||
heat_services = self.heat_services.list()
|
|
||||||
api.heat.service_list(IsA(http.HttpRequest)).\
|
|
||||||
AndReturn(heat_services)
|
|
||||||
|
|
||||||
self.mox.ReplayAll()
|
self.mox.ReplayAll()
|
||||||
|
|
||||||
res = self.client.get(INDEX_URL)
|
res = self.client.get(INDEX_URL)
|
||||||
@ -88,14 +84,3 @@ class SystemInfoViewTests(test.BaseAdminViewTests):
|
|||||||
)
|
)
|
||||||
|
|
||||||
self.mox.VerifyAll()
|
self.mox.VerifyAll()
|
||||||
|
|
||||||
def test_heat_index(self):
|
|
||||||
res = self._test_base_index()
|
|
||||||
heat_services_tab = res.context['tab_group'].\
|
|
||||||
get_tab('heat_services')
|
|
||||||
self.assertQuerysetEqual(
|
|
||||||
heat_services_tab._tables['heat_services'].data,
|
|
||||||
[service.__repr__() for service in self.heat_services.list()]
|
|
||||||
)
|
|
||||||
|
|
||||||
self.mox.VerifyAll()
|
|
||||||
|
@ -1,83 +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 openstack_dashboard.api import heat
|
|
||||||
|
|
||||||
from openstack_dashboard.dashboards.project.stacks import mappings
|
|
||||||
from openstack_dashboard.dashboards.project.stacks import sro
|
|
||||||
|
|
||||||
|
|
||||||
class Stack(object):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def d3_data(request, stack_id=''):
|
|
||||||
try:
|
|
||||||
stack = heat.stack_get(request, stack_id)
|
|
||||||
except Exception:
|
|
||||||
stack = Stack()
|
|
||||||
stack.id = stack_id
|
|
||||||
stack.stack_name = request.session.get('stack_name', '')
|
|
||||||
stack.stack_status = 'DELETE_COMPLETE'
|
|
||||||
stack.stack_status_reason = 'DELETE_COMPLETE'
|
|
||||||
|
|
||||||
try:
|
|
||||||
resources = heat.resources_list(request, stack.stack_name)
|
|
||||||
except Exception:
|
|
||||||
resources = []
|
|
||||||
|
|
||||||
d3_data = {"nodes": [], "stack": {}}
|
|
||||||
if stack:
|
|
||||||
stack_image = mappings.get_resource_image(stack.stack_status, 'stack')
|
|
||||||
stack_node = {
|
|
||||||
'stack_id': stack.id,
|
|
||||||
'name': stack.stack_name,
|
|
||||||
'status': stack.stack_status,
|
|
||||||
'image': stack_image,
|
|
||||||
'image_size': 60,
|
|
||||||
'image_x': -30,
|
|
||||||
'image_y': -30,
|
|
||||||
'text_x': 40,
|
|
||||||
'text_y': ".35em",
|
|
||||||
'in_progress': (stack.status == 'IN_PROGRESS'),
|
|
||||||
'info_box': sro.stack_info(stack, stack_image)
|
|
||||||
}
|
|
||||||
d3_data['stack'] = stack_node
|
|
||||||
|
|
||||||
if resources:
|
|
||||||
for resource in resources:
|
|
||||||
resource_image = mappings.get_resource_image(
|
|
||||||
resource.resource_status,
|
|
||||||
resource.resource_type)
|
|
||||||
resource_status = mappings.get_resource_status(
|
|
||||||
resource.resource_status)
|
|
||||||
if resource_status in ('IN_PROGRESS', 'INIT'):
|
|
||||||
in_progress = True
|
|
||||||
else:
|
|
||||||
in_progress = False
|
|
||||||
resource_node = {
|
|
||||||
'name': resource.resource_name,
|
|
||||||
'status': resource.resource_status,
|
|
||||||
'image': resource_image,
|
|
||||||
'required_by': resource.required_by,
|
|
||||||
'image_size': 50,
|
|
||||||
'image_x': -25,
|
|
||||||
'image_y': -25,
|
|
||||||
'text_x': 35,
|
|
||||||
'text_y': ".35em",
|
|
||||||
'in_progress': in_progress,
|
|
||||||
'info_box': sro.resource_info(resource)
|
|
||||||
}
|
|
||||||
d3_data['nodes'].append(resource_node)
|
|
||||||
return json.dumps(d3_data)
|
|
@ -1,488 +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
|
|
||||||
import logging
|
|
||||||
|
|
||||||
import django
|
|
||||||
from django.conf import settings
|
|
||||||
from django.utils import html
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
from django.views.decorators.debug import sensitive_variables
|
|
||||||
|
|
||||||
from oslo_utils import strutils
|
|
||||||
import six
|
|
||||||
|
|
||||||
from horizon import exceptions
|
|
||||||
from horizon import forms
|
|
||||||
from horizon import messages
|
|
||||||
|
|
||||||
from openstack_dashboard import api
|
|
||||||
from openstack_dashboard.dashboards.project.images \
|
|
||||||
import utils as image_utils
|
|
||||||
from openstack_dashboard.dashboards.project.instances \
|
|
||||||
import utils as instance_utils
|
|
||||||
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
def create_upload_form_attributes(prefix, input_type, name):
|
|
||||||
"""Creates attribute dicts for the switchable upload form
|
|
||||||
|
|
||||||
:type prefix: str
|
|
||||||
:param prefix: prefix (environment, template) of field
|
|
||||||
:type input_type: str
|
|
||||||
:param input_type: field type (file, raw, url)
|
|
||||||
:type name: str
|
|
||||||
:param name: translated text label to display to user
|
|
||||||
:rtype: dict
|
|
||||||
:return: an attribute set to pass to form build
|
|
||||||
"""
|
|
||||||
attributes = {'class': 'switched', 'data-switch-on': prefix + 'source'}
|
|
||||||
attributes['data-' + prefix + 'source-' + input_type] = name
|
|
||||||
return attributes
|
|
||||||
|
|
||||||
|
|
||||||
class TemplateForm(forms.SelfHandlingForm):
|
|
||||||
|
|
||||||
class Meta(object):
|
|
||||||
name = _('Select Template')
|
|
||||||
help_text = _('Select a template to launch a stack.')
|
|
||||||
|
|
||||||
# TODO(jomara) - update URL choice for template & environment files
|
|
||||||
# w/ client side download when applicable
|
|
||||||
base_choices = [('file', _('File')),
|
|
||||||
('raw', _('Direct Input'))]
|
|
||||||
url_choice = [('url', _('URL'))]
|
|
||||||
attributes = {'class': 'switchable', 'data-slug': 'templatesource'}
|
|
||||||
template_source = forms.ChoiceField(label=_('Template Source'),
|
|
||||||
choices=base_choices + url_choice,
|
|
||||||
widget=forms.ThemableSelectWidget(
|
|
||||||
attrs=attributes))
|
|
||||||
|
|
||||||
attributes = create_upload_form_attributes(
|
|
||||||
'template',
|
|
||||||
'file',
|
|
||||||
_('Template File'))
|
|
||||||
template_upload = forms.FileField(
|
|
||||||
label=_('Template File'),
|
|
||||||
help_text=_('A local template to upload.'),
|
|
||||||
widget=forms.FileInput(attrs=attributes),
|
|
||||||
required=False)
|
|
||||||
|
|
||||||
attributes = create_upload_form_attributes(
|
|
||||||
'template',
|
|
||||||
'url',
|
|
||||||
_('Template URL'))
|
|
||||||
template_url = forms.URLField(
|
|
||||||
label=_('Template URL'),
|
|
||||||
help_text=_('An external (HTTP) URL to load the template from.'),
|
|
||||||
widget=forms.TextInput(attrs=attributes),
|
|
||||||
required=False)
|
|
||||||
|
|
||||||
attributes = create_upload_form_attributes(
|
|
||||||
'template',
|
|
||||||
'raw',
|
|
||||||
_('Template Data'))
|
|
||||||
template_data = forms.CharField(
|
|
||||||
label=_('Template Data'),
|
|
||||||
help_text=_('The raw contents of the template.'),
|
|
||||||
widget=forms.widgets.Textarea(attrs=attributes),
|
|
||||||
required=False)
|
|
||||||
|
|
||||||
attributes = {'data-slug': 'envsource', 'class': 'switchable'}
|
|
||||||
environment_source = forms.ChoiceField(
|
|
||||||
label=_('Environment Source'),
|
|
||||||
choices=base_choices,
|
|
||||||
widget=forms.ThemableSelectWidget(attrs=attributes),
|
|
||||||
required=False)
|
|
||||||
|
|
||||||
attributes = create_upload_form_attributes(
|
|
||||||
'env',
|
|
||||||
'file',
|
|
||||||
_('Environment File'))
|
|
||||||
environment_upload = forms.FileField(
|
|
||||||
label=_('Environment File'),
|
|
||||||
help_text=_('A local environment to upload.'),
|
|
||||||
widget=forms.FileInput(attrs=attributes),
|
|
||||||
required=False)
|
|
||||||
|
|
||||||
attributes = create_upload_form_attributes(
|
|
||||||
'env',
|
|
||||||
'raw',
|
|
||||||
_('Environment Data'))
|
|
||||||
environment_data = forms.CharField(
|
|
||||||
label=_('Environment Data'),
|
|
||||||
help_text=_('The raw contents of the environment file.'),
|
|
||||||
widget=forms.widgets.Textarea(attrs=attributes),
|
|
||||||
required=False)
|
|
||||||
|
|
||||||
if django.VERSION >= (1, 9):
|
|
||||||
# Note(Itxaka): On django>=1.9 Charfield has an strip option that
|
|
||||||
# we need to set to False as to not hit
|
|
||||||
# https://bugs.launchpad.net/python-heatclient/+bug/1546166
|
|
||||||
environment_data.strip = False
|
|
||||||
template_data.strip = False
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
self.next_view = kwargs.pop('next_view')
|
|
||||||
super(TemplateForm, self).__init__(*args, **kwargs)
|
|
||||||
|
|
||||||
def clean(self):
|
|
||||||
cleaned = super(TemplateForm, self).clean()
|
|
||||||
|
|
||||||
files = self.request.FILES
|
|
||||||
self.clean_uploaded_files('template', _('template'), cleaned, files)
|
|
||||||
self.clean_uploaded_files('environment', _('environment'), cleaned,
|
|
||||||
files)
|
|
||||||
|
|
||||||
# Validate the template and get back the params.
|
|
||||||
kwargs = {}
|
|
||||||
if cleaned['environment_data']:
|
|
||||||
kwargs['environment'] = cleaned['environment_data']
|
|
||||||
try:
|
|
||||||
files, tpl =\
|
|
||||||
api.heat.get_template_files(cleaned.get('template_data'),
|
|
||||||
cleaned.get('template_url'))
|
|
||||||
kwargs['files'] = files
|
|
||||||
kwargs['template'] = tpl
|
|
||||||
validated = api.heat.template_validate(self.request, **kwargs)
|
|
||||||
cleaned['template_validate'] = validated
|
|
||||||
cleaned['template_validate']['files'] = files
|
|
||||||
cleaned['template_validate']['template'] = tpl
|
|
||||||
except Exception as e:
|
|
||||||
raise forms.ValidationError(six.text_type(e))
|
|
||||||
|
|
||||||
return cleaned
|
|
||||||
|
|
||||||
def clean_uploaded_files(self, prefix, field_label, cleaned, files):
|
|
||||||
"""Cleans Template & Environment data from form upload.
|
|
||||||
|
|
||||||
Does some of the crunchy bits for processing uploads vs raw
|
|
||||||
data depending on what the user specified. Identical process
|
|
||||||
for environment data & template data.
|
|
||||||
|
|
||||||
:type prefix: str
|
|
||||||
:param prefix: prefix (environment, template) of field
|
|
||||||
:type field_label: str
|
|
||||||
:param field_label: translated prefix str for messages
|
|
||||||
:type input_type: dict
|
|
||||||
:param prefix: existing cleaned fields from form
|
|
||||||
:rtype: dict
|
|
||||||
:return: cleaned dict including environment & template data
|
|
||||||
"""
|
|
||||||
|
|
||||||
upload_str = prefix + "_upload"
|
|
||||||
data_str = prefix + "_data"
|
|
||||||
url = cleaned.get(prefix + '_url')
|
|
||||||
data = cleaned.get(prefix + '_data')
|
|
||||||
|
|
||||||
has_upload = upload_str in files
|
|
||||||
# Uploaded file handler
|
|
||||||
if has_upload and not url:
|
|
||||||
log_template_name = files[upload_str].name
|
|
||||||
LOG.info('got upload %s', log_template_name)
|
|
||||||
|
|
||||||
tpl = files[upload_str].read()
|
|
||||||
if tpl.startswith('{'):
|
|
||||||
try:
|
|
||||||
json.loads(tpl)
|
|
||||||
except Exception as e:
|
|
||||||
msg = _('There was a problem parsing the'
|
|
||||||
' %(prefix)s: %(error)s')
|
|
||||||
msg = msg % {'prefix': prefix, 'error': six.text_type(e)}
|
|
||||||
raise forms.ValidationError(msg)
|
|
||||||
cleaned[data_str] = tpl
|
|
||||||
|
|
||||||
# URL handler
|
|
||||||
elif url and (has_upload or data):
|
|
||||||
msg = _('Please specify a %s using only one source method.')
|
|
||||||
msg = msg % field_label
|
|
||||||
raise forms.ValidationError(msg)
|
|
||||||
|
|
||||||
elif prefix == 'template':
|
|
||||||
# Check for raw template input - blank environment allowed
|
|
||||||
if not url and not data:
|
|
||||||
msg = _('You must specify a template via one of the '
|
|
||||||
'available sources.')
|
|
||||||
raise forms.ValidationError(msg)
|
|
||||||
|
|
||||||
def create_kwargs(self, data):
|
|
||||||
kwargs = {'parameters': data['template_validate'],
|
|
||||||
'environment_data': data['environment_data']}
|
|
||||||
if data.get('stack_id'):
|
|
||||||
kwargs['stack_id'] = data['stack_id']
|
|
||||||
return kwargs
|
|
||||||
|
|
||||||
def handle(self, request, data):
|
|
||||||
kwargs = self.create_kwargs(data)
|
|
||||||
# NOTE (gabriel): This is a bit of a hack, essentially rewriting this
|
|
||||||
# request so that we can chain it as an input to the next view...
|
|
||||||
# but hey, it totally works.
|
|
||||||
request.method = 'GET'
|
|
||||||
|
|
||||||
return self.next_view.as_view()(request, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class ChangeTemplateForm(TemplateForm):
|
|
||||||
class Meta(object):
|
|
||||||
name = _('Edit Template')
|
|
||||||
help_text = _('Select a new template to re-launch a stack.')
|
|
||||||
stack_id = forms.CharField(label=_('Stack ID'),
|
|
||||||
widget=forms.widgets.HiddenInput)
|
|
||||||
stack_name = forms.CharField(label=_('Stack Name'),
|
|
||||||
widget=forms.TextInput(attrs={'readonly':
|
|
||||||
'readonly'}))
|
|
||||||
|
|
||||||
|
|
||||||
class PreviewTemplateForm(TemplateForm):
|
|
||||||
class Meta(object):
|
|
||||||
name = _('Preview Template')
|
|
||||||
help_text = _('Select a new template to preview a stack.')
|
|
||||||
|
|
||||||
|
|
||||||
class CreateStackForm(forms.SelfHandlingForm):
|
|
||||||
|
|
||||||
param_prefix = '__param_'
|
|
||||||
|
|
||||||
class Meta(object):
|
|
||||||
name = _('Create Stack')
|
|
||||||
|
|
||||||
environment_data = forms.CharField(
|
|
||||||
widget=forms.widgets.HiddenInput,
|
|
||||||
required=False)
|
|
||||||
if django.VERSION >= (1, 9):
|
|
||||||
# Note(Itxaka): On django>=1.9 Charfield has an strip option that
|
|
||||||
# we need to set to False as to not hit
|
|
||||||
# https://bugs.launchpad.net/python-heatclient/+bug/1546166
|
|
||||||
environment_data.strip = False
|
|
||||||
|
|
||||||
parameters = forms.CharField(
|
|
||||||
widget=forms.widgets.HiddenInput)
|
|
||||||
stack_name = forms.RegexField(
|
|
||||||
max_length=255,
|
|
||||||
label=_('Stack Name'),
|
|
||||||
help_text=_('Name of the stack to create.'),
|
|
||||||
regex=r"^[a-zA-Z][a-zA-Z0-9_.-]*$",
|
|
||||||
error_messages={'invalid':
|
|
||||||
_('Name must start with a letter and may '
|
|
||||||
'only contain letters, numbers, underscores, '
|
|
||||||
'periods and hyphens.')})
|
|
||||||
timeout_mins = forms.IntegerField(
|
|
||||||
initial=60,
|
|
||||||
label=_('Creation Timeout (minutes)'),
|
|
||||||
help_text=_('Stack creation timeout in minutes.'))
|
|
||||||
enable_rollback = forms.BooleanField(
|
|
||||||
label=_('Rollback On Failure'),
|
|
||||||
help_text=_('Enable rollback on create/update failure.'),
|
|
||||||
required=False)
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
parameters = kwargs.pop('parameters')
|
|
||||||
# special case: load template data from API, not passed in params
|
|
||||||
if kwargs.get('validate_me'):
|
|
||||||
parameters = kwargs.pop('validate_me')
|
|
||||||
super(CreateStackForm, self).__init__(*args, **kwargs)
|
|
||||||
|
|
||||||
if self._stack_password_enabled():
|
|
||||||
self.fields['password'] = forms.CharField(
|
|
||||||
label=_('Password for user "%s"') % self.request.user.username,
|
|
||||||
help_text=_('This is required for operations to be performed '
|
|
||||||
'throughout the lifecycle of the stack'),
|
|
||||||
widget=forms.PasswordInput())
|
|
||||||
|
|
||||||
self._build_parameter_fields(parameters)
|
|
||||||
|
|
||||||
def _stack_password_enabled(self):
|
|
||||||
stack_settings = getattr(settings, 'OPENSTACK_HEAT_STACK', {})
|
|
||||||
return stack_settings.get('enable_user_pass', True)
|
|
||||||
|
|
||||||
def _build_parameter_fields(self, template_validate):
|
|
||||||
self.help_text = template_validate['Description']
|
|
||||||
|
|
||||||
params = template_validate.get('Parameters', {})
|
|
||||||
if template_validate.get('ParameterGroups'):
|
|
||||||
params_in_order = []
|
|
||||||
for group in template_validate['ParameterGroups']:
|
|
||||||
for param in group.get('parameters', []):
|
|
||||||
if param in params:
|
|
||||||
params_in_order.append((param, params[param]))
|
|
||||||
else:
|
|
||||||
# no parameter groups, simply sorted to make the order fixed
|
|
||||||
params_in_order = sorted(params.items())
|
|
||||||
for param_key, param in params_in_order:
|
|
||||||
field = None
|
|
||||||
field_key = self.param_prefix + param_key
|
|
||||||
initial = param.get('Value',
|
|
||||||
param.get('DefaultValue',
|
|
||||||
param.get('Default')))
|
|
||||||
field_args = {
|
|
||||||
'initial': initial,
|
|
||||||
'label': param.get('Label', param_key),
|
|
||||||
'help_text': html.escape(param.get('Description', '')),
|
|
||||||
'required': initial is None,
|
|
||||||
}
|
|
||||||
|
|
||||||
param_type = param.get('Type', None)
|
|
||||||
hidden = strutils.bool_from_string(param.get('NoEcho', 'false'))
|
|
||||||
if 'CustomConstraint' in param:
|
|
||||||
choices = self._populate_custom_choices(
|
|
||||||
param['CustomConstraint'])
|
|
||||||
field_args['choices'] = choices
|
|
||||||
field = forms.ChoiceField(**field_args)
|
|
||||||
|
|
||||||
elif 'AllowedValues' in param:
|
|
||||||
choices = map(lambda x: (x, x), param['AllowedValues'])
|
|
||||||
field_args['choices'] = choices
|
|
||||||
field = forms.ChoiceField(**field_args)
|
|
||||||
|
|
||||||
elif param_type == 'Json' and 'Default' in param:
|
|
||||||
field_args['initial'] = json.dumps(param['Default'])
|
|
||||||
field = forms.CharField(**field_args)
|
|
||||||
|
|
||||||
elif param_type in ('CommaDelimitedList', 'String', 'Json'):
|
|
||||||
if 'MinLength' in param:
|
|
||||||
field_args['min_length'] = int(param['MinLength'])
|
|
||||||
field_args['required'] = field_args['min_length'] > 0
|
|
||||||
if 'MaxLength' in param:
|
|
||||||
field_args['max_length'] = int(param['MaxLength'])
|
|
||||||
if hidden:
|
|
||||||
field_args['widget'] = forms.PasswordInput(
|
|
||||||
render_value=True)
|
|
||||||
field = forms.CharField(**field_args)
|
|
||||||
|
|
||||||
elif param_type == 'Number':
|
|
||||||
if 'MinValue' in param:
|
|
||||||
field_args['min_value'] = int(param['MinValue'])
|
|
||||||
if 'MaxValue' in param:
|
|
||||||
field_args['max_value'] = int(param['MaxValue'])
|
|
||||||
field = forms.IntegerField(**field_args)
|
|
||||||
|
|
||||||
elif param_type == 'Boolean':
|
|
||||||
field_args['required'] = False
|
|
||||||
field = forms.BooleanField(**field_args)
|
|
||||||
|
|
||||||
if field:
|
|
||||||
self.fields[field_key] = field
|
|
||||||
|
|
||||||
@sensitive_variables('password')
|
|
||||||
def handle(self, request, data):
|
|
||||||
prefix_length = len(self.param_prefix)
|
|
||||||
params_list = [(k[prefix_length:], v) for (k, v) in data.items()
|
|
||||||
if k.startswith(self.param_prefix)]
|
|
||||||
fields = {
|
|
||||||
'stack_name': data.get('stack_name'),
|
|
||||||
'timeout_mins': data.get('timeout_mins'),
|
|
||||||
'disable_rollback': not(data.get('enable_rollback')),
|
|
||||||
'parameters': dict(params_list),
|
|
||||||
'files': json.loads(data.get('parameters')).get('files'),
|
|
||||||
'template': json.loads(data.get('parameters')).get('template')
|
|
||||||
}
|
|
||||||
if data.get('password'):
|
|
||||||
fields['password'] = data.get('password')
|
|
||||||
|
|
||||||
if data.get('environment_data'):
|
|
||||||
fields['environment'] = data.get('environment_data')
|
|
||||||
|
|
||||||
try:
|
|
||||||
api.heat.stack_create(self.request, **fields)
|
|
||||||
messages.info(request, _("Stack creation started."))
|
|
||||||
return True
|
|
||||||
except Exception:
|
|
||||||
exceptions.handle(request)
|
|
||||||
|
|
||||||
def _populate_custom_choices(self, custom_type):
|
|
||||||
if custom_type == 'neutron.network':
|
|
||||||
return instance_utils.network_field_data(self.request, True)
|
|
||||||
if custom_type == 'nova.keypair':
|
|
||||||
return instance_utils.keypair_field_data(self.request, True)
|
|
||||||
if custom_type == 'glance.image':
|
|
||||||
return image_utils.image_field_data(self.request, True)
|
|
||||||
if custom_type == 'nova.flavor':
|
|
||||||
return instance_utils.flavor_field_data(self.request, True)
|
|
||||||
return []
|
|
||||||
|
|
||||||
|
|
||||||
class EditStackForm(CreateStackForm):
|
|
||||||
|
|
||||||
class Meta(object):
|
|
||||||
name = _('Update Stack Parameters')
|
|
||||||
|
|
||||||
stack_id = forms.CharField(
|
|
||||||
label=_('Stack ID'),
|
|
||||||
widget=forms.widgets.HiddenInput)
|
|
||||||
stack_name = forms.CharField(
|
|
||||||
label=_('Stack Name'),
|
|
||||||
widget=forms.TextInput(attrs={'readonly': 'readonly'}))
|
|
||||||
|
|
||||||
@sensitive_variables('password')
|
|
||||||
def handle(self, request, data):
|
|
||||||
prefix_length = len(self.param_prefix)
|
|
||||||
params_list = [(k[prefix_length:], v) for (k, v) in data.items()
|
|
||||||
if k.startswith(self.param_prefix)]
|
|
||||||
|
|
||||||
stack_id = data.get('stack_id')
|
|
||||||
fields = {
|
|
||||||
'stack_name': data.get('stack_name'),
|
|
||||||
'timeout_mins': data.get('timeout_mins'),
|
|
||||||
'disable_rollback': not(data.get('enable_rollback')),
|
|
||||||
'parameters': dict(params_list),
|
|
||||||
'files': json.loads(data.get('parameters')).get('files'),
|
|
||||||
'template': json.loads(data.get('parameters')).get('template')
|
|
||||||
}
|
|
||||||
if data.get('password'):
|
|
||||||
fields['password'] = data.get('password')
|
|
||||||
|
|
||||||
if data.get('environment_data'):
|
|
||||||
fields['environment'] = data.get('environment_data')
|
|
||||||
|
|
||||||
try:
|
|
||||||
api.heat.stack_update(self.request, stack_id=stack_id, **fields)
|
|
||||||
messages.info(request, _("Stack update started."))
|
|
||||||
return True
|
|
||||||
except Exception:
|
|
||||||
exceptions.handle(request)
|
|
||||||
|
|
||||||
|
|
||||||
class PreviewStackForm(CreateStackForm):
|
|
||||||
|
|
||||||
class Meta(object):
|
|
||||||
name = _('Preview Stack Parameters')
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
self.next_view = kwargs.pop('next_view')
|
|
||||||
super(CreateStackForm, self).__init__(*args, **kwargs)
|
|
||||||
|
|
||||||
def handle(self, request, data):
|
|
||||||
prefix_length = len(self.param_prefix)
|
|
||||||
params_list = [(k[prefix_length:], v) for (k, v) in data.items()
|
|
||||||
if k.startswith(self.param_prefix)]
|
|
||||||
fields = {
|
|
||||||
'stack_name': data.get('stack_name'),
|
|
||||||
'timeout_mins': data.get('timeout_mins'),
|
|
||||||
'disable_rollback': not(data.get('enable_rollback')),
|
|
||||||
'parameters': dict(params_list),
|
|
||||||
'files': json.loads(data.get('parameters')).get('files'),
|
|
||||||
'template': json.loads(data.get('parameters')).get('template')
|
|
||||||
}
|
|
||||||
|
|
||||||
if data.get('environment_data'):
|
|
||||||
fields['environment'] = data.get('environment_data')
|
|
||||||
|
|
||||||
try:
|
|
||||||
stack_preview = api.heat.stack_preview(self.request, **fields)
|
|
||||||
request.method = 'GET'
|
|
||||||
return self.next_view.as_view()(request,
|
|
||||||
stack_preview=stack_preview)
|
|
||||||
except Exception:
|
|
||||||
exceptions.handle(request)
|
|
@ -1,350 +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
|
|
||||||
import logging
|
|
||||||
|
|
||||||
from django.conf import settings
|
|
||||||
from django.core.urlresolvers import reverse
|
|
||||||
from django.template.defaultfilters import register
|
|
||||||
from django.utils import html
|
|
||||||
from django.utils import safestring
|
|
||||||
import six
|
|
||||||
import six.moves.urllib.parse as urlparse
|
|
||||||
|
|
||||||
from openstack_dashboard.api import swift
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
resource_urls = {
|
|
||||||
"AWS::AutoScaling::AutoScalingGroup": {
|
|
||||||
'link': 'horizon:project:stacks:detail'},
|
|
||||||
"AWS::CloudFormation::Stack": {
|
|
||||||
'link': 'horizon:project:stacks:detail'},
|
|
||||||
"AWS::EC2::Instance": {
|
|
||||||
'link': 'horizon:project:instances:detail'},
|
|
||||||
"AWS::EC2::InternetGateway": {
|
|
||||||
'link': 'horizon:project:networks:ports:detail'},
|
|
||||||
"AWS::EC2::NetworkInterface": {
|
|
||||||
'link': 'horizon:project:networks:ports:detail'},
|
|
||||||
"AWS::EC2::RouteTable": {
|
|
||||||
'link': 'horizon:project:routers:detail'},
|
|
||||||
"AWS::EC2::SecurityGroup": {
|
|
||||||
'link': 'horizon:project:security_groups:index'},
|
|
||||||
"AWS::EC2::Subnet": {
|
|
||||||
'link': 'horizon:project:networks:subnets:detail'},
|
|
||||||
"AWS::EC2::Volume": {
|
|
||||||
'link': 'horizon:project:volumes:detail'},
|
|
||||||
"AWS::EC2::VPC": {
|
|
||||||
'link': 'horizon:project:networks:detail'},
|
|
||||||
"AWS::S3::Bucket": {
|
|
||||||
'link': 'horizon:project:containers:index'},
|
|
||||||
"OS::Cinder::Volume": {
|
|
||||||
'link': 'horizon:project:volumes:detail'},
|
|
||||||
"OS::Heat::AccessPolicy": {
|
|
||||||
'link': 'horizon:project:stacks:detail'},
|
|
||||||
"OS::Heat::AutoScalingGroup": {
|
|
||||||
'link': 'horizon:project:stacks:detail'},
|
|
||||||
"OS::Heat::CloudConfig": {
|
|
||||||
'link': 'horizon:project:stacks:detail'},
|
|
||||||
"OS::Neutron::Firewall": {
|
|
||||||
'link': 'horizon:project:firewalls:firewalldetails'},
|
|
||||||
"OS::Neutron::FirewallPolicy": {
|
|
||||||
'link': 'horizon:project:firewalls:policydetails'},
|
|
||||||
"OS::Neutron::FirewallRule": {
|
|
||||||
'link': 'horizon:project:firewalls:ruledetails'},
|
|
||||||
"OS::Heat::HARestarter": {
|
|
||||||
'link': 'horizon:project:stacks:detail'},
|
|
||||||
"OS::Heat::InstanceGroup": {
|
|
||||||
'link': 'horizon:project:stacks:detail'},
|
|
||||||
"OS::Heat::MultipartMime": {
|
|
||||||
'link': 'horizon:project:stacks:detail'},
|
|
||||||
"OS::Heat::ResourceGroup": {
|
|
||||||
'link': 'horizon:project:stacks:detail'},
|
|
||||||
"OS::Heat::SoftwareConfig": {
|
|
||||||
'link': 'horizon:project:stacks:detail'},
|
|
||||||
"OS::Heat::StructuredConfig": {
|
|
||||||
'link': 'horizon:project:stacks:detail'},
|
|
||||||
"OS::Heat::StructuredDeployment": {
|
|
||||||
'link': 'horizon:project:stacks:detail'},
|
|
||||||
"OS::Heat::Stack": {
|
|
||||||
'link': 'horizon:project:stacks:detail'},
|
|
||||||
"OS::Heat::WaitCondition": {
|
|
||||||
'link': 'horizon:project:stacks:detail'},
|
|
||||||
"OS::Heat::WaitConditionHandle": {
|
|
||||||
'link': 'horizon:project:stacks:detail'},
|
|
||||||
"OS::Neutron::IKEPolicy": {
|
|
||||||
'link': 'horizon:project:vpn:ikepolicydetails'},
|
|
||||||
"OS::Neutron::IPsecPolicy": {
|
|
||||||
'link': 'horizon:project:vpn:ipsecpolicydetails'},
|
|
||||||
"OS::Neutron::IPsecSiteConnection": {
|
|
||||||
'link': 'horizon:project:vpn:ipsecsiteconnectiondetails'},
|
|
||||||
"OS::Neutron::Net": {
|
|
||||||
'link': 'horizon:project:networks:detail'},
|
|
||||||
"OS::Neutron::Port": {
|
|
||||||
'link': 'horizon:project:networks:ports:detail'},
|
|
||||||
"OS::Neutron::Router": {
|
|
||||||
'link': 'horizon:project:routers:detail'},
|
|
||||||
"OS::Neutron::Subnet": {
|
|
||||||
'link': 'horizon:project:networks:subnets:detail'},
|
|
||||||
"OS::Neutron::VPNService": {
|
|
||||||
'link': 'horizon:project:vpn:vpnservicedetails'},
|
|
||||||
"OS::Nova::KeyPair": {
|
|
||||||
'link': 'horizon:project:key_pairs:index'},
|
|
||||||
"OS::Nova::Server": {
|
|
||||||
'link': 'horizon:project:instances:detail'},
|
|
||||||
"OS::Swift::Container": {
|
|
||||||
'link': 'horizon:project:containers:index',
|
|
||||||
'format_pattern': '%s' + swift.FOLDER_DELIMITER},
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def resource_to_url(resource):
|
|
||||||
if (not resource or
|
|
||||||
not resource.physical_resource_id or
|
|
||||||
not hasattr(resource, 'resource_type')):
|
|
||||||
return None
|
|
||||||
|
|
||||||
mapping = resource_urls.get(resource.resource_type, {})
|
|
||||||
try:
|
|
||||||
if 'link' not in mapping:
|
|
||||||
return None
|
|
||||||
format_pattern = mapping.get('format_pattern') or '%s'
|
|
||||||
rid = format_pattern % resource.physical_resource_id
|
|
||||||
url = reverse(mapping['link'], args=(rid,))
|
|
||||||
except Exception as e:
|
|
||||||
LOG.exception(e)
|
|
||||||
return None
|
|
||||||
return url
|
|
||||||
|
|
||||||
|
|
||||||
@register.filter
|
|
||||||
def stack_output(output):
|
|
||||||
if not output:
|
|
||||||
return u''
|
|
||||||
if isinstance(output, six.string_types):
|
|
||||||
parts = urlparse.urlsplit(output)
|
|
||||||
if parts.netloc and parts.scheme in ('http', 'https'):
|
|
||||||
url = html.escape(output)
|
|
||||||
safe_link = u'<a href="%s" target="_blank">%s</a>' % (url, url)
|
|
||||||
return safestring.mark_safe(safe_link)
|
|
||||||
if isinstance(output, dict) or isinstance(output, list):
|
|
||||||
output = json.dumps(output, indent=2)
|
|
||||||
return safestring.mark_safe(u'<pre>%s</pre>' % html.escape(output))
|
|
||||||
|
|
||||||
static_url = getattr(settings, "STATIC_URL", "/static/")
|
|
||||||
resource_images = {
|
|
||||||
'LB_FAILED': static_url + 'dashboard/img/lb-red.svg',
|
|
||||||
'LB_DELETE': static_url + 'dashboard/img/lb-red.svg',
|
|
||||||
'LB_IN_PROGRESS': static_url + 'dashboard/img/lb-gray.gif',
|
|
||||||
'LB_INIT': static_url + 'dashboard/img/lb-gray.svg',
|
|
||||||
'LB_COMPLETE': static_url + 'dashboard/img/lb-green.svg',
|
|
||||||
'DB_FAILED': static_url + 'dashboard/img/db-red.svg',
|
|
||||||
'DB_DELETE': static_url + 'dashboard/img/db-red.svg',
|
|
||||||
'DB_IN_PROGRESS': static_url + 'dashboard/img/db-gray.gif',
|
|
||||||
'DB_INIT': static_url + 'dashboard/img/db-gray.svg',
|
|
||||||
'DB_COMPLETE': static_url + 'dashboard/img/db-green.svg',
|
|
||||||
'STACK_FAILED': static_url + 'dashboard/img/stack-red.svg',
|
|
||||||
'STACK_DELETE': static_url + 'dashboard/img/stack-red.svg',
|
|
||||||
'STACK_IN_PROGRESS': static_url + 'dashboard/img/stack-gray.gif',
|
|
||||||
'STACK_INIT': static_url + 'dashboard/img/stack-gray.svg',
|
|
||||||
'STACK_COMPLETE': static_url + 'dashboard/img/stack-green.svg',
|
|
||||||
'SERVER_FAILED': static_url + 'dashboard/img/server-red.svg',
|
|
||||||
'SERVER_DELETE': static_url + 'dashboard/img/server-red.svg',
|
|
||||||
'SERVER_IN_PROGRESS': static_url + 'dashboard/img/server-gray.gif',
|
|
||||||
'SERVER_INIT': static_url + 'dashboard/img/server-gray.svg',
|
|
||||||
'SERVER_COMPLETE': static_url + 'dashboard/img/server-green.svg',
|
|
||||||
'ALARM_FAILED': static_url + 'dashboard/img/alarm-red.svg',
|
|
||||||
'ALARM_DELETE': static_url + 'dashboard/img/alarm-red.svg',
|
|
||||||
'ALARM_IN_PROGRESS': static_url + 'dashboard/img/alarm-gray.gif',
|
|
||||||
'ALARM_INIT': static_url + 'dashboard/img/alarm-gray.svg',
|
|
||||||
'ALARM_COMPLETE': static_url + 'dashboard/img/alarm-green.svg',
|
|
||||||
'VOLUME_FAILED': static_url + 'dashboard/img/volume-red.svg',
|
|
||||||
'VOLUME_DELETE': static_url + 'dashboard/img/volume-red.svg',
|
|
||||||
'VOLUME_IN_PROGRESS': static_url + 'dashboard/img/volume-gray.gif',
|
|
||||||
'VOLUME_INIT': static_url + 'dashboard/img/volume-gray.svg',
|
|
||||||
'VOLUME_COMPLETE': static_url + 'dashboard/img/volume-green.svg',
|
|
||||||
'IMAGE_FAILED': static_url + 'dashboard/img/image-red.svg',
|
|
||||||
'IMAGE_DELETE': static_url + 'dashboard/img/image-red.svg',
|
|
||||||
'IMAGE_IN_PROGRESS': static_url + 'dashboard/img/image-gray.gif',
|
|
||||||
'IMAGE_INIT': static_url + 'dashboard/img/image-gray.svg',
|
|
||||||
'IMAGE_COMPLETE': static_url + 'dashboard/img/image-green.svg',
|
|
||||||
'WAIT_FAILED': static_url + 'dashboard/img/wait-red.svg',
|
|
||||||
'WAIT_DELETE': static_url + 'dashboard/img/wait-red.svg',
|
|
||||||
'WAIT_IN_PROGRESS': static_url + 'dashboard/img/wait-gray.gif',
|
|
||||||
'WAIT_INIT': static_url + 'dashboard/img/wait-gray.svg',
|
|
||||||
'WAIT_COMPLETE': static_url + 'dashboard/img/wait-green.svg',
|
|
||||||
'FIREWALL_FAILED': static_url + 'dashboard/img/firewall-red.svg',
|
|
||||||
'FIREWALL_DELETE': static_url + 'dashboard/img/firewall-red.svg',
|
|
||||||
'FIREWALL_IN_PROGRESS': static_url + 'dashboard/img/firewall-gray.gif',
|
|
||||||
'FIREWALL_INIT': static_url + 'dashboard/img/firewall-gray.svg',
|
|
||||||
'FIREWALL_COMPLETE': static_url + 'dashboard/img/firewall-green.svg',
|
|
||||||
'FLOATINGIP_FAILED': static_url + 'dashboard/img/floatingip-red.svg',
|
|
||||||
'FLOATINGIP_DELETE': static_url + 'dashboard/img/floatingip-red.svg',
|
|
||||||
'FLOATINGIP_IN_PROGRESS': static_url + 'dashboard/img/floatingip-gray.gif',
|
|
||||||
'FLOATINGIP_INIT': static_url + 'dashboard/img/floatingip-gray.svg',
|
|
||||||
'FLOATINGIP_COMPLETE': static_url + 'dashboard/img/floatingip-green.svg',
|
|
||||||
'ROUTER_FAILED': static_url + 'dashboard/img/router-red.svg',
|
|
||||||
'ROUTER_DELETE': static_url + 'dashboard/img/router-red.svg',
|
|
||||||
'ROUTER_IN_PROGRESS': static_url + 'dashboard/img/router-gray.gif',
|
|
||||||
'ROUTER_INIT': static_url + 'dashboard/img/router-gray.svg',
|
|
||||||
'ROUTER_COMPLETE': static_url + 'dashboard/img/router-green.svg',
|
|
||||||
'POLICY_FAILED': static_url + 'dashboard/img/policy-red.svg',
|
|
||||||
'POLICY_DELETE': static_url + 'dashboard/img/policy-red.svg',
|
|
||||||
'POLICY_IN_PROGRESS': static_url + 'dashboard/img/policy-gray.gif',
|
|
||||||
'POLICY_INIT': static_url + 'dashboard/img/policy-gray.svg',
|
|
||||||
'POLICY_COMPLETE': static_url + 'dashboard/img/policy-green.svg',
|
|
||||||
'CONFIG_FAILED': static_url + 'dashboard/img/config-red.svg',
|
|
||||||
'CONFIG_DELETE': static_url + 'dashboard/img/config-red.svg',
|
|
||||||
'CONFIG_IN_PROGRESS': static_url + 'dashboard/img/config-gray.gif',
|
|
||||||
'CONFIG_INIT': static_url + 'dashboard/img/config-gray.svg',
|
|
||||||
'CONFIG_COMPLETE': static_url + 'dashboard/img/config-green.svg',
|
|
||||||
'NETWORK_FAILED': static_url + 'dashboard/img/network-red.svg',
|
|
||||||
'NETWORK_DELETE': static_url + 'dashboard/img/network-red.svg',
|
|
||||||
'NETWORK_IN_PROGRESS': static_url + 'dashboard/img/network-gray.gif',
|
|
||||||
'NETWORK_INIT': static_url + 'dashboard/img/network-gray.svg',
|
|
||||||
'NETWORK_COMPLETE': static_url + 'dashboard/img/network-green.svg',
|
|
||||||
'PORT_FAILED': static_url + 'dashboard/img/port-red.svg',
|
|
||||||
'PORT_DELETE': static_url + 'dashboard/img/port-red.svg',
|
|
||||||
'PORT_IN_PROGRESS': static_url + 'dashboard/img/port-gray.gif',
|
|
||||||
'PORT_INIT': static_url + 'dashboard/img/port-gray.svg',
|
|
||||||
'PORT_COMPLETE': static_url + 'dashboard/img/port-green.svg',
|
|
||||||
'SECURITYGROUP_FAILED': static_url + 'dashboard/img/securitygroup-red.svg',
|
|
||||||
'SECURITYGROUP_DELETE': static_url + 'dashboard/img/securitygroup-red.svg',
|
|
||||||
'SECURITYGROUP_IN_PROGRESS':
|
|
||||||
static_url + 'dashboard/img/securitygroup-gray.gif',
|
|
||||||
'SECURITYGROUP_INIT': static_url + 'dashboard/img/securitygroup-gray.svg',
|
|
||||||
'SECURITYGROUP_COMPLETE':
|
|
||||||
static_url + 'dashboard/img/securitygroup-green.svg',
|
|
||||||
'VPN_FAILED': static_url + 'dashboard/img/vpn-red.svg',
|
|
||||||
'VPN_DELETE': static_url + 'dashboard/img/vpn-red.svg',
|
|
||||||
'VPN_IN_PROGRESS': static_url + 'dashboard/img/vpn-gray.gif',
|
|
||||||
'VPN_INIT': static_url + 'dashboard/img/vpn-gray.svg',
|
|
||||||
'VPN_COMPLETE': static_url + 'dashboard/img/vpn-green.svg',
|
|
||||||
'FLAVOR_FAILED': static_url + 'dashboard/img/flavor-red.svg',
|
|
||||||
'FLAVOR_DELETE': static_url + 'dashboard/img/flavor-red.svg',
|
|
||||||
'FLAVOR_IN_PROGRESS': static_url + 'dashboard/img/flavor-gray.gif',
|
|
||||||
'FLAVOR_INIT': static_url + 'dashboard/img/flavor-gray.svg',
|
|
||||||
'FLAVOR_COMPLETE': static_url + 'dashboard/img/flavor-green.svg',
|
|
||||||
'KEYPAIR_FAILED': static_url + 'dashboard/img/keypair-red.svg',
|
|
||||||
'KEYPAIR_DELETE': static_url + 'dashboard/img/keypair-red.svg',
|
|
||||||
'KEYPAIR_IN_PROGRESS': static_url + 'dashboard/img/keypair-gray.gif',
|
|
||||||
'KEYPAIR_INIT': static_url + 'dashboard/img/keypair-gray.svg',
|
|
||||||
'KEYPAIR_COMPLETE': static_url + 'dashboard/img/keypair-green.svg',
|
|
||||||
'UNKNOWN_FAILED': static_url + 'dashboard/img/unknown-red.svg',
|
|
||||||
'UNKNOWN_DELETE': static_url + 'dashboard/img/unknown-red.svg',
|
|
||||||
'UNKNOWN_IN_PROGRESS': static_url + 'dashboard/img/unknown-gray.gif',
|
|
||||||
'UNKNOWN_INIT': static_url + 'dashboard/img/unknown-gray.svg',
|
|
||||||
'UNKNOWN_COMPLETE': static_url + 'dashboard/img/unknown-green.svg',
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
resource_types = {
|
|
||||||
# LB
|
|
||||||
'LoadBalance': 'LB',
|
|
||||||
'HealthMonitor': 'LB',
|
|
||||||
'PoolMember': 'LB',
|
|
||||||
'Pool': 'LB',
|
|
||||||
# DB
|
|
||||||
'DBInstance': 'DB',
|
|
||||||
'Database': 'DB',
|
|
||||||
# SERVER
|
|
||||||
'Instance': 'SERVER',
|
|
||||||
'Server': 'SERVER',
|
|
||||||
# ALARM
|
|
||||||
'Alarm': 'ALARM',
|
|
||||||
'CombinationAlarm': 'ALARM',
|
|
||||||
'CWLiteAlarm': 'ALARM',
|
|
||||||
# VOLUME
|
|
||||||
'Volume': 'VOLUME',
|
|
||||||
'VolumeAttachment': 'VOLUME',
|
|
||||||
# STACK
|
|
||||||
'stack': 'STACK',
|
|
||||||
'AutoScalingGroup': 'STACK',
|
|
||||||
'InstanceGroup': 'STACK',
|
|
||||||
'ServerGroup': 'STACK',
|
|
||||||
'ResourceGroup': 'STACK',
|
|
||||||
# IMAGE
|
|
||||||
'Image': 'IMAGE',
|
|
||||||
# WAIT
|
|
||||||
'WaitCondition': 'WAIT',
|
|
||||||
'WaitConditionHandle': 'WAIT',
|
|
||||||
'UpdateWaitConditionHandle': 'WAIT',
|
|
||||||
# FIREWALL
|
|
||||||
'Firewall': 'FIREWALL',
|
|
||||||
'FirewallPolicy': 'FIREWALL',
|
|
||||||
'FirewallRule': 'FIREWALL',
|
|
||||||
# FLOATINGIP
|
|
||||||
'FloatingIP': 'FLOATINGIP',
|
|
||||||
'FloatingIPAssociation': 'FLOATINGIP',
|
|
||||||
# ROUTER
|
|
||||||
'Router': 'ROUTER',
|
|
||||||
'RouterGateway': 'ROUTER',
|
|
||||||
'RouterInterface': 'ROUTER',
|
|
||||||
# POLICY
|
|
||||||
'ScalingPolicy': 'POLICY',
|
|
||||||
# CONFIG
|
|
||||||
'CloudConfig': 'CONFIG',
|
|
||||||
'MultipartMime': 'CONFIG',
|
|
||||||
'SoftwareConfig': 'CONFIG',
|
|
||||||
'SoftwareDeployment': 'CONFIG',
|
|
||||||
'StructuredConfig': 'CONFIG',
|
|
||||||
'StructuredDeployment': 'CONFIG',
|
|
||||||
# NETWORK
|
|
||||||
'Net': 'NETWORK',
|
|
||||||
'Subnet': 'NETWORK',
|
|
||||||
'NetworkGateway': 'NETWORK',
|
|
||||||
'ProviderNet': 'NETWORK',
|
|
||||||
# PORT
|
|
||||||
'Port': 'PORT',
|
|
||||||
# SECURITYGROUP
|
|
||||||
'SecurityGroup': 'SECURITYGROUP',
|
|
||||||
# VPN
|
|
||||||
'VPNService': 'VPN',
|
|
||||||
# FLAVOR
|
|
||||||
'Flavor': 'FLAVOR',
|
|
||||||
# KEYPAIR
|
|
||||||
'KeyPair': 'KEYPAIR',
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def get_resource_type(type):
|
|
||||||
for key, value in resource_types.items():
|
|
||||||
if key in type:
|
|
||||||
return value
|
|
||||||
|
|
||||||
return 'UNKNOWN'
|
|
||||||
|
|
||||||
|
|
||||||
def get_resource_status(status):
|
|
||||||
if ('IN_PROGRESS' in status):
|
|
||||||
return 'IN_PROGRESS'
|
|
||||||
elif ('FAILED' in status):
|
|
||||||
return 'FAILED'
|
|
||||||
elif ('DELETE' in status):
|
|
||||||
return 'DELETE'
|
|
||||||
elif ('INIT' in status):
|
|
||||||
return 'INIT'
|
|
||||||
else:
|
|
||||||
return 'COMPLETE'
|
|
||||||
|
|
||||||
|
|
||||||
def get_resource_image(status, type):
|
|
||||||
"""Sets the image url and in_progress action sw based on status."""
|
|
||||||
resource_type = get_resource_type(type)
|
|
||||||
resource_status = get_resource_status(status)
|
|
||||||
resource_state = resource_type + "_" + resource_status
|
|
||||||
|
|
||||||
for key in resource_images:
|
|
||||||
if key == resource_state:
|
|
||||||
return resource_images.get(key)
|
|
@ -1,21 +0,0 @@
|
|||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
|
|
||||||
import horizon
|
|
||||||
|
|
||||||
|
|
||||||
class Stacks(horizon.Panel):
|
|
||||||
name = _("Stacks")
|
|
||||||
slug = "stacks"
|
|
||||||
permissions = ('openstack.services.orchestration',)
|
|
@ -1,23 +0,0 @@
|
|||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
||||||
# implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
|
|
||||||
import horizon
|
|
||||||
|
|
||||||
|
|
||||||
class ResourceTypes(horizon.Panel):
|
|
||||||
name = _("Resource Types")
|
|
||||||
slug = "stacks.resource_types"
|
|
||||||
permissions = ('openstack.services.orchestration',)
|
|
||||||
policy_rules = (("orchestration", "stacks:list_resource_types"),)
|
|
@ -1,36 +0,0 @@
|
|||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
||||||
# implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
|
|
||||||
from horizon import tables
|
|
||||||
|
|
||||||
|
|
||||||
class ResourceTypesFilterAction(tables.FilterAction):
|
|
||||||
filter_type = 'server'
|
|
||||||
filter_choices = (('name', _('Type ='), True, _("Case sensitive")),)
|
|
||||||
|
|
||||||
|
|
||||||
class ResourceTypesTable(tables.DataTable):
|
|
||||||
name = tables.Column("resource_type",
|
|
||||||
verbose_name=_("Type"),
|
|
||||||
link="horizon:project:stacks.resource_types:details",)
|
|
||||||
|
|
||||||
def get_object_id(self, resource):
|
|
||||||
return resource.resource_type
|
|
||||||
|
|
||||||
class Meta(object):
|
|
||||||
name = "resource_types"
|
|
||||||
verbose_name = _("Resource Types")
|
|
||||||
table_actions = (ResourceTypesFilterAction,)
|
|
||||||
multi_select = False
|
|
@ -1,32 +0,0 @@
|
|||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
||||||
# implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
|
|
||||||
from horizon import tabs
|
|
||||||
|
|
||||||
|
|
||||||
class ResourceTypeOverviewTab(tabs.Tab):
|
|
||||||
name = _("Overview")
|
|
||||||
slug = "resource_type_overview"
|
|
||||||
template_name = "project/stacks.resource_types/_details.html"
|
|
||||||
|
|
||||||
def get_context_data(self, request):
|
|
||||||
return {"r_type": self.tab_group.kwargs['rt'],
|
|
||||||
"r_type_attributes": self.tab_group.kwargs['rt_attributes'],
|
|
||||||
"r_type_properties": self.tab_group.kwargs['rt_properties']}
|
|
||||||
|
|
||||||
|
|
||||||
class ResourceTypeDetailsTabs(tabs.TabGroup):
|
|
||||||
slug = "resource_type_details"
|
|
||||||
tabs = (ResourceTypeOverviewTab,)
|
|
@ -1,15 +0,0 @@
|
|||||||
{% load i18n %}
|
|
||||||
|
|
||||||
<div class="detail">
|
|
||||||
<dl>
|
|
||||||
<dd>{{ r_type }}</dd>
|
|
||||||
</dl>
|
|
||||||
|
|
||||||
<h4>{% trans "Attributes" %}</h4>
|
|
||||||
<pre>{{ r_type_attributes }}
|
|
||||||
</pre>
|
|
||||||
|
|
||||||
<h4>{% trans "Properties" %}</h4>
|
|
||||||
<pre>{{ r_type_properties }}
|
|
||||||
</pre>
|
|
||||||
</div>
|
|
@ -1,52 +0,0 @@
|
|||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
||||||
# implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
|
|
||||||
from django.core.urlresolvers import reverse
|
|
||||||
from django import http
|
|
||||||
|
|
||||||
from mox3.mox import IsA
|
|
||||||
|
|
||||||
from openstack_dashboard import api
|
|
||||||
from openstack_dashboard.test import helpers as test
|
|
||||||
|
|
||||||
|
|
||||||
class ResourceTypesTests(test.TestCase):
|
|
||||||
|
|
||||||
@test.create_stubs({api.heat: ('resource_types_list',)})
|
|
||||||
def test_index(self):
|
|
||||||
filters = {}
|
|
||||||
api.heat.resource_types_list(
|
|
||||||
IsA(http.HttpRequest), filters=filters).AndReturn(
|
|
||||||
self.resource_types.list())
|
|
||||||
self.mox.ReplayAll()
|
|
||||||
|
|
||||||
res = self.client.get(
|
|
||||||
reverse('horizon:project:stacks.resource_types:index'))
|
|
||||||
self.assertTemplateUsed(
|
|
||||||
res, 'horizon/common/_data_table_view.html')
|
|
||||||
self.assertContains(res, 'AWS::CloudFormation::Stack')
|
|
||||||
|
|
||||||
@test.create_stubs({api.heat: ('resource_type_get',)})
|
|
||||||
def test_detail_view(self):
|
|
||||||
rt = self.api_resource_types.first()
|
|
||||||
|
|
||||||
api.heat.resource_type_get(
|
|
||||||
IsA(http.HttpRequest), rt['resource_type']).AndReturn(rt)
|
|
||||||
self.mox.ReplayAll()
|
|
||||||
|
|
||||||
url = reverse('horizon:project:stacks.resource_types:details',
|
|
||||||
args=[rt['resource_type']])
|
|
||||||
res = self.client.get(url)
|
|
||||||
|
|
||||||
self.assertTemplateUsed(res, 'horizon/common/_detail.html')
|
|
||||||
self.assertNoMessages()
|
|
@ -1,22 +0,0 @@
|
|||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
||||||
# implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
|
|
||||||
from django.conf.urls import url
|
|
||||||
|
|
||||||
from openstack_dashboard.dashboards.project.stacks.resource_types import views
|
|
||||||
|
|
||||||
urlpatterns = [
|
|
||||||
url(r'^$', views.ResourceTypesView.as_view(), name='index'),
|
|
||||||
url(r'^(?P<resource_type>[^/]+)/$',
|
|
||||||
views.DetailView.as_view(), name='details'),
|
|
||||||
]
|
|
@ -1,78 +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 yaml
|
|
||||||
|
|
||||||
from django.core.urlresolvers import reverse
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
|
|
||||||
from horizon import exceptions
|
|
||||||
from horizon import tables
|
|
||||||
from horizon import tabs
|
|
||||||
|
|
||||||
from openstack_dashboard import api
|
|
||||||
import openstack_dashboard.dashboards.project.stacks.resource_types.tables \
|
|
||||||
as project_tables
|
|
||||||
import openstack_dashboard.dashboards.project.stacks.resource_types.tabs \
|
|
||||||
as project_tabs
|
|
||||||
|
|
||||||
|
|
||||||
class ResourceTypesView(tables.DataTableView):
|
|
||||||
table_class = project_tables.ResourceTypesTable
|
|
||||||
page_title = _("Resource Types")
|
|
||||||
|
|
||||||
def get_data(self):
|
|
||||||
try:
|
|
||||||
filters = self.get_filters()
|
|
||||||
if 'name' in filters:
|
|
||||||
filters['name'] = '.*' + filters['name']
|
|
||||||
r_types = sorted(api.heat.resource_types_list(self.request,
|
|
||||||
filters=filters),
|
|
||||||
key=lambda resource: resource.resource_type)
|
|
||||||
except Exception:
|
|
||||||
r_types = []
|
|
||||||
msg = _('Unable to retrieve stack resource types.')
|
|
||||||
exceptions.handle(self.request, msg)
|
|
||||||
return r_types
|
|
||||||
|
|
||||||
|
|
||||||
class DetailView(tabs.TabView):
|
|
||||||
tab_group_class = project_tabs.ResourceTypeDetailsTabs
|
|
||||||
template_name = 'horizon/common/_detail.html'
|
|
||||||
page_title = "{{ resource_type }}"
|
|
||||||
|
|
||||||
def get_resource_type(self, request, **kwargs):
|
|
||||||
try:
|
|
||||||
resource_type_overview = api.heat.resource_type_get(
|
|
||||||
request,
|
|
||||||
kwargs['resource_type'])
|
|
||||||
return resource_type_overview
|
|
||||||
except Exception:
|
|
||||||
msg = _('Unable to retrieve resource type details.')
|
|
||||||
exceptions.handle(request, msg, redirect=self.get_redirect_url())
|
|
||||||
|
|
||||||
def get_tabs(self, request, **kwargs):
|
|
||||||
resource_type_overview = self.get_resource_type(request, **kwargs)
|
|
||||||
r_type = resource_type_overview['resource_type']
|
|
||||||
r_type_attributes = resource_type_overview['attributes']
|
|
||||||
r_type_properties = resource_type_overview['properties']
|
|
||||||
return self.tab_group_class(
|
|
||||||
request,
|
|
||||||
rt=r_type,
|
|
||||||
rt_attributes=yaml.safe_dump(r_type_attributes, indent=2),
|
|
||||||
rt_properties=yaml.safe_dump(r_type_properties, indent=2),
|
|
||||||
**kwargs)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_redirect_url():
|
|
||||||
return reverse('horizon:project:stacks.resources:index')
|
|
@ -1,44 +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.template.defaultfilters import title
|
|
||||||
from django.template.loader import render_to_string
|
|
||||||
|
|
||||||
from horizon.utils import filters
|
|
||||||
|
|
||||||
|
|
||||||
def stack_info(stack, stack_image):
|
|
||||||
stack.stack_status_desc = title(
|
|
||||||
filters.replace_underscores(stack.stack_status))
|
|
||||||
if stack.stack_status_reason:
|
|
||||||
stack.stack_status_reason = title(
|
|
||||||
filters.replace_underscores(stack.stack_status_reason)
|
|
||||||
)
|
|
||||||
context = {}
|
|
||||||
context['stack'] = stack
|
|
||||||
context['stack_image'] = stack_image
|
|
||||||
return render_to_string('project/stacks/_stack_info.html',
|
|
||||||
context)
|
|
||||||
|
|
||||||
|
|
||||||
def resource_info(resource):
|
|
||||||
resource.resource_status_desc = title(
|
|
||||||
filters.replace_underscores(resource.resource_status)
|
|
||||||
)
|
|
||||||
if resource.resource_status_reason:
|
|
||||||
resource.resource_status_reason = title(
|
|
||||||
filters.replace_underscores(resource.resource_status_reason)
|
|
||||||
)
|
|
||||||
context = {}
|
|
||||||
context['resource'] = resource
|
|
||||||
return render_to_string('project/stacks/_resource_info.html',
|
|
||||||
context)
|
|
@ -1,413 +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.core import urlresolvers
|
|
||||||
from django.http import Http404
|
|
||||||
from django.template.defaultfilters import title
|
|
||||||
from django.utils.translation import pgettext_lazy
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
from django.utils.translation import ungettext_lazy
|
|
||||||
from heatclient import exc
|
|
||||||
|
|
||||||
from horizon import exceptions
|
|
||||||
from horizon import messages
|
|
||||||
from horizon import tables
|
|
||||||
from horizon.utils import filters
|
|
||||||
|
|
||||||
from openstack_dashboard import api
|
|
||||||
from openstack_dashboard.dashboards.project.stacks import mappings
|
|
||||||
|
|
||||||
|
|
||||||
class LaunchStack(tables.LinkAction):
|
|
||||||
name = "launch"
|
|
||||||
verbose_name = _("Launch Stack")
|
|
||||||
url = "horizon:project:stacks:select_template"
|
|
||||||
classes = ("ajax-modal",)
|
|
||||||
icon = "plus"
|
|
||||||
policy_rules = (("orchestration", "stacks:validate_template"),
|
|
||||||
("orchestration", "stacks:create"),)
|
|
||||||
|
|
||||||
|
|
||||||
class PreviewStack(tables.LinkAction):
|
|
||||||
name = "preview"
|
|
||||||
verbose_name = _("Preview Stack")
|
|
||||||
url = "horizon:project:stacks:preview_template"
|
|
||||||
classes = ("ajax-modal",)
|
|
||||||
icon = "eye"
|
|
||||||
policy_rules = (("orchestration", "stacks:validate_template"),
|
|
||||||
("orchestration", "stacks:preview"),)
|
|
||||||
|
|
||||||
|
|
||||||
class CheckStack(tables.BatchAction):
|
|
||||||
name = "check"
|
|
||||||
verbose_name = _("Check Stack")
|
|
||||||
policy_rules = (("orchestration", "actions:action"),)
|
|
||||||
icon = "check-square"
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def action_present(count):
|
|
||||||
return ungettext_lazy(
|
|
||||||
u"Check Stack",
|
|
||||||
u"Check Stacks",
|
|
||||||
count
|
|
||||||
)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def action_past(count):
|
|
||||||
return ungettext_lazy(
|
|
||||||
u"Checked Stack",
|
|
||||||
u"Checked Stacks",
|
|
||||||
count
|
|
||||||
)
|
|
||||||
|
|
||||||
def action(self, request, stack_id):
|
|
||||||
api.heat.action_check(request, stack_id)
|
|
||||||
|
|
||||||
|
|
||||||
class SuspendStack(tables.BatchAction):
|
|
||||||
name = "suspend"
|
|
||||||
verbose_name = _("Suspend Stack")
|
|
||||||
policy_rules = (("orchestration", "actions:action"),)
|
|
||||||
icon = "pause"
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def action_present(count):
|
|
||||||
return ungettext_lazy(
|
|
||||||
u"Suspend Stack",
|
|
||||||
u"Suspend Stacks",
|
|
||||||
count
|
|
||||||
)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def action_past(count):
|
|
||||||
return ungettext_lazy(
|
|
||||||
u"Suspended Stack",
|
|
||||||
u"Suspended Stacks",
|
|
||||||
count
|
|
||||||
)
|
|
||||||
|
|
||||||
def action(self, request, stack_id):
|
|
||||||
try:
|
|
||||||
api.heat.action_suspend(request, stack_id)
|
|
||||||
except Exception:
|
|
||||||
msg = _('Failed to suspend stack.')
|
|
||||||
exceptions.handle(request, msg)
|
|
||||||
|
|
||||||
|
|
||||||
class ResumeStack(tables.BatchAction):
|
|
||||||
name = "resume"
|
|
||||||
verbose_name = _("Resume Stack")
|
|
||||||
policy_rules = (("orchestration", "actions:action"),)
|
|
||||||
icon = "play"
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def action_present(count):
|
|
||||||
return ungettext_lazy(
|
|
||||||
u"Resume Stack",
|
|
||||||
u"Resume Stacks",
|
|
||||||
count
|
|
||||||
)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def action_past(count):
|
|
||||||
return ungettext_lazy(
|
|
||||||
u"Resumed Stack",
|
|
||||||
u"Resumed Stacks",
|
|
||||||
count
|
|
||||||
)
|
|
||||||
|
|
||||||
def action(self, request, stack_id):
|
|
||||||
try:
|
|
||||||
api.heat.action_resume(request, stack_id)
|
|
||||||
except Exception:
|
|
||||||
msg = _('Failed to resume stack.')
|
|
||||||
exceptions.handle(request, msg)
|
|
||||||
|
|
||||||
|
|
||||||
class ChangeStackTemplate(tables.LinkAction):
|
|
||||||
name = "edit"
|
|
||||||
verbose_name = _("Change Stack Template")
|
|
||||||
url = "horizon:project:stacks:change_template"
|
|
||||||
classes = ("ajax-modal",)
|
|
||||||
icon = "pencil"
|
|
||||||
|
|
||||||
def get_link_url(self, stack):
|
|
||||||
return urlresolvers.reverse(self.url, args=[stack.id])
|
|
||||||
|
|
||||||
|
|
||||||
class DeleteStack(tables.DeleteAction):
|
|
||||||
@staticmethod
|
|
||||||
def action_present(count):
|
|
||||||
return ungettext_lazy(
|
|
||||||
u"Delete Stack",
|
|
||||||
u"Delete Stacks",
|
|
||||||
count
|
|
||||||
)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def action_past(count):
|
|
||||||
return ungettext_lazy(
|
|
||||||
u"Deleted Stack",
|
|
||||||
u"Deleted Stacks",
|
|
||||||
count
|
|
||||||
)
|
|
||||||
|
|
||||||
policy_rules = (("orchestration", "stacks:delete"),)
|
|
||||||
|
|
||||||
def delete(self, request, stack_id):
|
|
||||||
try:
|
|
||||||
api.heat.stack_delete(request, stack_id)
|
|
||||||
except Exception:
|
|
||||||
msg = _('Failed to delete stack.')
|
|
||||||
exceptions.handle(request, msg)
|
|
||||||
|
|
||||||
def allowed(self, request, stack):
|
|
||||||
if stack is not None:
|
|
||||||
return stack.stack_status != 'DELETE_COMPLETE'
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
class StacksUpdateRow(tables.Row):
|
|
||||||
ajax = True
|
|
||||||
|
|
||||||
def can_be_selected(self, datum):
|
|
||||||
return datum.stack_status != 'DELETE_COMPLETE'
|
|
||||||
|
|
||||||
def get_data(self, request, stack_id):
|
|
||||||
try:
|
|
||||||
stack = api.heat.stack_get(request, stack_id)
|
|
||||||
if stack.stack_status == 'DELETE_COMPLETE':
|
|
||||||
# returning 404 to the ajax call removes the
|
|
||||||
# row from the table on the ui
|
|
||||||
raise Http404
|
|
||||||
return stack
|
|
||||||
except Http404:
|
|
||||||
raise
|
|
||||||
except Exception as e:
|
|
||||||
messages.error(request, e)
|
|
||||||
raise
|
|
||||||
|
|
||||||
|
|
||||||
class StacksFilterAction(tables.FilterAction):
|
|
||||||
filter_type = 'server'
|
|
||||||
filter_choices = (('name', _('Stack Name ='), True, _('Case-sensitive')),
|
|
||||||
('id', _('Stack ID ='), True),
|
|
||||||
('status', _('Status ='), True))
|
|
||||||
|
|
||||||
|
|
||||||
class StacksTable(tables.DataTable):
|
|
||||||
STATUS_CHOICES = (
|
|
||||||
("Complete", True),
|
|
||||||
("Failed", False),
|
|
||||||
)
|
|
||||||
STACK_STATUS_DISPLAY_CHOICES = (
|
|
||||||
("init_in_progress", pgettext_lazy("current status of stack",
|
|
||||||
u"Init In Progress")),
|
|
||||||
("init_complete", pgettext_lazy("current status of stack",
|
|
||||||
u"Init Complete")),
|
|
||||||
("init_failed", pgettext_lazy("current status of stack",
|
|
||||||
u"Init Failed")),
|
|
||||||
("create_in_progress", pgettext_lazy("current status of stack",
|
|
||||||
u"Create In Progress")),
|
|
||||||
("create_complete", pgettext_lazy("current status of stack",
|
|
||||||
u"Create Complete")),
|
|
||||||
("create_failed", pgettext_lazy("current status of stack",
|
|
||||||
u"Create Failed")),
|
|
||||||
("delete_in_progress", pgettext_lazy("current status of stack",
|
|
||||||
u"Delete In Progress")),
|
|
||||||
("delete_complete", pgettext_lazy("current status of stack",
|
|
||||||
u"Delete Complete")),
|
|
||||||
("delete_failed", pgettext_lazy("current status of stack",
|
|
||||||
u"Delete Failed")),
|
|
||||||
("update_in_progress", pgettext_lazy("current status of stack",
|
|
||||||
u"Update In Progress")),
|
|
||||||
("update_complete", pgettext_lazy("current status of stack",
|
|
||||||
u"Update Complete")),
|
|
||||||
("update_failed", pgettext_lazy("current status of stack",
|
|
||||||
u"Update Failed")),
|
|
||||||
("rollback_in_progress", pgettext_lazy("current status of stack",
|
|
||||||
u"Rollback In Progress")),
|
|
||||||
("rollback_complete", pgettext_lazy("current status of stack",
|
|
||||||
u"Rollback Complete")),
|
|
||||||
("rollback_failed", pgettext_lazy("current status of stack",
|
|
||||||
u"Rollback Failed")),
|
|
||||||
("suspend_in_progress", pgettext_lazy("current status of stack",
|
|
||||||
u"Suspend In Progress")),
|
|
||||||
("suspend_complete", pgettext_lazy("current status of stack",
|
|
||||||
u"Suspend Complete")),
|
|
||||||
("suspend_failed", pgettext_lazy("current status of stack",
|
|
||||||
u"Suspend Failed")),
|
|
||||||
("resume_in_progress", pgettext_lazy("current status of stack",
|
|
||||||
u"Resume In Progress")),
|
|
||||||
("resume_complete", pgettext_lazy("current status of stack",
|
|
||||||
u"Resume Complete")),
|
|
||||||
("resume_failed", pgettext_lazy("current status of stack",
|
|
||||||
u"Resume Failed")),
|
|
||||||
("adopt_in_progress", pgettext_lazy("current status of stack",
|
|
||||||
u"Adopt In Progress")),
|
|
||||||
("adopt_complete", pgettext_lazy("current status of stack",
|
|
||||||
u"Adopt Complete")),
|
|
||||||
("adopt_failed", pgettext_lazy("current status of stack",
|
|
||||||
u"Adopt Failed")),
|
|
||||||
("snapshot_in_progress", pgettext_lazy("current status of stack",
|
|
||||||
u"Snapshot In Progress")),
|
|
||||||
("snapshot_complete", pgettext_lazy("current status of stack",
|
|
||||||
u"Snapshot Complete")),
|
|
||||||
("snapshot_failed", pgettext_lazy("current status of stack",
|
|
||||||
u"Snapshot Failed")),
|
|
||||||
("check_in_progress", pgettext_lazy("current status of stack",
|
|
||||||
u"Check In Progress")),
|
|
||||||
("check_complete", pgettext_lazy("current status of stack",
|
|
||||||
u"Check Complete")),
|
|
||||||
("check_failed", pgettext_lazy("current status of stack",
|
|
||||||
u"Check Failed")),
|
|
||||||
)
|
|
||||||
name = tables.Column("stack_name",
|
|
||||||
verbose_name=_("Stack Name"),
|
|
||||||
link="horizon:project:stacks:detail",)
|
|
||||||
created = tables.Column("creation_time",
|
|
||||||
verbose_name=_("Created"),
|
|
||||||
filters=(filters.parse_isotime,
|
|
||||||
filters.timesince_sortable),
|
|
||||||
attrs={'data-type': 'timesince'})
|
|
||||||
updated = tables.Column("updated_time",
|
|
||||||
verbose_name=_("Updated"),
|
|
||||||
filters=(filters.parse_isotime,
|
|
||||||
filters.timesince_or_never))
|
|
||||||
status = tables.Column("status",
|
|
||||||
hidden=True,
|
|
||||||
status=True,
|
|
||||||
status_choices=STATUS_CHOICES)
|
|
||||||
|
|
||||||
stack_status = tables.Column("stack_status",
|
|
||||||
verbose_name=_("Status"),
|
|
||||||
display_choices=STACK_STATUS_DISPLAY_CHOICES)
|
|
||||||
|
|
||||||
def get_object_display(self, stack):
|
|
||||||
return stack.stack_name
|
|
||||||
|
|
||||||
class Meta(object):
|
|
||||||
name = "stacks"
|
|
||||||
verbose_name = _("Stacks")
|
|
||||||
pagination_param = 'stack_marker'
|
|
||||||
status_columns = ["status", ]
|
|
||||||
row_class = StacksUpdateRow
|
|
||||||
table_actions_menu = (CheckStack,
|
|
||||||
SuspendStack,
|
|
||||||
ResumeStack,)
|
|
||||||
table_actions = (LaunchStack,
|
|
||||||
PreviewStack,
|
|
||||||
DeleteStack,
|
|
||||||
StacksFilterAction,)
|
|
||||||
row_actions = (CheckStack,
|
|
||||||
SuspendStack,
|
|
||||||
ResumeStack,
|
|
||||||
ChangeStackTemplate,
|
|
||||||
DeleteStack,)
|
|
||||||
|
|
||||||
|
|
||||||
def get_resource_url(obj):
|
|
||||||
if obj.physical_resource_id == obj.stack_id:
|
|
||||||
return None
|
|
||||||
return urlresolvers.reverse('horizon:project:stacks:resource',
|
|
||||||
args=(obj.stack_id, obj.resource_name))
|
|
||||||
|
|
||||||
|
|
||||||
class EventsTable(tables.DataTable):
|
|
||||||
|
|
||||||
logical_resource = tables.Column('resource_name',
|
|
||||||
verbose_name=_("Stack Resource"),
|
|
||||||
link=get_resource_url)
|
|
||||||
physical_resource = tables.Column('physical_resource_id',
|
|
||||||
verbose_name=_("Resource"))
|
|
||||||
timestamp = tables.Column('event_time',
|
|
||||||
verbose_name=_("Time Since Event"),
|
|
||||||
filters=(filters.parse_isotime,
|
|
||||||
filters.timesince_or_never))
|
|
||||||
status = tables.Column("resource_status",
|
|
||||||
filters=(title, filters.replace_underscores),
|
|
||||||
verbose_name=_("Status"),)
|
|
||||||
|
|
||||||
statusreason = tables.Column("resource_status_reason",
|
|
||||||
verbose_name=_("Status Reason"),)
|
|
||||||
|
|
||||||
class Meta(object):
|
|
||||||
name = "events"
|
|
||||||
verbose_name = _("Stack Events")
|
|
||||||
|
|
||||||
|
|
||||||
class ResourcesUpdateRow(tables.Row):
|
|
||||||
ajax = True
|
|
||||||
|
|
||||||
def get_data(self, request, resource_name):
|
|
||||||
try:
|
|
||||||
stack = self.table.stack
|
|
||||||
stack_identifier = '%s/%s' % (stack.stack_name, stack.id)
|
|
||||||
return api.heat.resource_get(
|
|
||||||
request, stack_identifier, resource_name)
|
|
||||||
except exc.HTTPNotFound:
|
|
||||||
# returning 404 to the ajax call removes the
|
|
||||||
# row from the table on the ui
|
|
||||||
raise Http404
|
|
||||||
except Exception as e:
|
|
||||||
messages.error(request, e)
|
|
||||||
|
|
||||||
|
|
||||||
class ResourcesTable(tables.DataTable):
|
|
||||||
class StatusColumn(tables.Column):
|
|
||||||
def get_raw_data(self, datum):
|
|
||||||
return datum.resource_status.partition("_")[2]
|
|
||||||
|
|
||||||
STATUS_CHOICES = (
|
|
||||||
("Complete", True),
|
|
||||||
("Failed", False),
|
|
||||||
)
|
|
||||||
STATUS_DISPLAY_CHOICES = StacksTable.STACK_STATUS_DISPLAY_CHOICES
|
|
||||||
|
|
||||||
logical_resource = tables.Column('resource_name',
|
|
||||||
verbose_name=_("Stack Resource"),
|
|
||||||
link=get_resource_url)
|
|
||||||
physical_resource = tables.Column('physical_resource_id',
|
|
||||||
verbose_name=_("Resource"),
|
|
||||||
link=mappings.resource_to_url)
|
|
||||||
resource_type = tables.Column("resource_type",
|
|
||||||
verbose_name=_("Stack Resource Type"),)
|
|
||||||
updated_time = tables.Column('updated_time',
|
|
||||||
verbose_name=_("Date Updated"),
|
|
||||||
filters=(filters.parse_isotime,
|
|
||||||
filters.timesince_or_never))
|
|
||||||
status = tables.Column("resource_status",
|
|
||||||
verbose_name=_("Status"),
|
|
||||||
display_choices=STATUS_DISPLAY_CHOICES)
|
|
||||||
|
|
||||||
statusreason = tables.Column("resource_status_reason",
|
|
||||||
verbose_name=_("Status Reason"),)
|
|
||||||
|
|
||||||
status_hidden = StatusColumn("status",
|
|
||||||
hidden=True,
|
|
||||||
status=True,
|
|
||||||
status_choices=STATUS_CHOICES)
|
|
||||||
|
|
||||||
def __init__(self, request, data=None,
|
|
||||||
needs_form_wrapper=None, **kwargs):
|
|
||||||
super(ResourcesTable, self).__init__(
|
|
||||||
request, data, needs_form_wrapper, **kwargs)
|
|
||||||
self.stack = kwargs['stack']
|
|
||||||
|
|
||||||
def get_object_id(self, datum):
|
|
||||||
return datum.resource_name
|
|
||||||
|
|
||||||
class Meta(object):
|
|
||||||
name = "resources"
|
|
||||||
verbose_name = _("Stack Resources")
|
|
||||||
status_columns = ["status_hidden", ]
|
|
||||||
row_class = ResourcesUpdateRow
|
|
@ -1,173 +0,0 @@
|
|||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
import logging
|
|
||||||
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
|
|
||||||
from horizon import messages
|
|
||||||
from horizon import tabs
|
|
||||||
from openstack_dashboard import api
|
|
||||||
from openstack_dashboard import policy
|
|
||||||
|
|
||||||
from openstack_dashboard.dashboards.project.stacks \
|
|
||||||
import api as project_api
|
|
||||||
from openstack_dashboard.dashboards.project.stacks import mappings
|
|
||||||
from openstack_dashboard.dashboards.project.stacks \
|
|
||||||
import tables as project_tables
|
|
||||||
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class StackTopologyTab(tabs.Tab):
|
|
||||||
name = _("Topology")
|
|
||||||
slug = "topology"
|
|
||||||
template_name = "project/stacks/_detail_topology.html"
|
|
||||||
preload = False
|
|
||||||
|
|
||||||
def allowed(self, request):
|
|
||||||
return policy.check(
|
|
||||||
(("orchestration", "stacks:template"),
|
|
||||||
("orchestration", "stacks:lookup"),
|
|
||||||
("orchestration", "stacks:show"),
|
|
||||||
("orchestration", "resource:index"),),
|
|
||||||
request)
|
|
||||||
|
|
||||||
def get_context_data(self, request):
|
|
||||||
context = {}
|
|
||||||
stack = self.tab_group.kwargs['stack']
|
|
||||||
context['stack_id'] = stack.id
|
|
||||||
context['d3_data'] = project_api.d3_data(request, stack_id=stack.id)
|
|
||||||
return context
|
|
||||||
|
|
||||||
|
|
||||||
class StackOverviewTab(tabs.Tab):
|
|
||||||
name = _("Overview")
|
|
||||||
slug = "overview"
|
|
||||||
template_name = "project/stacks/_detail_overview.html"
|
|
||||||
|
|
||||||
def allowed(self, request):
|
|
||||||
return policy.check(
|
|
||||||
(("orchestration", "stacks:template"),
|
|
||||||
("orchestration", "stacks:lookup"),
|
|
||||||
("orchestration", "stacks:show"),),
|
|
||||||
request)
|
|
||||||
|
|
||||||
def get_context_data(self, request):
|
|
||||||
return {"stack": self.tab_group.kwargs['stack']}
|
|
||||||
|
|
||||||
|
|
||||||
class ResourceOverviewTab(tabs.Tab):
|
|
||||||
name = _("Overview")
|
|
||||||
slug = "resource_overview"
|
|
||||||
template_name = "project/stacks/_resource_overview.html"
|
|
||||||
|
|
||||||
def get_context_data(self, request):
|
|
||||||
resource = self.tab_group.kwargs['resource']
|
|
||||||
resource_url = mappings.resource_to_url(resource)
|
|
||||||
return {
|
|
||||||
"resource": resource,
|
|
||||||
"resource_url": resource_url,
|
|
||||||
"metadata": self.tab_group.kwargs['metadata']}
|
|
||||||
|
|
||||||
|
|
||||||
class StackEventsTab(tabs.Tab):
|
|
||||||
name = _("Events")
|
|
||||||
slug = "events"
|
|
||||||
template_name = "project/stacks/_detail_events.html"
|
|
||||||
preload = False
|
|
||||||
|
|
||||||
def allowed(self, request):
|
|
||||||
return policy.check(
|
|
||||||
(("orchestration", "stacks:template"),
|
|
||||||
("orchestration", "stacks:lookup"),
|
|
||||||
("orchestration", "stacks:show"),
|
|
||||||
("orchestration", "events:index"),),
|
|
||||||
request)
|
|
||||||
|
|
||||||
def get_context_data(self, request):
|
|
||||||
stack = self.tab_group.kwargs['stack']
|
|
||||||
try:
|
|
||||||
stack_identifier = '%s/%s' % (stack.stack_name, stack.id)
|
|
||||||
events = api.heat.events_list(self.request, stack_identifier)
|
|
||||||
LOG.debug('got events %s', events)
|
|
||||||
# The stack id is needed to generate the resource URL.
|
|
||||||
for event in events:
|
|
||||||
event.stack_id = stack.id
|
|
||||||
except Exception:
|
|
||||||
events = []
|
|
||||||
messages.error(request, _(
|
|
||||||
'Unable to get events for stack "%s".') % stack.stack_name)
|
|
||||||
return {"stack": stack,
|
|
||||||
"table": project_tables.EventsTable(request, data=events), }
|
|
||||||
|
|
||||||
|
|
||||||
class StackResourcesTab(tabs.Tab):
|
|
||||||
name = _("Resources")
|
|
||||||
slug = "resources"
|
|
||||||
template_name = "project/stacks/_detail_resources.html"
|
|
||||||
preload = False
|
|
||||||
|
|
||||||
def allowed(self, request):
|
|
||||||
return policy.check(
|
|
||||||
(("orchestration", "stacks:template"),
|
|
||||||
("orchestration", "stacks:lookup"),
|
|
||||||
("orchestration", "stacks:show"),
|
|
||||||
("orchestration", "resource:index"),),
|
|
||||||
request)
|
|
||||||
|
|
||||||
def get_context_data(self, request):
|
|
||||||
stack = self.tab_group.kwargs['stack']
|
|
||||||
try:
|
|
||||||
stack_identifier = '%s/%s' % (stack.stack_name, stack.id)
|
|
||||||
resources = api.heat.resources_list(self.request, stack_identifier)
|
|
||||||
LOG.debug('got resources %s', resources)
|
|
||||||
# The stack id is needed to generate the resource URL.
|
|
||||||
for r in resources:
|
|
||||||
r.stack_id = stack.id
|
|
||||||
except Exception:
|
|
||||||
resources = []
|
|
||||||
messages.error(request, _(
|
|
||||||
'Unable to get resources for stack "%s".') % stack.stack_name)
|
|
||||||
return {"stack": stack,
|
|
||||||
"table": project_tables.ResourcesTable(
|
|
||||||
request, data=resources, stack=stack), }
|
|
||||||
|
|
||||||
|
|
||||||
class StackTemplateTab(tabs.Tab):
|
|
||||||
name = _("Template")
|
|
||||||
slug = "stack_template"
|
|
||||||
template_name = "project/stacks/_stack_template.html"
|
|
||||||
|
|
||||||
def allowed(self, request):
|
|
||||||
return policy.check(
|
|
||||||
(("orchestration", "stacks:template"),
|
|
||||||
("orchestration", "stacks:lookup"),
|
|
||||||
("orchestration", "stacks:show"),),
|
|
||||||
request)
|
|
||||||
|
|
||||||
def get_context_data(self, request):
|
|
||||||
return {"stack_template": self.tab_group.kwargs['stack_template']}
|
|
||||||
|
|
||||||
|
|
||||||
class StackDetailTabs(tabs.TabGroup):
|
|
||||||
slug = "stack_details"
|
|
||||||
tabs = (StackTopologyTab, StackOverviewTab, StackResourcesTab,
|
|
||||||
StackEventsTab, StackTemplateTab)
|
|
||||||
sticky = True
|
|
||||||
|
|
||||||
|
|
||||||
class ResourceDetailTabs(tabs.TabGroup):
|
|
||||||
slug = "resource_details"
|
|
||||||
tabs = (ResourceOverviewTab,)
|
|
||||||
sticky = True
|
|
@ -1,23 +0,0 @@
|
|||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
||||||
# implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
|
|
||||||
import horizon
|
|
||||||
|
|
||||||
|
|
||||||
class TemplateVersions(horizon.Panel):
|
|
||||||
name = _("Template Versions")
|
|
||||||
slug = "stacks.template_versions"
|
|
||||||
permissions = ('openstack.services.orchestration',)
|
|
||||||
policy_rules = (("orchestration", "stacks:list_template_versions"),)
|
|
@ -1,52 +0,0 @@
|
|||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
||||||
# implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
|
|
||||||
from django.template import defaultfilters as filters
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
|
|
||||||
from horizon import tables
|
|
||||||
|
|
||||||
|
|
||||||
class TemplateVersionsTable(tables.DataTable):
|
|
||||||
version = tables.Column(
|
|
||||||
"version",
|
|
||||||
verbose_name=_("Version"),
|
|
||||||
link="horizon:project:stacks.template_versions:details",)
|
|
||||||
type = tables.Column(
|
|
||||||
"type",
|
|
||||||
verbose_name=_("Type"),
|
|
||||||
filters=(filters.upper,))
|
|
||||||
|
|
||||||
def get_object_id(self, template_versions):
|
|
||||||
return template_versions.version
|
|
||||||
|
|
||||||
class Meta(object):
|
|
||||||
name = "template_versions"
|
|
||||||
table_actions = (tables.FilterAction,)
|
|
||||||
verbose_name = _("Template Versions")
|
|
||||||
table_actions = (tables.FilterAction,)
|
|
||||||
multi_select = False
|
|
||||||
|
|
||||||
|
|
||||||
class TemplateFunctionsTable(tables.DataTable):
|
|
||||||
functions = tables.Column('functions', verbose_name=_("Function"))
|
|
||||||
description = tables.Column('description', verbose_name=_("Description"))
|
|
||||||
|
|
||||||
def get_object_id(self, template_functions):
|
|
||||||
return template_functions.functions
|
|
||||||
|
|
||||||
class Meta(object):
|
|
||||||
name = "template_functions"
|
|
||||||
verbose_name = _("Template Functions")
|
|
||||||
table_actions = (tables.FilterAction,)
|
|
||||||
multi_select = False
|
|
@ -1,51 +0,0 @@
|
|||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
||||||
# implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
|
|
||||||
from horizon import messages
|
|
||||||
from horizon import tabs
|
|
||||||
from openstack_dashboard import api
|
|
||||||
from openstack_dashboard import policy
|
|
||||||
|
|
||||||
from openstack_dashboard.dashboards.project.stacks.template_versions \
|
|
||||||
import tables as project_tables
|
|
||||||
|
|
||||||
|
|
||||||
class TemplateFunctionsTab(tabs.Tab):
|
|
||||||
name = _("Template Functions")
|
|
||||||
slug = "template_functions"
|
|
||||||
template_name = "project/stacks.template_versions/_details.html"
|
|
||||||
preload = False
|
|
||||||
|
|
||||||
def allowed(self, request):
|
|
||||||
return policy.check(
|
|
||||||
(("orchestration", "stacks:list_template_functions"),),
|
|
||||||
request)
|
|
||||||
|
|
||||||
def get_context_data(self, request):
|
|
||||||
template_version = self.tab_group.kwargs['template_version']
|
|
||||||
try:
|
|
||||||
template_functions = api.heat.template_function_list(
|
|
||||||
self.request, template_version)
|
|
||||||
except Exception:
|
|
||||||
template_functions = []
|
|
||||||
messages.error(request, _('Unable to get functions for template '
|
|
||||||
'version "%s".') % template_version)
|
|
||||||
return {"table": project_tables.TemplateFunctionsTable(
|
|
||||||
request, data=template_functions), }
|
|
||||||
|
|
||||||
|
|
||||||
class TemplateVersionDetailsTabs(tabs.TabGroup):
|
|
||||||
slug = "template_version_details"
|
|
||||||
tabs = (TemplateFunctionsTab,)
|
|
@ -1,3 +0,0 @@
|
|||||||
{% load i18n %}
|
|
||||||
|
|
||||||
{{ table.render }}
|
|
@ -1,7 +0,0 @@
|
|||||||
{% extends 'base.html' %}
|
|
||||||
{% load i18n %}
|
|
||||||
{% block title %}{% trans "Template Versions" %}{% endblock %}
|
|
||||||
|
|
||||||
{% block main %}
|
|
||||||
{{ table.render }}
|
|
||||||
{% endblock %}
|
|
@ -1,79 +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.core.urlresolvers import reverse
|
|
||||||
from django import http
|
|
||||||
|
|
||||||
from mox3.mox import IsA
|
|
||||||
|
|
||||||
from openstack_dashboard import api
|
|
||||||
from openstack_dashboard.test import helpers as test
|
|
||||||
|
|
||||||
|
|
||||||
class TemplateVersionsTests(test.TestCase):
|
|
||||||
INDEX_URL = reverse('horizon:project:stacks.template_versions:index')
|
|
||||||
|
|
||||||
@test.create_stubs({api.heat: ('template_version_list',)})
|
|
||||||
def test_index(self):
|
|
||||||
api.heat.template_version_list(
|
|
||||||
IsA(http.HttpRequest)).AndReturn(self.template_versions.list())
|
|
||||||
self.mox.ReplayAll()
|
|
||||||
|
|
||||||
res = self.client.get(self.INDEX_URL)
|
|
||||||
self.assertTemplateUsed(
|
|
||||||
res, 'project/stacks.template_versions/index.html')
|
|
||||||
self.assertContains(res, 'HeatTemplateFormatVersion.2012-12-12')
|
|
||||||
|
|
||||||
@test.create_stubs({api.heat: ('template_version_list',)})
|
|
||||||
def test_index_exception(self):
|
|
||||||
api.heat.template_version_list(
|
|
||||||
IsA(http.HttpRequest)).AndRaise(self.exceptions.heat)
|
|
||||||
self.mox.ReplayAll()
|
|
||||||
|
|
||||||
res = self.client.get(self.INDEX_URL)
|
|
||||||
self.assertTemplateUsed(
|
|
||||||
res, 'project/stacks.template_versions/index.html')
|
|
||||||
self.assertEqual(len(res.context['table'].data), 0)
|
|
||||||
self.assertMessageCount(res, error=1)
|
|
||||||
|
|
||||||
@test.create_stubs({api.heat: ('template_function_list',)})
|
|
||||||
def test_detail_view(self):
|
|
||||||
t_version = self.template_versions.first().version
|
|
||||||
t_functions = self.template_functions.list()
|
|
||||||
|
|
||||||
api.heat.template_function_list(
|
|
||||||
IsA(http.HttpRequest), t_version).AndReturn(t_functions)
|
|
||||||
self.mox.ReplayAll()
|
|
||||||
|
|
||||||
url = reverse('horizon:project:stacks.template_versions:details',
|
|
||||||
args=[t_version])
|
|
||||||
res = self.client.get(url)
|
|
||||||
|
|
||||||
self.assertTemplateUsed(res, 'horizon/common/_detail.html')
|
|
||||||
self.assertNoMessages()
|
|
||||||
|
|
||||||
@test.create_stubs({api.heat: ('template_function_list',)})
|
|
||||||
def test_detail_view_with_exception(self):
|
|
||||||
t_version = self.template_versions.first().version
|
|
||||||
|
|
||||||
api.heat.template_function_list(
|
|
||||||
IsA(http.HttpRequest), t_version).AndRaise(self.exceptions.heat)
|
|
||||||
self.mox.ReplayAll()
|
|
||||||
|
|
||||||
url = reverse('horizon:project:stacks.template_versions:details',
|
|
||||||
args=[t_version])
|
|
||||||
res = self.client.get(url)
|
|
||||||
|
|
||||||
self.assertTemplateUsed(res, 'horizon/common/_detail.html')
|
|
||||||
self.assertEqual(len(res.context['table'].data), 0)
|
|
||||||
self.assertMessageCount(res, error=1)
|
|
@ -1,24 +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.urls import url
|
|
||||||
|
|
||||||
from openstack_dashboard.dashboards.project.stacks.template_versions \
|
|
||||||
import views
|
|
||||||
|
|
||||||
|
|
||||||
urlpatterns = [
|
|
||||||
url(r'^$', views.TemplateVersionsView.as_view(), name='index'),
|
|
||||||
url(r'^(?P<template_version>[^/]+)/$',
|
|
||||||
views.DetailView.as_view(), name='details'),
|
|
||||||
]
|
|
@ -1,61 +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.core.urlresolvers import reverse
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
|
|
||||||
from horizon import exceptions
|
|
||||||
from horizon import tables
|
|
||||||
from horizon import tabs
|
|
||||||
|
|
||||||
from openstack_dashboard import api
|
|
||||||
import openstack_dashboard.dashboards.project.stacks.template_versions.tables \
|
|
||||||
as project_tables
|
|
||||||
import openstack_dashboard.dashboards.project.stacks.template_versions.tabs \
|
|
||||||
as project_tabs
|
|
||||||
|
|
||||||
|
|
||||||
class TemplateVersionsView(tables.DataTableView):
|
|
||||||
table_class = project_tables.TemplateVersionsTable
|
|
||||||
template_name = 'project/stacks.template_versions/index.html'
|
|
||||||
page_title = _("Template Versions")
|
|
||||||
|
|
||||||
def get_data(self):
|
|
||||||
try:
|
|
||||||
template_versions = sorted(
|
|
||||||
api.heat.template_version_list(self.request),
|
|
||||||
key=lambda template_version: template_version.version)
|
|
||||||
except Exception:
|
|
||||||
template_versions = []
|
|
||||||
msg = _('Unable to retrieve template versions.')
|
|
||||||
exceptions.handle(self.request, msg)
|
|
||||||
return template_versions
|
|
||||||
|
|
||||||
|
|
||||||
class DetailView(tabs.TabView):
|
|
||||||
tab_group_class = project_tabs.TemplateVersionDetailsTabs
|
|
||||||
template_name = 'horizon/common/_detail.html'
|
|
||||||
page_title = "{{ template_version }}"
|
|
||||||
|
|
||||||
def get_template_version(self, request, **kwargs):
|
|
||||||
try:
|
|
||||||
template_functions = api.heat.template_function_list(
|
|
||||||
request, kwargs['template_version'])
|
|
||||||
return template_functions
|
|
||||||
except Exception:
|
|
||||||
msg = _('Unable to retrieve template functions.')
|
|
||||||
exceptions.handle(request, msg, redirect=self.get_redirect_url())
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_redirect_url():
|
|
||||||
return reverse('horizon:project:stacks.template_versions:index')
|
|
@ -1,7 +0,0 @@
|
|||||||
{% extends "horizon/common/_modal_form.html" %}
|
|
||||||
{% load i18n %}
|
|
||||||
{% block form_attrs %}enctype="multipart/form-data"{% endblock %}
|
|
||||||
{% block modal-body-right %}
|
|
||||||
<h3>{% trans "Description:" %}</h3>
|
|
||||||
<p>{% trans "Use one of the available template source options to specify the template to be used in creating this stack." %}</p>
|
|
||||||
{% endblock %}
|
|
@ -1,6 +0,0 @@
|
|||||||
{% extends "horizon/common/_modal_form.html" %}
|
|
||||||
{% load i18n %}
|
|
||||||
{% block modal-body-right %}
|
|
||||||
<h3>{% trans "Description:" %}</h3>
|
|
||||||
<p>{% trans "Create a new stack with the provided values." %}</p>
|
|
||||||
{% endblock %}
|
|
@ -1,3 +0,0 @@
|
|||||||
{% load i18n %}
|
|
||||||
|
|
||||||
{{ table.render }}
|
|
@ -1,55 +0,0 @@
|
|||||||
{% load i18n sizeformat %}
|
|
||||||
|
|
||||||
<div class="detail">
|
|
||||||
<dl class="dl-horizontal">
|
|
||||||
<dt>{% trans "Name" %}</dt>
|
|
||||||
<dd data-display="{{ stack.stack_name|default:stack.id }}">{{ stack.stack_name }}</dd>
|
|
||||||
<dt>{% trans "ID" %}</dt>
|
|
||||||
<dd>{{ stack.id }}</dd>
|
|
||||||
<dt>{% trans "Description" %}</dt>
|
|
||||||
<dd>{{ stack.description }}</dd>
|
|
||||||
</dl>
|
|
||||||
|
|
||||||
<h4>{% trans "Status" %}</h4>
|
|
||||||
<hr class="header_rule">
|
|
||||||
<dl class="dl-horizontal">
|
|
||||||
<dt>{% trans "Created" %}</dt>
|
|
||||||
<dd>{{ stack.creation_time|parse_isotime|timesince_or_never }}</dd>
|
|
||||||
<dt>{% trans "Last Updated" %}</dt>
|
|
||||||
<dd>{{ stack.updated_time|parse_isotime|timesince_or_never }}</dd>
|
|
||||||
<dt>{% trans "Status" %}</dt>
|
|
||||||
<dd>
|
|
||||||
{% blocktrans with stack_status_title=stack.stack_status|title stack_status_reason=stack.stack_status_reason %}{{ stack_status_title }}: {{ stack_status_reason }}{% endblocktrans %}
|
|
||||||
</dd>
|
|
||||||
</dl>
|
|
||||||
|
|
||||||
<h4>{% trans "Outputs" %}</h4>
|
|
||||||
<hr class="header_rule">
|
|
||||||
<dl class="dl-horizontal">
|
|
||||||
{% for output in stack.outputs %}
|
|
||||||
<dt>{{ output.output_key }}</dt>
|
|
||||||
<dd>{{ output.description }}</dd>
|
|
||||||
<dd>
|
|
||||||
{{ output.output_value|stack_output }}
|
|
||||||
</dd>
|
|
||||||
{% endfor %}
|
|
||||||
</dl>
|
|
||||||
|
|
||||||
<h4>{% trans "Stack Parameters" %}</h4>
|
|
||||||
<hr class="header_rule">
|
|
||||||
<dl class="dl-horizontal">
|
|
||||||
{% for key, value in stack.parameters.items %}
|
|
||||||
<dt>{{ key }}</dt>
|
|
||||||
<dd>{{ value }}</dd>
|
|
||||||
{% endfor %}
|
|
||||||
</dl>
|
|
||||||
|
|
||||||
<h4>{% trans "Launch Parameters" %}</h4>
|
|
||||||
<hr class="header_rule">
|
|
||||||
<dl class="dl-horizontal">
|
|
||||||
<dt>{% trans "Timeout" %}</dt>
|
|
||||||
<dd>{{ stack.timeout_mins }} {% trans "Minutes" %}</dd>
|
|
||||||
<dt>{% trans "Rollback" %}</dt>
|
|
||||||
<dd>{% if stack.disable_rollback %}{% trans "Disabled" %}{% else %}{% trans "Enabled" %}{% endif %}</dd>
|
|
||||||
</dl>
|
|
||||||
</div>
|
|
@ -1,3 +0,0 @@
|
|||||||
{% load i18n %}
|
|
||||||
|
|
||||||
{{ table.render }}
|
|
@ -1,9 +0,0 @@
|
|||||||
{% load i18n sizeformat %}
|
|
||||||
|
|
||||||
<div id="resource_container">
|
|
||||||
<div id="info_box"></div>
|
|
||||||
<div id="stack_box"></div>
|
|
||||||
<div id="heat_resource_topology"></div>
|
|
||||||
<div id="stack_id" data-stack_id="{{ stack_id }}"></div>
|
|
||||||
<div id="d3_data" data-d3_data="{{ d3_data }}"></div>
|
|
||||||
</div>
|
|
@ -1,6 +0,0 @@
|
|||||||
{% extends "horizon/common/_modal_form.html" %}
|
|
||||||
{% load i18n %}
|
|
||||||
{% block modal-body-right %}
|
|
||||||
<h3>{% trans "Description:" %}</h3>
|
|
||||||
<p>{% trans "Preview a new stack with the provided values." %}</p>
|
|
||||||
{% endblock %}
|
|
@ -1,58 +0,0 @@
|
|||||||
{% extends "horizon/common/_modal.html" %}
|
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
{% block modal-header %}{% trans "Stack Preview" %}{% endblock %}
|
|
||||||
|
|
||||||
{% block modal-body %}
|
|
||||||
<div class="row-fluid stack-preview detail">
|
|
||||||
<form>
|
|
||||||
<dl class="dl-horizontal">
|
|
||||||
{% for key, value in stack_preview.items %}
|
|
||||||
{% if key != 'parameters' and key != 'resources' and key != 'links' %}
|
|
||||||
<dt>{{ key }}</dt>
|
|
||||||
<dd>{{ value }}</dd>
|
|
||||||
{% endif %}
|
|
||||||
{% endfor %}
|
|
||||||
</dl>
|
|
||||||
|
|
||||||
{% if stack_preview.parameters %}
|
|
||||||
<dt>{% trans "Parameters" %}</dt>
|
|
||||||
<hr class="header_rule">
|
|
||||||
<dl class="dl-horizontal">
|
|
||||||
{% for key, value in stack_preview.parameters.items %}
|
|
||||||
<dt>{{ key }}</dt>
|
|
||||||
<dd>{{ value }}</dd>
|
|
||||||
{% endfor %}
|
|
||||||
</dl>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if stack_preview.links %}
|
|
||||||
<dt>{% trans "Links" %}</dt>
|
|
||||||
<hr class="header_rule">
|
|
||||||
{% for link in stack_preview.links %}
|
|
||||||
<dl class="dl-horizontal">
|
|
||||||
<dt>{{ link.rel }}</dt>
|
|
||||||
<dd>{{ link.href }}</dd>
|
|
||||||
</dl>
|
|
||||||
{% endfor %}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if stack_preview.resources %}
|
|
||||||
<dt>{% trans "Resources" %}</dt>
|
|
||||||
{% for resource in stack_preview.resources %}
|
|
||||||
<hr class="header_rule">
|
|
||||||
<dl class="dl-horizontal">
|
|
||||||
{% for key, value in resource.items %}
|
|
||||||
<dt>{{ key }}</dt>
|
|
||||||
<dd>{{ value }}</dd>
|
|
||||||
{% endfor %}
|
|
||||||
</dl>
|
|
||||||
{% endfor %}
|
|
||||||
{% endif %}
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block modal-footer %}
|
|
||||||
<a href="{% url 'horizon:project:stacks:index' %}" class="btn btn-default cancel">{% trans "Close" %}</a>
|
|
||||||
{% endblock %}
|
|
@ -1,7 +0,0 @@
|
|||||||
{% extends "horizon/common/_modal_form.html" %}
|
|
||||||
{% load i18n %}
|
|
||||||
{% block form_attrs %}enctype="multipart/form-data"{% endblock %}
|
|
||||||
{% block modal-body-right %}
|
|
||||||
<h3>{% trans "Description:" %}</h3>
|
|
||||||
<p>{% trans "Use one of the available template source options to specify the template to be used in previewing this stack." %}</p>
|
|
||||||
{% endblock %}
|
|
@ -1,10 +0,0 @@
|
|||||||
<h3>{{ resource.resource_name }}</h3>
|
|
||||||
|
|
||||||
{% if resource.resource_status == 'CREATE_FAILED' %}
|
|
||||||
<p class="error">{{ resource.resource_status_desc }}</p>
|
|
||||||
<p class="error">{{ resource.resource_status_reason }}</p>
|
|
||||||
{% else %}
|
|
||||||
<p>{{ resource.resource_status_desc }}</p>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<p>{{ resource.resource_type }}</p>
|
|
@ -1,38 +0,0 @@
|
|||||||
{% load i18n sizeformat %}
|
|
||||||
|
|
||||||
<div class="detail">
|
|
||||||
<dl class="dl-horizontal">
|
|
||||||
<dt>{% trans "Stack Resource ID" %}</dt>
|
|
||||||
<dd>{{ resource.resource_name }}</dd>
|
|
||||||
<dt>{% trans "Resource ID" %}</dt>
|
|
||||||
<dd>
|
|
||||||
{% if resource_url %}
|
|
||||||
<a href="{{ resource_url }}">
|
|
||||||
{{ resource.physical_resource_id }}
|
|
||||||
</a>
|
|
||||||
{% else %}
|
|
||||||
{{ resource.physical_resource_id }}
|
|
||||||
{% endif %}
|
|
||||||
</dd>
|
|
||||||
<dt>{% trans "Stack Resource Type" %}</dt>
|
|
||||||
<dd>{{ resource.resource_type }}</dd>
|
|
||||||
<dt>{% trans "Description" %}</dt>
|
|
||||||
<dd>{{ resource.description }}</dd>
|
|
||||||
</dl>
|
|
||||||
|
|
||||||
<h4>{% trans "Status" %}</h4>
|
|
||||||
<hr class="header_rule">
|
|
||||||
<dl class="dl-horizontal">
|
|
||||||
<dt>{% trans "Last Updated" %}</dt>
|
|
||||||
<dd>{{ resource.updated_time|parse_isotime|timesince_or_never }}</dd>
|
|
||||||
<dt>{% trans "Status" %}</dt>
|
|
||||||
<dd>
|
|
||||||
{% blocktrans with resource_status=resource.resource_status|title|replace_underscores resource_status_reason=resource.resource_status_reason %}{{ resource_status }}: {{ resource_status_reason }}{% endblocktrans %}
|
|
||||||
</dd>
|
|
||||||
</dl>
|
|
||||||
|
|
||||||
<h4>{% trans "Resource Metadata" %}</h4>
|
|
||||||
<hr class="header_rule">
|
|
||||||
<pre>{{ metadata }}
|
|
||||||
</pre>
|
|
||||||
</div>
|
|
@ -1,7 +0,0 @@
|
|||||||
{% extends "horizon/common/_modal_form.html" %}
|
|
||||||
{% load i18n %}
|
|
||||||
{% block form_attrs %}enctype="multipart/form-data"{% endblock %}
|
|
||||||
{% block modal-body-right %}
|
|
||||||
<h3>{% trans "Description:" %}</h3>
|
|
||||||
<p>{% trans "Use one of the available template source options to specify the template to be used in creating this stack." %}</p>
|
|
||||||
{% endblock %}
|
|
@ -1,14 +0,0 @@
|
|||||||
<img src="{{ stack_image }}" width="35px" height="35px" />
|
|
||||||
<div id="stack_info">
|
|
||||||
<h3>{{ stack.stack_name }}</h3>
|
|
||||||
<p class="error">{{ stack.stack_status_desc }}</p>
|
|
||||||
</div>
|
|
||||||
<div class="clear"></div>
|
|
||||||
{% if stack.stack_status == 'CREATE_FAILED' %}
|
|
||||||
<p class="error">{{ stack.stack_status_reason }}</p>
|
|
||||||
{% endif %}
|
|
||||||
{% for output in stack.outputs %}
|
|
||||||
{% if output.output_key == 'WebsiteURL' %}
|
|
||||||
<a href="{{ output.output_value }}">{{ output.description }}</a>
|
|
||||||
{% endif %}
|
|
||||||
{% endfor %}
|
|
@ -1,5 +0,0 @@
|
|||||||
{% load i18n sizeformat %}
|
|
||||||
|
|
||||||
<div class="detail">
|
|
||||||
<pre>{{ stack_template }}</pre>
|
|
||||||
</div>
|
|
@ -1,6 +0,0 @@
|
|||||||
{% extends "horizon/common/_modal_form.html" %}
|
|
||||||
{% load i18n %}
|
|
||||||
{% block modal-body-right %}
|
|
||||||
<h3>{% trans "Description:" %}</h3>
|
|
||||||
<p>{% trans "Update a stack with the provided values. Please note that any encrypted parameters, such as passwords, will be reset to default if you do not change them here." %}</p>
|
|
||||||
{% endblock %}
|
|
@ -1,7 +0,0 @@
|
|||||||
{% extends 'base.html' %}
|
|
||||||
{% load i18n %}
|
|
||||||
{% block title %}{% trans "Change Template" %}{% endblock %}
|
|
||||||
|
|
||||||
{% block main %}
|
|
||||||
{% include 'project/stacks/_change_template.html' %}
|
|
||||||
{% endblock %}
|
|
@ -1,7 +0,0 @@
|
|||||||
{% extends 'base.html' %}
|
|
||||||
{% load i18n %}
|
|
||||||
{% block title %}{% trans "Launch Stack" %}{% endblock %}
|
|
||||||
|
|
||||||
{% block main %}
|
|
||||||
{% include 'project/stacks/_create.html' %}
|
|
||||||
{% endblock %}
|
|
@ -1,7 +0,0 @@
|
|||||||
{% extends 'base.html' %}
|
|
||||||
{% load i18n %}
|
|
||||||
{% block title %}{% trans "Preview Stack" %}{% endblock %}
|
|
||||||
|
|
||||||
{% block main %}
|
|
||||||
{% include 'project/stacks/_preview.html' %}
|
|
||||||
{% endblock %}
|
|
@ -1,7 +0,0 @@
|
|||||||
{% extends 'base.html' %}
|
|
||||||
{% load i18n %}
|
|
||||||
{% block title %}{% trans "Preview Stack Details" %}{% endblock %}
|
|
||||||
|
|
||||||
{% block main %}
|
|
||||||
{% include 'project/stacks/_preview_details.html' %}
|
|
||||||
{% endblock %}
|
|
@ -1,7 +0,0 @@
|
|||||||
{% extends 'base.html' %}
|
|
||||||
{% load i18n %}
|
|
||||||
{% block title %}{% trans "Preview Template" %}{% endblock %}
|
|
||||||
|
|
||||||
{% block main %}
|
|
||||||
{% include 'project/stacks/_preview_template.html' %}
|
|
||||||
{% endblock %}
|
|
@ -1,7 +0,0 @@
|
|||||||
{% extends 'base.html' %}
|
|
||||||
{% load i18n %}
|
|
||||||
{% block title %}{% trans "Select Template" %}{% endblock %}
|
|
||||||
|
|
||||||
{% block main %}
|
|
||||||
{% include 'project/stacks/_select_template.html' %}
|
|
||||||
{% endblock %}
|
|
@ -1,7 +0,0 @@
|
|||||||
{% extends 'base.html' %}
|
|
||||||
{% load i18n %}
|
|
||||||
{% block title %}{% trans "Update Stack Parameters" %}{% endblock %}
|
|
||||||
|
|
||||||
{% block main %}
|
|
||||||
{% include 'project/stacks/_update.html' %}
|
|
||||||
{% endblock %}
|
|
File diff suppressed because it is too large
Load Diff
@ -1,38 +0,0 @@
|
|||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
from django.conf.urls import url
|
|
||||||
|
|
||||||
from openstack_dashboard.dashboards.project.stacks import views
|
|
||||||
|
|
||||||
urlpatterns = [
|
|
||||||
url(r'^$', views.IndexView.as_view(), name='index'),
|
|
||||||
url(r'^select_template$',
|
|
||||||
views.SelectTemplateView.as_view(),
|
|
||||||
name='select_template'),
|
|
||||||
url(r'^launch$', views.CreateStackView.as_view(), name='launch'),
|
|
||||||
url(r'^preview_template$',
|
|
||||||
views.PreviewTemplateView.as_view(), name='preview_template'),
|
|
||||||
url(r'^preview$', views.PreviewStackView.as_view(), name='preview'),
|
|
||||||
url(r'^preview_details$',
|
|
||||||
views.PreviewStackDetailsView.as_view(), name='preview_details'),
|
|
||||||
url(r'^stack/(?P<stack_id>[^/]+)/$',
|
|
||||||
views.DetailView.as_view(), name='detail'),
|
|
||||||
url(r'^(?P<stack_id>[^/]+)/change_template$',
|
|
||||||
views.ChangeTemplateView.as_view(), name='change_template'),
|
|
||||||
url(r'^(?P<stack_id>[^/]+)/edit_stack$',
|
|
||||||
views.EditStackView.as_view(), name='edit_stack'),
|
|
||||||
url(r'^stack/(?P<stack_id>[^/]+)/(?P<resource_name>[^/]+)/$',
|
|
||||||
views.ResourceView.as_view(), name='resource'),
|
|
||||||
url(r'^get_d3_data/(?P<stack_id>[^/]+)/$',
|
|
||||||
views.JSONView.as_view(), name='d3_data'),
|
|
||||||
]
|
|
@ -1,358 +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 operator import attrgetter
|
|
||||||
|
|
||||||
import yaml
|
|
||||||
|
|
||||||
from django.core.urlresolvers import reverse
|
|
||||||
from django.core.urlresolvers import reverse_lazy
|
|
||||||
from django.http import HttpResponse
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
import django.views.generic
|
|
||||||
|
|
||||||
from horizon import exceptions
|
|
||||||
from horizon import forms
|
|
||||||
from horizon import tables
|
|
||||||
from horizon import tabs
|
|
||||||
from horizon.utils import memoized
|
|
||||||
from horizon import views
|
|
||||||
from openstack_dashboard import api
|
|
||||||
from openstack_dashboard.dashboards.project.stacks \
|
|
||||||
import api as project_api
|
|
||||||
from openstack_dashboard.dashboards.project.stacks \
|
|
||||||
import forms as project_forms
|
|
||||||
from openstack_dashboard.dashboards.project.stacks \
|
|
||||||
import tables as project_tables
|
|
||||||
from openstack_dashboard.dashboards.project.stacks \
|
|
||||||
import tabs as project_tabs
|
|
||||||
|
|
||||||
|
|
||||||
class IndexView(tables.DataTableView):
|
|
||||||
table_class = project_tables.StacksTable
|
|
||||||
page_title = _("Stacks")
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super(IndexView, self).__init__(*args, **kwargs)
|
|
||||||
self._more = None
|
|
||||||
|
|
||||||
def has_prev_data(self, table):
|
|
||||||
return self._prev
|
|
||||||
|
|
||||||
def has_more_data(self, table):
|
|
||||||
return self._more
|
|
||||||
|
|
||||||
def get_data(self):
|
|
||||||
stacks = []
|
|
||||||
filters = self.get_filters()
|
|
||||||
prev_marker = self.request.GET.get(
|
|
||||||
project_tables.StacksTable._meta.prev_pagination_param)
|
|
||||||
if prev_marker is not None:
|
|
||||||
sort_dir = 'asc'
|
|
||||||
marker = prev_marker
|
|
||||||
else:
|
|
||||||
sort_dir = 'desc'
|
|
||||||
marker = self.request.GET.get(
|
|
||||||
project_tables.StacksTable._meta.pagination_param)
|
|
||||||
try:
|
|
||||||
stacks, self._more, self._prev = api.heat.stacks_list(
|
|
||||||
self.request,
|
|
||||||
marker=marker,
|
|
||||||
paginate=True,
|
|
||||||
sort_dir=sort_dir,
|
|
||||||
filters=filters)
|
|
||||||
if prev_marker is not None:
|
|
||||||
stacks = sorted(stacks, key=attrgetter('creation_time'),
|
|
||||||
reverse=True)
|
|
||||||
except Exception:
|
|
||||||
self._prev = False
|
|
||||||
self._more = False
|
|
||||||
msg = _('Unable to retrieve stack list.')
|
|
||||||
exceptions.handle(self.request, msg)
|
|
||||||
return stacks
|
|
||||||
|
|
||||||
|
|
||||||
class SelectTemplateView(forms.ModalFormView):
|
|
||||||
template_name = 'project/stacks/select_template.html'
|
|
||||||
form_id = "select_template"
|
|
||||||
form_class = project_forms.TemplateForm
|
|
||||||
submit_label = _("Next")
|
|
||||||
submit_url = reverse_lazy("horizon:project:stacks:select_template")
|
|
||||||
success_url = reverse_lazy('horizon:project:stacks:launch')
|
|
||||||
page_title = _("Select Template")
|
|
||||||
|
|
||||||
def get_initial(self):
|
|
||||||
initial = {}
|
|
||||||
for name in [
|
|
||||||
'template_url',
|
|
||||||
'template_source',
|
|
||||||
'template_data',
|
|
||||||
'environment_source',
|
|
||||||
'environment_data'
|
|
||||||
]:
|
|
||||||
tmp = self.request.GET.get(name)
|
|
||||||
if tmp:
|
|
||||||
initial[name] = tmp
|
|
||||||
return initial
|
|
||||||
|
|
||||||
def get_form_kwargs(self):
|
|
||||||
kwargs = super(SelectTemplateView, self).get_form_kwargs()
|
|
||||||
kwargs['next_view'] = CreateStackView
|
|
||||||
return kwargs
|
|
||||||
|
|
||||||
|
|
||||||
class ChangeTemplateView(forms.ModalFormView):
|
|
||||||
template_name = 'project/stacks/change_template.html'
|
|
||||||
form_id = "change_template"
|
|
||||||
form_class = project_forms.ChangeTemplateForm
|
|
||||||
submit_label = _("Next")
|
|
||||||
submit_url = "horizon:project:stacks:change_template"
|
|
||||||
cancel_url = reverse_lazy('horizon:project:stacks:index')
|
|
||||||
success_url = reverse_lazy('horizon:project:stacks:edit_stack')
|
|
||||||
page_title = _("Change Template")
|
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
|
||||||
context = super(ChangeTemplateView, self).get_context_data(**kwargs)
|
|
||||||
args = (self.get_object().id,)
|
|
||||||
context['submit_url'] = reverse(self.submit_url, args=args)
|
|
||||||
return context
|
|
||||||
|
|
||||||
@memoized.memoized_method
|
|
||||||
def get_object(self):
|
|
||||||
stack_id = self.kwargs['stack_id']
|
|
||||||
try:
|
|
||||||
self._stack = api.heat.stack_get(self.request, stack_id)
|
|
||||||
except Exception:
|
|
||||||
msg = _("Unable to retrieve stack.")
|
|
||||||
redirect = reverse('horizon:project:stacks:index')
|
|
||||||
exceptions.handle(self.request, msg, redirect=redirect)
|
|
||||||
return self._stack
|
|
||||||
|
|
||||||
def get_initial(self):
|
|
||||||
stack = self.get_object()
|
|
||||||
return {'stack_id': stack.id,
|
|
||||||
'stack_name': stack.stack_name
|
|
||||||
}
|
|
||||||
|
|
||||||
def get_form_kwargs(self):
|
|
||||||
kwargs = super(ChangeTemplateView, self).get_form_kwargs()
|
|
||||||
kwargs['next_view'] = EditStackView
|
|
||||||
return kwargs
|
|
||||||
|
|
||||||
|
|
||||||
class PreviewTemplateView(forms.ModalFormView):
|
|
||||||
template_name = 'project/stacks/preview_template.html'
|
|
||||||
form_id = "preview_template"
|
|
||||||
form_class = project_forms.PreviewTemplateForm
|
|
||||||
submit_label = _("Next")
|
|
||||||
submit_url = reverse_lazy('horizon:project:stacks:preview_template')
|
|
||||||
success_url = reverse_lazy('horizon:project:stacks:preview')
|
|
||||||
page_title = _("Preview Template")
|
|
||||||
|
|
||||||
def get_form_kwargs(self):
|
|
||||||
kwargs = super(PreviewTemplateView, self).get_form_kwargs()
|
|
||||||
kwargs['next_view'] = PreviewStackView
|
|
||||||
return kwargs
|
|
||||||
|
|
||||||
|
|
||||||
class CreateStackView(forms.ModalFormView):
|
|
||||||
template_name = 'project/stacks/create.html'
|
|
||||||
form_id = "launch_stack"
|
|
||||||
form_class = project_forms.CreateStackForm
|
|
||||||
submit_label = _("Launch")
|
|
||||||
submit_url = reverse_lazy("horizon:project:stacks:launch")
|
|
||||||
success_url = reverse_lazy('horizon:project:stacks:index')
|
|
||||||
page_title = _("Launch Stack")
|
|
||||||
|
|
||||||
def get_initial(self):
|
|
||||||
initial = {}
|
|
||||||
if 'environment_data' in self.kwargs:
|
|
||||||
initial['environment_data'] = self.kwargs['environment_data']
|
|
||||||
if 'parameters' in self.kwargs:
|
|
||||||
initial['parameters'] = json.dumps(self.kwargs['parameters'])
|
|
||||||
return initial
|
|
||||||
|
|
||||||
def get_form_kwargs(self):
|
|
||||||
kwargs = super(CreateStackView, self).get_form_kwargs()
|
|
||||||
if 'parameters' in self.kwargs:
|
|
||||||
kwargs['parameters'] = self.kwargs['parameters']
|
|
||||||
else:
|
|
||||||
data = json.loads(self.request.POST['parameters'])
|
|
||||||
kwargs['parameters'] = data
|
|
||||||
return kwargs
|
|
||||||
|
|
||||||
|
|
||||||
# edit stack parameters, coming from template selector
|
|
||||||
class EditStackView(CreateStackView):
|
|
||||||
template_name = 'project/stacks/update.html'
|
|
||||||
form_id = "update_stack"
|
|
||||||
form_class = project_forms.EditStackForm
|
|
||||||
submit_label = _("Update")
|
|
||||||
submit_url = "horizon:project:stacks:edit_stack"
|
|
||||||
success_url = reverse_lazy('horizon:project:stacks:index')
|
|
||||||
page_title = _("Update Stack")
|
|
||||||
|
|
||||||
def get_initial(self):
|
|
||||||
initial = super(EditStackView, self).get_initial()
|
|
||||||
|
|
||||||
initial['stack'] = self.get_object()['stack']
|
|
||||||
if initial['stack']:
|
|
||||||
initial['stack_id'] = initial['stack'].id
|
|
||||||
initial['stack_name'] = initial['stack'].stack_name
|
|
||||||
|
|
||||||
return initial
|
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
|
||||||
context = super(EditStackView, self).get_context_data(**kwargs)
|
|
||||||
args = (self.get_object()['stack'].id,)
|
|
||||||
context['submit_url'] = reverse(self.submit_url, args=args)
|
|
||||||
return context
|
|
||||||
|
|
||||||
@memoized.memoized_method
|
|
||||||
def get_object(self):
|
|
||||||
stack_id = self.kwargs['stack_id']
|
|
||||||
try:
|
|
||||||
stack = {}
|
|
||||||
stack['stack'] = api.heat.stack_get(self.request, stack_id)
|
|
||||||
stack['template'] = api.heat.template_get(self.request, stack_id)
|
|
||||||
self._stack = stack
|
|
||||||
except Exception:
|
|
||||||
msg = _("Unable to retrieve stack.")
|
|
||||||
redirect = reverse('horizon:project:stacks:index')
|
|
||||||
exceptions.handle(self.request, msg, redirect=redirect)
|
|
||||||
return self._stack
|
|
||||||
|
|
||||||
|
|
||||||
class PreviewStackView(CreateStackView):
|
|
||||||
template_name = 'project/stacks/preview.html'
|
|
||||||
form_id = "preview_stack"
|
|
||||||
form_class = project_forms.PreviewStackForm
|
|
||||||
submit_label = _("Preview")
|
|
||||||
submit_url = reverse_lazy('horizon:project:stacks:preview')
|
|
||||||
success_url = reverse_lazy('horizon:project:stacks:index')
|
|
||||||
page_title = _("Preview Stack")
|
|
||||||
|
|
||||||
def get_form_kwargs(self):
|
|
||||||
kwargs = super(CreateStackView, self).get_form_kwargs()
|
|
||||||
kwargs['next_view'] = PreviewStackDetailsView
|
|
||||||
return kwargs
|
|
||||||
|
|
||||||
|
|
||||||
class PreviewStackDetailsView(forms.ModalFormMixin, views.HorizonTemplateView):
|
|
||||||
template_name = 'project/stacks/preview_details.html'
|
|
||||||
page_title = _("Preview Stack Details")
|
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
|
||||||
context = super(
|
|
||||||
PreviewStackDetailsView, self).get_context_data(**kwargs)
|
|
||||||
context['stack_preview'] = self.kwargs['stack_preview'].to_dict()
|
|
||||||
return context
|
|
||||||
|
|
||||||
|
|
||||||
class DetailView(tabs.TabView):
|
|
||||||
tab_group_class = project_tabs.StackDetailTabs
|
|
||||||
template_name = 'horizon/common/_detail.html'
|
|
||||||
page_title = "{{ stack.stack_name|default:stack.id }}"
|
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
|
||||||
context = super(DetailView, self).get_context_data(**kwargs)
|
|
||||||
stack = self.get_data(self.request, **kwargs)
|
|
||||||
table = project_tables.StacksTable(self.request)
|
|
||||||
context["stack"] = stack
|
|
||||||
context["url"] = self.get_redirect_url()
|
|
||||||
context["actions"] = table.render_row_actions(stack)
|
|
||||||
return context
|
|
||||||
|
|
||||||
@memoized.memoized_method
|
|
||||||
def get_data(self, request, **kwargs):
|
|
||||||
stack_id = kwargs['stack_id']
|
|
||||||
try:
|
|
||||||
stack = api.heat.stack_get(request, stack_id)
|
|
||||||
request.session['stack_id'] = stack.id
|
|
||||||
request.session['stack_name'] = stack.stack_name
|
|
||||||
return stack
|
|
||||||
except Exception:
|
|
||||||
msg = _("Unable to retrieve stack.")
|
|
||||||
exceptions.handle(request, msg, redirect=self.get_redirect_url())
|
|
||||||
|
|
||||||
@memoized.memoized_method
|
|
||||||
def get_template(self, request, **kwargs):
|
|
||||||
try:
|
|
||||||
stack_template = api.heat.template_get(
|
|
||||||
request,
|
|
||||||
kwargs['stack_id'])
|
|
||||||
return yaml.safe_dump(stack_template, indent=2)
|
|
||||||
except Exception:
|
|
||||||
msg = _("Unable to retrieve stack template.")
|
|
||||||
exceptions.handle(request, msg, redirect=self.get_redirect_url())
|
|
||||||
|
|
||||||
def get_tabs(self, request, **kwargs):
|
|
||||||
stack = self.get_data(request, **kwargs)
|
|
||||||
stack_template = self.get_template(request, **kwargs)
|
|
||||||
return self.tab_group_class(
|
|
||||||
request, stack=stack, stack_template=stack_template, **kwargs)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_redirect_url():
|
|
||||||
return reverse('horizon:project:stacks:index')
|
|
||||||
|
|
||||||
|
|
||||||
class ResourceView(tabs.TabView):
|
|
||||||
tab_group_class = project_tabs.ResourceDetailTabs
|
|
||||||
template_name = 'horizon/common/_detail.html'
|
|
||||||
page_title = "{{ resource.resource_name|"\
|
|
||||||
"default:resource.logical_resource_id }}"
|
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
|
||||||
context = super(ResourceView, self).get_context_data(**kwargs)
|
|
||||||
context["resource"] = self.get_data(self.request, **kwargs)
|
|
||||||
context["metadata"] = self.get_metadata(self.request, **kwargs)
|
|
||||||
return context
|
|
||||||
|
|
||||||
@memoized.memoized_method
|
|
||||||
def get_data(self, request, **kwargs):
|
|
||||||
try:
|
|
||||||
resource = api.heat.resource_get(
|
|
||||||
request,
|
|
||||||
kwargs['stack_id'],
|
|
||||||
kwargs['resource_name'])
|
|
||||||
return resource
|
|
||||||
except Exception:
|
|
||||||
msg = _("Unable to retrieve resource.")
|
|
||||||
redirect = reverse('horizon:project:stacks:index')
|
|
||||||
exceptions.handle(request, msg, redirect=redirect)
|
|
||||||
|
|
||||||
@memoized.memoized_method
|
|
||||||
def get_metadata(self, request, **kwargs):
|
|
||||||
try:
|
|
||||||
metadata = api.heat.resource_metadata_get(
|
|
||||||
request,
|
|
||||||
kwargs['stack_id'],
|
|
||||||
kwargs['resource_name'])
|
|
||||||
return json.dumps(metadata, indent=2)
|
|
||||||
except Exception:
|
|
||||||
msg = _("Unable to retrieve metadata.")
|
|
||||||
redirect = reverse('horizon:project:stacks:index')
|
|
||||||
exceptions.handle(request, msg, redirect=redirect)
|
|
||||||
|
|
||||||
def get_tabs(self, request, **kwargs):
|
|
||||||
resource = self.get_data(request, **kwargs)
|
|
||||||
metadata = self.get_metadata(request, **kwargs)
|
|
||||||
return self.tab_group_class(
|
|
||||||
request, resource=resource, metadata=metadata, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class JSONView(django.views.generic.View):
|
|
||||||
def get(self, request, stack_id=''):
|
|
||||||
return HttpResponse(project_api.d3_data(request, stack_id=stack_id),
|
|
||||||
content_type="application/json")
|
|
@ -1,8 +0,0 @@
|
|||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
|
|
||||||
# The slug of the panel group to be added to HORIZON_CONFIG. Required.
|
|
||||||
PANEL_GROUP = 'orchestration'
|
|
||||||
# The display name of the PANEL_GROUP. Required.
|
|
||||||
PANEL_GROUP_NAME = _('Orchestration')
|
|
||||||
# The slug of the dashboard the PANEL_GROUP associated with. Required.
|
|
||||||
PANEL_GROUP_DASHBOARD = 'project'
|
|
@ -1,9 +0,0 @@
|
|||||||
# The slug of the panel to be added to HORIZON_CONFIG. Required.
|
|
||||||
PANEL = 'stacks'
|
|
||||||
# The slug of the dashboard the PANEL associated with. Required.
|
|
||||||
PANEL_DASHBOARD = 'project'
|
|
||||||
# The slug of the panel group the PANEL is associated with.
|
|
||||||
PANEL_GROUP = 'orchestration'
|
|
||||||
|
|
||||||
# Python panel class of the PANEL to be added.
|
|
||||||
ADD_PANEL = 'openstack_dashboard.dashboards.project.stacks.panel.Stacks'
|
|
@ -1,10 +0,0 @@
|
|||||||
# The slug of the panel to be added to HORIZON_CONFIG. Required.
|
|
||||||
PANEL = 'stacks.resource_types'
|
|
||||||
# The slug of the dashboard the PANEL associated with. Required.
|
|
||||||
PANEL_DASHBOARD = 'project'
|
|
||||||
# The slug of the panel group the PANEL is associated with.
|
|
||||||
PANEL_GROUP = 'orchestration'
|
|
||||||
|
|
||||||
# Python panel class of the PANEL to be added.
|
|
||||||
ADD_PANEL = ('openstack_dashboard.dashboards.project.'
|
|
||||||
'stacks.resource_types.panel.ResourceTypes')
|
|
@ -1,10 +0,0 @@
|
|||||||
# The slug of the panel to be added to HORIZON_CONFIG. Required.
|
|
||||||
PANEL = 'stacks.template_versions'
|
|
||||||
# The slug of the dashboard the PANEL associated with. Required.
|
|
||||||
PANEL_DASHBOARD = 'project'
|
|
||||||
# The slug of the panel group the PANEL is associated with.
|
|
||||||
PANEL_GROUP = 'orchestration'
|
|
||||||
|
|
||||||
# Python panel class of the PANEL to be added.
|
|
||||||
ADD_PANEL = ('openstack_dashboard.dashboards.project.'
|
|
||||||
'stacks.template_versions.panel.TemplateVersions')
|
|
@ -18,7 +18,6 @@
|
|||||||
|
|
||||||
from cinderclient import exceptions as cinderclient
|
from cinderclient import exceptions as cinderclient
|
||||||
from glanceclient.common import exceptions as glanceclient
|
from glanceclient.common import exceptions as glanceclient
|
||||||
from heatclient import exc as heatclient
|
|
||||||
from keystoneclient import exceptions as keystoneclient
|
from keystoneclient import exceptions as keystoneclient
|
||||||
from neutronclient.common import exceptions as neutronclient
|
from neutronclient.common import exceptions as neutronclient
|
||||||
from novaclient import exceptions as novaclient
|
from novaclient import exceptions as novaclient
|
||||||
@ -32,7 +31,6 @@ UNAUTHORIZED = (
|
|||||||
novaclient.Unauthorized,
|
novaclient.Unauthorized,
|
||||||
glanceclient.Unauthorized,
|
glanceclient.Unauthorized,
|
||||||
neutronclient.Unauthorized,
|
neutronclient.Unauthorized,
|
||||||
heatclient.HTTPUnauthorized,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -42,7 +40,6 @@ NOT_FOUND = (
|
|||||||
novaclient.NotFound,
|
novaclient.NotFound,
|
||||||
glanceclient.NotFound,
|
glanceclient.NotFound,
|
||||||
neutronclient.NotFound,
|
neutronclient.NotFound,
|
||||||
heatclient.HTTPNotFound,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -62,7 +59,5 @@ RECOVERABLE = (
|
|||||||
neutronclient.Forbidden,
|
neutronclient.Forbidden,
|
||||||
neutronclient.NeutronClientException,
|
neutronclient.NeutronClientException,
|
||||||
swiftclient.ClientException,
|
swiftclient.ClientException,
|
||||||
heatclient.HTTPForbidden,
|
|
||||||
heatclient.HTTPException,
|
|
||||||
requests.RequestException,
|
requests.RequestException,
|
||||||
)
|
)
|
||||||
|
@ -502,7 +502,6 @@ TIME_ZONE = "UTC"
|
|||||||
# 'compute': 'nova_policy.json',
|
# 'compute': 'nova_policy.json',
|
||||||
# 'volume': 'cinder_policy.json',
|
# 'volume': 'cinder_policy.json',
|
||||||
# 'image': 'glance_policy.json',
|
# 'image': 'glance_policy.json',
|
||||||
# 'orchestration': 'heat_policy.json',
|
|
||||||
# 'network': 'neutron_policy.json',
|
# 'network': 'neutron_policy.json',
|
||||||
#}
|
#}
|
||||||
|
|
||||||
@ -608,11 +607,6 @@ LOGGING = {
|
|||||||
'level': 'DEBUG',
|
'level': 'DEBUG',
|
||||||
'propagate': False,
|
'propagate': False,
|
||||||
},
|
},
|
||||||
'heatclient': {
|
|
||||||
'handlers': ['console'],
|
|
||||||
'level': 'DEBUG',
|
|
||||||
'propagate': False,
|
|
||||||
},
|
|
||||||
'swiftclient': {
|
'swiftclient': {
|
||||||
'handlers': ['console'],
|
'handlers': ['console'],
|
||||||
'level': 'DEBUG',
|
'level': 'DEBUG',
|
||||||
|
@ -259,7 +259,6 @@ POLICY_FILES = {
|
|||||||
'compute': 'nova_policy.json',
|
'compute': 'nova_policy.json',
|
||||||
'volume': 'cinder_policy.json',
|
'volume': 'cinder_policy.json',
|
||||||
'image': 'glance_policy.json',
|
'image': 'glance_policy.json',
|
||||||
'orchestration': 'heat_policy.json',
|
|
||||||
'network': 'neutron_policy.json',
|
'network': 'neutron_policy.json',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,80 +0,0 @@
|
|||||||
/*
|
|
||||||
* (c) Copyright 2015 Hewlett-Packard Development Company, L.P.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
(function () {
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
angular
|
|
||||||
.module('horizon.app.core.openstack-service-api')
|
|
||||||
.factory('horizon.app.core.openstack-service-api.heat', heatAPI);
|
|
||||||
|
|
||||||
heatAPI.$inject = [
|
|
||||||
'horizon.framework.util.http.service',
|
|
||||||
'horizon.framework.widgets.toast.service'
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc service
|
|
||||||
* @name heatAPI
|
|
||||||
* @param {Object} apiService
|
|
||||||
* @param {Object} toastService
|
|
||||||
* @description Provides direct pass through to Heat with NO abstraction.
|
|
||||||
* @returns {Object} The service
|
|
||||||
*/
|
|
||||||
function heatAPI(apiService, toastService) {
|
|
||||||
var service = {
|
|
||||||
validate: validate,
|
|
||||||
getServices: getServices
|
|
||||||
};
|
|
||||||
|
|
||||||
return service;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @name validate
|
|
||||||
* @description
|
|
||||||
* Validate a template.
|
|
||||||
*
|
|
||||||
* @param {string} params
|
|
||||||
* - template_url
|
|
||||||
* Specifies the template to validate.
|
|
||||||
*
|
|
||||||
* @param {boolean} suppressError
|
|
||||||
* If passed in, this will not show the default error handling
|
|
||||||
* (horizon alert).
|
|
||||||
* @returns {Object} The result of the API call
|
|
||||||
*/
|
|
||||||
function validate(params, suppressError) {
|
|
||||||
var promise = apiService.post('/api/heat/validate/', params);
|
|
||||||
return suppressError ? promise : promise.error(function() {
|
|
||||||
toastService.add('error', gettext('Unable to validate the template.'));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @name getServices
|
|
||||||
* @description Get the list of heat services.
|
|
||||||
*
|
|
||||||
* @returns {Object} The listing result is an object with property "services." Each item is
|
|
||||||
* a service.
|
|
||||||
*/
|
|
||||||
function getServices() {
|
|
||||||
return apiService.get('/api/heat/services/')
|
|
||||||
.error(function () {
|
|
||||||
toastService.add('error', gettext('Unable to retrieve the heat services.'));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}());
|
|
@ -1,80 +0,0 @@
|
|||||||
/*
|
|
||||||
* (c) Copyright 2015 Hewlett-Packard Development Company, L.P.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
(function() {
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
describe('Heat API', function() {
|
|
||||||
var testCall, service;
|
|
||||||
var apiService = {};
|
|
||||||
var toastService = {};
|
|
||||||
|
|
||||||
beforeEach(
|
|
||||||
module('horizon.mock.openstack-service-api',
|
|
||||||
function($provide, initServices) {
|
|
||||||
testCall = initServices($provide, apiService, toastService);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
beforeEach(module('horizon.app.core.openstack-service-api'));
|
|
||||||
|
|
||||||
beforeEach(inject(['horizon.app.core.openstack-service-api.heat', function(heatAPI) {
|
|
||||||
service = heatAPI;
|
|
||||||
}]));
|
|
||||||
|
|
||||||
it('defines the service', function() {
|
|
||||||
expect(service).toBeDefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
var tests = [
|
|
||||||
{
|
|
||||||
'func': 'validate',
|
|
||||||
'method': 'post',
|
|
||||||
'path': '/api/heat/validate/',
|
|
||||||
'data': {
|
|
||||||
'template_url':'http://localhost/test.template'
|
|
||||||
},
|
|
||||||
'error': 'Unable to validate the template.',
|
|
||||||
'testInput': [
|
|
||||||
{
|
|
||||||
'template_url':'http://localhost/test.template'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'func': 'getServices',
|
|
||||||
'method': 'get',
|
|
||||||
'path': '/api/heat/services/',
|
|
||||||
'error': 'Unable to retrieve the heat services.'
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
// Iterate through the defined tests and apply as Jasmine specs.
|
|
||||||
angular.forEach(tests, function(params) {
|
|
||||||
it('defines the ' + params.func + ' call properly', function() {
|
|
||||||
var callParams = [apiService, service, toastService, params];
|
|
||||||
testCall.apply(this, callParams);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('suppresses the error for template validation as instructed by the param', function() {
|
|
||||||
spyOn(apiService, 'post').and.returnValue("promise");
|
|
||||||
expect(service.validate("whatever", true)).toBe("promise");
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
})();
|
|
@ -1,69 +0,0 @@
|
|||||||
# (c) Copyright 2015 Hewlett-Packard Development Company, L.P.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
import json
|
|
||||||
|
|
||||||
import mock
|
|
||||||
|
|
||||||
from openstack_dashboard import api
|
|
||||||
from openstack_dashboard.api.rest import heat
|
|
||||||
from openstack_dashboard.test import helpers as test
|
|
||||||
|
|
||||||
|
|
||||||
class ValidateRestTestCase(test.TestCase):
|
|
||||||
@mock.patch.object(heat.api, 'heat')
|
|
||||||
def test_validate_post(self, hc):
|
|
||||||
body = '''{"template_url":"http://localhost/template.yaml"}'''
|
|
||||||
request = self.mock_rest_request(body=body)
|
|
||||||
hc.template_validate.return_value = ({'Description': 'foo'})
|
|
||||||
response = heat.Validate().post(request)
|
|
||||||
self.assertStatusCode(response, 200)
|
|
||||||
self.assertEqual(response.json, {"Description": "foo"})
|
|
||||||
kwargs = json.loads(body)
|
|
||||||
hc.template_validate.assert_called_once_with(request, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class HeatRestTestCase(test.TestCase):
|
|
||||||
#
|
|
||||||
# Services
|
|
||||||
#
|
|
||||||
|
|
||||||
@test.create_stubs({api.base: ('is_service_enabled',)})
|
|
||||||
@mock.patch.object(heat.api, 'heat')
|
|
||||||
def test_services_get(self, hc):
|
|
||||||
request = self.mock_rest_request(GET={})
|
|
||||||
|
|
||||||
api.base.is_service_enabled(request, 'orchestration').AndReturn(True)
|
|
||||||
|
|
||||||
hc.service_list.return_value = [
|
|
||||||
mock.Mock(**{'to_dict.return_value': {'id': '1'}}),
|
|
||||||
mock.Mock(**{'to_dict.return_value': {'id': '2'}})
|
|
||||||
]
|
|
||||||
self.mox.ReplayAll()
|
|
||||||
|
|
||||||
response = heat.Services().get(request)
|
|
||||||
self.assertStatusCode(response, 200)
|
|
||||||
self.assertEqual(response.content.decode('utf-8'),
|
|
||||||
'{"items": [{"id": "1"}, {"id": "2"}]}')
|
|
||||||
hc.service_list.assert_called_once_with(request)
|
|
||||||
|
|
||||||
@test.create_stubs({api.base: ('is_service_enabled',)})
|
|
||||||
def test_services_get_disabled(self):
|
|
||||||
request = self.mock_rest_request(GET={})
|
|
||||||
|
|
||||||
api.base.is_service_enabled(request, 'orchestration').AndReturn(False)
|
|
||||||
|
|
||||||
self.mox.ReplayAll()
|
|
||||||
|
|
||||||
response = heat.Services().get(request)
|
|
||||||
self.assertStatusCode(response, 501)
|
|
@ -1,358 +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 six
|
|
||||||
|
|
||||||
from django.conf import settings
|
|
||||||
from django.test.utils import override_settings
|
|
||||||
|
|
||||||
from horizon import exceptions
|
|
||||||
from openstack_dashboard import api
|
|
||||||
from openstack_dashboard.test import helpers as test
|
|
||||||
|
|
||||||
|
|
||||||
class HeatApiTests(test.APITestCase):
|
|
||||||
def test_stack_list(self):
|
|
||||||
api_stacks = self.stacks.list()
|
|
||||||
limit = getattr(settings, 'API_RESULT_LIMIT', 1000)
|
|
||||||
|
|
||||||
heatclient = self.stub_heatclient()
|
|
||||||
heatclient.stacks = self.mox.CreateMockAnything()
|
|
||||||
heatclient.stacks.list(limit=limit,
|
|
||||||
sort_dir='desc',
|
|
||||||
sort_key='created_at',) \
|
|
||||||
.AndReturn(iter(api_stacks))
|
|
||||||
self.mox.ReplayAll()
|
|
||||||
stacks, has_more, has_prev = api.heat.stacks_list(self.request)
|
|
||||||
self.assertItemsEqual(stacks, api_stacks)
|
|
||||||
self.assertFalse(has_more)
|
|
||||||
self.assertFalse(has_prev)
|
|
||||||
|
|
||||||
@override_settings(API_RESULT_PAGE_SIZE=2)
|
|
||||||
def test_stack_list_sort_options(self):
|
|
||||||
# Verify that sort_dir and sort_key work
|
|
||||||
api_stacks = self.stacks.list()
|
|
||||||
limit = getattr(settings, 'API_RESULT_LIMIT', 1000)
|
|
||||||
sort_dir = 'asc'
|
|
||||||
sort_key = 'size'
|
|
||||||
|
|
||||||
heatclient = self.stub_heatclient()
|
|
||||||
heatclient.stacks = self.mox.CreateMockAnything()
|
|
||||||
heatclient.stacks.list(limit=limit,
|
|
||||||
sort_dir=sort_dir,
|
|
||||||
sort_key=sort_key,) \
|
|
||||||
.AndReturn(iter(api_stacks))
|
|
||||||
self.mox.ReplayAll()
|
|
||||||
|
|
||||||
stacks, has_more, has_prev = api.heat.stacks_list(self.request,
|
|
||||||
sort_dir=sort_dir,
|
|
||||||
sort_key=sort_key)
|
|
||||||
self.assertItemsEqual(stacks, api_stacks)
|
|
||||||
self.assertFalse(has_more)
|
|
||||||
self.assertFalse(has_prev)
|
|
||||||
|
|
||||||
@override_settings(API_RESULT_PAGE_SIZE=20)
|
|
||||||
def test_stack_list_pagination_less_page_size(self):
|
|
||||||
api_stacks = self.stacks.list()
|
|
||||||
page_size = settings.API_RESULT_PAGE_SIZE
|
|
||||||
sort_dir = 'desc'
|
|
||||||
sort_key = 'created_at'
|
|
||||||
|
|
||||||
heatclient = self.stub_heatclient()
|
|
||||||
heatclient.stacks = self.mox.CreateMockAnything()
|
|
||||||
heatclient.stacks.list(limit=page_size + 1,
|
|
||||||
sort_dir=sort_dir,
|
|
||||||
sort_key=sort_key,) \
|
|
||||||
.AndReturn(iter(api_stacks))
|
|
||||||
self.mox.ReplayAll()
|
|
||||||
|
|
||||||
stacks, has_more, has_prev = api.heat.stacks_list(self.request,
|
|
||||||
sort_dir=sort_dir,
|
|
||||||
sort_key=sort_key,
|
|
||||||
paginate=True)
|
|
||||||
expected_stacks = api_stacks[:page_size]
|
|
||||||
self.assertItemsEqual(stacks, expected_stacks)
|
|
||||||
self.assertFalse(has_more)
|
|
||||||
self.assertFalse(has_prev)
|
|
||||||
|
|
||||||
@override_settings(API_RESULT_PAGE_SIZE=10)
|
|
||||||
def test_stack_list_pagination_equal_page_size(self):
|
|
||||||
api_stacks = self.stacks.list()
|
|
||||||
page_size = settings.API_RESULT_PAGE_SIZE
|
|
||||||
sort_dir = 'desc'
|
|
||||||
sort_key = 'created_at'
|
|
||||||
|
|
||||||
heatclient = self.stub_heatclient()
|
|
||||||
heatclient.stacks = self.mox.CreateMockAnything()
|
|
||||||
heatclient.stacks.list(limit=page_size + 1,
|
|
||||||
sort_dir=sort_dir,
|
|
||||||
sort_key=sort_key,) \
|
|
||||||
.AndReturn(iter(api_stacks))
|
|
||||||
self.mox.ReplayAll()
|
|
||||||
|
|
||||||
stacks, has_more, has_prev = api.heat.stacks_list(self.request,
|
|
||||||
sort_dir=sort_dir,
|
|
||||||
sort_key=sort_key,
|
|
||||||
paginate=True)
|
|
||||||
expected_stacks = api_stacks[:page_size]
|
|
||||||
self.assertItemsEqual(stacks, expected_stacks)
|
|
||||||
self.assertFalse(has_more)
|
|
||||||
self.assertFalse(has_prev)
|
|
||||||
|
|
||||||
@override_settings(API_RESULT_PAGE_SIZE=2)
|
|
||||||
def test_stack_list_pagination_marker(self):
|
|
||||||
page_size = getattr(settings, 'API_RESULT_PAGE_SIZE', 20)
|
|
||||||
sort_dir = 'desc'
|
|
||||||
sort_key = 'created_at'
|
|
||||||
marker = 'nonsense'
|
|
||||||
|
|
||||||
api_stacks = self.stacks.list()
|
|
||||||
|
|
||||||
heatclient = self.stub_heatclient()
|
|
||||||
heatclient.stacks = self.mox.CreateMockAnything()
|
|
||||||
heatclient.stacks.list(limit=page_size + 1,
|
|
||||||
marker=marker,
|
|
||||||
sort_dir=sort_dir,
|
|
||||||
sort_key=sort_key,) \
|
|
||||||
.AndReturn(iter(api_stacks[:page_size + 1]))
|
|
||||||
self.mox.ReplayAll()
|
|
||||||
|
|
||||||
stacks, has_more, has_prev = api.heat.stacks_list(self.request,
|
|
||||||
marker=marker,
|
|
||||||
paginate=True,
|
|
||||||
sort_dir=sort_dir,
|
|
||||||
sort_key=sort_key,)
|
|
||||||
|
|
||||||
self.assertEqual(len(stacks), page_size)
|
|
||||||
self.assertItemsEqual(stacks, api_stacks[:page_size])
|
|
||||||
self.assertTrue(has_more)
|
|
||||||
self.assertTrue(has_prev)
|
|
||||||
|
|
||||||
@override_settings(API_RESULT_PAGE_SIZE=2)
|
|
||||||
def test_stack_list_pagination_marker_prev(self):
|
|
||||||
page_size = getattr(settings, 'API_RESULT_PAGE_SIZE', 20)
|
|
||||||
sort_dir = 'asc'
|
|
||||||
sort_key = 'created_at'
|
|
||||||
marker = 'nonsense'
|
|
||||||
|
|
||||||
api_stacks = self.stacks.list()
|
|
||||||
|
|
||||||
heatclient = self.stub_heatclient()
|
|
||||||
heatclient.stacks = self.mox.CreateMockAnything()
|
|
||||||
heatclient.stacks.list(limit=page_size + 1,
|
|
||||||
marker=marker,
|
|
||||||
sort_dir=sort_dir,
|
|
||||||
sort_key=sort_key,) \
|
|
||||||
.AndReturn(iter(api_stacks[:page_size + 1]))
|
|
||||||
self.mox.ReplayAll()
|
|
||||||
|
|
||||||
stacks, has_more, has_prev = api.heat.stacks_list(self.request,
|
|
||||||
marker=marker,
|
|
||||||
paginate=True,
|
|
||||||
sort_dir=sort_dir,
|
|
||||||
sort_key=sort_key,)
|
|
||||||
|
|
||||||
self.assertEqual(len(stacks), page_size)
|
|
||||||
self.assertItemsEqual(stacks, api_stacks[:page_size])
|
|
||||||
self.assertTrue(has_more)
|
|
||||||
self.assertTrue(has_prev)
|
|
||||||
|
|
||||||
def test_template_get(self):
|
|
||||||
api_stacks = self.stacks.list()
|
|
||||||
stack_id = api_stacks[0].id
|
|
||||||
mock_data_template = self.stack_templates.list()[0]
|
|
||||||
|
|
||||||
heatclient = self.stub_heatclient()
|
|
||||||
heatclient.stacks = self.mox.CreateMockAnything()
|
|
||||||
heatclient.stacks.template(stack_id).AndReturn(mock_data_template)
|
|
||||||
self.mox.ReplayAll()
|
|
||||||
|
|
||||||
template = api.heat.template_get(self.request, stack_id)
|
|
||||||
self.assertEqual(mock_data_template.data, template.data)
|
|
||||||
|
|
||||||
def test_stack_create(self):
|
|
||||||
api_stacks = self.stacks.list()
|
|
||||||
stack = api_stacks[0]
|
|
||||||
|
|
||||||
heatclient = self.stub_heatclient()
|
|
||||||
heatclient.stacks = self.mox.CreateMockAnything()
|
|
||||||
form_data = {'timeout_mins': 600}
|
|
||||||
password = 'secret'
|
|
||||||
heatclient.stacks.create(**form_data).AndReturn(stack)
|
|
||||||
self.mox.ReplayAll()
|
|
||||||
|
|
||||||
returned_stack = api.heat.stack_create(self.request,
|
|
||||||
password,
|
|
||||||
**form_data)
|
|
||||||
from heatclient.v1 import stacks
|
|
||||||
self.assertIsInstance(returned_stack, stacks.Stack)
|
|
||||||
|
|
||||||
def test_stack_update(self):
|
|
||||||
api_stacks = self.stacks.list()
|
|
||||||
stack = api_stacks[0]
|
|
||||||
stack_id = stack.id
|
|
||||||
|
|
||||||
heatclient = self.stub_heatclient()
|
|
||||||
heatclient.stacks = self.mox.CreateMockAnything()
|
|
||||||
form_data = {'timeout_mins': 600}
|
|
||||||
password = 'secret'
|
|
||||||
heatclient.stacks.update(stack_id, **form_data).AndReturn(stack)
|
|
||||||
self.mox.ReplayAll()
|
|
||||||
|
|
||||||
returned_stack = api.heat.stack_update(self.request,
|
|
||||||
stack_id,
|
|
||||||
password,
|
|
||||||
**form_data)
|
|
||||||
from heatclient.v1 import stacks
|
|
||||||
self.assertIsInstance(returned_stack, stacks.Stack)
|
|
||||||
|
|
||||||
def test_snapshot_create(self):
|
|
||||||
stack_id = self.stacks.first().id
|
|
||||||
snapshot_create = self.stack_snapshot_create.list()[0]
|
|
||||||
|
|
||||||
heatclient = self.stub_heatclient()
|
|
||||||
heatclient.stacks = self.mox.CreateMockAnything()
|
|
||||||
heatclient.stacks.snapshot(stack_id).AndReturn(snapshot_create)
|
|
||||||
self.mox.ReplayAll()
|
|
||||||
|
|
||||||
returned_snapshot_create_info = api.heat.snapshot_create(self.request,
|
|
||||||
stack_id)
|
|
||||||
|
|
||||||
self.assertEqual(returned_snapshot_create_info, snapshot_create)
|
|
||||||
|
|
||||||
def test_snapshot_list(self):
|
|
||||||
stack_id = self.stacks.first().id
|
|
||||||
snapshot_list = self.stack_snapshot.list()
|
|
||||||
|
|
||||||
heatclient = self.stub_heatclient()
|
|
||||||
heatclient.stacks = self.mox.CreateMockAnything()
|
|
||||||
heatclient.stacks.snapshot_list(stack_id).AndReturn(snapshot_list)
|
|
||||||
self.mox.ReplayAll()
|
|
||||||
|
|
||||||
returned_snapshots = api.heat.snapshot_list(self.request, stack_id)
|
|
||||||
|
|
||||||
self.assertItemsEqual(returned_snapshots, snapshot_list)
|
|
||||||
|
|
||||||
def test_get_template_files_with_template_data(self):
|
|
||||||
tmpl = '''
|
|
||||||
# comment
|
|
||||||
|
|
||||||
heat_template_version: 2013-05-23
|
|
||||||
resources:
|
|
||||||
server1:
|
|
||||||
type: OS::Nova::Server
|
|
||||||
properties:
|
|
||||||
flavor: m1.medium
|
|
||||||
image: cirros
|
|
||||||
'''
|
|
||||||
expected_files = {}
|
|
||||||
files = api.heat.get_template_files(template_data=tmpl)[0]
|
|
||||||
self.assertEqual(files, expected_files)
|
|
||||||
|
|
||||||
def test_get_template_files(self):
|
|
||||||
tmpl = '''
|
|
||||||
# comment
|
|
||||||
|
|
||||||
heat_template_version: 2013-05-23
|
|
||||||
resources:
|
|
||||||
server1:
|
|
||||||
type: OS::Nova::Server
|
|
||||||
properties:
|
|
||||||
flavor: m1.medium
|
|
||||||
image: cirros
|
|
||||||
user_data_format: RAW
|
|
||||||
user_data:
|
|
||||||
get_file: http://test.example/example
|
|
||||||
'''
|
|
||||||
expected_files = {u'http://test.example/example': b'echo "test"'}
|
|
||||||
url = 'http://test.example/example'
|
|
||||||
data = b'echo "test"'
|
|
||||||
self.mox.StubOutWithMock(six.moves.urllib.request, 'urlopen')
|
|
||||||
six.moves.urllib.request.urlopen(url).AndReturn(
|
|
||||||
six.BytesIO(data))
|
|
||||||
self.mox.ReplayAll()
|
|
||||||
files = api.heat.get_template_files(template_data=tmpl)[0]
|
|
||||||
self.assertEqual(files, expected_files)
|
|
||||||
|
|
||||||
def test_get_template_files_with_template_url(self):
|
|
||||||
url = 'https://test.example/example.yaml'
|
|
||||||
data = b'''
|
|
||||||
# comment
|
|
||||||
|
|
||||||
heat_template_version: 2013-05-23
|
|
||||||
resources:
|
|
||||||
server1:
|
|
||||||
type: OS::Nova::Server
|
|
||||||
properties:
|
|
||||||
flavor: m1.medium
|
|
||||||
image: cirros
|
|
||||||
user_data_format: RAW
|
|
||||||
user_data:
|
|
||||||
get_file: http://test.example/example
|
|
||||||
'''
|
|
||||||
url2 = 'http://test.example/example'
|
|
||||||
data2 = b'echo "test"'
|
|
||||||
expected_files = {'http://test.example/example': b'echo "test"'}
|
|
||||||
self.mox.StubOutWithMock(six.moves.urllib.request, 'urlopen')
|
|
||||||
six.moves.urllib.request.urlopen(url).AndReturn(
|
|
||||||
six.BytesIO(data))
|
|
||||||
six.moves.urllib.request.urlopen(url2).AndReturn(
|
|
||||||
six.BytesIO(data2))
|
|
||||||
self.mox.ReplayAll()
|
|
||||||
files = api.heat.get_template_files(template_url=url)[0]
|
|
||||||
self.assertEqual(files, expected_files)
|
|
||||||
|
|
||||||
def test_get_template_files_invalid(self):
|
|
||||||
tmpl = '''
|
|
||||||
# comment
|
|
||||||
|
|
||||||
heat_template_version: 2013-05-23
|
|
||||||
resources:
|
|
||||||
server1:
|
|
||||||
type: OS::Nova::Server
|
|
||||||
properties:
|
|
||||||
flavor: m1.medium
|
|
||||||
image: cirros
|
|
||||||
user_data_format: RAW
|
|
||||||
user_data:
|
|
||||||
get_file: file:///example
|
|
||||||
'''
|
|
||||||
try:
|
|
||||||
api.heat.get_template_files(template_data=tmpl)[0]
|
|
||||||
except exceptions.GetFileError:
|
|
||||||
self.assertRaises(exceptions.GetFileError)
|
|
||||||
|
|
||||||
def test_template_version_list(self):
|
|
||||||
api_template_versions = self.template_versions.list()
|
|
||||||
|
|
||||||
heatclient = self.stub_heatclient()
|
|
||||||
heatclient.template_versions = self.mox.CreateMockAnything()
|
|
||||||
heatclient.template_versions.list().AndReturn(api_template_versions)
|
|
||||||
self.mox.ReplayAll()
|
|
||||||
|
|
||||||
template_versions = api.heat.template_version_list(self.request)
|
|
||||||
|
|
||||||
self.assertItemsEqual(template_versions, api_template_versions)
|
|
||||||
|
|
||||||
def test_template_function_list(self):
|
|
||||||
template_version = self.template_versions.first().version
|
|
||||||
api_template_functions = self.template_functions.list()
|
|
||||||
|
|
||||||
heatclient = self.stub_heatclient()
|
|
||||||
heatclient.template_versions = self.mox.CreateMockAnything()
|
|
||||||
heatclient.template_versions.get(
|
|
||||||
template_version).AndReturn(api_template_functions)
|
|
||||||
self.mox.ReplayAll()
|
|
||||||
|
|
||||||
template_functions = api.heat.template_function_list(
|
|
||||||
self.request, template_version)
|
|
||||||
|
|
||||||
self.assertItemsEqual(template_functions, api_template_functions)
|
|
@ -37,7 +37,6 @@ from django.utils import http
|
|||||||
|
|
||||||
from cinderclient import client as cinder_client
|
from cinderclient import client as cinder_client
|
||||||
import glanceclient
|
import glanceclient
|
||||||
from heatclient import client as heat_client
|
|
||||||
from keystoneclient.v2_0 import client as keystone_client
|
from keystoneclient.v2_0 import client as keystone_client
|
||||||
import mock
|
import mock
|
||||||
from mox3 import mox
|
from mox3 import mox
|
||||||
@ -433,7 +432,6 @@ class APITestCase(TestCase):
|
|||||||
self._original_novaclient = api.nova.novaclient
|
self._original_novaclient = api.nova.novaclient
|
||||||
self._original_neutronclient = api.neutron.neutronclient
|
self._original_neutronclient = api.neutron.neutronclient
|
||||||
self._original_cinderclient = api.cinder.cinderclient
|
self._original_cinderclient = api.cinder.cinderclient
|
||||||
self._original_heatclient = api.heat.heatclient
|
|
||||||
|
|
||||||
# Replace the clients with our stubs.
|
# Replace the clients with our stubs.
|
||||||
api.glance.glanceclient = fake_glanceclient
|
api.glance.glanceclient = fake_glanceclient
|
||||||
@ -441,8 +439,6 @@ class APITestCase(TestCase):
|
|||||||
api.nova.novaclient = fake_novaclient
|
api.nova.novaclient = fake_novaclient
|
||||||
api.neutron.neutronclient = lambda request: self.stub_neutronclient()
|
api.neutron.neutronclient = lambda request: self.stub_neutronclient()
|
||||||
api.cinder.cinderclient = lambda request: self.stub_cinderclient()
|
api.cinder.cinderclient = lambda request: self.stub_cinderclient()
|
||||||
api.heat.heatclient = (lambda request, password=None:
|
|
||||||
self.stub_heatclient())
|
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
super(APITestCase, self).tearDown()
|
super(APITestCase, self).tearDown()
|
||||||
@ -451,7 +447,6 @@ class APITestCase(TestCase):
|
|||||||
api.keystone.keystoneclient = self._original_keystoneclient
|
api.keystone.keystoneclient = self._original_keystoneclient
|
||||||
api.neutron.neutronclient = self._original_neutronclient
|
api.neutron.neutronclient = self._original_neutronclient
|
||||||
api.cinder.cinderclient = self._original_cinderclient
|
api.cinder.cinderclient = self._original_cinderclient
|
||||||
api.heat.heatclient = self._original_heatclient
|
|
||||||
|
|
||||||
def stub_novaclient(self):
|
def stub_novaclient(self):
|
||||||
if not hasattr(self, "novaclient"):
|
if not hasattr(self, "novaclient"):
|
||||||
@ -518,12 +513,6 @@ class APITestCase(TestCase):
|
|||||||
expected_calls -= 1
|
expected_calls -= 1
|
||||||
return self.swiftclient
|
return self.swiftclient
|
||||||
|
|
||||||
def stub_heatclient(self):
|
|
||||||
if not hasattr(self, "heatclient"):
|
|
||||||
self.mox.StubOutWithMock(heat_client, 'Client')
|
|
||||||
self.heatclient = self.mox.CreateMock(heat_client.Client)
|
|
||||||
return self.heatclient
|
|
||||||
|
|
||||||
|
|
||||||
class APIMockTestCase(APITestCase):
|
class APIMockTestCase(APITestCase):
|
||||||
def stub_cinderclient(self):
|
def stub_cinderclient(self):
|
||||||
|
@ -80,8 +80,6 @@ NetworkGroup = [
|
|||||||
AvailableServiceGroup = [
|
AvailableServiceGroup = [
|
||||||
cfg.BoolOpt('neutron',
|
cfg.BoolOpt('neutron',
|
||||||
default=True),
|
default=True),
|
||||||
cfg.BoolOpt('heat',
|
|
||||||
default=True),
|
|
||||||
]
|
]
|
||||||
|
|
||||||
SeleniumGroup = [
|
SeleniumGroup = [
|
||||||
|
@ -77,8 +77,6 @@ tenant_network_cidr=10.100.0.0/16
|
|||||||
[service_available]
|
[service_available]
|
||||||
# Whether is Neutron expected to be available (boolean value)
|
# Whether is Neutron expected to be available (boolean value)
|
||||||
neutron=True
|
neutron=True
|
||||||
# Whether is Heat expected to be available (boolean value)
|
|
||||||
heat=True
|
|
||||||
|
|
||||||
[scenario]
|
[scenario]
|
||||||
# ssh username for image file (string value)
|
# ssh username for image file (string value)
|
||||||
|
@ -106,13 +106,6 @@ class Navigation(object):
|
|||||||
"Containers",
|
"Containers",
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
"Orchestration":
|
|
||||||
{
|
|
||||||
ITEMS:
|
|
||||||
(
|
|
||||||
"Stacks",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"Admin":
|
"Admin":
|
||||||
{
|
{
|
||||||
|
@ -1,99 +0,0 @@
|
|||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
from openstack_dashboard.test.integration_tests import config
|
|
||||||
from openstack_dashboard.test.integration_tests.pages import basepage
|
|
||||||
from openstack_dashboard.test.integration_tests.regions import forms
|
|
||||||
from openstack_dashboard.test.integration_tests.regions import tables
|
|
||||||
|
|
||||||
|
|
||||||
class StacksTable(tables.TableRegion):
|
|
||||||
name = "stacks"
|
|
||||||
SELECT_TEMPLATE_FORM_FIELDS = ("template_source", "template_upload",
|
|
||||||
"template_data", "template_url",
|
|
||||||
"environment_source", "environment_upload",
|
|
||||||
"environment_data")
|
|
||||||
LAUNCH_STACK_FORM_FIELDS = ("stack_name", "timeout_mins",
|
|
||||||
"enable_rollback", "password")
|
|
||||||
|
|
||||||
@tables.bind_table_action('launch')
|
|
||||||
def select_template(self, launch_button):
|
|
||||||
launch_button.click()
|
|
||||||
return forms.FormRegion(
|
|
||||||
self.driver, self.conf,
|
|
||||||
field_mappings=self.SELECT_TEMPLATE_FORM_FIELDS)
|
|
||||||
|
|
||||||
def launch_stack(self):
|
|
||||||
return forms.FormRegion(self.driver, self.conf,
|
|
||||||
field_mappings=self.LAUNCH_STACK_FORM_FIELDS)
|
|
||||||
|
|
||||||
@tables.bind_table_action('delete')
|
|
||||||
def delete_stack(self, delete_button):
|
|
||||||
delete_button.click()
|
|
||||||
return forms.BaseFormRegion(self.driver, self.conf)
|
|
||||||
|
|
||||||
|
|
||||||
class StacksPage(basepage.BaseNavigationPage):
|
|
||||||
DEFAULT_TEMPLATE_SOURCE = 'raw'
|
|
||||||
|
|
||||||
CONFIG = config.get_config()
|
|
||||||
DEFAULT_PASSWORD = CONFIG.identity.admin_password
|
|
||||||
STACKS_TABLE_NAME_COLUMN = 'name'
|
|
||||||
STACKS_TABLE_STATUS_COLUMN = 'stack_status'
|
|
||||||
|
|
||||||
def __init__(self, driver, conf):
|
|
||||||
super(StacksPage, self).__init__(driver, conf)
|
|
||||||
self._page_title = "Stacks"
|
|
||||||
|
|
||||||
@property
|
|
||||||
def stacks_table(self):
|
|
||||||
return StacksTable(self.driver, self.conf)
|
|
||||||
|
|
||||||
def _get_row_with_stack_name(self, name):
|
|
||||||
return self.stacks_table.get_row(self.STACKS_TABLE_NAME_COLUMN, name)
|
|
||||||
|
|
||||||
def create_stack(self, stack_name, template_data,
|
|
||||||
template_source=DEFAULT_TEMPLATE_SOURCE,
|
|
||||||
environment_source=None,
|
|
||||||
environment_upload=None,
|
|
||||||
timeout_mins=None,
|
|
||||||
enable_rollback=None,
|
|
||||||
password=DEFAULT_PASSWORD):
|
|
||||||
select_template_form = self.stacks_table.select_template()
|
|
||||||
select_template_form.template_source.value = template_source
|
|
||||||
select_template_form.template_data.text = template_data
|
|
||||||
select_template_form.submit()
|
|
||||||
launch_stack_form = self.stacks_table.launch_stack()
|
|
||||||
launch_stack_form.stack_name.text = stack_name
|
|
||||||
launch_stack_form.password.text = password
|
|
||||||
launch_stack_form.submit()
|
|
||||||
|
|
||||||
def delete_stack(self, name):
|
|
||||||
row = self._get_row_with_stack_name(name)
|
|
||||||
row.mark()
|
|
||||||
confirm_delete_stacks_form = self.stacks_table.delete_stack()
|
|
||||||
confirm_delete_stacks_form.submit()
|
|
||||||
|
|
||||||
def is_stack_present(self, name):
|
|
||||||
return bool(self._get_row_with_stack_name(name))
|
|
||||||
|
|
||||||
def is_stack_create_complete(self, name):
|
|
||||||
def cell_getter():
|
|
||||||
row = self._get_row_with_stack_name(name)
|
|
||||||
return row and row.cells[self.STACKS_TABLE_STATUS_COLUMN]
|
|
||||||
|
|
||||||
return bool(self.stacks_table.wait_cell_status(cell_getter,
|
|
||||||
'Create Complete'))
|
|
||||||
|
|
||||||
def is_stack_deleted(self, name):
|
|
||||||
return self.stacks_table.is_row_deleted(
|
|
||||||
lambda: self._get_row_with_stack_name(name))
|
|
@ -1,73 +0,0 @@
|
|||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
import os
|
|
||||||
|
|
||||||
from openstack_dashboard.test.integration_tests import decorators
|
|
||||||
from openstack_dashboard.test.integration_tests import helpers
|
|
||||||
from openstack_dashboard.test.integration_tests.regions import messages
|
|
||||||
|
|
||||||
|
|
||||||
class TestStacks(helpers.AdminTestCase):
|
|
||||||
KEYPAIR_NAME = 'keypair_for_stack'
|
|
||||||
STACKS_NAME = helpers.gen_random_resource_name('stack', timestamp=False)
|
|
||||||
STACK_TEMPLATE_PATH = os.path.join(
|
|
||||||
os.path.dirname(__file__), 'test-data/stack_template')
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(TestStacks, self).setUp()
|
|
||||||
keypair_page = self.home_pg.\
|
|
||||||
go_to_compute_accessandsecurity_keypairspage()
|
|
||||||
keypair_page.create_keypair(self.KEYPAIR_NAME)
|
|
||||||
keypair_page = self.home_pg.\
|
|
||||||
go_to_compute_accessandsecurity_keypairspage()
|
|
||||||
self.assertTrue(keypair_page.is_keypair_present(self.KEYPAIR_NAME))
|
|
||||||
|
|
||||||
def cleanup():
|
|
||||||
keypair_page = self.home_pg.\
|
|
||||||
go_to_compute_accessandsecurity_keypairspage()
|
|
||||||
keypair_page.delete_keypairs(self.KEYPAIR_NAME)
|
|
||||||
keypair_page.find_message_and_dismiss(messages.SUCCESS)
|
|
||||||
|
|
||||||
self.addCleanup(cleanup)
|
|
||||||
|
|
||||||
@decorators.skip_because(bugs=['1584057'])
|
|
||||||
@decorators.services_required("heat")
|
|
||||||
def test_create_delete_stack(self):
|
|
||||||
"""tests the stack creation and deletion functionality
|
|
||||||
|
|
||||||
* creates a new stack
|
|
||||||
* verifies the stack appears in the stacks table in Create Complete
|
|
||||||
state
|
|
||||||
* deletes the newly created stack
|
|
||||||
* verifies the stack does not appear in the table after deletion
|
|
||||||
"""
|
|
||||||
with open(self.STACK_TEMPLATE_PATH, 'r') as f:
|
|
||||||
template = f.read()
|
|
||||||
input_template = template.format(self.KEYPAIR_NAME,
|
|
||||||
self.CONFIG.image.images_list[0],
|
|
||||||
"public")
|
|
||||||
stacks_page = self.home_pg.go_to_orchestration_stackspage()
|
|
||||||
|
|
||||||
stacks_page.create_stack(self.STACKS_NAME, input_template)
|
|
||||||
self.assertTrue(
|
|
||||||
stacks_page.find_message_and_dismiss(messages.INFO))
|
|
||||||
self.assertFalse(
|
|
||||||
stacks_page.find_message_and_dismiss(messages.ERROR))
|
|
||||||
self.assertTrue(stacks_page.is_stack_present(self.STACKS_NAME))
|
|
||||||
self.assertTrue(stacks_page.is_stack_create_complete(self.STACKS_NAME))
|
|
||||||
|
|
||||||
stacks_page.delete_stack(self.STACKS_NAME)
|
|
||||||
self.assertTrue(
|
|
||||||
stacks_page.find_message_and_dismiss(messages.SUCCESS))
|
|
||||||
self.assertFalse(
|
|
||||||
stacks_page.find_message_and_dismiss(messages.ERROR))
|
|
||||||
self.assertTrue(stacks_page.is_stack_deleted(self.STACKS_NAME))
|
|
@ -14,7 +14,6 @@
|
|||||||
|
|
||||||
from cinderclient import exceptions as cinder_exceptions
|
from cinderclient import exceptions as cinder_exceptions
|
||||||
import glanceclient.exc as glance_exceptions
|
import glanceclient.exc as glance_exceptions
|
||||||
import heatclient.exc as heat_exceptions
|
|
||||||
from keystoneclient import exceptions as keystone_exceptions
|
from keystoneclient import exceptions as keystone_exceptions
|
||||||
from neutronclient.common import exceptions as neutron_exceptions
|
from neutronclient.common import exceptions as neutron_exceptions
|
||||||
from novaclient import exceptions as nova_exceptions
|
from novaclient import exceptions as nova_exceptions
|
||||||
@ -81,6 +80,3 @@ def data(TEST):
|
|||||||
|
|
||||||
cinder_exception = cinder_exceptions.BadRequest
|
cinder_exception = cinder_exceptions.BadRequest
|
||||||
TEST.exceptions.cinder = create_stubbed_exception(cinder_exception)
|
TEST.exceptions.cinder = create_stubbed_exception(cinder_exception)
|
||||||
|
|
||||||
heat_exception = heat_exceptions.HTTPException
|
|
||||||
TEST.exceptions.heat = create_stubbed_exception(heat_exception)
|
|
||||||
|
@ -1,617 +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 heatclient.v1 import resource_types
|
|
||||||
from heatclient.v1 import resources
|
|
||||||
from heatclient.v1 import services
|
|
||||||
from heatclient.v1 import stacks
|
|
||||||
from heatclient.v1 import template_versions
|
|
||||||
|
|
||||||
from openstack_dashboard.test.test_data import utils
|
|
||||||
|
|
||||||
# A slightly hacked up copy of a sample cloudformation template for testing.
|
|
||||||
TEMPLATE = """
|
|
||||||
{
|
|
||||||
"AWSTemplateFormatVersion": "2010-09-09",
|
|
||||||
"Description": "AWS CloudFormation Sample Template.",
|
|
||||||
"Parameters": {
|
|
||||||
"KeyName": {
|
|
||||||
"Description": "Name of an EC2 Key Pair to enable SSH access to the instances",
|
|
||||||
"Type": "String"
|
|
||||||
},
|
|
||||||
"InstanceType": {
|
|
||||||
"Description": "WebServer EC2 instance type",
|
|
||||||
"Type": "String",
|
|
||||||
"Default": "m1.small",
|
|
||||||
"AllowedValues": [
|
|
||||||
"m1.tiny",
|
|
||||||
"m1.small",
|
|
||||||
"m1.medium",
|
|
||||||
"m1.large",
|
|
||||||
"m1.xlarge"
|
|
||||||
],
|
|
||||||
"ConstraintDescription": "must be a valid EC2 instance type."
|
|
||||||
},
|
|
||||||
"DBName": {
|
|
||||||
"Default": "wordpress",
|
|
||||||
"Description": "The WordPress database name",
|
|
||||||
"Type": "String",
|
|
||||||
"MinLength": "1",
|
|
||||||
"MaxLength": "64",
|
|
||||||
"AllowedPattern": "[a-zA-Z][a-zA-Z0-9]*",
|
|
||||||
"ConstraintDescription": "must begin with a letter and..."
|
|
||||||
},
|
|
||||||
"DBUsername": {
|
|
||||||
"Default": "admin",
|
|
||||||
"NoEcho": "true",
|
|
||||||
"Description": "The WordPress database admin account username",
|
|
||||||
"Type": "String",
|
|
||||||
"MinLength": "1",
|
|
||||||
"MaxLength": "16",
|
|
||||||
"AllowedPattern": "[a-zA-Z][a-zA-Z0-9]*",
|
|
||||||
"ConstraintDescription": "must begin with a letter and..."
|
|
||||||
},
|
|
||||||
"DBPassword": {
|
|
||||||
"Default": "admin",
|
|
||||||
"NoEcho": "true",
|
|
||||||
"Description": "The WordPress database admin account password",
|
|
||||||
"Type": "String",
|
|
||||||
"MinLength": "1",
|
|
||||||
"MaxLength": "41",
|
|
||||||
"AllowedPattern": "[a-zA-Z0-9]*",
|
|
||||||
"ConstraintDescription": "must contain only alphanumeric characters."
|
|
||||||
},
|
|
||||||
"DBRootPassword": {
|
|
||||||
"Default": "admin",
|
|
||||||
"NoEcho": "true",
|
|
||||||
"Description": "Root password for MySQL",
|
|
||||||
"Type": "String",
|
|
||||||
"MinLength": "1",
|
|
||||||
"MaxLength": "41",
|
|
||||||
"AllowedPattern": "[a-zA-Z0-9]*",
|
|
||||||
"ConstraintDescription": "must contain only alphanumeric characters."
|
|
||||||
},
|
|
||||||
"LinuxDistribution": {
|
|
||||||
"Default": "F17",
|
|
||||||
"Description": "Distribution of choice",
|
|
||||||
"Type": "String",
|
|
||||||
"AllowedValues": [
|
|
||||||
"F18",
|
|
||||||
"F17",
|
|
||||||
"U10",
|
|
||||||
"RHEL-6.1",
|
|
||||||
"RHEL-6.2",
|
|
||||||
"RHEL-6.3"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"Network": {
|
|
||||||
"Type": "String",
|
|
||||||
"CustomConstraint": "neutron.network"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"Mappings": {
|
|
||||||
"AWSInstanceType2Arch": {
|
|
||||||
"m1.tiny": {
|
|
||||||
"Arch": "32"
|
|
||||||
},
|
|
||||||
"m1.small": {
|
|
||||||
"Arch": "64"
|
|
||||||
},
|
|
||||||
"m1.medium": {
|
|
||||||
"Arch": "64"
|
|
||||||
},
|
|
||||||
"m1.large": {
|
|
||||||
"Arch": "64"
|
|
||||||
},
|
|
||||||
"m1.xlarge": {
|
|
||||||
"Arch": "64"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"DistroArch2AMI": {
|
|
||||||
"F18": {
|
|
||||||
"32": "F18-i386-cfntools",
|
|
||||||
"64": "F18-x86_64-cfntools"
|
|
||||||
},
|
|
||||||
"F17": {
|
|
||||||
"32": "F17-i386-cfntools",
|
|
||||||
"64": "F17-x86_64-cfntools"
|
|
||||||
},
|
|
||||||
"U10": {
|
|
||||||
"32": "U10-i386-cfntools",
|
|
||||||
"64": "U10-x86_64-cfntools"
|
|
||||||
},
|
|
||||||
"RHEL-6.1": {
|
|
||||||
"32": "rhel61-i386-cfntools",
|
|
||||||
"64": "rhel61-x86_64-cfntools"
|
|
||||||
},
|
|
||||||
"RHEL-6.2": {
|
|
||||||
"32": "rhel62-i386-cfntools",
|
|
||||||
"64": "rhel62-x86_64-cfntools"
|
|
||||||
},
|
|
||||||
"RHEL-6.3": {
|
|
||||||
"32": "rhel63-i386-cfntools",
|
|
||||||
"64": "rhel63-x86_64-cfntools"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"Resources": {
|
|
||||||
"WikiDatabase": {
|
|
||||||
"Type": "AWS::EC2::Instance",
|
|
||||||
"Metadata": {
|
|
||||||
"AWS::CloudFormation::Init": {
|
|
||||||
"config": {
|
|
||||||
"packages": {
|
|
||||||
"yum": {
|
|
||||||
"mysql": [],
|
|
||||||
"mysql-server": [],
|
|
||||||
"httpd": [],
|
|
||||||
"wordpress": []
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"services": {
|
|
||||||
"systemd": {
|
|
||||||
"mysqld": {
|
|
||||||
"enabled": "true",
|
|
||||||
"ensureRunning": "true"
|
|
||||||
},
|
|
||||||
"httpd": {
|
|
||||||
"enabled": "true",
|
|
||||||
"ensureRunning": "true"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"Properties": {
|
|
||||||
"ImageId": {
|
|
||||||
"Fn::FindInMap": [
|
|
||||||
"DistroArch2AMI",
|
|
||||||
{
|
|
||||||
"Ref": "LinuxDistribution"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Fn::FindInMap": [
|
|
||||||
"AWSInstanceType2Arch",
|
|
||||||
{
|
|
||||||
"Ref": "InstanceType"
|
|
||||||
},
|
|
||||||
"Arch"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"InstanceType": {
|
|
||||||
"Ref": "InstanceType"
|
|
||||||
},
|
|
||||||
"KeyName": {
|
|
||||||
"Ref": "KeyName"
|
|
||||||
},
|
|
||||||
"UserData": {
|
|
||||||
"Fn::Base64": {
|
|
||||||
"Fn::Join": [
|
|
||||||
"",
|
|
||||||
[
|
|
||||||
"#!/bin/bash -v\\n",
|
|
||||||
"/opt/aws/bin/cfn-init\\n"
|
|
||||||
]
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"Outputs": {
|
|
||||||
"WebsiteURL": {
|
|
||||||
"Value": {
|
|
||||||
"Fn::Join": [
|
|
||||||
"",
|
|
||||||
[
|
|
||||||
"http://",
|
|
||||||
{
|
|
||||||
"Fn::GetAtt": [
|
|
||||||
"WikiDatabase",
|
|
||||||
"PublicIp"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"/wordpress"
|
|
||||||
]
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"Description": "URL for Wordpress wiki"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
|
|
||||||
VALIDATE = """
|
|
||||||
{
|
|
||||||
"Description": "AWS CloudFormation Sample Template.",
|
|
||||||
"Parameters": {
|
|
||||||
"DBUsername": {
|
|
||||||
"Type": "String",
|
|
||||||
"Description": "The WordPress database admin account username",
|
|
||||||
"Default": "admin",
|
|
||||||
"MinLength": "1",
|
|
||||||
"AllowedPattern": "[a-zA-Z][a-zA-Z0-9]*",
|
|
||||||
"NoEcho": "true",
|
|
||||||
"MaxLength": "16",
|
|
||||||
"ConstraintDescription": "must begin with a letter and..."
|
|
||||||
},
|
|
||||||
"LinuxDistribution": {
|
|
||||||
"Default": "F17",
|
|
||||||
"Type": "String",
|
|
||||||
"Description": "Distribution of choice",
|
|
||||||
"AllowedValues": [
|
|
||||||
"F18",
|
|
||||||
"F17",
|
|
||||||
"U10",
|
|
||||||
"RHEL-6.1",
|
|
||||||
"RHEL-6.2",
|
|
||||||
"RHEL-6.3"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"DBRootPassword": {
|
|
||||||
"Type": "String",
|
|
||||||
"Description": "Root password for MySQL",
|
|
||||||
"Default": "admin",
|
|
||||||
"MinLength": "1",
|
|
||||||
"AllowedPattern": "[a-zA-Z0-9]*",
|
|
||||||
"NoEcho": "true",
|
|
||||||
"MaxLength": "41",
|
|
||||||
"ConstraintDescription": "must contain only alphanumeric characters."
|
|
||||||
},
|
|
||||||
"KeyName": {
|
|
||||||
"Type": "String",
|
|
||||||
"Description": "Name of an EC2 Key Pair to enable SSH access to the instances"
|
|
||||||
},
|
|
||||||
"DBName": {
|
|
||||||
"Type": "String",
|
|
||||||
"Description": "The WordPress database name",
|
|
||||||
"Default": "wordpress",
|
|
||||||
"MinLength": "1",
|
|
||||||
"AllowedPattern": "[a-zA-Z][a-zA-Z0-9]*",
|
|
||||||
"MaxLength": "64",
|
|
||||||
"ConstraintDescription": "must begin with a letter and..."
|
|
||||||
},
|
|
||||||
"DBPassword": {
|
|
||||||
"Type": "String",
|
|
||||||
"Description": "The WordPress database admin account password",
|
|
||||||
"Default": "admin",
|
|
||||||
"MinLength": "1",
|
|
||||||
"AllowedPattern": "[a-zA-Z0-9]*",
|
|
||||||
"NoEcho": "true",
|
|
||||||
"MaxLength": "41",
|
|
||||||
"ConstraintDescription": "must contain only alphanumeric characters."
|
|
||||||
},
|
|
||||||
"InstanceType": {
|
|
||||||
"Default": "m1.small",
|
|
||||||
"Type": "String",
|
|
||||||
"ConstraintDescription": "must be a valid EC2 instance type.",
|
|
||||||
"Description": "WebServer EC2 instance type",
|
|
||||||
"AllowedValues": [
|
|
||||||
"m1.tiny",
|
|
||||||
"m1.small",
|
|
||||||
"m1.medium",
|
|
||||||
"m1.large",
|
|
||||||
"m1.xlarge"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"Network": {
|
|
||||||
"Type": "String",
|
|
||||||
"CustomConstraint": "neutron.network"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
|
|
||||||
ENVIRONMENT = """
|
|
||||||
parameters:
|
|
||||||
InstanceType: m1.xsmall
|
|
||||||
db_password: verybadpass
|
|
||||||
KeyName: heat_key
|
|
||||||
"""
|
|
||||||
|
|
||||||
SNAPSHOT_CREATE = """
|
|
||||||
{
|
|
||||||
"status": "IN_PROGRESS",
|
|
||||||
"name": "None",
|
|
||||||
"data": "None",
|
|
||||||
"creation_time": "2016-02-19T07:25:23.494152",
|
|
||||||
"status_reason": "None",
|
|
||||||
"id": "8af90c07-b788-44ee-a8ab-5990197f5e32"
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class Environment(object):
|
|
||||||
def __init__(self, data):
|
|
||||||
self.data = data
|
|
||||||
|
|
||||||
|
|
||||||
class Template(object):
|
|
||||||
def __init__(self, data, validate):
|
|
||||||
self.data = data
|
|
||||||
self.validate = validate
|
|
||||||
|
|
||||||
|
|
||||||
class Snapshot(object):
|
|
||||||
def __init__(self, data):
|
|
||||||
self.data = data
|
|
||||||
|
|
||||||
|
|
||||||
def data(TEST):
|
|
||||||
TEST.stacks = utils.TestDataContainer()
|
|
||||||
TEST.stack_templates = utils.TestDataContainer()
|
|
||||||
TEST.stack_environments = utils.TestDataContainer()
|
|
||||||
TEST.stack_snapshot_create = utils.TestDataContainer()
|
|
||||||
TEST.stack_snapshot = utils.TestDataContainer()
|
|
||||||
TEST.resource_types = utils.TestDataContainer()
|
|
||||||
TEST.heat_resources = utils.TestDataContainer()
|
|
||||||
TEST.heat_services = utils.TestDataContainer()
|
|
||||||
TEST.template_versions = utils.TestDataContainer()
|
|
||||||
TEST.template_functions = utils.TestDataContainer()
|
|
||||||
|
|
||||||
# Services
|
|
||||||
service_1 = services.Service(services.ServiceManager(None), {
|
|
||||||
"status": "up",
|
|
||||||
"binary": "heat-engine",
|
|
||||||
"report_interval": 60,
|
|
||||||
"engine_id": "2f7b5a9b-c50b-4b01-8248-f89f5fb338d1",
|
|
||||||
"created_at": "2015-02-06T03:23:32.000000",
|
|
||||||
"hostname": "mrkanag",
|
|
||||||
"updated_at": "2015-02-20T09:49:52.000000",
|
|
||||||
"topic": "engine",
|
|
||||||
"host": "engine-1",
|
|
||||||
"deleted_at": None,
|
|
||||||
"id": "1efd7015-5016-4caa-b5c8-12438af7b100"
|
|
||||||
})
|
|
||||||
|
|
||||||
service_2 = services.Service(services.ServiceManager(None), {
|
|
||||||
"status": "up",
|
|
||||||
"binary": "heat-engine",
|
|
||||||
"report_interval": 60,
|
|
||||||
"engine_id": "2f7b5a9b-c50b-4b01-8248-f89f5fb338d2",
|
|
||||||
"created_at": "2015-02-06T03:23:32.000000",
|
|
||||||
"hostname": "mrkanag",
|
|
||||||
"updated_at": "2015-02-20T09:49:52.000000",
|
|
||||||
"topic": "engine",
|
|
||||||
"host": "engine-2",
|
|
||||||
"deleted_at": None,
|
|
||||||
"id": "1efd7015-5016-4caa-b5c8-12438af7b100"
|
|
||||||
})
|
|
||||||
|
|
||||||
TEST.heat_services.add(service_1)
|
|
||||||
TEST.heat_services.add(service_2)
|
|
||||||
|
|
||||||
# Data return by heatclient.
|
|
||||||
TEST.api_resource_types = utils.TestDataContainer()
|
|
||||||
|
|
||||||
for i in range(10):
|
|
||||||
stack_data = {
|
|
||||||
"description": "No description",
|
|
||||||
"links": [{
|
|
||||||
"href": "http://192.168.1.70:8004/v1/"
|
|
||||||
"051c727ee67040d6a7b7812708485a97/"
|
|
||||||
"stacks/stack-test{0}/"
|
|
||||||
"05b4f39f-ea96-4d91-910c-e758c078a089{0}".format(i),
|
|
||||||
"rel": "self"
|
|
||||||
}],
|
|
||||||
"parameters": {
|
|
||||||
'DBUsername': '******',
|
|
||||||
'InstanceType': 'm1.small',
|
|
||||||
'AWS::StackId': (
|
|
||||||
'arn:openstack:heat::2ce287:stacks/teststack/88553ec'),
|
|
||||||
'DBRootPassword': '******',
|
|
||||||
'AWS::StackName': "teststack{0}".format(i),
|
|
||||||
'DBPassword': '******',
|
|
||||||
'AWS::Region': 'ap-southeast-1',
|
|
||||||
'DBName': u'wordpress'
|
|
||||||
},
|
|
||||||
"stack_status_reason": "Stack successfully created",
|
|
||||||
"stack_name": "stack-test{0}".format(i),
|
|
||||||
"creation_time": "2013-04-22T00:11:39Z",
|
|
||||||
"updated_time": "2013-04-22T00:11:39Z",
|
|
||||||
"stack_status": "CREATE_COMPLETE",
|
|
||||||
"id": "05b4f39f-ea96-4d91-910c-e758c078a089{0}".format(i)
|
|
||||||
}
|
|
||||||
stack = stacks.Stack(stacks.StackManager(None), stack_data)
|
|
||||||
TEST.stacks.add(stack)
|
|
||||||
|
|
||||||
for i in range(10):
|
|
||||||
snapshot_data = {
|
|
||||||
"status": "COMPLETE",
|
|
||||||
"name": 'null',
|
|
||||||
"data": {
|
|
||||||
"files": {},
|
|
||||||
"status": "COMPLETE",
|
|
||||||
"name": "zhao3",
|
|
||||||
"tags": ["a", " 123", " b", " 456"],
|
|
||||||
"stack_user_project_id": "3cba4460875444049a2a7cc5420ccddb",
|
|
||||||
"environment": {
|
|
||||||
"encrypted_param_names": [],
|
|
||||||
"parameter_defaults": {},
|
|
||||||
"event_sinks": [],
|
|
||||||
"parameters": {},
|
|
||||||
"resource_registry": {
|
|
||||||
"resources": {}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"template": {
|
|
||||||
"heat_template_version": "2013-05-23",
|
|
||||||
"description":
|
|
||||||
"HOT template for Test.",
|
|
||||||
"resources": {
|
|
||||||
"private_subnet": {
|
|
||||||
"type": "OS::Neutron::Subnet",
|
|
||||||
"properties": {
|
|
||||||
"network_id": {"get_resource": "private_net"},
|
|
||||||
"cidr": "172.16.2.0/24",
|
|
||||||
"gateway_ip": "172.16.2.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"private_net": {
|
|
||||||
"type": "OS::Neutron::Net",
|
|
||||||
"properties": {"name": "private-net"}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"action": "SNAPSHOT",
|
|
||||||
"project_id": "1acd0026829f4d28bb2eff912d7aad0d",
|
|
||||||
"id": "70650725-bdbd-419f-b53f-5707767bfe0e",
|
|
||||||
"resources": {
|
|
||||||
"private_subnet": {
|
|
||||||
"status": "COMPLETE",
|
|
||||||
"name": "private_subnet",
|
|
||||||
"resource_data": {},
|
|
||||||
"resource_id": "9c7211b3-31c7-41f6-b92a-442ad3f71ef0",
|
|
||||||
"action": "SNAPSHOT",
|
|
||||||
"type": "OS::Neutron::Subnet",
|
|
||||||
"metadata": {}
|
|
||||||
},
|
|
||||||
"private_net": {
|
|
||||||
"status": "COMPLETE",
|
|
||||||
"name": "private_net",
|
|
||||||
"resource_data": {},
|
|
||||||
"resource_id": "ff4fd287-31b2-4d00-bc96-c409bc1db027",
|
|
||||||
"action": "SNAPSHOT",
|
|
||||||
"type": "OS::Neutron::Net",
|
|
||||||
"metadata": {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
"creation_time": "2016-02-21T04:02:54",
|
|
||||||
"status_reason": "Stack SNAPSHOT completed successfully",
|
|
||||||
"id": "01558a3b-ba05-4427-bbb4-1e4ab71cfca{0}".format(i)
|
|
||||||
}
|
|
||||||
TEST.stack_snapshot.add(snapshot_data)
|
|
||||||
|
|
||||||
TEST.stack_templates.add(Template(TEMPLATE, VALIDATE))
|
|
||||||
TEST.stack_environments.add(Environment(ENVIRONMENT))
|
|
||||||
TEST.stack_snapshot_create.add(Snapshot(SNAPSHOT_CREATE))
|
|
||||||
|
|
||||||
# Resource types list
|
|
||||||
r_type_1 = {
|
|
||||||
"resource_type": "AWS::CloudFormation::Stack",
|
|
||||||
"attributes": {},
|
|
||||||
"properties": {
|
|
||||||
"Parameters": {
|
|
||||||
"description":
|
|
||||||
"The set of parameters passed to this nested stack.",
|
|
||||||
"immutable": False,
|
|
||||||
"required": False,
|
|
||||||
"type": "map",
|
|
||||||
"update_allowed": True},
|
|
||||||
"TemplateURL": {
|
|
||||||
"description": "The URL of a template that specifies"
|
|
||||||
" the stack to be created as a resource.",
|
|
||||||
"immutable": False,
|
|
||||||
"required": True,
|
|
||||||
"type": "string",
|
|
||||||
"update_allowed": True},
|
|
||||||
"TimeoutInMinutes": {
|
|
||||||
"description": "The length of time, in minutes,"
|
|
||||||
" to wait for the nested stack creation.",
|
|
||||||
"immutable": False,
|
|
||||||
"required": False,
|
|
||||||
"type": "number",
|
|
||||||
"update_allowed": True}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
r_type_2 = {
|
|
||||||
"resource_type": "OS::Heat::CloudConfig",
|
|
||||||
"attributes": {
|
|
||||||
"config": {
|
|
||||||
"description": "The config value of the software config."}
|
|
||||||
},
|
|
||||||
"properties": {
|
|
||||||
"cloud_config": {
|
|
||||||
"description": "Map representing the cloud-config data"
|
|
||||||
" structure which will be formatted as YAML.",
|
|
||||||
"immutable": False,
|
|
||||||
"required": False,
|
|
||||||
"type": "map",
|
|
||||||
"update_allowed": False}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
r_types_list = [r_type_1, r_type_2]
|
|
||||||
|
|
||||||
for rt in r_types_list:
|
|
||||||
r_type = resource_types.ResourceType(
|
|
||||||
resource_types.ResourceTypeManager(None), rt['resource_type'])
|
|
||||||
TEST.resource_types.add(r_type)
|
|
||||||
TEST.api_resource_types.add(rt)
|
|
||||||
|
|
||||||
# Resources
|
|
||||||
resource_1 = resources.Resource(resources.ResourceManager(None), {
|
|
||||||
"logical_resource_id": "my_resource",
|
|
||||||
"physical_resource_id": "7b5e29b1-c94d-402d-b69c-df9ac6dfc0ce",
|
|
||||||
"resource_name": "my_resource",
|
|
||||||
"links": [
|
|
||||||
{
|
|
||||||
"href": "http://192.168.1.70:8004/v1/"
|
|
||||||
"051c727ee67040d6a7b7812708485a97/"
|
|
||||||
"stacks/%s/%s/resources/my_resource" %
|
|
||||||
(TEST.stacks.first().stack_name,
|
|
||||||
TEST.stacks.first().id),
|
|
||||||
"rel": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"href": "http://192.168.1.70:8004/v1/"
|
|
||||||
"051c727ee67040d6a7b7812708485a97/"
|
|
||||||
"stacks/%s/%s" %
|
|
||||||
(TEST.stacks.first().stack_name,
|
|
||||||
TEST.stacks.first().id),
|
|
||||||
"rel": "stack"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"attributes": {
|
|
||||||
"metadata": {}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
TEST.heat_resources.add(resource_1)
|
|
||||||
|
|
||||||
# Template versions
|
|
||||||
template_version_1 = template_versions.TemplateVersion(
|
|
||||||
template_versions.TemplateVersionManager(None), {
|
|
||||||
"version": "HeatTemplateFormatVersion.2012-12-12",
|
|
||||||
"type": "cfn"
|
|
||||||
})
|
|
||||||
|
|
||||||
template_version_2 = template_versions.TemplateVersion(
|
|
||||||
template_versions.TemplateVersionManager(None), {
|
|
||||||
"version": "heat_template_version.2013-05-23",
|
|
||||||
"type": "hot"
|
|
||||||
})
|
|
||||||
|
|
||||||
TEST.template_versions.add(template_version_1)
|
|
||||||
TEST.template_versions.add(template_version_2)
|
|
||||||
|
|
||||||
# Template functions
|
|
||||||
template_function_1 = template_versions.TemplateVersion(
|
|
||||||
template_versions.TemplateVersionManager(None), {
|
|
||||||
"functions": "Fn::GetAZs",
|
|
||||||
"description": "A function for retrieving the availability zones."
|
|
||||||
})
|
|
||||||
|
|
||||||
template_function_2 = template_versions.TemplateVersion(
|
|
||||||
template_versions.TemplateVersionManager(None), {
|
|
||||||
"functions": "Fn::Join",
|
|
||||||
"description": "A function for joining strings."
|
|
||||||
})
|
|
||||||
|
|
||||||
TEST.template_functions.add(template_function_1)
|
|
||||||
TEST.template_functions.add(template_function_2)
|
|
@ -103,14 +103,6 @@ SERVICE_CATALOG = [
|
|||||||
"adminURL": "http://admin.nova.example.com:8773/services/Admin",
|
"adminURL": "http://admin.nova.example.com:8773/services/Admin",
|
||||||
"publicURL": "http://public.nova.example.com:8773/services/Cloud",
|
"publicURL": "http://public.nova.example.com:8773/services/Cloud",
|
||||||
"internalURL": "http://int.nova.example.com:8773/services/Cloud"}]},
|
"internalURL": "http://int.nova.example.com:8773/services/Cloud"}]},
|
||||||
{"type": "orchestration",
|
|
||||||
"name": "Heat",
|
|
||||||
"endpoints_links": [],
|
|
||||||
"endpoints": [
|
|
||||||
{"region": "RegionOne",
|
|
||||||
"adminURL": "http://admin.heat.example.com:8004/v1",
|
|
||||||
"publicURL": "http://public.heat.example.com:8004/v1",
|
|
||||||
"internalURL": "http://int.heat.example.com:8004/v1"}]}
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -17,7 +17,6 @@ def load_test_data(load_onto=None):
|
|||||||
from openstack_dashboard.test.test_data import cinder_data
|
from openstack_dashboard.test.test_data import cinder_data
|
||||||
from openstack_dashboard.test.test_data import exceptions
|
from openstack_dashboard.test.test_data import exceptions
|
||||||
from openstack_dashboard.test.test_data import glance_data
|
from openstack_dashboard.test.test_data import glance_data
|
||||||
from openstack_dashboard.test.test_data import heat_data
|
|
||||||
from openstack_dashboard.test.test_data import keystone_data
|
from openstack_dashboard.test.test_data import keystone_data
|
||||||
from openstack_dashboard.test.test_data import neutron_data
|
from openstack_dashboard.test.test_data import neutron_data
|
||||||
from openstack_dashboard.test.test_data import nova_data
|
from openstack_dashboard.test.test_data import nova_data
|
||||||
@ -32,7 +31,6 @@ def load_test_data(load_onto=None):
|
|||||||
cinder_data.data,
|
cinder_data.data,
|
||||||
neutron_data.data,
|
neutron_data.data,
|
||||||
swift_data.data,
|
swift_data.data,
|
||||||
heat_data.data,
|
|
||||||
)
|
)
|
||||||
if load_onto:
|
if load_onto:
|
||||||
for data_func in loaders:
|
for data_func in loaders:
|
||||||
|
11
releasenotes/notes/heat-panel-splitout-b609b157aa4bf29b.yaml
Normal file
11
releasenotes/notes/heat-panel-splitout-b609b157aa4bf29b.yaml
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
---
|
||||||
|
upgrade:
|
||||||
|
- |
|
||||||
|
Heat dashboard is now split out into a separate project
|
||||||
|
``heat-dashboard``. All new features and maintenances are
|
||||||
|
provided from the new project from now on. The new project provides
|
||||||
|
all features available in Horizon in the past release.
|
||||||
|
To continue to use heat dashboard, install ``heat-dashboard``
|
||||||
|
and set up the horizon plugin configuration file in ``enabled`` directory.
|
||||||
|
For more information, see ``heat-dashboard`` documentation
|
||||||
|
https://docs.openstack.org/heat-dashboard/latest/.
|
@ -30,7 +30,6 @@ pymongo!=3.1,>=3.0.2 # Apache-2.0
|
|||||||
pyScss!=1.3.5,>=1.3.4 # MIT License
|
pyScss!=1.3.5,>=1.3.4 # MIT License
|
||||||
python-cinderclient>=3.2.0 # Apache-2.0
|
python-cinderclient>=3.2.0 # Apache-2.0
|
||||||
python-glanceclient>=2.8.0 # Apache-2.0
|
python-glanceclient>=2.8.0 # Apache-2.0
|
||||||
python-heatclient>=1.10.0 # Apache-2.0
|
|
||||||
python-keystoneclient>=3.8.0 # Apache-2.0
|
python-keystoneclient>=3.8.0 # Apache-2.0
|
||||||
python-neutronclient>=6.3.0 # Apache-2.0
|
python-neutronclient>=6.3.0 # Apache-2.0
|
||||||
python-novaclient>=9.1.0 # Apache-2.0
|
python-novaclient>=9.1.0 # Apache-2.0
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# This file contains various customized Devstack settings that Horizon uses at
|
# This file contains various customized Devstack settings that Horizon uses at
|
||||||
# gate for integration tests job
|
# gate for integration tests job
|
||||||
|
|
||||||
export ENABLED_SERVICES=heat,h-eng,h-api,h-api-cfn,h-api-cw
|
|
||||||
|
Loading…
Reference in New Issue
Block a user