summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/source/admin/manage-services.rst5
-rw-r--r--doc/source/configuration/customizing.rst1
-rw-r--r--doc/source/configuration/settings.rst24
-rw-r--r--doc/source/contributor/quickstart.rst2
-rw-r--r--doc/source/contributor/ref/local_conf.rst3
-rw-r--r--doc/source/install/from-source.rst1
-rw-r--r--doc/source/user/index.rst1
-rw-r--r--doc/source/user/log-in.rst17
-rw-r--r--doc/source/user/stacks.rst149
-rw-r--r--openstack_dashboard/api/__init__.py2
-rw-r--r--openstack_dashboard/api/heat.py265
-rw-r--r--openstack_dashboard/api/rest/__init__.py2
-rw-r--r--openstack_dashboard/api/rest/heat.py51
-rw-r--r--openstack_dashboard/conf/heat_policy.json92
-rw-r--r--openstack_dashboard/dashboards/admin/dashboard.py3
-rw-r--r--openstack_dashboard/dashboards/admin/info/panel.py3
-rw-r--r--openstack_dashboard/dashboards/admin/info/tables.py42
-rw-r--r--openstack_dashboard/dashboards/admin/info/tabs.py27
-rw-r--r--openstack_dashboard/dashboards/admin/info/tests.py17
-rw-r--r--openstack_dashboard/dashboards/project/stacks/__init__.py0
-rw-r--r--openstack_dashboard/dashboards/project/stacks/api.py83
-rw-r--r--openstack_dashboard/dashboards/project/stacks/forms.py488
-rw-r--r--openstack_dashboard/dashboards/project/stacks/mappings.py350
-rw-r--r--openstack_dashboard/dashboards/project/stacks/panel.py21
-rw-r--r--openstack_dashboard/dashboards/project/stacks/resource_types/__init__.py0
-rw-r--r--openstack_dashboard/dashboards/project/stacks/resource_types/panel.py23
-rw-r--r--openstack_dashboard/dashboards/project/stacks/resource_types/tables.py36
-rw-r--r--openstack_dashboard/dashboards/project/stacks/resource_types/tabs.py32
-rw-r--r--openstack_dashboard/dashboards/project/stacks/resource_types/templates/stacks.resource_types/_details.html15
-rw-r--r--openstack_dashboard/dashboards/project/stacks/resource_types/tests.py52
-rw-r--r--openstack_dashboard/dashboards/project/stacks/resource_types/urls.py22
-rw-r--r--openstack_dashboard/dashboards/project/stacks/resource_types/views.py78
-rw-r--r--openstack_dashboard/dashboards/project/stacks/sro.py44
-rw-r--r--openstack_dashboard/dashboards/project/stacks/tables.py413
-rw-r--r--openstack_dashboard/dashboards/project/stacks/tabs.py173
-rw-r--r--openstack_dashboard/dashboards/project/stacks/template_versions/__init__.py0
-rw-r--r--openstack_dashboard/dashboards/project/stacks/template_versions/panel.py23
-rw-r--r--openstack_dashboard/dashboards/project/stacks/template_versions/tables.py52
-rw-r--r--openstack_dashboard/dashboards/project/stacks/template_versions/tabs.py51
-rw-r--r--openstack_dashboard/dashboards/project/stacks/template_versions/templates/stacks.template_versions/_details.html3
-rw-r--r--openstack_dashboard/dashboards/project/stacks/template_versions/templates/stacks.template_versions/index.html7
-rw-r--r--openstack_dashboard/dashboards/project/stacks/template_versions/tests.py79
-rw-r--r--openstack_dashboard/dashboards/project/stacks/template_versions/urls.py24
-rw-r--r--openstack_dashboard/dashboards/project/stacks/template_versions/views.py61
-rw-r--r--openstack_dashboard/dashboards/project/stacks/templates/stacks/_change_template.html7
-rw-r--r--openstack_dashboard/dashboards/project/stacks/templates/stacks/_create.html6
-rw-r--r--openstack_dashboard/dashboards/project/stacks/templates/stacks/_detail_events.html3
-rw-r--r--openstack_dashboard/dashboards/project/stacks/templates/stacks/_detail_overview.html55
-rw-r--r--openstack_dashboard/dashboards/project/stacks/templates/stacks/_detail_resources.html3
-rw-r--r--openstack_dashboard/dashboards/project/stacks/templates/stacks/_detail_topology.html9
-rw-r--r--openstack_dashboard/dashboards/project/stacks/templates/stacks/_preview.html6
-rw-r--r--openstack_dashboard/dashboards/project/stacks/templates/stacks/_preview_details.html58
-rw-r--r--openstack_dashboard/dashboards/project/stacks/templates/stacks/_preview_template.html7
-rw-r--r--openstack_dashboard/dashboards/project/stacks/templates/stacks/_resource_info.html10
-rw-r--r--openstack_dashboard/dashboards/project/stacks/templates/stacks/_resource_overview.html38
-rw-r--r--openstack_dashboard/dashboards/project/stacks/templates/stacks/_select_template.html7
-rw-r--r--openstack_dashboard/dashboards/project/stacks/templates/stacks/_stack_info.html14
-rw-r--r--openstack_dashboard/dashboards/project/stacks/templates/stacks/_stack_template.html5
-rw-r--r--openstack_dashboard/dashboards/project/stacks/templates/stacks/_update.html6
-rw-r--r--openstack_dashboard/dashboards/project/stacks/templates/stacks/change_template.html7
-rw-r--r--openstack_dashboard/dashboards/project/stacks/templates/stacks/create.html7
-rw-r--r--openstack_dashboard/dashboards/project/stacks/templates/stacks/preview.html7
-rw-r--r--openstack_dashboard/dashboards/project/stacks/templates/stacks/preview_details.html7
-rw-r--r--openstack_dashboard/dashboards/project/stacks/templates/stacks/preview_template.html7
-rw-r--r--openstack_dashboard/dashboards/project/stacks/templates/stacks/select_template.html7
-rw-r--r--openstack_dashboard/dashboards/project/stacks/templates/stacks/update.html7
-rw-r--r--openstack_dashboard/dashboards/project/stacks/tests.py1003
-rw-r--r--openstack_dashboard/dashboards/project/stacks/urls.py38
-rw-r--r--openstack_dashboard/dashboards/project/stacks/views.py358
-rw-r--r--openstack_dashboard/enabled/_1610_orchestration_panel_group.py8
-rw-r--r--openstack_dashboard/enabled/_1620_project_stacks_panel.py9
-rw-r--r--openstack_dashboard/enabled/_1630_project_resource_types_panel.py10
-rw-r--r--openstack_dashboard/enabled/_1640_project_template_versions_panel.py10
-rw-r--r--openstack_dashboard/exceptions.py5
-rw-r--r--openstack_dashboard/local/local_settings.py.example6
-rw-r--r--openstack_dashboard/settings.py1
-rw-r--r--openstack_dashboard/static/app/core/openstack-service-api/heat.service.js80
-rw-r--r--openstack_dashboard/static/app/core/openstack-service-api/heat.service.spec.js80
-rw-r--r--openstack_dashboard/test/api_tests/heat_rest_tests.py69
-rw-r--r--openstack_dashboard/test/api_tests/heat_tests.py358
-rw-r--r--openstack_dashboard/test/helpers.py11
-rw-r--r--openstack_dashboard/test/integration_tests/config.py2
-rw-r--r--openstack_dashboard/test/integration_tests/horizon.conf2
-rw-r--r--openstack_dashboard/test/integration_tests/pages/navigation.py7
-rw-r--r--openstack_dashboard/test/integration_tests/pages/project/orchestration/__init__.py0
-rw-r--r--openstack_dashboard/test/integration_tests/pages/project/orchestration/stackspage.py99
-rw-r--r--openstack_dashboard/test/integration_tests/tests/test_stacks.py73
-rw-r--r--openstack_dashboard/test/test_data/exceptions.py4
-rw-r--r--openstack_dashboard/test/test_data/heat_data.py617
-rw-r--r--openstack_dashboard/test/test_data/keystone_data.py8
-rw-r--r--openstack_dashboard/test/test_data/utils.py2
-rw-r--r--releasenotes/notes/heat-panel-splitout-b609b157aa4bf29b.yaml11
-rw-r--r--requirements.txt1
-rw-r--r--tools/gate/integration/devstack_gate_rc2
94 files changed, 17 insertions, 5977 deletions
diff --git a/doc/source/admin/manage-services.rst b/doc/source/admin/manage-services.rst
index 7afaae7..d611911 100644
--- a/doc/source/admin/manage-services.rst
+++ b/doc/source/admin/manage-services.rst
@@ -30,8 +30,3 @@ As an administrative user, you can view information for OpenStack services.
30 * :guilabel:`Network Agents`: 30 * :guilabel:`Network Agents`:
31 Displays the network agents active within the cluster, such as L3 and 31 Displays the network agents active within the cluster, such as L3 and
32 DHCP agents, and the status of each agent. 32 DHCP agents, and the status of each agent.
33
34 * :guilabel:`Orchestration Services`:
35 Displays information specific to the Orchestration service. Name,
36 engine id, host and topic are listed for each service, as well as its
37 activation status.
diff --git a/doc/source/configuration/customizing.rst b/doc/source/configuration/customizing.rst
index 89ff916..bff71f3 100644
--- a/doc/source/configuration/customizing.rst
+++ b/doc/source/configuration/customizing.rst
@@ -116,7 +116,6 @@ You can also override existing methods with your own versions::
116 116
117 NO = lambda *x: False 117 NO = lambda *x: False
118 118
119 tabs.HeatServiceTab.allowed = NO
120 tables.AssociateIP.allowed = NO 119 tables.AssociateIP.allowed = NO
121 tables.SimpleAssociateIP.allowed = NO 120 tables.SimpleAssociateIP.allowed = NO
122 tables.SimpleDisassociateIP.allowed = NO 121 tables.SimpleDisassociateIP.allowed = NO
diff --git a/doc/source/configuration/settings.rst b/doc/source/configuration/settings.rst
index e01f30e..791cc11 100644
--- a/doc/source/configuration/settings.rst
+++ b/doc/source/configuration/settings.rst
@@ -804,7 +804,6 @@ Default:
804 'compute': 'nova_policy.json', 804 'compute': 'nova_policy.json',
805 'volume': 'cinder_policy.json', 805 'volume': 'cinder_policy.json',
806 'image': 'glance_policy.json', 806 'image': 'glance_policy.json',
807 'orchestration': 'heat_policy.json',
808 'network': 'neutron_policy.json', 807 'network': 'neutron_policy.json',
809 } 808 }
810 809
@@ -1125,29 +1124,6 @@ Default:
1125Used to customize features related to the image service, such as the list of 1124Used to customize features related to the image service, such as the list of
1126supported image formats. 1125supported image formats.
1127 1126
1128Heat
1129----
1130
1131OPENSTACK_HEAT_STACK
1132~~~~~~~~~~~~~~~~~~~~
1133
1134.. versionadded:: 9.0.0(Mitaka)
1135
1136Default:
1137
1138.. code-block:: python
1139
1140 {
1141 'enable_user_pass': True
1142 }
1143
1144A dictionary of settings to use with heat stacks. Currently, the only setting
1145available is "enable_user_pass", which can be used to disable the password
1146field while launching the stack. Currently HEAT API needs user password to
1147perform all the heat operations because in HEAT API trusts is not enabled by
1148default. So, this setting can be set as "False" in-case HEAT uses trusts by
1149default otherwise it needs to be set as "True".
1150
1151Keystone 1127Keystone
1152-------- 1128--------
1153 1129
diff --git a/doc/source/contributor/quickstart.rst b/doc/source/contributor/quickstart.rst
index b464a9e..e903139 100644
--- a/doc/source/contributor/quickstart.rst
+++ b/doc/source/contributor/quickstart.rst
@@ -83,7 +83,7 @@ To start the Horizon development server use the command below
83 83
84.. note:: 84.. note::
85 85
86 The default port for runserver is 8000 which is already consumed by 86 The default port for runserver is 8000 which might be already consumed by
87 heat-api-cfn in DevStack. If running in DevStack 87 heat-api-cfn in DevStack. If running in DevStack
88 ``tox -e runserver -- localhost:9000`` will start the test server at 88 ``tox -e runserver -- localhost:9000`` will start the test server at
89 ``http://localhost:9000`` 89 ``http://localhost:9000``
diff --git a/doc/source/contributor/ref/local_conf.rst b/doc/source/contributor/ref/local_conf.rst
index 813a51f..7066712 100644
--- a/doc/source/contributor/ref/local_conf.rst
+++ b/doc/source/contributor/ref/local_conf.rst
@@ -69,9 +69,6 @@ see https://docs.openstack.org/devstack/latest/
69 SWIFT_REPLICAS=1 69 SWIFT_REPLICAS=1
70 SWIFT_DATA_DIR=$DEST/data/swift 70 SWIFT_DATA_DIR=$DEST/data/swift
71 71
72 # Enable Heat
73 enable_plugin heat https://git.openstack.org/openstack/heat
74
75 # Enable Neutron 72 # Enable Neutron
76 enable_plugin neutron https://git.openstack.org/openstack/neutron 73 enable_plugin neutron https://git.openstack.org/openstack/neutron
77 74
diff --git a/doc/source/install/from-source.rst b/doc/source/install/from-source.rst
index d34492c..c0ca1e1 100644
--- a/doc/source/install/from-source.rst
+++ b/doc/source/install/from-source.rst
@@ -22,7 +22,6 @@ System Requirements
22 22
23 * `cinder <https://docs.openstack.org/cinder/latest/>`_: Block Storage 23 * `cinder <https://docs.openstack.org/cinder/latest/>`_: Block Storage
24 * `glance <https://docs.openstack.org/glance/latest/>`_: Image Management 24 * `glance <https://docs.openstack.org/glance/latest/>`_: Image Management
25 * `heat <https://docs.openstack.org/heat/latest/>`_: Orchestration
26 * `neutron <https://docs.openstack.org/neutron/latest/>`_: Networking 25 * `neutron <https://docs.openstack.org/neutron/latest/>`_: Networking
27 * `nova <https://docs.openstack.org/nova/latest/>`_: Compute 26 * `nova <https://docs.openstack.org/nova/latest/>`_: Compute
28 * `swift <https://docs.openstack.org/swift/latest/>`_: Object Storage 27 * `swift <https://docs.openstack.org/swift/latest/>`_: Object Storage
diff --git a/doc/source/user/index.rst b/doc/source/user/index.rst
index cde57d3..0112c3e 100644
--- a/doc/source/user/index.rst
+++ b/doc/source/user/index.rst
@@ -18,7 +18,6 @@ sizes of server instances.
18 manage-containers.rst 18 manage-containers.rst
19 manage-volumes.rst 19 manage-volumes.rst
20 manage-shares.rst 20 manage-shares.rst
21 stacks.rst
22 databases.rst 21 databases.rst
23 manage-lbaasv2.rst 22 manage-lbaasv2.rst
24 browser_support 23 browser_support
diff --git a/doc/source/user/log-in.rst b/doc/source/user/log-in.rst
index 0f80d6e..25b560f 100644
--- a/doc/source/user/log-in.rst
+++ b/doc/source/user/log-in.rst
@@ -50,11 +50,6 @@ The dashboard is generally installed on the controller node.
50 (:ref:`dashboard-admin-tab`) and :guilabel:`Identity` tab 50 (:ref:`dashboard-admin-tab`) and :guilabel:`Identity` tab
51 (:ref:`dashboard-identity-tab`) are displayed. 51 (:ref:`dashboard-identity-tab`) are displayed.
52 52
53.. note::
54
55 Some tabs, such as :guilabel:`Orchestration` and :guilabel:`Firewalls`,
56 only appear on the dashboard if they are properly configured.
57
58.. _dashboard-project-tab: 53.. _dashboard-project-tab:
59 54
60OpenStack dashboard — Project tab 55OpenStack dashboard — Project tab
@@ -143,15 +138,6 @@ Network tab
143 138
144 * :guilabel:`Firewall Rules`: Add and manage firewall rules. 139 * :guilabel:`Firewall Rules`: Add and manage firewall rules.
145 140
146Orchestration tab
147-----------------
148
149* :guilabel:`Stacks`: Use the REST API to orchestrate multiple composite
150 cloud applications.
151
152* :guilabel:`Resource Types`: Show a list of all the supported resource
153 types for HOT templates.
154
155Object Store tab 141Object Store tab
156---------------- 142----------------
157 143
@@ -234,9 +220,6 @@ System tab
234 220
235 * :guilabel:`Network Agents`: View the network agents. 221 * :guilabel:`Network Agents`: View the network agents.
236 222
237 * :guilabel:`Orchestration Services`: View a list of all Orchestration
238 services.
239
240* :guilabel:`Shares`: Use the following tabs to complete these tasks: 223* :guilabel:`Shares`: Use the following tabs to complete these tasks:
241 224
242 * :guilabel:`Shares`: View, create, manage, and delete shares. 225 * :guilabel:`Shares`: View, create, manage, and delete shares.
diff --git a/doc/source/user/stacks.rst b/doc/source/user/stacks.rst
deleted file mode 100644
index 7767976..0000000
--- a/doc/source/user/stacks.rst
+++ /dev/null
@@ -1,149 +0,0 @@
1========================
2Launch and manage stacks
3========================
4
5OpenStack Orchestration is a service that you can use to
6orchestrate multiple composite cloud applications. This
7service supports the use of both the Amazon Web Services (AWS)
8CloudFormation template format through both a Query API that
9is compatible with CloudFormation and the native OpenStack
10Heat Orchestration Template (HOT) format through a REST API.
11
12These flexible template languages enable application
13developers to describe and automate the deployment of
14infrastructure, services, and applications. The templates
15enable creation of most OpenStack resource types, such as
16instances, floating IP addresses, volumes, security groups,
17and users. Once created, the resources are referred to as
18stacks.
19
20The template languages are described in the `Template Guide
21<https://docs.openstack.org/heat/latest/template_guide/>`_.
22
23Launch a stack
24~~~~~~~~~~~~~~
25
26#. Log in to the dashboard.
27#. Select the appropriate project from the drop down menu at the top left.
28#. On the :guilabel:`Project` tab, open the :guilabel:`Orchestration` tab and
29 click :guilabel:`Stacks` category.
30#. Click :guilabel:`Launch Stack`.
31#. In the :guilabel:`Select Template` dialog box, specify the
32 following values:
33
34 +---------------------------------------+-------------------------------+
35 | :guilabel:`Template Source` | Choose the source of the |
36 | | template from the list. |
37 +---------------------------------------+-------------------------------+
38 | :guilabel:`Template URL/File/Data` | Depending on the source that |
39 | | you select, enter the URL, |
40 | | browse to the file location, |
41 | | or directly include the |
42 | | template. |
43 +---------------------------------------+-------------------------------+
44 | :guilabel:`Environment Source` | Choose the source of the |
45 | | environment from the list. |
46 | | The environment files contain |
47 | | additional settings for the |
48 | | stack. |
49 +---------------------------------------+-------------------------------+
50 | :guilabel:`Environment File/Data` | Depending on the source that |
51 | | you select, browse to the |
52 | | file location, directly |
53 | | include the environment |
54 +---------------------------------------+-------------------------------+
55
56#. Click :guilabel:`Next`.
57#. In the :guilabel:`Launch Stack` dialog box, specify the
58 following values:
59
60 +---------------------------------+---------------------------------+
61 | :guilabel:`Stack Name` | Enter a name to identify |
62 | | the stack. |
63 +---------------------------------+---------------------------------+
64 | :guilabel:`Creation Timeout` | Specify the number of minutes |
65 | :guilabel:`(minutes)` | that can elapse before the |
66 | | launch of the stack times out. |
67 +---------------------------------+---------------------------------+
68 | :guilabel:`Rollback On Failure` | Select this check box if you |
69 | | want the service to roll back |
70 | | changes if the stack fails to |
71 | | launch. |
72 +---------------------------------+---------------------------------+
73 | :guilabel:`Password for user` | Specify the password that |
74 | :guilabel:`"demo"` | the default user uses when the |
75 | | stack is created. |
76 +---------------------------------+---------------------------------+
77 | :guilabel:`DBUsername` | Specify the name of the |
78 | | database user. |
79 +---------------------------------+---------------------------------+
80 | :guilabel:`LinuxDistribution` | Specify the Linux distribution |
81 | | that is used in the stack. |
82 +---------------------------------+---------------------------------+
83 | :guilabel:`DBRootPassword` | Specify the root password for |
84 | | the database. |
85 +---------------------------------+---------------------------------+
86 | :guilabel:`KeyName` | Specify the name of the key pair|
87 | | to use to log in to the stack. |
88 +---------------------------------+---------------------------------+
89 | :guilabel:`DBName` | Specify the name of the |
90 | | database. |
91 +---------------------------------+---------------------------------+
92 | :guilabel:`DBPassword` | Specify the password of the |
93 | | database. |
94 +---------------------------------+---------------------------------+
95 | :guilabel:`InstanceType` | Specify the flavor for the |
96 | | instance. |
97 +---------------------------------+---------------------------------+
98
99#. Click :guilabel:`Launch` to create a stack. The :guilabel:`Stacks`
100 tab shows the stack.
101
102After the stack is created, click on the stack name to see the
103following details:
104
105Topology
106 The topology of the stack.
107
108Overview
109 The parameters and details of the stack.
110
111Resources
112 The resources used by the stack.
113
114Events
115 The events related to the stack.
116
117Template
118 The template for the stack.
119
120Manage a stack
121~~~~~~~~~~~~~~
122
123#. Log in to the dashboard.
124#. Select the appropriate project from the drop down menu at the top left.
125#. On the :guilabel:`Project` tab, open the :guilabel:`Orchestration` tab and
126 click :guilabel:`Stacks` category.
127#. Select the stack that you want to update.
128#. Click :guilabel:`Change Stack Template`.
129#. In the :guilabel:`Select Template` dialog box, select the
130 new template source or environment source.
131#. Click :guilabel:`Next`.
132
133 The :guilabel:`Update Stack Parameters` window appears.
134#. Enter new values for any parameters that you want to update.
135#. Click :guilabel:`Update`.
136
137Delete a stack
138~~~~~~~~~~~~~~
139
140When you delete a stack, you cannot undo this action.
141
142#. Log in to the dashboard.
143#. Select the appropriate project from the drop down menu at the top left.
144#. On the :guilabel:`Project` tab, open the :guilabel:`Orchestration` tab and
145 click :guilabel:`Stacks` category.
146#. Select the stack that you want to delete.
147#. Click :guilabel:`Delete Stack`.
148#. In the confirmation dialog box, click :guilabel:`Delete Stack`
149 to confirm the deletion.
diff --git a/openstack_dashboard/api/__init__.py b/openstack_dashboard/api/__init__.py
index 6262470..9483b39 100644
--- a/openstack_dashboard/api/__init__.py
+++ b/openstack_dashboard/api/__init__.py
@@ -34,7 +34,6 @@ Keystone/Nova/Glance/Swift et. al.
34from openstack_dashboard.api import base 34from openstack_dashboard.api import base
35from openstack_dashboard.api import cinder 35from openstack_dashboard.api import cinder
36from openstack_dashboard.api import glance 36from openstack_dashboard.api import glance
37from openstack_dashboard.api import heat
38from openstack_dashboard.api import keystone 37from openstack_dashboard.api import keystone
39from openstack_dashboard.api import network 38from openstack_dashboard.api import network
40from openstack_dashboard.api import neutron 39from openstack_dashboard.api import neutron
@@ -46,7 +45,6 @@ __all__ = [
46 "base", 45 "base",
47 "cinder", 46 "cinder",
48 "glance", 47 "glance",
49 "heat",
50 "keystone", 48 "keystone",
51 "network", 49 "network",
52 "neutron", 50 "neutron",
diff --git a/openstack_dashboard/api/heat.py b/openstack_dashboard/api/heat.py
deleted file mode 100644
index c31c61d..0000000
--- a/openstack_dashboard/api/heat.py
+++ /dev/null
@@ -1,265 +0,0 @@
1# Licensed under the Apache License, Version 2.0 (the "License"); you may
2# not use this file except in compliance with the License. You may obtain
3# a copy of the License at
4#
5# http://www.apache.org/licenses/LICENSE-2.0
6#
7# Unless required by applicable law or agreed to in writing, software
8# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10# License for the specific language governing permissions and limitations
11# under the License.
12
13import contextlib
14
15from django.conf import settings
16from heatclient import client as heat_client
17from heatclient.common import template_format
18from heatclient.common import template_utils
19from heatclient.common import utils as heat_utils
20from oslo_serialization import jsonutils
21import six
22from six.moves.urllib import request
23
24from horizon import exceptions
25from horizon.utils import functions as utils
26from horizon.utils.memoized import memoized
27from openstack_dashboard.api import base
28from openstack_dashboard.contrib.developer.profiler import api as profiler
29
30
31def format_parameters(params):
32 parameters = {}
33 for count, p in enumerate(params, 1):
34 parameters['Parameters.member.%d.ParameterKey' % count] = p
35 parameters['Parameters.member.%d.ParameterValue' % count] = params[p]
36 return parameters
37
38
39@memoized
40def heatclient(request, password=None):
41 api_version = "1"
42 insecure = getattr(settings, 'OPENSTACK_SSL_NO_VERIFY', False)
43 cacert = getattr(settings, 'OPENSTACK_SSL_CACERT', None)
44 endpoint = base.url_for(request, 'orchestration')
45 kwargs = {
46 'token': request.user.token.id,
47 'insecure': insecure,
48 'ca_file': cacert,
49 'username': request.user.username,
50 'password': password
51 # 'timeout': args.timeout,
52 # 'ca_file': args.ca_file,
53 # 'cert_file': args.cert_file,
54 # 'key_file': args.key_file,
55 }
56 client = heat_client.Client(api_version, endpoint, **kwargs)
57 client.format_parameters = format_parameters
58 return client
59
60
61@profiler.trace
62def stacks_list(request, marker=None, sort_dir='desc', sort_key='created_at',
63 paginate=False, filters=None):
64 limit = getattr(settings, 'API_RESULT_LIMIT', 1000)
65 page_size = utils.get_page_size(request)
66
67 if paginate:
68 request_size = page_size + 1
69 else:
70 request_size = limit
71
72 kwargs = {'sort_dir': sort_dir, 'sort_key': sort_key}
73 if marker:
74 kwargs['marker'] = marker
75
76 if filters:
77 kwargs.update(filters)
78 if 'status' in kwargs:
79 kwargs['status'] = kwargs['status'].replace(' ', '_').upper()
80
81 stacks_iter = heatclient(request).stacks.list(limit=request_size,
82 **kwargs)
83
84 has_prev_data = False
85 has_more_data = False
86 stacks = list(stacks_iter)
87
88 if paginate:
89 if len(stacks) > page_size:
90 stacks.pop()
91 has_more_data = True
92 if marker is not None:
93 has_prev_data = True
94 elif sort_dir == 'asc' and marker is not None:
95 has_more_data = True
96 elif marker is not None:
97 has_prev_data = True
98 return (stacks, has_more_data, has_prev_data)
99
100
101def _ignore_if(key, value):
102 if key != 'get_file' and key != 'type':
103 return True
104 if not isinstance(value, six.string_types):
105 return True
106 if (key == 'type' and
107 not value.endswith(('.yaml', '.template'))):
108 return True
109 return False
110
111
112@profiler.trace
113def get_template_files(template_data=None, template_url=None):
114 if template_data:
115 tpl = template_data
116 elif template_url:
117 with contextlib.closing(request.urlopen(template_url)) as u:
118 tpl = u.read()
119 else:
120 return {}, None
121 if not tpl:
122 return {}, None
123 if isinstance(tpl, six.binary_type):
124 tpl = tpl.decode('utf-8')
125 template = template_format.parse(tpl)
126 files = {}
127 _get_file_contents(template, files)
128 return files, template
129
130
131def _get_file_contents(from_data, files):
132 if not isinstance(from_data, (dict, list)):
133 return
134 if isinstance(from_data, dict):
135 recurse_data = from_data.values()
136 for key, value in from_data.items():
137 if _ignore_if(key, value):
138 continue
139 if not value.startswith(('http://', 'https://')):
140 raise exceptions.GetFileError(value, 'get_file')
141 if value not in files:
142 file_content = heat_utils.read_url_content(value)
143 if template_utils.is_template(file_content):
144 template = get_template_files(template_url=value)[1]
145 file_content = jsonutils.dumps(template)
146 files[value] = file_content
147 else:
148 recurse_data = from_data
149 for value in recurse_data:
150 _get_file_contents(value, files)
151
152
153@profiler.trace
154def stack_delete(request, stack_id):
155 return heatclient(request).stacks.delete(stack_id)
156
157
158@profiler.trace
159def stack_get(request, stack_id):
160 return heatclient(request).stacks.get(stack_id)
161
162
163@profiler.trace
164def template_get(request, stack_id):
165 return heatclient(request).stacks.template(stack_id)
166
167
168@profiler.trace
169def stack_create(request, password=None, **kwargs):
170 return heatclient(request, password).stacks.create(**kwargs)
171
172
173@profiler.trace
174def stack_preview(request, password=None, **kwargs):
175 return heatclient(request, password).stacks.preview(**kwargs)
176
177
178@profiler.trace
179def stack_update(request, stack_id, password=None, **kwargs):
180 return heatclient(request, password).stacks.update(stack_id, **kwargs)
181
182
183@profiler.trace
184def snapshot_create(request, stack_id):
185 return heatclient(request).stacks.snapshot(stack_id)
186
187
188@profiler.trace
189def snapshot_list(request, stack_id):
190 return heatclient(request).stacks.snapshot_list(stack_id)
191
192
193@profiler.trace
194def snapshot_show(request, stack_id, snapshot_id):
195 return heatclient(request).stacks.snapshot_show(stack_id, snapshot_id)
196
197
198@profiler.trace
199def snapshot_delete(request, stack_id, snapshot_id):
200 return heatclient(request).stacks.snapshot_delete(stack_id, snapshot_id)
201
202
203@profiler.trace
204def events_list(request, stack_name):
205 return heatclient(request).events.list(stack_name)
206
207
208@profiler.trace
209def resources_list(request, stack_name):
210 return heatclient(request).resources.list(stack_name)
211
212
213@profiler.trace
214def resource_get(request, stack_id, resource_name):
215 return heatclient(request).resources.get(stack_id, resource_name)
216
217
218@profiler.trace
219def resource_metadata_get(request, stack_id, resource_name):
220 return heatclient(request).resources.metadata(stack_id, resource_name)
221
222
223@profiler.trace
224def template_validate(request, **kwargs):
225 return heatclient(request).stacks.validate(**kwargs)
226
227
228@profiler.trace
229def action_check(request, stack_id):
230 return heatclient(request).actions.check(stack_id)
231
232
233@profiler.trace
234def action_suspend(request, stack_id):
235 return heatclient(request).actions.suspend(stack_id)
236
237
238@profiler.trace
239def action_resume(request, stack_id):
240 return heatclient(request).actions.resume(stack_id)
241
242
243@profiler.trace
244def resource_types_list(request, filters=None):
245 return heatclient(request).resource_types.list(filters=filters)
246
247
248@profiler.trace
249def resource_type_get(request, resource_type):
250 return heatclient(request).resource_types.get(resource_type)
251
252
253@profiler.trace
254def service_list(request):
255 return heatclient(request).services.list()
256
257
258@profiler.trace
259def template_version_list(request):
260 return heatclient(request).template_versions.list()
261
262
263@profiler.trace
264def template_function_list(request, template_version):
265 return heatclient(request).template_versions.get(template_version)
diff --git a/openstack_dashboard/api/rest/__init__.py b/openstack_dashboard/api/rest/__init__.py
index 925cbb0..11df2d5 100644
--- a/openstack_dashboard/api/rest/__init__.py
+++ b/openstack_dashboard/api/rest/__init__.py
@@ -24,7 +24,6 @@ in https://wiki.openstack.org/wiki/APIChangeGuidelines.
24from openstack_dashboard.api.rest import cinder 24from openstack_dashboard.api.rest import cinder
25from openstack_dashboard.api.rest import config 25from openstack_dashboard.api.rest import config
26from openstack_dashboard.api.rest import glance 26from openstack_dashboard.api.rest import glance
27from openstack_dashboard.api.rest import heat
28from openstack_dashboard.api.rest import keystone 27from openstack_dashboard.api.rest import keystone
29from openstack_dashboard.api.rest import network 28from openstack_dashboard.api.rest import network
30from openstack_dashboard.api.rest import neutron 29from openstack_dashboard.api.rest import neutron
@@ -37,7 +36,6 @@ __all__ = [
37 'cinder', 36 'cinder',
38 'config', 37 'config',
39 'glance', 38 'glance',
40 'heat',
41 'keystone', 39 'keystone',
42 'network', 40 'network',
43 'neutron', 41 'neutron',
diff --git a/openstack_dashboard/api/rest/heat.py b/openstack_dashboard/api/rest/heat.py
deleted file mode 100644
index df8752f..0000000
--- a/openstack_dashboard/api/rest/heat.py
+++ /dev/null
@@ -1,51 +0,0 @@
1# Licensed under the Apache License, Version 2.0 (the "License");
2# you may not use this file except in compliance with the License.
3# You may obtain a copy of the License at
4#
5# http://www.apache.org/licenses/LICENSE-2.0
6#
7# Unless required by applicable law or agreed to in writing, software
8# distributed under the License is distributed on an "AS IS" BASIS,
9# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10# See the License for the specific language governing permissions and
11# limitations under the License.
12"""API for the heat service."""
13
14from django.views import generic
15
16from openstack_dashboard import api
17from openstack_dashboard.api.rest import urls
18from openstack_dashboard.api.rest import utils as rest_utils
19
20
21@urls.register
22class Validate(generic.View):
23 """API for validating a template"""
24 url_regex = r'heat/validate/$'
25
26 @rest_utils.ajax(data_required=True)
27 def post(self, request):
28 """Validate a template
29
30 The following parameters may be passed in the POST
31 application/json object. The parameters are:
32 request:
33
34 :param template_url: The template to validate
35 """
36 return api.heat.template_validate(request, **(request.DATA))
37
38
39@urls.register
40class Services(generic.View):
41 """API for heat services."""
42 url_regex = r'heat/services/$'
43
44 @rest_utils.ajax()
45 def get(self, request):
46 """Get a list of heat services."""
47 if api.base.is_service_enabled(request, 'orchestration'):
48 result = api.heat.service_list(request)
49 return {'items': [u.to_dict() for u in result]}
50 else:
51 raise rest_utils.AjaxError(501, '')
diff --git a/openstack_dashboard/conf/heat_policy.json b/openstack_dashboard/conf/heat_policy.json
deleted file mode 100644
index b40b1ee..0000000
--- a/openstack_dashboard/conf/heat_policy.json
+++ /dev/null
@@ -1,92 +0,0 @@
1{
2 "context_is_admin": "role:admin",
3 "deny_stack_user": "not role:heat_stack_user",
4 "deny_everybody": "!",
5
6 "cloudformation:ListStacks": "rule:deny_stack_user",
7 "cloudformation:CreateStack": "rule:deny_stack_user",
8 "cloudformation:DescribeStacks": "rule:deny_stack_user",
9 "cloudformation:DeleteStack": "rule:deny_stack_user",
10 "cloudformation:UpdateStack": "rule:deny_stack_user",
11 "cloudformation:CancelUpdateStack": "rule:deny_stack_user",
12 "cloudformation:DescribeStackEvents": "rule:deny_stack_user",
13 "cloudformation:ValidateTemplate": "rule:deny_stack_user",
14 "cloudformation:GetTemplate": "rule:deny_stack_user",
15 "cloudformation:EstimateTemplateCost": "rule:deny_stack_user",
16 "cloudformation:DescribeStackResource": "",
17 "cloudformation:DescribeStackResources": "rule:deny_stack_user",
18 "cloudformation:ListStackResources": "rule:deny_stack_user",
19
20 "cloudwatch:DeleteAlarms": "rule:deny_stack_user",
21 "cloudwatch:DescribeAlarmHistory": "rule:deny_stack_user",
22 "cloudwatch:DescribeAlarms": "rule:deny_stack_user",
23 "cloudwatch:DescribeAlarmsForMetric": "rule:deny_stack_user",
24 "cloudwatch:DisableAlarmActions": "rule:deny_stack_user",
25 "cloudwatch:EnableAlarmActions": "rule:deny_stack_user",
26 "cloudwatch:GetMetricStatistics": "rule:deny_stack_user",
27 "cloudwatch:ListMetrics": "rule:deny_stack_user",
28 "cloudwatch:PutMetricAlarm": "rule:deny_stack_user",
29 "cloudwatch:PutMetricData": "",
30 "cloudwatch:SetAlarmState": "rule:deny_stack_user",
31
32 "actions:action": "rule:deny_stack_user",
33 "build_info:build_info": "rule:deny_stack_user",
34 "events:index": "rule:deny_stack_user",
35 "events:show": "rule:deny_stack_user",
36 "resource:index": "rule:deny_stack_user",
37 "resource:metadata": "",
38 "resource:signal": "",
39 "resource:mark_unhealthy": "rule:deny_stack_user",
40 "resource:show": "rule:deny_stack_user",
41 "stacks:abandon": "rule:deny_stack_user",
42 "stacks:create": "rule:deny_stack_user",
43 "stacks:delete": "rule:deny_stack_user",
44 "stacks:detail": "rule:deny_stack_user",
45 "stacks:export": "rule:deny_stack_user",
46 "stacks:generate_template": "rule:deny_stack_user",
47 "stacks:global_index": "rule:deny_everybody",
48 "stacks:index": "rule:deny_stack_user",
49 "stacks:list_resource_types": "rule:deny_stack_user",
50 "stacks:list_template_versions": "rule:deny_stack_user",
51 "stacks:list_template_functions": "rule:deny_stack_user",
52 "stacks:lookup": "",
53 "stacks:preview": "rule:deny_stack_user",
54 "stacks:resource_schema": "rule:deny_stack_user",
55 "stacks:show": "rule:deny_stack_user",
56 "stacks:template": "rule:deny_stack_user",
57 "stacks:environment": "rule:deny_stack_user",
58 "stacks:update": "rule:deny_stack_user",
59 "stacks:update_patch": "rule:deny_stack_user",
60 "stacks:preview_update": "rule:deny_stack_user",
61 "stacks:preview_update_patch": "rule:deny_stack_user",
62 "stacks:validate_template": "rule:deny_stack_user",
63 "stacks:snapshot": "rule:deny_stack_user",
64 "stacks:show_snapshot": "rule:deny_stack_user",
65 "stacks:delete_snapshot": "rule:deny_stack_user",
66 "stacks:list_snapshots": "rule:deny_stack_user",
67 "stacks:restore_snapshot": "rule:deny_stack_user",
68 "stacks:list_outputs": "rule:deny_stack_user",
69 "stacks:show_output": "rule:deny_stack_user",
70
71 "software_configs:global_index": "rule:deny_everybody",
72 "software_configs:index": "rule:deny_stack_user",
73 "software_configs:create": "rule:deny_stack_user",
74 "software_configs:show": "rule:deny_stack_user",
75 "software_configs:delete": "rule:deny_stack_user",
76 "software_deployments:index": "rule:deny_stack_user",
77 "software_deployments:create": "rule:deny_stack_user",
78 "software_deployments:show": "rule:deny_stack_user",
79 "software_deployments:update": "rule:deny_stack_user",
80 "software_deployments:delete": "rule:deny_stack_user",
81 "software_deployments:metadata": "",
82
83 "service:index": "rule:context_is_admin",
84
85 "resource_types:OS::Nova::Flavor": "rule:context_is_admin",
86 "resource_types:OS::Cinder::EncryptedVolumeType": "rule:context_is_admin",
87 "resource_types:OS::Cinder::VolumeType": "rule:context_is_admin",
88 "resource_types:OS::Manila::ShareType": "rule:context_is_admin",
89 "resource_types:OS::Neutron::QoSPolicy": "rule:context_is_admin",
90 "resource_types:OS::Neutron::QoSBandwidthLimitRule": "rule:context_is_admin",
91 "resource_types:OS::Nova::HostAggregate": "rule:context_is_admin"
92}
diff --git a/openstack_dashboard/dashboards/admin/dashboard.py b/openstack_dashboard/dashboards/admin/dashboard.py
index a1eb5fe..f526d95 100644
--- a/openstack_dashboard/dashboards/admin/dashboard.py
+++ b/openstack_dashboard/dashboards/admin/dashboard.py
@@ -28,8 +28,7 @@ class Admin(horizon.Dashboard):
28 ('image', 'context_is_admin'), 28 ('image', 'context_is_admin'),
29 ('volume', 'context_is_admin'), 29 ('volume', 'context_is_admin'),
30 ('compute', 'context_is_admin'), 30 ('compute', 'context_is_admin'),
31 ('network', 'context_is_admin'), 31 ('network', 'context_is_admin'),)
32 ('orchestration', 'context_is_admin'),)
33 else: 32 else:
34 permissions = (tuple(utils.get_admin_permissions()),) 33 permissions = (tuple(utils.get_admin_permissions()),)
35 34
diff --git a/openstack_dashboard/dashboards/admin/info/panel.py b/openstack_dashboard/dashboards/admin/info/panel.py
index 8979032..85a9642 100644
--- a/openstack_dashboard/dashboards/admin/info/panel.py
+++ b/openstack_dashboard/dashboards/admin/info/panel.py
@@ -26,5 +26,4 @@ class Info(horizon.Panel):
26 slug = 'info' 26 slug = 'info'
27 policy_rules = (("compute", "context_is_admin"), 27 policy_rules = (("compute", "context_is_admin"),
28 ("volume", "context_is_admin"), 28 ("volume", "context_is_admin"),
29 ("network", "context_is_admin"), 29 ("network", "context_is_admin"),)
30 ("orchestration", "context_is_admin"),)
diff --git a/openstack_dashboard/dashboards/admin/info/tables.py b/openstack_dashboard/dashboards/admin/info/tables.py
index 44f0758..40894f0 100644
--- a/openstack_dashboard/dashboards/admin/info/tables.py
+++ b/openstack_dashboard/dashboards/admin/info/tables.py
@@ -236,45 +236,3 @@ class NetworkAgentsTable(tables.DataTable):
236 table_actions = (NetworkAgentsFilterAction, ) 236 table_actions = (NetworkAgentsFilterAction, )
237 row_actions = (NetworkL3AgentRoutersLinkAction, ) 237 row_actions = (NetworkL3AgentRoutersLinkAction, )
238 multi_select = False 238 multi_select = False
239
240
241class HeatServiceFilterAction(tables.FilterAction):
242 filter_field = 'type'
243
244 def filter(self, table, services, filter_string):
245 q = filter_string.lower()
246
247 def comp(service):
248 attr = getattr(service, self.filter_field, '')
249 if attr is not None and q in attr.lower():
250 return True
251 return False
252
253 return filter(comp, services)
254
255
256class HeatServiceTable(tables.DataTable):
257 hostname = tables.Column('hostname', verbose_name=_('Hostname'))
258 binary = tables.Column("binary", verbose_name=_('Name'))
259 engine_id = tables.Column('engine_id', verbose_name=_('Engine Id'))
260 host = tables.Column('host', verbose_name=_('Host'))
261 topic = tables.Column('topic', verbose_name=_('Topic'))
262 # For consistent with other tables in system info, set column name to
263 # 'state'
264 state = tables.Column('status', verbose_name=_('State'),
265 display_choices=SERVICE_STATE_DISPLAY_CHOICES)
266 updated_at = tables.Column('updated_at',
267 verbose_name=pgettext_lazy(
268 'Time since the last update',
269 u'Last Updated'),
270 filters=(utils_filters.parse_isotime,
271 filters.timesince))
272
273 def get_object_id(self, obj):
274 return "%s" % obj.engine_id
275
276 class Meta(object):
277 name = "heat_services"
278 verbose_name = _("Orchestration Services")
279 table_actions = (HeatServiceFilterAction,)
280 multi_select = False
diff --git a/openstack_dashboard/dashboards/admin/info/tabs.py b/openstack_dashboard/dashboards/admin/info/tabs.py
index cdda728..5cd265f 100644
--- a/openstack_dashboard/dashboards/admin/info/tabs.py
+++ b/openstack_dashboard/dashboards/admin/info/tabs.py
@@ -18,7 +18,6 @@ from horizon import exceptions
18from horizon import tabs 18from horizon import tabs
19from openstack_dashboard.api import base 19from openstack_dashboard.api import base
20from openstack_dashboard.api import cinder 20from openstack_dashboard.api import cinder
21from openstack_dashboard.api import heat
22from openstack_dashboard.api import neutron 21from openstack_dashboard.api import neutron
23from openstack_dashboard.api import nova 22from openstack_dashboard.api import nova
24from openstack_dashboard.dashboards.admin.info import constants 23from openstack_dashboard.dashboards.admin.info import constants
@@ -118,32 +117,8 @@ class NetworkAgentsTab(tabs.TableTab):
118 return agents 117 return agents
119 118
120 119
121class HeatServiceTab(tabs.TableTab):
122 table_classes = (tables.HeatServiceTable,)
123 name = tables.HeatServiceTable.Meta.verbose_name
124 slug = tables.HeatServiceTable.Meta.name
125 template_name = constants.INFO_DETAIL_TEMPLATE_NAME
126
127 def allowed(self, request):
128 try:
129 return base.is_service_enabled(request, 'orchestration')
130 except Exception:
131 exceptions.handle(request, _('Orchestration service is disabled.'))
132 return False
133
134 def get_heat_services_data(self):
135 try:
136 services = heat.service_list(self.tab_group.request)
137 except Exception:
138 msg = _('Unable to get Orchestration service list.')
139 exceptions.check_message(["Connection", "refused"], msg)
140 exceptions.handle(self.request, msg)
141 services = []
142 return services
143
144
145class SystemInfoTabs(tabs.TabGroup): 120class SystemInfoTabs(tabs.TabGroup):
146 slug = "system_info" 121 slug = "system_info"
147 tabs = (ServicesTab, NovaServicesTab, CinderServicesTab, 122 tabs = (ServicesTab, NovaServicesTab, CinderServicesTab,
148 NetworkAgentsTab, HeatServiceTab) 123 NetworkAgentsTab)
149 sticky = True 124 sticky = True
diff --git a/openstack_dashboard/dashboards/admin/info/tests.py b/openstack_dashboard/dashboards/admin/info/tests.py
index c7405d3..9cbe865 100644
--- a/openstack_dashboard/dashboards/admin/info/tests.py
+++ b/openstack_dashboard/dashboards/admin/info/tests.py
@@ -29,7 +29,7 @@ class SystemInfoViewTests(test.BaseAdminViewTests):
29 api.nova: ('service_list',), 29 api.nova: ('service_list',),
30 api.neutron: ('agent_list', 'is_extension_supported'), 30 api.neutron: ('agent_list', 'is_extension_supported'),
31 api.cinder: ('service_list',), 31 api.cinder: ('service_list',),
32 api.heat: ('service_list',)}) 32 })
33 def _test_base_index(self): 33 def _test_base_index(self):
34 api.base.is_service_enabled(IsA(http.HttpRequest), IgnoreArg()) \ 34 api.base.is_service_enabled(IsA(http.HttpRequest), IgnoreArg()) \
35 .MultipleTimes().AndReturn(True) 35 .MultipleTimes().AndReturn(True)
@@ -49,10 +49,6 @@ class SystemInfoViewTests(test.BaseAdminViewTests):
49 api.cinder.service_list(IsA(http.HttpRequest)).\ 49 api.cinder.service_list(IsA(http.HttpRequest)).\
50 AndReturn(cinder_services) 50 AndReturn(cinder_services)
51 51
52 heat_services = self.heat_services.list()
53 api.heat.service_list(IsA(http.HttpRequest)).\
54 AndReturn(heat_services)
55
56 self.mox.ReplayAll() 52 self.mox.ReplayAll()
57 53
58 res = self.client.get(INDEX_URL) 54 res = self.client.get(INDEX_URL)
@@ -88,14 +84,3 @@ class SystemInfoViewTests(test.BaseAdminViewTests):
88 ) 84 )
89 85
90 self.mox.VerifyAll() 86 self.mox.VerifyAll()
91
92 def test_heat_index(self):
93 res = self._test_base_index()
94 heat_services_tab = res.context['tab_group'].\
95 get_tab('heat_services')
96 self.assertQuerysetEqual(
97 heat_services_tab._tables['heat_services'].data,
98 [service.__repr__() for service in self.heat_services.list()]
99 )
100
101 self.mox.VerifyAll()
diff --git a/openstack_dashboard/dashboards/project/stacks/__init__.py b/openstack_dashboard/dashboards/project/stacks/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/openstack_dashboard/dashboards/project/stacks/__init__.py
+++ /dev/null
diff --git a/openstack_dashboard/dashboards/project/stacks/api.py b/openstack_dashboard/dashboards/project/stacks/api.py
deleted file mode 100644
index ce5affd..0000000
--- a/openstack_dashboard/dashboards/project/stacks/api.py
+++ /dev/null
@@ -1,83 +0,0 @@
1# Licensed under the Apache License, Version 2.0 (the "License"); you may
2# not use this file except in compliance with the License. You may obtain
3# a copy of the License at
4#
5# http://www.apache.org/licenses/LICENSE-2.0
6#
7# Unless required by applicable law or agreed to in writing, software
8# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10# License for the specific language governing permissions and limitations
11# under the License.
12
13import json
14
15from openstack_dashboard.api import heat
16
17from openstack_dashboard.dashboards.project.stacks import mappings
18from openstack_dashboard.dashboards.project.stacks import sro
19
20
21class Stack(object):
22 pass
23
24
25def d3_data(request, stack_id=''):
26 try:
27 stack = heat.stack_get(request, stack_id)
28 except Exception:
29 stack = Stack()
30 stack.id = stack_id
31 stack.stack_name = request.session.get('stack_name', '')
32 stack.stack_status = 'DELETE_COMPLETE'
33 stack.stack_status_reason = 'DELETE_COMPLETE'
34
35 try:
36 resources = heat.resources_list(request, stack.stack_name)
37 except Exception:
38 resources = []
39
40 d3_data = {"nodes": [], "stack": {}}
41 if stack:
42 stack_image = mappings.get_resource_image(stack.stack_status, 'stack')
43 stack_node = {
44 'stack_id': stack.id,
45 'name': stack.stack_name,
46 'status': stack.stack_status,
47 'image': stack_image,
48 'image_size': 60,
49 'image_x': -30,
50 'image_y': -30,
51 'text_x': 40,
52 'text_y': ".35em",
53 'in_progress': (stack.status == 'IN_PROGRESS'),
54 'info_box': sro.stack_info(stack, stack_image)
55 }
56 d3_data['stack'] = stack_node
57
58 if resources:
59 for resource in resources:
60 resource_image = mappings.get_resource_image(
61 resource.resource_status,
62 resource.resource_type)
63 resource_status = mappings.get_resource_status(
64 resource.resource_status)
65 if resource_status in ('IN_PROGRESS', 'INIT'):
66 in_progress = True
67 else:
68 in_progress = False
69 resource_node = {
70 'name': resource.resource_name,
71 'status': resource.resource_status,
72 'image': resource_image,
73 'required_by': resource.required_by,
74 'image_size': 50,
75 'image_x': -25,
76 'image_y': -25,
77 'text_x': 35,
78 'text_y': ".35em",
79 'in_progress': in_progress,
80 'info_box': sro.resource_info(resource)
81 }
82 d3_data['nodes'].append(resource_node)
83 return json.dumps(d3_data)
diff --git a/openstack_dashboard/dashboards/project/stacks/forms.py b/openstack_dashboard/dashboards/project/stacks/forms.py
deleted file mode 100644
index e8b9702..0000000
--- a/openstack_dashboard/dashboards/project/stacks/forms.py
+++ /dev/null
@@ -1,488 +0,0 @@
1# Licensed under the Apache License, Version 2.0 (the "License"); you may
2# not use this file except in compliance with the License. You may obtain
3# a copy of the License at
4#
5# http://www.apache.org/licenses/LICENSE-2.0
6#
7# Unless required by applicable law or agreed to in writing, software
8# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10# License for the specific language governing permissions and limitations
11# under the License.
12
13import json
14import logging
15
16import django
17from django.conf import settings
18from django.utils import html
19from django.utils.translation import ugettext_lazy as _
20from django.views.decorators.debug import sensitive_variables
21
22from oslo_utils import strutils
23import six
24
25from horizon import exceptions
26from horizon import forms
27from horizon import messages
28
29from openstack_dashboard import api
30from openstack_dashboard.dashboards.project.images \
31 import utils as image_utils
32from openstack_dashboard.dashboards.project.instances \
33 import utils as instance_utils
34
35
36LOG = logging.getLogger(__name__)
37
38
39def create_upload_form_attributes(prefix, input_type, name):
40 """Creates attribute dicts for the switchable upload form
41
42 :type prefix: str
43 :param prefix: prefix (environment, template) of field
44 :type input_type: str
45 :param input_type: field type (file, raw, url)
46 :type name: str
47 :param name: translated text label to display to user
48 :rtype: dict
49 :return: an attribute set to pass to form build
50 """
51 attributes = {'class': 'switched', 'data-switch-on': prefix + 'source'}
52 attributes['data-' + prefix + 'source-' + input_type] = name
53 return attributes
54
55
56class TemplateForm(forms.SelfHandlingForm):
57
58 class Meta(object):
59 name = _('Select Template')
60 help_text = _('Select a template to launch a stack.')
61
62 # TODO(jomara) - update URL choice for template & environment files
63 # w/ client side download when applicable
64 base_choices = [('file', _('File')),
65 ('raw', _('Direct Input'))]
66 url_choice = [('url', _('URL'))]
67 attributes = {'class': 'switchable', 'data-slug': 'templatesource'}
68 template_source = forms.ChoiceField(label=_('Template Source'),
69 choices=base_choices + url_choice,
70 widget=forms.ThemableSelectWidget(
71 attrs=attributes))
72
73 attributes = create_upload_form_attributes(
74 'template',
75 'file',
76 _('Template File'))
77 template_upload = forms.FileField(
78 label=_('Template File'),
79 help_text=_('A local template to upload.'),
80 widget=forms.FileInput(attrs=attributes),
81 required=False)
82
83 attributes = create_upload_form_attributes(
84 'template',
85 'url',
86 _('Template URL'))
87 template_url = forms.URLField(
88 label=_('Template URL'),
89 help_text=_('An external (HTTP) URL to load the template from.'),
90 widget=forms.TextInput(attrs=attributes),
91 required=False)
92
93 attributes = create_upload_form_attributes(
94 'template',
95 'raw',
96 _('Template Data'))
97 template_data = forms.CharField(
98 label=_('Template Data'),
99 help_text=_('The raw contents of the template.'),
100 widget=forms.widgets.Textarea(attrs=attributes),
101 required=False)
102
103 attributes = {'data-slug': 'envsource', 'class': 'switchable'}
104 environment_source = forms.ChoiceField(
105 label=_('Environment Source'),
106 choices=base_choices,
107 widget=forms.ThemableSelectWidget(attrs=attributes),
108 required=False)
109
110 attributes = create_upload_form_attributes(
111 'env',
112 'file',
113 _('Environment File'))
114 environment_upload = forms.FileField(
115 label=_('Environment File'),
116 help_text=_('A local environment to upload.'),
117 widget=forms.FileInput(attrs=attributes),
118 required=False)
119
120 attributes = create_upload_form_attributes(
121 'env',
122 'raw',
123 _('Environment Data'))
124 environment_data = forms.CharField(
125 label=_('Environment Data'),
126 help_text=_('The raw contents of the environment file.'),
127 widget=forms.widgets.Textarea(attrs=attributes),
128 required=False)
129
130 if django.VERSION >= (1, 9):
131 # Note(Itxaka): On django>=1.9 Charfield has an strip option that
132 # we need to set to False as to not hit
133 # https://bugs.launchpad.net/python-heatclient/+bug/1546166
134 environment_data.strip = False
135 template_data.strip = False
136
137 def __init__(self, *args, **kwargs):
138 self.next_view = kwargs.pop('next_view')
139 super(TemplateForm, self).__init__(*args, **kwargs)
140
141 def clean(self):
142 cleaned = super(TemplateForm, self).clean()
143
144 files = self.request.FILES
145 self.clean_uploaded_files('template', _('template'), cleaned, files)
146 self.clean_uploaded_files('environment', _('environment'), cleaned,
147 files)
148
149 # Validate the template and get back the params.
150 kwargs = {}
151 if cleaned['environment_data']:
152 kwargs['environment'] = cleaned['environment_data']
153 try:
154 files, tpl =\
155 api.heat.get_template_files(cleaned.get('template_data'),
156 cleaned.get('template_url'))
157 kwargs['files'] = files
158 kwargs['template'] = tpl
159 validated = api.heat.template_validate(self.request, **kwargs)
160 cleaned['template_validate'] = validated
161 cleaned['template_validate']['files'] = files
162 cleaned['template_validate']['template'] = tpl
163 except Exception as e:
164 raise forms.ValidationError(six.text_type(e))
165
166 return cleaned
167
168 def clean_uploaded_files(self, prefix, field_label, cleaned, files):
169 """Cleans Template & Environment data from form upload.
170
171 Does some of the crunchy bits for processing uploads vs raw
172 data depending on what the user specified. Identical process
173 for environment data & template data.
174
175 :type prefix: str
176 :param prefix: prefix (environment, template) of field
177 :type field_label: str
178 :param field_label: translated prefix str for messages
179 :type input_type: dict
180 :param prefix: existing cleaned fields from form
181 :rtype: dict
182 :return: cleaned dict including environment & template data
183 """
184
185 upload_str = prefix + "_upload"
186 data_str = prefix + "_data"
187 url = cleaned.get(prefix + '_url')
188 data = cleaned.get(prefix + '_data')
189
190 has_upload = upload_str in files
191 # Uploaded file handler
192 if has_upload and not url:
193 log_template_name = files[upload_str].name
194 LOG.info('got upload %s', log_template_name)
195
196 tpl = files[upload_str].read()
197 if tpl.startswith('{'):
198 try:
199 json.loads(tpl)
200 except Exception as e:
201 msg = _('There was a problem parsing the'
202 ' %(prefix)s: %(error)s')
203 msg = msg % {'prefix': prefix, 'error': six.text_type(e)}
204 raise forms.ValidationError(msg)
205 cleaned[data_str] = tpl
206
207 # URL handler
208 elif url and (has_upload or data):
209 msg = _('Please specify a %s using only one source method.')
210 msg = msg % field_label
211 raise forms.ValidationError(msg)
212
213 elif prefix == 'template':
214 # Check for raw template input - blank environment allowed
215 if not url and not data:
216 msg = _('You must specify a template via one of the '
217 'available sources.')
218 raise forms.ValidationError(msg)
219
220 def create_kwargs(self, data):
221 kwargs = {'parameters': data['template_validate'],
222 'environment_data': data['environment_data']}
223 if data.get('stack_id'):
224 kwargs['stack_id'] = data['stack_id']
225 return kwargs
226
227 def handle(self, request, data):
228 kwargs = self.create_kwargs(data)
229 # NOTE (gabriel): This is a bit of a hack, essentially rewriting this
230 # request so that we can chain it as an input to the next view...
231 # but hey, it totally works.
232 request.method = 'GET'
233
234 return self.next_view.as_view()(request, **kwargs)
235
236
237class ChangeTemplateForm(TemplateForm):
238 class Meta(object):
239 name = _('Edit Template')
240 help_text = _('Select a new template to re-launch a stack.')
241 stack_id = forms.CharField(label=_('Stack ID'),
242 widget=forms.widgets.HiddenInput)
243 stack_name = forms.CharField(label=_('Stack Name'),
244 widget=forms.TextInput(attrs={'readonly':
245 'readonly'}))
246
247
248class PreviewTemplateForm(TemplateForm):
249 class Meta(object):
250 name = _('Preview Template')
251 help_text = _('Select a new template to preview a stack.')
252
253
254class CreateStackForm(forms.SelfHandlingForm):
255
256 param_prefix = '__param_'
257
258 class Meta(object):
259 name = _('Create Stack')
260
261 environment_data = forms.CharField(
262 widget=forms.widgets.HiddenInput,
263 required=False)
264 if django.VERSION >= (1, 9):
265 # Note(Itxaka): On django>=1.9 Charfield has an strip option that
266 # we need to set to False as to not hit
267 # https://bugs.launchpad.net/python-heatclient/+bug/1546166
268 environment_data.strip = False
269
270 parameters = forms.CharField(
271 widget=forms.widgets.HiddenInput)
272 stack_name = forms.RegexField(
273 max_length=255,
274 label=_('Stack Name'),
275 help_text=_('Name of the stack to create.'),
276 regex=r"^[a-zA-Z][a-zA-Z0-9_.-]*$",
277 error_messages={'invalid':
278 _('Name must start with a letter and may '
279 'only contain letters, numbers, underscores, '
280 'periods and hyphens.')})
281 timeout_mins = forms.IntegerField(
282 initial=60,
283 label=_('Creation Timeout (minutes)'),
284 help_text=_('Stack creation timeout in minutes.'))
285 enable_rollback = forms.BooleanField(
286 label=_('Rollback On Failure'),
287 help_text=_('Enable rollback on create/update failure.'),
288 required=False)
289
290 def __init__(self, *args, **kwargs):
291 parameters = kwargs.pop('parameters')
292 # special case: load template data from API, not passed in params
293 if kwargs.get('validate_me'):
294 parameters = kwargs.pop('validate_me')
295 super(CreateStackForm, self).__init__(*args, **kwargs)
296
297 if self._stack_password_enabled():
298 self.fields['password'] = forms.CharField(
299 label=_('Password for user "%s"') % self.request.user.username,
300 help_text=_('This is required for operations to be performed '
301 'throughout the lifecycle of the stack'),
302 widget=forms.PasswordInput())
303
304 self._build_parameter_fields(parameters)
305
306 def _stack_password_enabled(self):
307 stack_settings = getattr(settings, 'OPENSTACK_HEAT_STACK', {})
308 return stack_settings.get('enable_user_pass', True)
309
310 def _build_parameter_fields(self, template_validate):
311 self.help_text = template_validate['Description']
312
313 params = template_validate.get('Parameters', {})
314 if template_validate.get('ParameterGroups'):
315 params_in_order = []
316 for group in template_validate['ParameterGroups']:
317 for param in group.get('parameters', []):
318 if param in params:
319 params_in_order.append((param, params[param]))
320 else:
321 # no parameter groups, simply sorted to make the order fixed
322 params_in_order = sorted(params.items())
323 for param_key, param in params_in_order:
324 field = None
325 field_key = self.param_prefix + param_key
326 initial = param.get('Value',
327 param.get('DefaultValue',
328 param.get('Default')))
329 field_args = {
330 'initial': initial,
331 'label': param.get('Label', param_key),
332 'help_text': html.escape(param.get('Description', '')),
333 'required': initial is None,
334 }
335
336 param_type = param.get('Type', None)
337 hidden = strutils.bool_from_string(param.get('NoEcho', 'false'))
338 if 'CustomConstraint' in param:
339 choices = self._populate_custom_choices(
340 param['CustomConstraint'])
341 field_args['choices'] = choices
342 field = forms.ChoiceField(**field_args)
343
344 elif 'AllowedValues' in param:
345 choices = map(lambda x: (x, x), param['AllowedValues'])
346 field_args['choices'] = choices
347 field = forms.ChoiceField(**field_args)
348
349 elif param_type == 'Json' and 'Default' in param:
350 field_args['initial'] = json.dumps(param['Default'])
351 field = forms.CharField(**field_args)
352
353 elif param_type in ('CommaDelimitedList', 'String', 'Json'):
354 if 'MinLength' in param:
355 field_args['min_length'] = int(param['MinLength'])
356 field_args['required'] = field_args['min_length'] > 0
357 if 'MaxLength' in param:
358 field_args['max_length'] = int(param['MaxLength'])
359 if hidden:
360 field_args['widget'] = forms.PasswordInput(
361 render_value=True)
362 field = forms.CharField(**field_args)
363
364 elif param_type == 'Number':
365 if 'MinValue' in param:
366 field_args['min_value'] = int(param['MinValue'])
367 if 'MaxValue' in param:
368 field_args['max_value'] = int(param['MaxValue'])
369 field = forms.IntegerField(**field_args)
370
371 elif param_type == 'Boolean':
372 field_args['required'] = False
373 field = forms.BooleanField(**field_args)
374
375 if field:
376 self.fields[field_key] = field
377
378 @sensitive_variables('password')
379 def handle(self, request, data):
380 prefix_length = len(self.param_prefix)
381 params_list = [(k[prefix_length:], v) for (k, v) in data.items()
382 if k.startswith(self.param_prefix)]
383 fields = {
384 'stack_name': data.get('stack_name'),
385 'timeout_mins': data.get('timeout_mins'),
386 'disable_rollback': not(data.get('enable_rollback')),
387 'parameters': dict(params_list),
388 'files': json.loads(data.get('parameters')).get('files'),
389 'template': json.loads(data.get('parameters')).get('template')
390 }
391 if data.get('password'):
392 fields['password'] = data.get('password')
393
394 if data.get('environment_data'):
395 fields['environment'] = data.get('environment_data')
396
397 try:
398 api.heat.stack_create(self.request, **fields)
399 messages.info(request, _("Stack creation started."))
400 return True
401 except Exception:
402 exceptions.handle(request)
403
404 def _populate_custom_choices(self, custom_type):
405 if custom_type == 'neutron.network':
406 return instance_utils.network_field_data(self.request, True)
407 if custom_type == 'nova.keypair':
408 return instance_utils.keypair_field_data(self.request, True)
409 if custom_type == 'glance.image':
410 return image_utils.image_field_data(self.request, True)
411 if custom_type == 'nova.flavor':
412 return instance_utils.flavor_field_data(self.request, True)
413 return []
414
415
416class EditStackForm(CreateStackForm):
417
418 class Meta(object):
419 name = _('Update Stack Parameters')
420
421 stack_id = forms.CharField(
422 label=_('Stack ID'),
423 widget=forms.widgets.HiddenInput)
424 stack_name = forms.CharField(
425 label=_('Stack Name'),
426 widget=forms.TextInput(attrs={'readonly': 'readonly'}))
427
428 @sensitive_variables('password')
429 def handle(self, request, data):
430 prefix_length = len(self.param_prefix)
431 params_list = [(k[prefix_length:], v) for (k, v) in data.items()
432 if k.startswith(self.param_prefix)]
433
434 stack_id = data.get('stack_id')
435 fields = {
436 'stack_name': data.get('stack_name'),
437 'timeout_mins': data.get('timeout_mins'),
438 'disable_rollback': not(data.get('enable_rollback')),
439 'parameters': dict(params_list),
440 'files': json.loads(data.get('parameters')).get('files'),
441 'template': json.loads(data.get('parameters')).get('template')
442 }
443 if data.get('password'):
444 fields['password'] = data.get('password')
445
446 if data.get('environment_data'):
447 fields['environment'] = data.get('environment_data')
448
449 try:
450 api.heat.stack_update(self.request, stack_id=stack_id, **fields)
451 messages.info(request, _("Stack update started."))
452 return True
453 except Exception:
454 exceptions.handle(request)
455
456
457class PreviewStackForm(CreateStackForm):
458
459 class Meta(object):
460 name = _('Preview Stack Parameters')
461
462 def __init__(self, *args, **kwargs):
463 self.next_view = kwargs.pop('next_view')
464 super(CreateStackForm, self).__init__(*args, **kwargs)
465
466 def handle(self, request, data):
467 prefix_length = len(self.param_prefix)
468 params_list = [(k[prefix_length:], v) for (k, v) in data.items()
469 if k.startswith(self.param_prefix)]
470 fields = {
471 'stack_name': data.get('stack_name'),
472 'timeout_mins': data.get('timeout_mins'),
473 'disable_rollback': not(data.get('enable_rollback')),
474 'parameters': dict(params_list),
475 'files': json.loads(data.get('parameters')).get('files'),
476 'template': json.loads(data.get('parameters')).get('template')
477 }
478
479 if data.get('environment_data'):
480 fields['environment'] = data.get('environment_data')
481
482 try:
483 stack_preview = api.heat.stack_preview(self.request, **fields)
484 request.method = 'GET'
485 return self.next_view.as_view()(request,
486 stack_preview=stack_preview)
487 except Exception:
488 exceptions.handle(request)
diff --git a/openstack_dashboard/dashboards/project/stacks/mappings.py b/openstack_dashboard/dashboards/project/stacks/mappings.py
deleted file mode 100644
index eeab18c..0000000
--- a/openstack_dashboard/dashboards/project/stacks/mappings.py
+++ /dev/null
@@ -1,350 +0,0 @@
1# Licensed under the Apache License, Version 2.0 (the "License"); you may
2# not use this file except in compliance with the License. You may obtain
3# a copy of the License at
4#
5# http://www.apache.org/licenses/LICENSE-2.0
6#
7# Unless required by applicable law or agreed to in writing, software
8# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10# License for the specific language governing permissions and limitations
11# under the License.
12
13import json
14import logging
15
16from django.conf import settings
17from django.core.urlresolvers import reverse
18from django.template.defaultfilters import register
19from django.utils import html
20from django.utils import safestring
21import six
22import six.moves.urllib.parse as urlparse
23
24from openstack_dashboard.api import swift
25
26LOG = logging.getLogger(__name__)
27
28
29resource_urls = {
30 "AWS::AutoScaling::AutoScalingGroup": {
31 'link': 'horizon:project:stacks:detail'},
32 "AWS::CloudFormation::Stack": {
33 'link': 'horizon:project:stacks:detail'},
34 "AWS::EC2::Instance": {
35 'link': 'horizon:project:instances:detail'},
36 "AWS::EC2::InternetGateway": {
37 'link': 'horizon:project:networks:ports:detail'},
38 "AWS::EC2::NetworkInterface": {
39 'link': 'horizon:project:networks:ports:detail'},
40 "AWS::EC2::RouteTable": {
41 'link': 'horizon:project:routers:detail'},
42 "AWS::EC2::SecurityGroup": {
43 'link': 'horizon:project:security_groups:index'},
44 "AWS::EC2::Subnet": {
45 'link': 'horizon:project:networks:subnets:detail'},
46 "AWS::EC2::Volume": {
47 'link': 'horizon:project:volumes:detail'},
48 "AWS::EC2::VPC": {
49 'link': 'horizon:project:networks:detail'},
50 "AWS::S3::Bucket": {
51 'link': 'horizon:project:containers:index'},
52 "OS::Cinder::Volume": {
53 'link': 'horizon:project:volumes:detail'},
54 "OS::Heat::AccessPolicy": {
55 'link': 'horizon:project:stacks:detail'},
56 "OS::Heat::AutoScalingGroup": {
57 'link': 'horizon:project:stacks:detail'},
58 "OS::Heat::CloudConfig": {
59 'link': 'horizon:project:stacks:detail'},
60 "OS::Neutron::Firewall": {
61 'link': 'horizon:project:firewalls:firewalldetails'},
62 "OS::Neutron::FirewallPolicy": {
63 'link': 'horizon:project:firewalls:policydetails'},
64 "OS::Neutron::FirewallRule": {
65 'link': 'horizon:project:firewalls:ruledetails'},
66 "OS::Heat::HARestarter": {
67 'link': 'horizon:project:stacks:detail'},
68 "OS::Heat::InstanceGroup": {
69 'link': 'horizon:project:stacks:detail'},
70 "OS::Heat::MultipartMime": {
71 'link': 'horizon:project:stacks:detail'},
72 "OS::Heat::ResourceGroup": {
73 'link': 'horizon:project:stacks:detail'},
74 "OS::Heat::SoftwareConfig": {
75 'link': 'horizon:project:stacks:detail'},
76 "OS::Heat::StructuredConfig": {
77 'link': 'horizon:project:stacks:detail'},
78 "OS::Heat::StructuredDeployment": {
79 'link': 'horizon:project:stacks:detail'},
80 "OS::Heat::Stack": {
81 'link': 'horizon:project:stacks:detail'},
82 "OS::Heat::WaitCondition": {
83 'link': 'horizon:project:stacks:detail'},
84 "OS::Heat::WaitConditionHandle": {
85 'link': 'horizon:project:stacks:detail'},
86 "OS::Neutron::IKEPolicy": {
87 'link': 'horizon:project:vpn:ikepolicydetails'},
88 "OS::Neutron::IPsecPolicy": {
89 'link': 'horizon:project:vpn:ipsecpolicydetails'},
90 "OS::Neutron::IPsecSiteConnection": {
91 'link': 'horizon:project:vpn:ipsecsiteconnectiondetails'},
92 "OS::Neutron::Net": {
93 'link': 'horizon:project:networks:detail'},
94 "OS::Neutron::Port": {
95 'link': 'horizon:project:networks:ports:detail'},
96 "OS::Neutron::Router": {
97 'link': 'horizon:project:routers:detail'},
98 "OS::Neutron::Subnet": {
99 'link': 'horizon:project:networks:subnets:detail'},
100 "OS::Neutron::VPNService": {
101 'link': 'horizon:project:vpn:vpnservicedetails'},
102 "OS::Nova::KeyPair": {
103 'link': 'horizon:project:key_pairs:index'},
104 "OS::Nova::Server": {
105 'link': 'horizon:project:instances:detail'},
106 "OS::Swift::Container": {
107 'link': 'horizon:project:containers:index',
108 'format_pattern': '%s' + swift.FOLDER_DELIMITER},
109}
110
111
112def resource_to_url(resource):
113 if (not resource or
114 not resource.physical_resource_id or
115 not hasattr(resource, 'resource_type')):
116 return None
117
118 mapping = resource_urls.get(resource.resource_type, {})
119 try:
120 if 'link' not in mapping:
121 return None
122 format_pattern = mapping.get('format_pattern') or '%s'
123 rid = format_pattern % resource.physical_resource_id
124 url = reverse(mapping['link'], args=(rid,))
125 except Exception as e:
126 LOG.exception(e)
127 return None
128 return url
129
130
131@register.filter
132def stack_output(output):
133 if not output:
134 return u''
135 if isinstance(output, six.string_types):
136 parts = urlparse.urlsplit(output)
137 if parts.netloc and parts.scheme in ('http', 'https'):
138 url = html.escape(output)
139 safe_link = u'<a href="%s" target="_blank">%s</a>' % (url, url)
140 return safestring.mark_safe(safe_link)
141 if isinstance(output, dict) or isinstance(output, list):
142 output = json.dumps(output, indent=2)
143 return safestring.mark_safe(u'<pre>%s</pre>' % html.escape(output))
144
145static_url = getattr(settings, "STATIC_URL", "/static/")
146resource_images = {
147 'LB_FAILED': static_url + 'dashboard/img/lb-red.svg',
148 'LB_DELETE': static_url + 'dashboard/img/lb-red.svg',
149 'LB_IN_PROGRESS': static_url + 'dashboard/img/lb-gray.gif',
150 'LB_INIT': static_url + 'dashboard/img/lb-gray.svg',
151 'LB_COMPLETE': static_url + 'dashboard/img/lb-green.svg',
152 'DB_FAILED': static_url + 'dashboard/img/db-red.svg',
153 'DB_DELETE': static_url + 'dashboard/img/db-red.svg',
154 'DB_IN_PROGRESS': static_url + 'dashboard/img/db-gray.gif',
155 'DB_INIT': static_url + 'dashboard/img/db-gray.svg',
156 'DB_COMPLETE': static_url + 'dashboard/img/db-green.svg',
157 'STACK_FAILED': static_url + 'dashboard/img/stack-red.svg',
158 'STACK_DELETE': static_url + 'dashboard/img/stack-red.svg',
159 'STACK_IN_PROGRESS': static_url + 'dashboard/img/stack-gray.gif',
160 'STACK_INIT': static_url + 'dashboard/img/stack-gray.svg',
161 'STACK_COMPLETE': static_url + 'dashboard/img/stack-green.svg',
162 'SERVER_FAILED': static_url + 'dashboard/img/server-red.svg',
163 'SERVER_DELETE': static_url + 'dashboard/img/server-red.svg',
164 'SERVER_IN_PROGRESS': static_url + 'dashboard/img/server-gray.gif',
165 'SERVER_INIT': static_url + 'dashboard/img/server-gray.svg',
166 'SERVER_COMPLETE': static_url + 'dashboard/img/server-green.svg',
167 'ALARM_FAILED': static_url + 'dashboard/img/alarm-red.svg',
168 'ALARM_DELETE': static_url + 'dashboard/img/alarm-red.svg',
169 'ALARM_IN_PROGRESS': static_url + 'dashboard/img/alarm-gray.gif',
170 'ALARM_INIT': static_url + 'dashboard/img/alarm-gray.svg',
171 'ALARM_COMPLETE': static_url + 'dashboard/img/alarm-green.svg',
172 'VOLUME_FAILED': static_url + 'dashboard/img/volume-red.svg',
173 'VOLUME_DELETE': static_url + 'dashboard/img/volume-red.svg',
174 'VOLUME_IN_PROGRESS': static_url + 'dashboard/img/volume-gray.gif',
175 'VOLUME_INIT': static_url + 'dashboard/img/volume-gray.svg',
176 'VOLUME_COMPLETE': static_url + 'dashboard/img/volume-green.svg',
177 'IMAGE_FAILED': static_url + 'dashboard/img/image-red.svg',
178 'IMAGE_DELETE': static_url + 'dashboard/img/image-red.svg',
179 'IMAGE_IN_PROGRESS': static_url + 'dashboard/img/image-gray.gif',
180 'IMAGE_INIT': static_url + 'dashboard/img/image-gray.svg',
181 'IMAGE_COMPLETE': static_url + 'dashboard/img/image-green.svg',
182 'WAIT_FAILED': static_url + 'dashboard/img/wait-red.svg',
183 'WAIT_DELETE': static_url + 'dashboard/img/wait-red.svg',
184 'WAIT_IN_PROGRESS': static_url + 'dashboard/img/wait-gray.gif',
185 'WAIT_INIT': static_url + 'dashboard/img/wait-gray.svg',
186 'WAIT_COMPLETE': static_url + 'dashboard/img/wait-green.svg',
187 'FIREWALL_FAILED': static_url + 'dashboard/img/firewall-red.svg',
188 'FIREWALL_DELETE': static_url + 'dashboard/img/firewall-red.svg',
189 'FIREWALL_IN_PROGRESS': static_url + 'dashboard/img/firewall-gray.gif',
190 'FIREWALL_INIT': static_url + 'dashboard/img/firewall-gray.svg',
191 'FIREWALL_COMPLETE': static_url + 'dashboard/img/firewall-green.svg',
192 'FLOATINGIP_FAILED': static_url + 'dashboard/img/floatingip-red.svg',
193 'FLOATINGIP_DELETE': static_url + 'dashboard/img/floatingip-red.svg',
194 'FLOATINGIP_IN_PROGRESS': static_url + 'dashboard/img/floatingip-gray.gif',
195 'FLOATINGIP_INIT': static_url + 'dashboard/img/floatingip-gray.svg',
196 'FLOATINGIP_COMPLETE': static_url + 'dashboard/img/floatingip-green.svg',
197 'ROUTER_FAILED': static_url + 'dashboard/img/router-red.svg',
198 'ROUTER_DELETE': static_url + 'dashboard/img/router-red.svg',
199 'ROUTER_IN_PROGRESS': static_url + 'dashboard/img/router-gray.gif',
200 'ROUTER_INIT': static_url + 'dashboard/img/router-gray.svg',
201 'ROUTER_COMPLETE': static_url + 'dashboard/img/router-green.svg',
202 'POLICY_FAILED': static_url + 'dashboard/img/policy-red.svg',
203 'POLICY_DELETE': static_url + 'dashboard/img/policy-red.svg',
204 'POLICY_IN_PROGRESS': static_url + 'dashboard/img/policy-gray.gif',
205 'POLICY_INIT': static_url + 'dashboard/img/policy-gray.svg',
206 'POLICY_COMPLETE': static_url + 'dashboard/img/policy-green.svg',
207 'CONFIG_FAILED': static_url + 'dashboard/img/config-red.svg',
208 'CONFIG_DELETE': static_url + 'dashboard/img/config-red.svg',
209 'CONFIG_IN_PROGRESS': static_url + 'dashboard/img/config-gray.gif',
210 'CONFIG_INIT': static_url + 'dashboard/img/config-gray.svg',
211 'CONFIG_COMPLETE': static_url + 'dashboard/img/config-green.svg',
212 'NETWORK_FAILED': static_url + 'dashboard/img/network-red.svg',
213 'NETWORK_DELETE': static_url + 'dashboard/img/network-red.svg',
214 'NETWORK_IN_PROGRESS': static_url + 'dashboard/img/network-gray.gif',
215 'NETWORK_INIT': static_url + 'dashboard/img/network-gray.svg',
216 'NETWORK_COMPLETE': static_url + 'dashboard/img/network-green.svg',
217 'PORT_FAILED': static_url + 'dashboard/img/port-red.svg',
218 'PORT_DELETE': static_url + 'dashboard/img/port-red.svg',
219 'PORT_IN_PROGRESS': static_url + 'dashboard/img/port-gray.gif',
220 'PORT_INIT': static_url + 'dashboard/img/port-gray.svg',
221 'PORT_COMPLETE': static_url + 'dashboard/img/port-green.svg',
222 'SECURITYGROUP_FAILED': static_url + 'dashboard/img/securitygroup-red.svg',
223 'SECURITYGROUP_DELETE': static_url + 'dashboard/img/securitygroup-red.svg',
224 'SECURITYGROUP_IN_PROGRESS':
225 static_url + 'dashboard/img/securitygroup-gray.gif',
226 'SECURITYGROUP_INIT': static_url + 'dashboard/img/securitygroup-gray.svg',
227 'SECURITYGROUP_COMPLETE':
228 static_url + 'dashboard/img/securitygroup-green.svg',
229 'VPN_FAILED': static_url + 'dashboard/img/vpn-red.svg',
230 'VPN_DELETE': static_url + 'dashboard/img/vpn-red.svg',
231 'VPN_IN_PROGRESS': static_url + 'dashboard/img/vpn-gray.gif',
232 'VPN_INIT': static_url + 'dashboard/img/vpn-gray.svg',
233 'VPN_COMPLETE': static_url + 'dashboard/img/vpn-green.svg',
234 'FLAVOR_FAILED': static_url + 'dashboard/img/flavor-red.svg',
235 'FLAVOR_DELETE': static_url + 'dashboard/img/flavor-red.svg',
236 'FLAVOR_IN_PROGRESS': static_url + 'dashboard/img/flavor-gray.gif',
237 'FLAVOR_INIT': static_url + 'dashboard/img/flavor-gray.svg',
238 'FLAVOR_COMPLETE': static_url + 'dashboard/img/flavor-green.svg',
239 'KEYPAIR_FAILED': static_url + 'dashboard/img/keypair-red.svg',
240 'KEYPAIR_DELETE': static_url + 'dashboard/img/keypair-red.svg',
241 'KEYPAIR_IN_PROGRESS': static_url + 'dashboard/img/keypair-gray.gif',
242 'KEYPAIR_INIT': static_url + 'dashboard/img/keypair-gray.svg',
243 'KEYPAIR_COMPLETE': static_url + 'dashboard/img/keypair-green.svg',
244 'UNKNOWN_FAILED': static_url + 'dashboard/img/unknown-red.svg',
245 'UNKNOWN_DELETE': static_url + 'dashboard/img/unknown-red.svg',
246 'UNKNOWN_IN_PROGRESS': static_url + 'dashboard/img/unknown-gray.gif',
247 'UNKNOWN_INIT': static_url + 'dashboard/img/unknown-gray.svg',
248 'UNKNOWN_COMPLETE': static_url + 'dashboard/img/unknown-green.svg',
249}
250
251
252resource_types = {
253 # LB
254 'LoadBalance': 'LB',
255 'HealthMonitor': 'LB',
256 'PoolMember': 'LB',
257 'Pool': 'LB',
258 # DB
259 'DBInstance': 'DB',
260 'Database': 'DB',
261 # SERVER
262 'Instance': 'SERVER',
263 'Server': 'SERVER',
264 # ALARM
265 'Alarm': 'ALARM',
266 'CombinationAlarm': 'ALARM',
267 'CWLiteAlarm': 'ALARM',
268 # VOLUME
269 'Volume': 'VOLUME',
270 'VolumeAttachment': 'VOLUME',
271 # STACK
272 'stack': 'STACK',
273 'AutoScalingGroup': 'STACK',
274 'InstanceGroup': 'STACK',
275 'ServerGroup': 'STACK',
276 'ResourceGroup': 'STACK',
277 # IMAGE
278 'Image': 'IMAGE',
279 # WAIT
280 'WaitCondition': 'WAIT',
281 'WaitConditionHandle': 'WAIT',
282 'UpdateWaitConditionHandle': 'WAIT',
283 # FIREWALL
284 'Firewall': 'FIREWALL',
285 'FirewallPolicy': 'FIREWALL',
286 'FirewallRule': 'FIREWALL',
287 # FLOATINGIP
288 'FloatingIP': 'FLOATINGIP',
289 'FloatingIPAssociation': 'FLOATINGIP',
290 # ROUTER
291 'Router': 'ROUTER',
292 'RouterGateway': 'ROUTER',
293 'RouterInterface': 'ROUTER',
294 # POLICY
295 'ScalingPolicy': 'POLICY',
296 # CONFIG
297 'CloudConfig': 'CONFIG',
298 'MultipartMime': 'CONFIG',
299 'SoftwareConfig': 'CONFIG',
300 'SoftwareDeployment': 'CONFIG',
301 'StructuredConfig': 'CONFIG',
302 'StructuredDeployment': 'CONFIG',
303 # NETWORK
304 'Net': 'NETWORK',
305 'Subnet': 'NETWORK',
306 'NetworkGateway': 'NETWORK',
307 'ProviderNet': 'NETWORK',
308 # PORT
309 'Port': 'PORT',
310 # SECURITYGROUP
311 'SecurityGroup': 'SECURITYGROUP',
312 # VPN
313 'VPNService': 'VPN',
314 # FLAVOR
315 'Flavor': 'FLAVOR',
316 # KEYPAIR
317 'KeyPair': 'KEYPAIR',
318}
319
320
321def get_resource_type(type):
322 for key, value in resource_types.items():
323 if key in type:
324 return value
325
326 return 'UNKNOWN'
327
328
329def get_resource_status(status):
330 if ('IN_PROGRESS' in status):
331 return 'IN_PROGRESS'
332 elif ('FAILED' in status):
333 return 'FAILED'
334 elif ('DELETE' in status):
335 return 'DELETE'
336 elif ('INIT' in status):
337 return 'INIT'
338 else:
339 return 'COMPLETE'
340
341
342def get_resource_image(status, type):
343 """Sets the image url and in_progress action sw based on status."""
344 resource_type = get_resource_type(type)
345 resource_status = get_resource_status(status)
346 resource_state = resource_type + "_" + resource_status
347
348 for key in resource_images:
349 if key == resource_state:
350 return resource_images.get(key)
diff --git a/openstack_dashboard/dashboards/project/stacks/panel.py b/openstack_dashboard/dashboards/project/stacks/panel.py
deleted file mode 100644
index f9e8800..0000000
--- a/openstack_dashboard/dashboards/project/stacks/panel.py
+++ /dev/null
@@ -1,21 +0,0 @@
1# Licensed under the Apache License, Version 2.0 (the "License"); you may
2# not use this file except in compliance with the License. You may obtain
3# a copy of the License at
4#
5# http://www.apache.org/licenses/LICENSE-2.0
6#
7# Unless required by applicable law or agreed to in writing, software
8# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10# License for the specific language governing permissions and limitations
11# under the License.
12
13from django.utils.translation import ugettext_lazy as _
14
15import horizon
16
17
18class Stacks(horizon.Panel):
19 name = _("Stacks")
20 slug = "stacks"
21 permissions = ('openstack.services.orchestration',)
diff --git a/openstack_dashboard/dashboards/project/stacks/resource_types/__init__.py b/openstack_dashboard/dashboards/project/stacks/resource_types/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/openstack_dashboard/dashboards/project/stacks/resource_types/__init__.py
+++ /dev/null
diff --git a/openstack_dashboard/dashboards/project/stacks/resource_types/panel.py b/openstack_dashboard/dashboards/project/stacks/resource_types/panel.py
deleted file mode 100644
index b1f3744..0000000
--- a/openstack_dashboard/dashboards/project/stacks/resource_types/panel.py
+++ /dev/null
@@ -1,23 +0,0 @@
1# Licensed under the Apache License, Version 2.0 (the "License");
2# you may not use this file except in compliance with the License.
3# You may obtain a copy of the License at
4#
5# http://www.apache.org/licenses/LICENSE-2.0
6#
7# Unless required by applicable law or agreed to in writing, software
8# distributed under the License is distributed on an "AS IS" BASIS,
9# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
10# implied.
11# See the License for the specific language governing permissions and
12# limitations under the License.
13
14from django.utils.translation import ugettext_lazy as _
15
16import horizon
17
18
19class ResourceTypes(horizon.Panel):
20 name = _("Resource Types")
21 slug = "stacks.resource_types"
22 permissions = ('openstack.services.orchestration',)
23 policy_rules = (("orchestration", "stacks:list_resource_types"),)
diff --git a/openstack_dashboard/dashboards/project/stacks/resource_types/tables.py b/openstack_dashboard/dashboards/project/stacks/resource_types/tables.py
deleted file mode 100644
index 66e7702..0000000
--- a/openstack_dashboard/dashboards/project/stacks/resource_types/tables.py
+++ /dev/null
@@ -1,36 +0,0 @@
1# Licensed under the Apache License, Version 2.0 (the "License");
2# you may not use this file except in compliance with the License.
3# You may obtain a copy of the License at
4#
5# http://www.apache.org/licenses/LICENSE-2.0
6#
7# Unless required by applicable law or agreed to in writing, software
8# distributed under the License is distributed on an "AS IS" BASIS,
9# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
10# implied.
11# See the License for the specific language governing permissions and
12# limitations under the License.
13
14from django.utils.translation import ugettext_lazy as _
15
16from horizon import tables
17
18
19class ResourceTypesFilterAction(tables.FilterAction):
20 filter_type = 'server'
21 filter_choices = (('name', _('Type ='), True, _("Case sensitive")),)
22
23
24class ResourceTypesTable(tables.DataTable):
25 name = tables.Column("resource_type",
26 verbose_name=_("Type"),
27 link="horizon:project:stacks.resource_types:details",)
28
29 def get_object_id(self, resource):
30 return resource.resource_type
31
32 class Meta(object):
33 name = "resource_types"
34 verbose_name = _("Resource Types")
35 table_actions = (ResourceTypesFilterAction,)
36 multi_select = False
diff --git a/openstack_dashboard/dashboards/project/stacks/resource_types/tabs.py b/openstack_dashboard/dashboards/project/stacks/resource_types/tabs.py
deleted file mode 100644
index b0d5467..0000000
--- a/openstack_dashboard/dashboards/project/stacks/resource_types/tabs.py
+++ /dev/null
@@ -1,32 +0,0 @@
1# Licensed under the Apache License, Version 2.0 (the "License");
2# you may not use this file except in compliance with the License.
3# You may obtain a copy of the License at
4#
5# http://www.apache.org/licenses/LICENSE-2.0
6#
7# Unless required by applicable law or agreed to in writing, software
8# distributed under the License is distributed on an "AS IS" BASIS,
9# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
10# implied.
11# See the License for the specific language governing permissions and
12# limitations under the License.
13
14from django.utils.translation import ugettext_lazy as _
15
16from horizon import tabs
17
18
19class ResourceTypeOverviewTab(tabs.Tab):
20 name = _("Overview")
21 slug = "resource_type_overview"
22 template_name = "project/stacks.resource_types/_details.html"
23
24 def get_context_data(self, request):
25 return {"r_type": self.tab_group.kwargs['rt'],
26 "r_type_attributes": self.tab_group.kwargs['rt_attributes'],
27 "r_type_properties": self.tab_group.kwargs['rt_properties']}
28
29
30class ResourceTypeDetailsTabs(tabs.TabGroup):
31 slug = "resource_type_details"
32 tabs = (ResourceTypeOverviewTab,)
diff --git a/openstack_dashboard/dashboards/project/stacks/resource_types/templates/stacks.resource_types/_details.html b/openstack_dashboard/dashboards/project/stacks/resource_types/templates/stacks.resource_types/_details.html
deleted file mode 100644
index 704787e..0000000
--- a/openstack_dashboard/dashboards/project/stacks/resource_types/templates/stacks.resource_types/_details.html
+++ /dev/null
@@ -1,15 +0,0 @@
1{% load i18n %}
2
3<div class="detail">
4 <dl>
5 <dd>{{ r_type }}</dd>
6 </dl>
7
8 <h4>{% trans "Attributes" %}</h4>
9 <pre>{{ r_type_attributes }}
10 </pre>
11
12 <h4>{% trans "Properties" %}</h4>
13 <pre>{{ r_type_properties }}
14 </pre>
15</div>
diff --git a/openstack_dashboard/dashboards/project/stacks/resource_types/tests.py b/openstack_dashboard/dashboards/project/stacks/resource_types/tests.py
deleted file mode 100644
index 4c73644..0000000
--- a/openstack_dashboard/dashboards/project/stacks/resource_types/tests.py
+++ /dev/null
@@ -1,52 +0,0 @@
1# Licensed under the Apache License, Version 2.0 (the "License");
2# you may not use this file except in compliance with the License.
3# You may obtain a copy of the License at
4#
5# http://www.apache.org/licenses/LICENSE-2.0
6#
7# Unless required by applicable law or agreed to in writing, software
8# distributed under the License is distributed on an "AS IS" BASIS,
9# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
10# implied.
11# See the License for the specific language governing permissions and
12# limitations under the License.
13
14from django.core.urlresolvers import reverse
15from django import http
16
17from mox3.mox import IsA
18
19from openstack_dashboard import api
20from openstack_dashboard.test import helpers as test
21
22
23class ResourceTypesTests(test.TestCase):
24
25 @test.create_stubs({api.heat: ('resource_types_list',)})
26 def test_index(self):
27 filters = {}
28 api.heat.resource_types_list(
29 IsA(http.HttpRequest), filters=filters).AndReturn(
30 self.resource_types.list())
31 self.mox.ReplayAll()
32
33 res = self.client.get(
34 reverse('horizon:project:stacks.resource_types:index'))
35 self.assertTemplateUsed(
36 res, 'horizon/common/_data_table_view.html')
37 self.assertContains(res, 'AWS::CloudFormation::Stack')
38
39 @test.create_stubs({api.heat: ('resource_type_get',)})
40 def test_detail_view(self):
41 rt = self.api_resource_types.first()
42
43 api.heat.resource_type_get(
44 IsA(http.HttpRequest), rt['resource_type']).AndReturn(rt)
45 self.mox.ReplayAll()
46
47 url = reverse('horizon:project:stacks.resource_types:details',
48 args=[rt['resource_type']])
49 res = self.client.get(url)
50
51 self.assertTemplateUsed(res, 'horizon/common/_detail.html')
52 self.assertNoMessages()
diff --git a/openstack_dashboard/dashboards/project/stacks/resource_types/urls.py b/openstack_dashboard/dashboards/project/stacks/resource_types/urls.py
deleted file mode 100644
index 8ab8cb0..0000000
--- a/openstack_dashboard/dashboards/project/stacks/resource_types/urls.py
+++ /dev/null
@@ -1,22 +0,0 @@
1# Licensed under the Apache License, Version 2.0 (the "License");
2# you may not use this file except in compliance with the License.
3# You may obtain a copy of the License at
4#
5# http://www.apache.org/licenses/LICENSE-2.0
6#
7# Unless required by applicable law or agreed to in writing, software
8# distributed under the License is distributed on an "AS IS" BASIS,
9# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
10# implied.
11# See the License for the specific language governing permissions and
12# limitations under the License.
13
14from django.conf.urls import url
15
16from openstack_dashboard.dashboards.project.stacks.resource_types import views
17
18urlpatterns = [
19 url(r'^$', views.ResourceTypesView.as_view(), name='index'),
20 url(r'^(?P<resource_type>[^/]+)/$',
21 views.DetailView.as_view(), name='details'),
22]
diff --git a/openstack_dashboard/dashboards/project/stacks/resource_types/views.py b/openstack_dashboard/dashboards/project/stacks/resource_types/views.py
deleted file mode 100644
index 8afcdf8..0000000
--- a/openstack_dashboard/dashboards/project/stacks/resource_types/views.py
+++ /dev/null
@@ -1,78 +0,0 @@
1# Licensed under the Apache License, Version 2.0 (the "License");
2# you may not use this file except in compliance with the License.
3# You may obtain a copy of the License at
4#
5# http://www.apache.org/licenses/LICENSE-2.0
6#
7# Unless required by applicable law or agreed to in writing, software
8# distributed under the License is distributed on an "AS IS" BASIS,
9# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
10# implied.
11# See the License for the specific language governing permissions and
12# limitations under the License.
13
14import yaml
15
16from django.core.urlresolvers import reverse
17from django.utils.translation import ugettext_lazy as _
18
19from horizon import exceptions
20from horizon import tables
21from horizon import tabs
22
23from openstack_dashboard import api
24import openstack_dashboard.dashboards.project.stacks.resource_types.tables \
25 as project_tables
26import openstack_dashboard.dashboards.project.stacks.resource_types.tabs \
27 as project_tabs
28
29
30class ResourceTypesView(tables.DataTableView):
31 table_class = project_tables.ResourceTypesTable
32 page_title = _("Resource Types")
33
34 def get_data(self):
35 try:
36 filters = self.get_filters()
37 if 'name' in filters:
38 filters['name'] = '.*' + filters['name']
39 r_types = sorted(api.heat.resource_types_list(self.request,
40 filters=filters),
41 key=lambda resource: resource.resource_type)
42 except Exception:
43 r_types = []
44 msg = _('Unable to retrieve stack resource types.')
45 exceptions.handle(self.request, msg)
46 return r_types
47
48
49class DetailView(tabs.TabView):
50 tab_group_class = project_tabs.ResourceTypeDetailsTabs
51 template_name = 'horizon/common/_detail.html'
52 page_title = "{{ resource_type }}"
53
54 def get_resource_type(self, request, **kwargs):
55 try:
56 resource_type_overview = api.heat.resource_type_get(
57 request,
58 kwargs['resource_type'])
59 return resource_type_overview
60 except Exception:
61 msg = _('Unable to retrieve resource type details.')
62 exceptions.handle(request, msg, redirect=self.get_redirect_url())
63
64 def get_tabs(self, request, **kwargs):
65 resource_type_overview = self.get_resource_type(request, **kwargs)
66 r_type = resource_type_overview['resource_type']
67 r_type_attributes = resource_type_overview['attributes']
68 r_type_properties = resource_type_overview['properties']
69 return self.tab_group_class(
70 request,
71 rt=r_type,
72 rt_attributes=yaml.safe_dump(r_type_attributes, indent=2),
73 rt_properties=yaml.safe_dump(r_type_properties, indent=2),
74 **kwargs)
75
76 @staticmethod
77 def get_redirect_url():
78 return reverse('horizon:project:stacks.resources:index')
diff --git a/openstack_dashboard/dashboards/project/stacks/sro.py b/openstack_dashboard/dashboards/project/stacks/sro.py
deleted file mode 100644
index 4250e7e..0000000
--- a/openstack_dashboard/dashboards/project/stacks/sro.py
+++ /dev/null
@@ -1,44 +0,0 @@
1# Licensed under the Apache License, Version 2.0 (the "License"); you may
2# not use this file except in compliance with the License. You may obtain
3# a copy of the License at
4#
5# http://www.apache.org/licenses/LICENSE-2.0
6#
7# Unless required by applicable law or agreed to in writing, software
8# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10# License for the specific language governing permissions and limitations
11# under the License.
12
13from django.template.defaultfilters import title
14from django.template.loader import render_to_string
15
16from horizon.utils import filters
17
18
19def stack_info(stack, stack_image):
20 stack.stack_status_desc = title(
21 filters.replace_underscores(stack.stack_status))
22 if stack.stack_status_reason:
23 stack.stack_status_reason = title(
24 filters.replace_underscores(stack.stack_status_reason)
25 )
26 context = {}
27 context['stack'] = stack
28 context['stack_image'] = stack_image
29 return render_to_string('project/stacks/_stack_info.html',
30 context)
31
32
33def resource_info(resource):
34 resource.resource_status_desc = title(
35 filters.replace_underscores(resource.resource_status)
36 )
37 if resource.resource_status_reason:
38 resource.resource_status_reason = title(
39 filters.replace_underscores(resource.resource_status_reason)
40 )
41 context = {}
42 context['resource'] = resource
43 return render_to_string('project/stacks/_resource_info.html',
44 context)
diff --git a/openstack_dashboard/dashboards/project/stacks/tables.py b/openstack_dashboard/dashboards/project/stacks/tables.py
deleted file mode 100644
index 96391ab..0000000
--- a/openstack_dashboard/dashboards/project/stacks/tables.py
+++ /dev/null
@@ -1,413 +0,0 @@
1# Licensed under the Apache License, Version 2.0 (the "License"); you may
2# not use this file except in compliance with the License. You may obtain
3# a copy of the License at
4#
5# http://www.apache.org/licenses/LICENSE-2.0
6#
7# Unless required by applicable law or agreed to in writing, software
8# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10# License for the specific language governing permissions and limitations
11# under the License.
12
13from django.core import urlresolvers
14from django.http import Http404
15from django.template.defaultfilters import title
16from django.utils.translation import pgettext_lazy
17from django.utils.translation import ugettext_lazy as _
18from django.utils.translation import ungettext_lazy
19from heatclient import exc
20
21from horizon import exceptions
22from horizon import messages
23from horizon import tables
24from horizon.utils import filters
25
26from openstack_dashboard import api
27from openstack_dashboard.dashboards.project.stacks import mappings
28
29
30class LaunchStack(tables.LinkAction):
31 name = "launch"
32 verbose_name = _("Launch Stack")
33 url = "horizon:project:stacks:select_template"
34 classes = ("ajax-modal",)
35 icon = "plus"
36 policy_rules = (("orchestration", "stacks:validate_template"),
37 ("orchestration", "stacks:create"),)
38
39
40class PreviewStack(tables.LinkAction):
41 name = "preview"
42 verbose_name = _("Preview Stack")
43 url = "horizon:project:stacks:preview_template"
44 classes = ("ajax-modal",)
45 icon = "eye"
46 policy_rules = (("orchestration", "stacks:validate_template"),
47 ("orchestration", "stacks:preview"),)
48
49
50class CheckStack(tables.BatchAction):
51 name = "check"
52 verbose_name = _("Check Stack")
53 policy_rules = (("orchestration", "actions:action"),)
54 icon = "check-square"
55
56 @staticmethod
57 def action_present(count):
58 return ungettext_lazy(
59 u"Check Stack",
60 u"Check Stacks",
61 count
62 )
63
64 @staticmethod
65 def action_past(count):
66 return ungettext_lazy(
67 u"Checked Stack",
68 u"Checked Stacks",
69 count
70 )
71
72 def action(self, request, stack_id):
73 api.heat.action_check(request, stack_id)
74
75
76class SuspendStack(tables.BatchAction):
77 name = "suspend"
78 verbose_name = _("Suspend Stack")
79 policy_rules = (("orchestration", "actions:action"),)
80 icon = "pause"
81
82 @staticmethod
83 def action_present(count):
84 return ungettext_lazy(
85 u"Suspend Stack",
86 u"Suspend Stacks",
87 count
88 )
89
90 @staticmethod
91 def action_past(count):
92 return ungettext_lazy(
93 u"Suspended Stack",
94 u"Suspended Stacks",
95 count
96 )
97
98 def action(self, request, stack_id):
99 try:
100 api.heat.action_suspend(request, stack_id)
101 except Exception:
102 msg = _('Failed to suspend stack.')
103 exceptions.handle(request, msg)
104
105
106class ResumeStack(tables.BatchAction):
107 name = "resume"
108 verbose_name = _("Resume Stack")
109 policy_rules = (("orchestration", "actions:action"),)
110 icon = "play"
111
112 @staticmethod
113 def action_present(count):
114 return ungettext_lazy(
115 u"Resume Stack",
116 u"Resume Stacks",
117 count
118 )
119
120 @staticmethod
121 def action_past(count):
122 return ungettext_lazy(
123 u"Resumed Stack",
124 u"Resumed Stacks",
125 count
126 )
127
128 def action(self, request, stack_id):
129 try:
130 api.heat.action_resume(request, stack_id)
131 except Exception:
132 msg = _('Failed to resume stack.')
133 exceptions.handle(request, msg)
134
135
136class ChangeStackTemplate(tables.LinkAction):
137 name = "edit"
138 verbose_name = _("Change Stack Template")
139 url = "horizon:project:stacks:change_template"
140 classes = ("ajax-modal",)
141 icon = "pencil"
142
143 def get_link_url(self, stack):
144 return urlresolvers.reverse(self.url, args=[stack.id])
145
146
147class DeleteStack(tables.DeleteAction):
148 @staticmethod
149 def action_present(count):
150 return ungettext_lazy(
151 u"Delete Stack",
152 u"Delete Stacks",
153 count
154 )
155
156 @staticmethod
157 def action_past(count):
158 return ungettext_lazy(
159 u"Deleted Stack",
160 u"Deleted Stacks",
161 count
162 )
163
164 policy_rules = (("orchestration", "stacks:delete"),)
165
166 def delete(self, request, stack_id):
167 try:
168 api.heat.stack_delete(request, stack_id)
169 except Exception:
170 msg = _('Failed to delete stack.')
171 exceptions.handle(request, msg)
172
173 def allowed(self, request, stack):
174 if stack is not None:
175 return stack.stack_status != 'DELETE_COMPLETE'
176 return True
177
178
179class StacksUpdateRow(tables.Row):
180 ajax = True
181
182 def can_be_selected(self, datum):
183 return datum.stack_status != 'DELETE_COMPLETE'
184
185 def get_data(self, request, stack_id):
186 try:
187 stack = api.heat.stack_get(request, stack_id)
188 if stack.stack_status == 'DELETE_COMPLETE':
189 # returning 404 to the ajax call removes the
190 # row from the table on the ui
191 raise Http404
192 return stack
193 except Http404:
194 raise
195 except Exception as e:
196 messages.error(request, e)
197 raise
198
199
200class StacksFilterAction(tables.FilterAction):
201 filter_type = 'server'
202 filter_choices = (('name', _('Stack Name ='), True, _('Case-sensitive')),
203 ('id', _('Stack ID ='), True),
204 ('status', _('Status ='), True))
205
206
207class StacksTable(tables.DataTable):
208 STATUS_CHOICES = (
209 ("Complete", True),
210 ("Failed", False),
211 )
212 STACK_STATUS_DISPLAY_CHOICES = (
213 ("init_in_progress", pgettext_lazy("current status of stack",
214 u"Init In Progress")),
215 ("init_complete", pgettext_lazy("current status of stack",
216 u"Init Complete")),
217 ("init_failed", pgettext_lazy("current status of stack",
218 u"Init Failed")),
219 ("create_in_progress", pgettext_lazy("current status of stack",
220 u"Create In Progress")),
221 ("create_complete", pgettext_lazy("current status of stack",
222 u"Create Complete")),
223 ("create_failed", pgettext_lazy("current status of stack",
224 u"Create Failed")),
225 ("delete_in_progress", pgettext_lazy("current status of stack",
226 u"Delete In Progress")),
227 ("delete_complete", pgettext_lazy("current status of stack",
228 u"Delete Complete")),
229 ("delete_failed", pgettext_lazy("current status of stack",
230 u"Delete Failed")),
231 ("update_in_progress", pgettext_lazy("current status of stack",
232 u"Update In Progress")),
233 ("update_complete", pgettext_lazy("current status of stack",
234 u"Update Complete")),
235 ("update_failed", pgettext_lazy("current status of stack",
236 u"Update Failed")),
237 ("rollback_in_progress", pgettext_lazy("current status of stack",
238 u"Rollback In Progress")),
239 ("rollback_complete", pgettext_lazy("current status of stack",
240 u"Rollback Complete")),
241 ("rollback_failed", pgettext_lazy("current status of stack",
242 u"Rollback Failed")),
243 ("suspend_in_progress", pgettext_lazy("current status of stack",
244 u"Suspend In Progress")),
245 ("suspend_complete", pgettext_lazy("current status of stack",
246 u"Suspend Complete")),
247 ("suspend_failed", pgettext_lazy("current status of stack",
248 u"Suspend Failed")),
249 ("resume_in_progress", pgettext_lazy("current status of stack",
250 u"Resume In Progress")),
251 ("resume_complete", pgettext_lazy("current status of stack",
252 u"Resume Complete")),
253 ("resume_failed", pgettext_lazy("current status of stack",
254 u"Resume Failed")),
255 ("adopt_in_progress", pgettext_lazy("current status of stack",
256 u"Adopt In Progress")),
257 ("adopt_complete", pgettext_lazy("current status of stack",
258 u"Adopt Complete")),
259 ("adopt_failed", pgettext_lazy("current status of stack",
260 u"Adopt Failed")),
261 ("snapshot_in_progress", pgettext_lazy("current status of stack",
262 u"Snapshot In Progress")),
263 ("snapshot_complete", pgettext_lazy("current status of stack",
264 u"Snapshot Complete")),
265 ("snapshot_failed", pgettext_lazy("current status of stack",
266 u"Snapshot Failed")),
267 ("check_in_progress", pgettext_lazy("current status of stack",
268 u"Check In Progress")),
269 ("check_complete", pgettext_lazy("current status of stack",
270 u"Check Complete")),
271 ("check_failed", pgettext_lazy("current status of stack",
272 u"Check Failed")),
273 )
274 name = tables.Column("stack_name",
275 verbose_name=_("Stack Name"),
276 link="horizon:project:stacks:detail",)
277 created = tables.Column("creation_time",
278 verbose_name=_("Created"),
279 filters=(filters.parse_isotime,
280 filters.timesince_sortable),
281 attrs={'data-type': 'timesince'})
282 updated = tables.Column("updated_time",
283 verbose_name=_("Updated"),
284 filters=(filters.parse_isotime,
285 filters.timesince_or_never))
286 status = tables.Column("status",
287 hidden=True,
288 status=True,
289 status_choices=STATUS_CHOICES)
290
291 stack_status = tables.Column("stack_status",
292 verbose_name=_("Status"),
293 display_choices=STACK_STATUS_DISPLAY_CHOICES)
294
295 def get_object_display(self, stack):
296 return stack.stack_name
297
298 class Meta(object):
299 name = "stacks"
300 verbose_name = _("Stacks")
301 pagination_param = 'stack_marker'
302 status_columns = ["status", ]
303 row_class = StacksUpdateRow
304 table_actions_menu = (CheckStack,
305 SuspendStack,
306 ResumeStack,)
307 table_actions = (LaunchStack,
308 PreviewStack,
309 DeleteStack,
310 StacksFilterAction,)
311 row_actions = (CheckStack,
312 SuspendStack,
313 ResumeStack,
314 ChangeStackTemplate,
315 DeleteStack,)
316
317
318def get_resource_url(obj):
319 if obj.physical_resource_id == obj.stack_id:
320 return None
321 return urlresolvers.reverse('horizon:project:stacks:resource',
322 args=(obj.stack_id, obj.resource_name))
323
324
325class EventsTable(tables.DataTable):
326
327 logical_resource = tables.Column('resource_name',
328 verbose_name=_("Stack Resource"),
329 link=get_resource_url)
330 physical_resource = tables.Column('physical_resource_id',
331 verbose_name=_("Resource"))
332 timestamp = tables.Column('event_time',
333 verbose_name=_("Time Since Event"),
334 filters=(filters.parse_isotime,
335 filters.timesince_or_never))
336 status = tables.Column("resource_status",
337 filters=(title, filters.replace_underscores),
338 verbose_name=_("Status"),)
339
340 statusreason = tables.Column("resource_status_reason",
341 verbose_name=_("Status Reason"),)
342
343 class Meta(object):
344 name = "events"
345 verbose_name = _("Stack Events")
346
347
348class ResourcesUpdateRow(tables.Row):
349 ajax = True
350
351 def get_data(self, request, resource_name):
352 try:
353 stack = self.table.stack
354 stack_identifier = '%s/%s' % (stack.stack_name, stack.id)
355 return api.heat.resource_get(
356 request, stack_identifier, resource_name)
357 except exc.HTTPNotFound:
358 # returning 404 to the ajax call removes the
359 # row from the table on the ui
360 raise Http404
361 except Exception as e:
362 messages.error(request, e)
363
364
365class ResourcesTable(tables.DataTable):
366 class StatusColumn(tables.Column):
367 def get_raw_data(self, datum):
368 return datum.resource_status.partition("_")[2]
369
370 STATUS_CHOICES = (
371 ("Complete", True),
372 ("Failed", False),
373 )
374 STATUS_DISPLAY_CHOICES = StacksTable.STACK_STATUS_DISPLAY_CHOICES
375
376 logical_resource = tables.Column('resource_name',
377 verbose_name=_("Stack Resource"),
378 link=get_resource_url)
379 physical_resource = tables.Column('physical_resource_id',
380 verbose_name=_("Resource"),
381 link=mappings.resource_to_url)
382 resource_type = tables.Column("resource_type",
383 verbose_name=_("Stack Resource Type"),)
384 updated_time = tables.Column('updated_time',
385 verbose_name=_("Date Updated"),
386 filters=(filters.parse_isotime,
387 filters.timesince_or_never))
388 status = tables.Column("resource_status",
389 verbose_name=_("Status"),
390 display_choices=STATUS_DISPLAY_CHOICES)
391
392 statusreason = tables.Column("resource_status_reason",
393 verbose_name=_("Status Reason"),)
394
395 status_hidden = StatusColumn("status",
396 hidden=True,
397 status=True,
398 status_choices=STATUS_CHOICES)
399
400 def __init__(self, request, data=None,
401 needs_form_wrapper=None, **kwargs):
402 super(ResourcesTable, self).__init__(
403 request, data, needs_form_wrapper, **kwargs)
404 self.stack = kwargs['stack']
405
406 def get_object_id(self, datum):
407 return datum.resource_name
408
409 class Meta(object):
410 name = "resources"
411 verbose_name = _("Stack Resources")
412 status_columns = ["status_hidden", ]
413 row_class = ResourcesUpdateRow
diff --git a/openstack_dashboard/dashboards/project/stacks/tabs.py b/openstack_dashboard/dashboards/project/stacks/tabs.py
deleted file mode 100644
index 1e6c92b..0000000
--- a/openstack_dashboard/dashboards/project/stacks/tabs.py
+++ /dev/null
@@ -1,173 +0,0 @@
1# Licensed under the Apache License, Version 2.0 (the "License"); you may
2# not use this file except in compliance with the License. You may obtain
3# a copy of the License at
4#
5# http://www.apache.org/licenses/LICENSE-2.0
6#
7# Unless required by applicable law or agreed to in writing, software
8# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10# License for the specific language governing permissions and limitations
11# under the License.
12
13import logging
14
15from django.utils.translation import ugettext_lazy as _
16
17from horizon import messages
18from horizon import tabs
19from openstack_dashboard import api
20from openstack_dashboard import policy
21
22from openstack_dashboard.dashboards.project.stacks \
23 import api as project_api
24from openstack_dashboard.dashboards.project.stacks import mappings
25from openstack_dashboard.dashboards.project.stacks \
26 import tables as project_tables
27
28
29LOG = logging.getLogger(__name__)
30
31
32class StackTopologyTab(tabs.Tab):
33 name = _("Topology")
34 slug = "topology"
35 template_name = "project/stacks/_detail_topology.html"
36 preload = False
37
38 def allowed(self, request):
39 return policy.check(
40 (("orchestration", "stacks:template"),
41 ("orchestration", "stacks:lookup"),
42 ("orchestration", "stacks:show"),
43 ("orchestration", "resource:index"),),
44 request)
45
46 def get_context_data(self, request):
47 context = {}
48 stack = self.tab_group.kwargs['stack']
49 context['stack_id'] = stack.id
50 context['d3_data'] = project_api.d3_data(request, stack_id=stack.id)
51 return context
52
53
54class StackOverviewTab(tabs.Tab):
55 name = _("Overview")
56 slug = "overview"
57 template_name = "project/stacks/_detail_overview.html"
58
59 def allowed(self, request):
60 return policy.check(
61 (("orchestration", "stacks:template"),
62 ("orchestration", "stacks:lookup"),
63 ("orchestration", "stacks:show"),),
64 request)
65
66 def get_context_data(self, request):
67 return {"stack": self.tab_group.kwargs['stack']}
68
69
70class ResourceOverviewTab(tabs.Tab):
71 name = _("Overview")
72 slug = "resource_overview"
73 template_name = "project/stacks/_resource_overview.html"
74
75 def get_context_data(self, request):
76 resource = self.tab_group.kwargs['resource']
77 resource_url = mappings.resource_to_url(resource)
78 return {
79 "resource": resource,
80 "resource_url": resource_url,
81 "metadata": self.tab_group.kwargs['metadata']}
82
83
84class StackEventsTab(tabs.Tab):
85 name = _("Events")
86 slug = "events"
87 template_name = "project/stacks/_detail_events.html"
88 preload = False
89
90 def allowed(self, request):
91 return policy.check(
92 (("orchestration", "stacks:template"),
93 ("orchestration", "stacks:lookup"),
94 ("orchestration", "stacks:show"),
95 ("orchestration", "events:index"),),
96 request)
97
98 def get_context_data(self, request):
99 stack = self.tab_group.kwargs['stack']
100 try:
101 stack_identifier = '%s/%s' % (stack.stack_name, stack.id)
102 events = api.heat.events_list(self.request, stack_identifier)
103 LOG.debug('got events %s', events)
104 # The stack id is needed to generate the resource URL.
105 for event in events:
106 event.stack_id = stack.id
107 except Exception:
108 events = []
109 messages.error(request, _(
110 'Unable to get events for stack "%s".') % stack.stack_name)
111 return {"stack": stack,
112 "table": project_tables.EventsTable(request, data=events), }
113
114
115class StackResourcesTab(tabs.Tab):
116 name = _("Resources")
117 slug = "resources"
118 template_name = "project/stacks/_detail_resources.html"
119 preload = False
120
121 def allowed(self, request):
122 return policy.check(
123 (("orchestration", "stacks:template"),
124 ("orchestration", "stacks:lookup"),
125 ("orchestration", "stacks:show"),
126 ("orchestration", "resource:index"),),
127 request)
128
129 def get_context_data(self, request):
130 stack = self.tab_group.kwargs['stack']
131 try:
132 stack_identifier = '%s/%s' % (stack.stack_name, stack.id)
133 resources = api.heat.resources_list(self.request, stack_identifier)
134 LOG.debug('got resources %s', resources)
135 # The stack id is needed to generate the resource URL.
136 for r in resources:
137 r.stack_id = stack.id
138 except Exception:
139 resources = []
140 messages.error(request, _(
141 'Unable to get resources for stack "%s".') % stack.stack_name)
142 return {"stack": stack,
143 "table": project_tables.ResourcesTable(
144 request, data=resources, stack=stack), }
145
146
147class StackTemplateTab(tabs.Tab):
148 name = _("Template")
149 slug = "stack_template"
150 template_name = "project/stacks/_stack_template.html"
151
152 def allowed(self, request):
153 return policy.check(
154 (("orchestration", "stacks:template"),
155 ("orchestration", "stacks:lookup"),
156 ("orchestration", "stacks:show"),),
157 request)
158
159 def get_context_data(self, request):
160 return {"stack_template": self.tab_group.kwargs['stack_template']}
161
162
163class StackDetailTabs(tabs.TabGroup):
164 slug = "stack_details"
165 tabs = (StackTopologyTab, StackOverviewTab, StackResourcesTab,
166 StackEventsTab, StackTemplateTab)
167 sticky = True
168
169
170class ResourceDetailTabs(tabs.TabGroup):
171 slug = "resource_details"
172 tabs = (ResourceOverviewTab,)
173 sticky = True
diff --git a/openstack_dashboard/dashboards/project/stacks/template_versions/__init__.py b/openstack_dashboard/dashboards/project/stacks/template_versions/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/openstack_dashboard/dashboards/project/stacks/template_versions/__init__.py
+++ /dev/null
diff --git a/openstack_dashboard/dashboards/project/stacks/template_versions/panel.py b/openstack_dashboard/dashboards/project/stacks/template_versions/panel.py
deleted file mode 100644
index 3492d59..0000000
--- a/openstack_dashboard/dashboards/project/stacks/template_versions/panel.py
+++ /dev/null
@@ -1,23 +0,0 @@
1# Licensed under the Apache License, Version 2.0 (the "License");
2# you may not use this file except in compliance with the License.
3# You may obtain a copy of the License at
4#
5# http://www.apache.org/licenses/LICENSE-2.0
6#
7# Unless required by applicable law or agreed to in writing, software
8# distributed under the License is distributed on an "AS IS" BASIS,
9# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
10# implied.
11# See the License for the specific language governing permissions and
12# limitations under the License.
13
14from django.utils.translation import ugettext_lazy as _
15
16import horizon
17
18
19class TemplateVersions(horizon.Panel):
20 name = _("Template Versions")
21 slug = "stacks.template_versions"
22 permissions = ('openstack.services.orchestration',)
23 policy_rules = (("orchestration", "stacks:list_template_versions"),)
diff --git a/openstack_dashboard/dashboards/project/stacks/template_versions/tables.py b/openstack_dashboard/dashboards/project/stacks/template_versions/tables.py
deleted file mode 100644
index 8c63d14..0000000
--- a/openstack_dashboard/dashboards/project/stacks/template_versions/tables.py
+++ /dev/null
@@ -1,52 +0,0 @@
1# Licensed under the Apache License, Version 2.0 (the "License");
2# you may not use this file except in compliance with the License.
3# You may obtain a copy of the License at
4#
5# http://www.apache.org/licenses/LICENSE-2.0
6#
7# Unless required by applicable law or agreed to in writing, software
8# distributed under the License is distributed on an "AS IS" BASIS,
9# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
10# implied.
11# See the License for the specific language governing permissions and
12# limitations under the License.
13
14from django.template import defaultfilters as filters
15from django.utils.translation import ugettext_lazy as _
16
17from horizon import tables
18
19
20class TemplateVersionsTable(tables.DataTable):
21 version = tables.Column(
22 "version",
23 verbose_name=_("Version"),
24 link="horizon:project:stacks.template_versions:details",)
25 type = tables.Column(
26 "type",
27 verbose_name=_("Type"),
28 filters=(filters.upper,))
29
30 def get_object_id(self, template_versions):
31 return template_versions.version
32
33 class Meta(object):
34 name = "template_versions"
35 table_actions = (tables.FilterAction,)
36 verbose_name = _("Template Versions")
37 table_actions = (tables.FilterAction,)
38 multi_select = False
39
40
41class TemplateFunctionsTable(tables.DataTable):
42 functions = tables.Column('functions', verbose_name=_("Function"))
43 description = tables.Column('description', verbose_name=_("Description"))
44
45 def get_object_id(self, template_functions):
46 return template_functions.functions
47
48 class Meta(object):
49 name = "template_functions"
50 verbose_name = _("Template Functions")
51 table_actions = (tables.FilterAction,)
52 multi_select = False
diff --git a/openstack_dashboard/dashboards/project/stacks/template_versions/tabs.py b/openstack_dashboard/dashboards/project/stacks/template_versions/tabs.py
deleted file mode 100644
index 9cb9096..0000000
--- a/openstack_dashboard/dashboards/project/stacks/template_versions/tabs.py
+++ /dev/null
@@ -1,51 +0,0 @@
1# Licensed under the Apache License, Version 2.0 (the "License");
2# you may not use this file except in compliance with the License.
3# You may obtain a copy of the License at
4#
5# http://www.apache.org/licenses/LICENSE-2.0
6#
7# Unless required by applicable law or agreed to in writing, software
8# distributed under the License is distributed on an "AS IS" BASIS,
9# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
10# implied.
11# See the License for the specific language governing permissions and
12# limitations under the License.
13
14from django.utils.translation import ugettext_lazy as _
15
16from horizon import messages
17from horizon import tabs
18from openstack_dashboard import api
19from openstack_dashboard import policy
20
21from openstack_dashboard.dashboards.project.stacks.template_versions \
22 import tables as project_tables
23
24
25class TemplateFunctionsTab(tabs.Tab):
26 name = _("Template Functions")
27 slug = "template_functions"
28 template_name = "project/stacks.template_versions/_details.html"
29 preload = False
30
31 def allowed(self, request):
32 return policy.check(
33 (("orchestration", "stacks:list_template_functions"),),
34 request)
35
36 def get_context_data(self, request):
37 template_version = self.tab_group.kwargs['template_version']
38 try:
39 template_functions = api.heat.template_function_list(
40 self.request, template_version)
41 except Exception:
42 template_functions = []
43 messages.error(request, _('Unable to get functions for template '
44 'version "%s".') % template_version)
45 return {"table": project_tables.TemplateFunctionsTable(
46 request, data=template_functions), }
47
48
49class TemplateVersionDetailsTabs(tabs.TabGroup):
50 slug = "template_version_details"
51 tabs = (TemplateFunctionsTab,)
diff --git a/openstack_dashboard/dashboards/project/stacks/template_versions/templates/stacks.template_versions/_details.html b/openstack_dashboard/dashboards/project/stacks/template_versions/templates/stacks.template_versions/_details.html
deleted file mode 100644
index 9976f88..0000000
--- a/openstack_dashboard/dashboards/project/stacks/template_versions/templates/stacks.template_versions/_details.html
+++ /dev/null
@@ -1,3 +0,0 @@
1{% load i18n %}
2
3{{ table.render }}
diff --git a/openstack_dashboard/dashboards/project/stacks/template_versions/templates/stacks.template_versions/index.html b/openstack_dashboard/dashboards/project/stacks/template_versions/templates/stacks.template_versions/index.html
deleted file mode 100644
index 0a39d4a..0000000
--- a/openstack_dashboard/dashboards/project/stacks/template_versions/templates/stacks.template_versions/index.html
+++ /dev/null
@@ -1,7 +0,0 @@
1{% extends 'base.html' %}
2{% load i18n %}
3{% block title %}{% trans "Template Versions" %}{% endblock %}
4
5{% block main %}
6 {{ table.render }}
7{% endblock %}
diff --git a/openstack_dashboard/dashboards/project/stacks/template_versions/tests.py b/openstack_dashboard/dashboards/project/stacks/template_versions/tests.py
deleted file mode 100644
index fcace15..0000000
--- a/openstack_dashboard/dashboards/project/stacks/template_versions/tests.py
+++ /dev/null
@@ -1,79 +0,0 @@
1# Licensed under the Apache License, Version 2.0 (the "License");
2# you may not use this file except in compliance with the License.
3# You may obtain a copy of the License at
4#
5# http://www.apache.org/licenses/LICENSE-2.0
6#
7# Unless required by applicable law or agreed to in writing, software
8# distributed under the License is distributed on an "AS IS" BASIS,
9# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
10# implied.
11# See the License for the specific language governing permissions and
12# limitations under the License.
13
14from django.core.urlresolvers import reverse
15from django import http
16
17from mox3.mox import IsA
18
19from openstack_dashboard import api
20from openstack_dashboard.test import helpers as test
21
22
23class TemplateVersionsTests(test.TestCase):
24 INDEX_URL = reverse('horizon:project:stacks.template_versions:index')
25
26 @test.create_stubs({api.heat: ('template_version_list',)})
27 def test_index(self):
28 api.heat.template_version_list(
29 IsA(http.HttpRequest)).AndReturn(self.template_versions.list())
30 self.mox.ReplayAll()
31
32 res = self.client.get(self.INDEX_URL)
33 self.assertTemplateUsed(
34 res, 'project/stacks.template_versions/index.html')
35 self.assertContains(res, 'HeatTemplateFormatVersion.2012-12-12')
36
37 @test.create_stubs({api.heat: ('template_version_list',)})
38 def test_index_exception(self):
39 api.heat.template_version_list(
40 IsA(http.HttpRequest)).AndRaise(self.exceptions.heat)
41 self.mox.ReplayAll()
42
43 res = self.client.get(self.INDEX_URL)
44 self.assertTemplateUsed(
45 res, 'project/stacks.template_versions/index.html')
46 self.assertEqual(len(res.context['table'].data), 0)
47 self.assertMessageCount(res, error=1)
48
49 @test.create_stubs({api.heat: ('template_function_list',)})
50 def test_detail_view(self):
51 t_version = self.template_versions.first().version
52 t_functions = self.template_functions.list()
53
54 api.heat.template_function_list(
55 IsA(http.HttpRequest), t_version).AndReturn(t_functions)
56 self.mox.ReplayAll()
57
58 url = reverse('horizon:project:stacks.template_versions:details',
59 args=[t_version])
60 res = self.client.get(url)
61
62 self.assertTemplateUsed(res, 'horizon/common/_detail.html')
63 self.assertNoMessages()
64
65 @test.create_stubs({api.heat: ('template_function_list',)})
66 def test_detail_view_with_exception(self):
67 t_version = self.template_versions.first().version
68
69 api.heat.template_function_list(
70 IsA(http.HttpRequest), t_version).AndRaise(self.exceptions.heat)
71 self.mox.ReplayAll()
72
73 url = reverse('horizon:project:stacks.template_versions:details',
74 args=[t_version])
75 res = self.client.get(url)
76
77 self.assertTemplateUsed(res, 'horizon/common/_detail.html')
78 self.assertEqual(len(res.context['table'].data), 0)
79 self.assertMessageCount(res, error=1)
diff --git a/openstack_dashboard/dashboards/project/stacks/template_versions/urls.py b/openstack_dashboard/dashboards/project/stacks/template_versions/urls.py
deleted file mode 100644
index 5e2bbc2..0000000
--- a/openstack_dashboard/dashboards/project/stacks/template_versions/urls.py
+++ /dev/null
@@ -1,24 +0,0 @@
1# Licensed under the Apache License, Version 2.0 (the "License");
2# you may not use this file except in compliance with the License.
3# You may obtain a copy of the License at
4#
5# http://www.apache.org/licenses/LICENSE-2.0
6#
7# Unless required by applicable law or agreed to in writing, software
8# distributed under the License is distributed on an "AS IS" BASIS,
9# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
10# implied.
11# See the License for the specific language governing permissions and
12# limitations under the License.
13
14from django.conf.urls import url
15
16from openstack_dashboard.dashboards.project.stacks.template_versions \
17 import views
18
19
20urlpatterns = [
21 url(r'^$', views.TemplateVersionsView.as_view(), name='index'),
22 url(r'^(?P<template_version>[^/]+)/$',
23 views.DetailView.as_view(), name='details'),
24]
diff --git a/openstack_dashboard/dashboards/project/stacks/template_versions/views.py b/openstack_dashboard/dashboards/project/stacks/template_versions/views.py
deleted file mode 100644
index 22eabd7..0000000
--- a/openstack_dashboard/dashboards/project/stacks/template_versions/views.py
+++ /dev/null
@@ -1,61 +0,0 @@
1# Licensed under the Apache License, Version 2.0 (the "License");
2# you may not use this file except in compliance with the License.
3# You may obtain a copy of the License at
4#
5# http://www.apache.org/licenses/LICENSE-2.0
6#
7# Unless required by applicable law or agreed to in writing, software
8# distributed under the License is distributed on an "AS IS" BASIS,
9# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
10# implied.
11# See the License for the specific language governing permissions and
12# limitations under the License.
13
14from django.core.urlresolvers import reverse
15from django.utils.translation import ugettext_lazy as _
16
17from horizon import exceptions
18from horizon import tables
19from horizon import tabs
20
21from openstack_dashboard import api
22import openstack_dashboard.dashboards.project.stacks.template_versions.tables \
23 as project_tables
24import openstack_dashboard.dashboards.project.stacks.template_versions.tabs \
25 as project_tabs
26
27
28class TemplateVersionsView(tables.DataTableView):
29 table_class = project_tables.TemplateVersionsTable
30 template_name = 'project/stacks.template_versions/index.html'
31 page_title = _("Template Versions")
32
33 def get_data(self):
34 try:
35 template_versions = sorted(
36 api.heat.template_version_list(self.request),
37 key=lambda template_version: template_version.version)
38 except Exception:
39 template_versions = []
40 msg = _('Unable to retrieve template versions.')
41 exceptions.handle(self.request, msg)
42 return template_versions
43
44
45class DetailView(tabs.TabView):
46 tab_group_class = project_tabs.TemplateVersionDetailsTabs
47 template_name = 'horizon/common/_detail.html'
48 page_title = "{{ template_version }}"
49
50 def get_template_version(self, request, **kwargs):
51 try:
52 template_functions = api.heat.template_function_list(
53 request, kwargs['template_version'])
54 return template_functions
55 except Exception:
56 msg = _('Unable to retrieve template functions.')
57 exceptions.handle(request, msg, redirect=self.get_redirect_url())
58
59 @staticmethod
60 def get_redirect_url():
61 return reverse('horizon:project:stacks.template_versions:index')
diff --git a/openstack_dashboard/dashboards/project/stacks/templates/stacks/_change_template.html b/openstack_dashboard/dashboards/project/stacks/templates/stacks/_change_template.html
deleted file mode 100644
index bd31f70..0000000
--- a/openstack_dashboard/dashboards/project/stacks/templates/stacks/_change_template.html
+++ /dev/null
@@ -1,7 +0,0 @@
1{% extends "horizon/common/_modal_form.html" %}
2{% load i18n %}
3{% block form_attrs %}enctype="multipart/form-data"{% endblock %}
4{% block modal-body-right %}
5 <h3>{% trans "Description:" %}</h3>
6 <p>{% trans "Use one of the available template source options to specify the template to be used in creating this stack." %}</p>
7{% endblock %}
diff --git a/openstack_dashboard/dashboards/project/stacks/templates/stacks/_create.html b/openstack_dashboard/dashboards/project/stacks/templates/stacks/_create.html
deleted file mode 100644
index 19a3f1b..0000000
--- a/openstack_dashboard/dashboards/project/stacks/templates/stacks/_create.html
+++ /dev/null
@@ -1,6 +0,0 @@
1{% extends "horizon/common/_modal_form.html" %}
2{% load i18n %}
3{% block modal-body-right %}
4 <h3>{% trans "Description:" %}</h3>
5 <p>{% trans "Create a new stack with the provided values." %}</p>
6{% endblock %}
diff --git a/openstack_dashboard/dashboards/project/stacks/templates/stacks/_detail_events.html b/openstack_dashboard/dashboards/project/stacks/templates/stacks/_detail_events.html
deleted file mode 100644
index 9976f88..0000000
--- a/openstack_dashboard/dashboards/project/stacks/templates/stacks/_detail_events.html
+++ /dev/null
@@ -1,3 +0,0 @@
1{% load i18n %}
2
3{{ table.render }}
diff --git a/openstack_dashboard/dashboards/project/stacks/templates/stacks/_detail_overview.html b/openstack_dashboard/dashboards/project/stacks/templates/stacks/_detail_overview.html
deleted file mode 100644
index 56534bb..0000000
--- a/openstack_dashboard/dashboards/project/stacks/templates/stacks/_detail_overview.html
+++ /dev/null
@@ -1,55 +0,0 @@
1{% load i18n sizeformat %}
2
3<div class="detail">
4 <dl class="dl-horizontal">
5 <dt>{% trans "Name" %}</dt>
6 <dd data-display="{{ stack.stack_name|default:stack.id }}">{{ stack.stack_name }}</dd>
7 <dt>{% trans "ID" %}</dt>
8 <dd>{{ stack.id }}</dd>
9 <dt>{% trans "Description" %}</dt>
10 <dd>{{ stack.description }}</dd>
11 </dl>
12
13 <h4>{% trans "Status" %}</h4>
14 <hr class="header_rule">
15 <dl class="dl-horizontal">
16 <dt>{% trans "Created" %}</dt>
17 <dd>{{ stack.creation_time|parse_isotime|timesince_or_never }}</dd>
18 <dt>{% trans "Last Updated" %}</dt>
19 <dd>{{ stack.updated_time|parse_isotime|timesince_or_never }}</dd>
20 <dt>{% trans "Status" %}</dt>
21 <dd>
22 {% blocktrans with stack_status_title=stack.stack_status|title stack_status_reason=stack.stack_status_reason %}{{ stack_status_title }}: {{ stack_status_reason }}{% endblocktrans %}
23 </dd>
24 </dl>
25
26 <h4>{% trans "Outputs" %}</h4>
27 <hr class="header_rule">
28 <dl class="dl-horizontal">
29 {% for output in stack.outputs %}
30 <dt>{{ output.output_key }}</dt>
31 <dd>{{ output.description }}</dd>
32 <dd>
33 {{ output.output_value|stack_output }}
34 </dd>
35 {% endfor %}
36 </dl>
37
38 <h4>{% trans "Stack Parameters" %}</h4>
39 <hr class="header_rule">
40 <dl class="dl-horizontal">
41 {% for key, value in stack.parameters.items %}
42 <dt>{{ key }}</dt>
43 <dd>{{ value }}</dd>
44 {% endfor %}
45 </dl>
46
47 <h4>{% trans "Launch Parameters" %}</h4>
48 <hr class="header_rule">
49 <dl class="dl-horizontal">
50 <dt>{% trans "Timeout" %}</dt>
51 <dd>{{ stack.timeout_mins }} {% trans "Minutes" %}</dd>
52 <dt>{% trans "Rollback" %}</dt>
53 <dd>{% if stack.disable_rollback %}{% trans "Disabled" %}{% else %}{% trans "Enabled" %}{% endif %}</dd>
54 </dl>
55</div>
diff --git a/openstack_dashboard/dashboards/project/stacks/templates/stacks/_detail_resources.html b/openstack_dashboard/dashboards/project/stacks/templates/stacks/_detail_resources.html
deleted file mode 100644
index 9976f88..0000000
--- a/openstack_dashboard/dashboards/project/stacks/templates/stacks/_detail_resources.html
+++ /dev/null
@@ -1,3 +0,0 @@
1{% load i18n %}
2
3{{ table.render }}
diff --git a/openstack_dashboard/dashboards/project/stacks/templates/stacks/_detail_topology.html b/openstack_dashboard/dashboards/project/stacks/templates/stacks/_detail_topology.html
deleted file mode 100644
index d906cea..0000000
--- a/openstack_dashboard/dashboards/project/stacks/templates/stacks/_detail_topology.html
+++ /dev/null
@@ -1,9 +0,0 @@
1{% load i18n sizeformat %}
2
3<div id="resource_container">
4 <div id="info_box"></div>
5 <div id="stack_box"></div>
6 <div id="heat_resource_topology"></div>
7 <div id="stack_id" data-stack_id="{{ stack_id }}"></div>
8 <div id="d3_data" data-d3_data="{{ d3_data }}"></div>
9</div> \ No newline at end of file
diff --git a/openstack_dashboard/dashboards/project/stacks/templates/stacks/_preview.html b/openstack_dashboard/dashboards/project/stacks/templates/stacks/_preview.html
deleted file mode 100644
index 2402478..0000000
--- a/openstack_dashboard/dashboards/project/stacks/templates/stacks/_preview.html
+++ /dev/null
@@ -1,6 +0,0 @@
1{% extends "horizon/common/_modal_form.html" %}
2{% load i18n %}
3{% block modal-body-right %}
4 <h3>{% trans "Description:" %}</h3>
5 <p>{% trans "Preview a new stack with the provided values." %}</p>
6{% endblock %}
diff --git a/openstack_dashboard/dashboards/project/stacks/templates/stacks/_preview_details.html b/openstack_dashboard/dashboards/project/stacks/templates/stacks/_preview_details.html
deleted file mode 100644
index 12d131a..0000000
--- a/openstack_dashboard/dashboards/project/stacks/templates/stacks/_preview_details.html
+++ /dev/null
@@ -1,58 +0,0 @@
1{% extends "horizon/common/_modal.html" %}
2{% load i18n %}
3
4{% block modal-header %}{% trans "Stack Preview" %}{% endblock %}
5
6{% block modal-body %}
7<div class="row-fluid stack-preview detail">
8<form>
9 <dl class="dl-horizontal">
10 {% for key, value in stack_preview.items %}
11 {% if key != 'parameters' and key != 'resources' and key != 'links' %}
12 <dt>{{ key }}</dt>
13 <dd>{{ value }}</dd>
14 {% endif %}
15 {% endfor %}
16 </dl>
17
18 {% if stack_preview.parameters %}
19 <dt>{% trans "Parameters" %}</dt>
20 <hr class="header_rule">
21 <dl class="dl-horizontal">
22 {% for key, value in stack_preview.parameters.items %}
23 <dt>{{ key }}</dt>
24 <dd>{{ value }}</dd>
25 {% endfor %}
26 </dl>
27 {% endif %}
28
29 {% if stack_preview.links %}
30 <dt>{% trans "Links" %}</dt>
31 <hr class="header_rule">
32 {% for link in stack_preview.links %}
33 <dl class="dl-horizontal">
34 <dt>{{ link.rel }}</dt>
35 <dd>{{ link.href }}</dd>
36 </dl>
37 {% endfor %}
38 {% endif %}
39
40 {% if stack_preview.resources %}
41 <dt>{% trans "Resources" %}</dt>
42 {% for resource in stack_preview.resources %}
43 <hr class="header_rule">
44 <dl class="dl-horizontal">
45 {% for key, value in resource.items %}
46 <dt>{{ key }}</dt>
47 <dd>{{ value }}</dd>
48 {% endfor %}
49 </dl>
50 {% endfor %}
51 {% endif %}
52</form>
53</div>
54{% endblock %}
55
56{% block modal-footer %}